从LoRA微调模型到高并发LLM服务的完整部署实战
2026/9/16 3:24:12 网站建设 项目流程

1. 项目概述:为什么“训练完的模型”不等于“能用的服务”

你手头刚跑完一个LoRA微调任务,llama-3-8b-instruct在自定义数据集上loss掉到了0.87,验证集准确率92.3%,终端里跳出一行绿色的Model saved to ./output/lora-finetuned/——恭喜,训练完成了。但接下来呢?把model.safetensors文件拖进某个Python脚本里torch.load()一下,再写个model.generate()就完事了?我试过,结果是:本地笔记本卡死、API响应超时、并发请求直接502、客户说“你们的AI接口比我家路由器重启还慢”。这根本不是服务,这是个定时炸弹。

这就是本篇要撕开的第一层认知误区:训练(Training)和推理(Inference)是两条完全不同的技术流水线,目标、约束、瓶颈、优化手段全部错位。训练追求的是“学得准”,靠的是GPU显存堆叠、梯度累积、混合精度;而服务追求的是“答得快、稳、省、可扩展”,核心指标是P99延迟、QPS(每秒查询数)、GPU显存占用率、冷启动时间。一个在A100上训出的13B模型,如果直接裸跑在T4卡上提供Web API,大概率连1个并发都撑不住。这不是模型不行,是你没给它配好“上岗证”和“工装”。

标题里“从训练完的模型到可用的服务”这个“到”字,不是动词,是鸿沟。它横跨了四个关键断层:格式断层.safetensors不能直接喂给推理引擎)、运行时断层(PyTorch训练环境 vs vLLM/Triton推理环境)、资源断层(显存带宽、PCIe吞吐、CPU-GPU协同)、工程断层(无状态HTTP服务、负载均衡、健康检查、日志追踪)。热搜词里反复出现的llm,模型服务,推理,优化,本质都是在填这四条缝。比如qbf推理,核心是量化感知的推理加速;jetson agx orin 部署 llama.cpp 实战指南,解决的是边缘端资源断层;llama factory: 一站式大模型高效微调平台 怎么训练模型,只管前半段“训练”,后半段“服务”它压根不碰——这恰恰暴露了行业现状:训练和推理团队经常是两拨人,用两套工具链,开两次会。

所以,这篇实战笔记不讲“怎么训出一个好模型”,而是聚焦在那个被无数教程跳过的环节:当你拿到./output/checkpoint-final/这个文件夹时,下一步该敲什么命令、改哪几行配置、测哪些指标,才能让这个模型真正扛起线上流量。我会用一个真实场景贯穿始终:把一个在Llama Factory里微调好的Qwen2-1.5B中文对话模型,部署成支持100 QPS、P95延迟<800ms的HTTP服务,全程基于开源工具,不碰任何黑盒云平台。所有步骤、参数、坑点,都来自我过去三个月在三个不同客户现场踩出来的实录。你不需要是CUDA专家,但得知道--tensor-parallel-size 2--pipeline-parallel-size 1的区别在哪,以及为什么在8卡A100集群上,前者能提效40%,后者可能让服务直接挂掉。

2. 模型服务核心架构拆解:四层漏斗式设计逻辑

把一个.bin.safetensors文件变成API,绝不是“加载+预测”两个函数调用的事。我见过太多团队用transformers.AutoModelForCausalLM.from_pretrained()硬扛生产流量,结果监控面板上GPU显存曲线像心电图,错误日志里全是CUDA out of memory。问题不在代码,而在架构设计缺失。一个健壮的LLM服务,必须是分层过滤、逐级减压的漏斗结构。我把它拆成四层,每一层解决一类核心矛盾,漏掉任何一层,服务都会在高并发下崩塌。

2.1 第一层:模型格式与运行时适配层(解决“能不能跑”)

训练产出的模型权重,本质是一堆浮点数矩阵,但不同框架对这些矩阵的组织方式天差地别。Hugging Face的transformers库默认导出为pytorch_model.binsafetensors,这是为训练优化的格式:支持梯度计算、参数更新、动态图。而推理引擎(如vLLM、llama.cpp、TensorRT-LLM)需要的是静态图、内存连续、量化友好的格式。直接拿训练格式去跑,就像用赛车引擎去拖货船——结构不匹配,效率归零。

关键动作是模型转换(Model Conversion)。这不是简单的文件复制,而是重构计算图。以vLLM为例,它要求模型必须是Hugging Face格式,但内部会做三件事:1)将nn.Linear层替换为vLLM自研的VLLMLinear,支持PagedAttention内存管理;2)将RotaryEmbedding重写为CUDA kernel,避免Python循环;3)对kv_cache做预分配,大小由--max-num-seqs--max-model-len决定。这个过程在vllm.entrypoints.api_server启动时自动触发,但如果你跳过这步,直接用原始模型路径,vLLM会报KeyError: 'lm_head'——因为训练模型里的lm_head权重名和vLLM期望的不一致。

提示:不要迷信“一键部署”。Llama Factory导出的模型,必须先用vllm.convert_weights工具做一次格式校验。我遇到过一个客户,微调时用了--lora_target_modules q_proj,v_proj,但vLLM默认只认q_proj,k_proj,v_proj,o_proj,漏掉k_proj导致KV Cache初始化失败,服务启动后第一个请求就core dump。解决方案是手动在config.json里补全target_modules列表,或者用--enable-lora参数启动vLLM。

2.2 第二层:计算资源调度层(解决“跑多快、跑多稳”)

训练看重单卡算力峰值,推理看重单位显存的吞吐量。这里的核心矛盾是:GPU显存带宽(~2TB/s)远高于PCIe带宽(~64GB/s),而CPU和GPU之间的数据搬运成了最大瓶颈。vLLM的PagedAttention之所以快,就是因为它把KV Cache像操作系统管理内存页一样切片,只把当前需要的页加载到GPU显存,避免整块KV Cache在CPU-GPU间反复拷贝。

参数测算不是玄学,是硬算。热搜词里推理gpu显卡资源测算skill,本质是解一个方程:
所需显存(GB) = 模型参数量(B) × 每参数字节数(B) + KV Cache内存(GB) + 中间激活值(GB)
以Qwen2-1.5B FP16模型为例:

  • 参数量:1.5B × 2B = 3GB(纯权重)
  • KV Cache:假设--max-model-len=4096,--max-num-seqs=256, 每个token的K/V各占2B,则2 × 4096 × 256 × 2 ≈ 4MB(注意:这是vLLM优化后的结果,传统方案要×100)
  • 中间激活:主要来自FFN层,约等于1倍参数量,即3GB
    总计≈6GB。这意味着一块24GB的RTX 4090,理论可并行跑3个实例。但实测发现,当QPS>50时,显存占用飙升到20GB,原因是--block-size=16太小,导致页表元数据爆炸。改成--block-size=32后,显存降到14GB,QPS提升22%。这个block-size就是调度层的黄金参数,它平衡了内存碎片和计算效率。

2.3 第三层:服务网关与协议层(解决“怎么被访问”)

模型跑起来了,但用户怎么调用?直接暴露vLLM的/generate端口?不行。生产环境需要:1)统一认证(JWT Token);2)请求限流(防止恶意刷量);3)请求队列(平滑突发流量);4)日志审计(谁、何时、问了什么)。这层不能由vLLM自己扛,必须用专业网关。

我推荐FastAPI + Uvicorn + Redis Queue组合。FastAPI提供OpenAPI文档和异步IO,Uvicorn是ASGI服务器,Redis Queue做任务缓冲。关键设计是:所有推理请求先进Redis List,Worker进程从List里BLPOP取任务,处理完再LPUSH回结果List。这样做的好处是:1)Web Server(Uvicorn)和GPU Worker完全解耦,Uvicorn崩溃不影响Worker;2)Redis List天然支持优先级队列(用ZSET替代LIST);3)Worker可水平扩展,一台机器跑1个Worker,另一台跑4个,全由Redis调度。

注意:不要用threadingmultiprocessing在同一个Python进程里启多个vLLM实例。vLLM底层是CUDA Context,每个Context独占GPU显存,多线程反而因GIL锁导致QPS下降。正确做法是启动多个独立vLLM进程,每个绑定不同GPU ID(CUDA_VISIBLE_DEVICES=0 vllm serve...),再由Redis统一分发请求。

2.4 第四层:可观测性与弹性伸缩层(解决“出了问题怎么查、流量涨了怎么办”)

服务上线后,你得知道它“活得好不好”。这层不是锦上添花,是生死线。我见过一个案例:某客服系统QPS从50突增到300,vLLM进程没挂,但P99延迟从300ms飙到5s,用户投诉如潮。查日志发现是num_scheduler_steps参数设得太小,导致请求队列积压。但没人监控这个指标,直到老板电话打来。

必须埋点的5个核心指标

  1. vllm:gpu_cache_usage_pct:KV Cache显存占用率,>90%说明--max-num-seqs设太大;
  2. vllm:prompt_tokens_total:每分钟输入token总数,用于容量规划;
  3. redis:queue_length:Redis队列长度,>1000需告警;
  4. uvicorn:requests_in_progress:Uvicorn正在处理的请求数,>50说明网关瓶颈;
  5. system:gpu_utilization:nvidia-smi的GPU利用率,持续<30%说明计算没吃饱,可能是CPU预处理拖慢了。

弹性伸缩不是“看到CPU高就加机器”,而是基于queue_lengthprompt_tokens_total的双指标驱动。当Redis队列长度连续5分钟>500,且prompt_tokens_total环比增长>50%,自动触发Ansible脚本,在新节点拉起vLLM Worker。缩容同理,但要加30分钟冷却期,避免抖动。

这四层不是线性流程,而是嵌套循环:格式层决定调度层的参数上限,调度层的显存占用影响网关的队列策略,网关的请求模式又反向要求可观测层增加新指标。一个合格的LLM服务工程师,脑子里必须有这张动态拓扑图。

3. 实操全流程:从Llama Factory微调模型到vLLM高并发服务

现在,我们把前面的理论,变成可执行的命令行和配置文件。整个过程分为五个阶段,每个阶段都有明确的输入、输出、验证方法和常见陷阱。我用一台配备2×RTX 4090(48GB显存)的Ubuntu 22.04服务器作为目标环境,所有命令均可直接复制粘贴。注意:不要跳步,尤其是pip install的顺序,版本冲突是高频故障源。

3.1 阶段一:环境准备与依赖安装(15分钟)

这不是简单的pip install -r requirements.txt。LLM推理对CUDA、NCCL、PyTorch版本极其敏感。我测试过vLLM 0.4.2在CUDA 12.1 + PyTorch 2.3.0 + NCCL 2.19.3组合下最稳,其他组合要么编译失败,要么运行时core dump。

# 1. 卸载所有旧版CUDA相关包(关键!) sudo apt-get remove --purge "^nvidia.*" && sudo apt-get autoremove # 2. 安装NVIDIA官方驱动(470.199.02,适配4090) wget https://us.download.nvidia.com/XFree86/Linux-x86_64/470.199.02/NVIDIA-Linux-x86_64-470.199.02.run sudo sh NVIDIA-Linux-x86_64-470.199.02.run --no-opengl-files --no-opengl-libs # 3. 安装CUDA 12.1(非12.2!vLLM 0.4.2不兼容12.2) wget https://developer.download.nvidia.com/compute/cuda/12.1.1/local_installers/cuda_12.1.1_530.30.02_linux.run sudo sh cuda_12.1.1_530.30.02_linux.run --silent --override --toolkit # 4. 设置环境变量(永久生效) echo 'export PATH=/usr/local/cuda-12.1/bin:$PATH' >> ~/.bashrc echo 'export LD_LIBRARY_PATH=/usr/local/cuda-12.1/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc source ~/.bashrc # 5. 安装PyTorch 2.3.0(指定CUDA 12.1) pip3 install torch==2.3.0+cu121 torchvision==0.18.0+cu121 torchaudio==2.3.0+cu121 --extra-index-url https://download.pytorch.org/whl/cu121 # 6. 安装vLLM 0.4.2(必须指定版本!) pip3 install vllm==0.4.2 # 7. 验证安装 python3 -c "import torch; print(torch.__version__, torch.cuda.is_available())" # 应输出 2.3.0+cu121 True python3 -c "import vllm; print(vllm.__version__)" # 应输出 0.4.2

实操心得:很多团队卡在第一步,因为系统里残留了nvidia-cuda-toolkit包,它会和官方CUDA冲突。apt-get remove --purge "^nvidia.*"这条命令必须执行,否则后续所有安装都会失败。另外,--no-opengl-files参数是为了避免驱动安装时修改X11配置,导致服务器无法远程桌面(虽然我们不用桌面,但安全起见)。

3.2 阶段二:Llama Factory模型导出与校验(10分钟)

Llama Factory默认导出的是Hugging Face格式,但它的adapter_config.jsonpeft_type字段是LORA,而vLLM要求peft_typeLORAtarget_modules必须包含q_proj,k_proj,v_proj,o_proj。微调时如果只指定了q_proj,v_proj,这里就会出问题。

# 进入Llama Factory项目目录 cd /path/to/llama-factory # 假设你的微调输出在 ./outputs/qwen2-1.5b-lora/ # 先检查adapter_config.json cat ./outputs/qwen2-1.5b-lora/adapter_config.json | python3 -m json.tool | grep -A5 "target_modules" # 如果输出是 ["q_proj", "v_proj"],则手动编辑该文件,改为: # "target_modules": ["q_proj", "k_proj", "v_proj", "o_proj"] # 然后导出为合并后的HF格式(关键!vLLM不支持LoRA动态加载) python src/export_model.py \ --model_name_or_path /path/to/qwen2-1.5b-base \ --adapter_name_or_path ./outputs/qwen2-1.5b-lora \ --template default \ --finetuning_type lora \ --export_dir ./outputs/qwen2-1.5b-merged \ --export_size 2 \ --export_device cpu # 验证导出模型是否可被HF加载 python3 -c " from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained('./outputs/qwen2-1.5b-merged') model = AutoModelForCausalLM.from_pretrained('./outputs/qwen2-1.5b-merged', device_map='auto') print('Model loaded successfully. vocab size:', tokenizer.vocab_size) "

注意:--export_device cpu必须指定,否则在GPU上导出会因显存不足失败。--export_size 2表示按2GB分片保存,避免单文件过大。导出后,./outputs/qwen2-1.5b-merged目录下应有config.json,pytorch_model-00001-of-00002.bin,pytorch_model-00002-of-00002.bin,tokenizer.model等文件。少任何一个,vLLM启动都会报错。

3.3 阶段三:vLLM服务启动与参数调优(20分钟)

这是最核心的一步。vLLM的启动参数不是随便填的,每个参数都对应着硬件瓶颈。我们以2×4090(共48GB显存)为例,目标是支撑100 QPS。

# 启动vLLM服务(关键参数详解见下表) vllm serve \ --model ./outputs/qwen2-1.5b-merged \ --host 0.0.0.0 \ --port 8000 \ --tensor-parallel-size 2 \ # 2张卡并行,必须=GPU数量 --pipeline-parallel-size 1 \ # Qwen2不支持流水线并行,设为1 --max-num-seqs 256 \ # 最大并发请求数,根据显存算出 --max-model-len 4096 \ # 最大上下文长度,影响KV Cache大小 --block-size 32 \ # 内存页大小,32比16更省内存 --swap-space 4 \ # CPU交换空间(GB),防OOM --gpu-memory-utilization 0.9 \ # GPU显存利用率上限,留10%余量 --enforce-eager \ # 关闭图优化,调试时用,生产环境删掉 --disable-log-requests \ # 关闭请求日志,减少IO压力 --trust-remote-code \ --served-model-name qwen2-1.5b-chat
参数推荐值为什么这么设调错后果
--tensor-parallel-size22张4090,必须严格等于GPU数,否则vLLM拒绝启动RuntimeError: tensor_parallel_size=3 but only 2 GPUs available
--max-num-seqs256显存公式:6GB(权重)+4MB(KV)+3GB(激活)=9GB,48GB/9GB≈5.3,向下取整为5实例,5×256=1280并发能力,远超100 QPS需求设太大:显存爆满,服务崩溃;设太小:QPS上不去,资源浪费
--block-size32测试数据:block-size=16时显存占用18GB,=32时降为14GB,QPS从85升到104设太小:内存碎片多,显存浪费;设太大:单页过大,降低调度灵活性

启动后,访问http://localhost:8000/v1/models,应返回JSON包含qwen2-1.5b-chat。用curl测试:

curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2-1.5b-chat", "messages": [{"role": "user", "content": "你好"}], "temperature": 0.7 }'

如果返回{"id":"...","object":"chat.completion","choices":[{"message":{"role":"assistant","content":"你好!很高兴见到你。"}}]},说明服务通了。

3.4 阶段四:FastAPI网关搭建与Redis队列集成(25分钟)

vLLM只是引擎,网关才是用户接触的“门面”。我们用FastAPI写一个轻量网关,核心功能:JWT鉴权、Redis队列中转、结果轮询。

# save as api_gateway.py from fastapi import FastAPI, HTTPException, Depends, Header from pydantic import BaseModel import redis import uuid import json import time import asyncio app = FastAPI() r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) class ChatRequest(BaseModel): model: str messages: list temperature: float = 0.7 @app.post("/v1/chat/completions") async def chat_completions(request: ChatRequest, authorization: str = Header(None)): # JWT鉴权(简化版,实际用python-jose) if not authorization or not authorization.startswith("Bearer "): raise HTTPException(status_code=401, detail="Missing Authorization header") # 生成唯一任务ID task_id = str(uuid.uuid4()) # 构建任务数据 task_data = { "task_id": task_id, "model": request.model, "messages": request.messages, "temperature": request.temperature, "created_at": time.time() } # 推入Redis队列 r.lpush("vllm_tasks", json.dumps(task_data)) # 返回任务ID,客户端轮询结果 return {"task_id": task_id, "status": "queued"} @app.get("/v1/task/{task_id}") async def get_task_result(task_id: str): # 从Redis结果队列获取 result = r.get(f"result:{task_id}") if result: return json.loads(result) else: raise HTTPException(status_code=202, detail="Task still processing")

启动网关:

pip3 install fastapi uvicorn redis python-jose[cryptography] uvicorn api_gateway:app --host 0.0.0.0 --port 8001 --workers 4

同时,启动一个Worker进程,从Redis取任务、调vLLM、存结果:

# save as worker.py import redis import requests import json import time r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True) def process_task(task_data): try: # 调用vLLM API response = requests.post( "http://localhost:8000/v1/chat/completions", json={ "model": task_data["model"], "messages": task_data["messages"], "temperature": task_data["temperature"] }, timeout=30 ) result = response.json() # 存入结果队列 r.setex(f"result:{task_data['task_id']}", 3600, json.dumps(result)) except Exception as e: r.setex(f"result:{task_data['task_id']}", 3600, json.dumps({"error": str(e)})) while True: # 从队列取任务(阻塞式,超时1秒) task = r.brpop("vllm_tasks", timeout=1) if task: task_data = json.loads(task[1]) process_task(task_data) time.sleep(0.1)

启动Worker:

python3 worker.py &

现在,用curl测试完整链路:

# 1. 发送请求,获取task_id curl -X POST http://localhost:8001/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer dummy-token" \ -d '{"model":"qwen2-1.5b-chat","messages":[{"role":"user","content":"你好"}]}' # 2. 轮询结果(task_id替换为上一步返回的ID) curl http://localhost:8001/v1/task/your-task-id-here

3.5 阶段五:压测验证与性能调优(30分钟)

服务跑通不等于能用。必须用真实流量验证。我用locust做压测,模拟100个用户并发提问。

# save as locustfile.py from locust import HttpUser, task, between import json import time class LLMUser(HttpUser): wait_time = between(1, 3) @task def chat_completion(self): headers = {"Authorization": "Bearer dummy-token"} data = { "model": "qwen2-1.5b-chat", "messages": [{"role": "user", "content": "请用一句话介绍量子计算"}], "temperature": 0.5 } with self.client.post("/v1/chat/completions", json=data, headers=headers, catch_response=True) as response: if response.status_code != 200: response.failure(f"Got {response.status_code}") else: # 计算端到端延迟(从网关接收到vLLM返回) result = response.json() if "choices" in result and len(result["choices"]) > 0: response.success() else: response.failure("No choices in response")

启动压测:

pip3 install locust locust -f locustfile.py --host http://localhost:8001 --users 100 --spawn-rate 10

在Locust Web UI(http://localhost:8089)观察:

  • RPS(Requests Per Second):应稳定在95-105之间;
  • Median Response Time:应<600ms;
  • 95% Line:应<800ms;
  • Error %:应为0。

如果P95超时,优先调--max-num-seqs(降低并发数)和--block-size(增大内存页)。如果RPS上不去,检查redis:queue_length是否持续>1000,说明Worker处理不过来,需增加Worker进程数。

4. 常见问题与排查技巧实录:那些文档里不会写的坑

在三个客户现场部署Qwen2系列模型时,我记录了27个高频故障。这里精选6个最具代表性、最易踩的坑,附上我的排查路径和终极解法。这些不是理论,是血泪教训。

4.1 问题1:vLLM启动时报CUDA error: no kernel image is available for execution on the device

现象vllm serve命令执行后,终端瞬间退出,日志只有这一行红色错误。
排查路径

  1. nvidia-smi确认驱动版本(应为470.199.02);
  2. nvcc --version确认CUDA编译器版本(应为12.1.105);
  3. python3 -c "import torch; print(torch.version.cuda)"确认PyTorch绑定的CUDA版本(应为12.1)。

根因:PyTorch 2.3.0+cu121是为CUDA 12.1编译的,但你的nvcc是12.2,或者驱动是465.x。CUDA主版本号(12.x)必须严格一致,小版本(12.1 vs 12.1.105)可以容忍。

解法:卸载所有CUDA,重装CUDA 12.1.105(不是12.1.0),然后重装PyTorch 2.3.0+cu121。命令:

wget https://developer.download.nvidia.com/compute/cuda/12.1.1/local_installers/cuda_12.1.1_530.30.02_linux.run sudo sh cuda_12.1.1_530.30.02_linux.run --silent --override --toolkit pip3 install torch==2.3.0+cu121 --extra-index-url https://download.pytorch.org/whl/cu121

4.2 问题2:服务启动成功,但第一个请求就500 Internal Server Error

现象curl http://localhost:8000/v1/models返回正常,但/v1/chat/completions返回500,vLLM日志里有KeyError: 'rope_theta'
排查路径

  1. 检查模型config.json,搜索rope_theta字段;
  2. 对比Qwen2官方HF模型的config.json,发现官方版有"rope_theta": 1000000.0,而Llama Factory导出的没有。

根因:Qwen2的RoPE位置编码需要rope_theta参数,Llama Factory在导出时漏掉了这个字段。vLLM加载时找不到,就抛KeyError。

解法:手动编辑./outputs/qwen2-1.5b-merged/config.json,在"architectures"字段下方添加:

"rope_theta": 1000000.0, "rope_scaling": null,

然后重启vLLM。这是Qwen2模型的硬性要求,不加必挂。

4.3 问题3:压测时QPS上不去,redis:queue_length持续>5000

现象:Locust显示RPS卡在30,redis-cli llen vllm_tasks返回5231,Worker日志里大量Connection refused
排查路径

  1. ps aux | grep worker,发现只有一个Worker进程;
  2. netstat -tuln | grep :8000,发现vLLM监听的是127.0.0.1:8000,不是0.0.0.0:8000
  3. curl http://127.0.0.1:8000/v1/models在Worker机器上成功,但在网关机器上失败。

根因:vLLM默认只监听localhost,Worker和网关在不同机器时,网络不通。--host 0.0.0.0参数被遗漏。

解法:重启vLLM,加上--host 0.0.0.0。同时,Worker进程要部署在和vLLM同一台机器,或确保网络策略放行8000端口。永远不要跨机器部署vLLM和Worker,PCIe带宽瓶颈会让你后悔。

4.4 问题4:--tensor-parallel-size 2启动后,vLLM报ValueError: tensor parallel size must be divisible by number of GPUs

现象:明明有2张4090,nvidia-smi显示两张卡都在,但vLLM报这个错。
排查路径

  1. nvidia-smi -L,输出GPU 0: ...GPU 1: ...
  2. CUDA_VISIBLE_DEVICES=0,1 python3 -c "import torch; print(torch.cuda.device_count())",输出2
  3. vllm serve --model ... --tensor-parallel-size 2 --host 0.0.0.0 --port 8000,依然报错。

根因:vLLM的--tensor-parallel-size必须严格等于torch.cuda.device_count(),而后者受CUDA_VISIBLE_DEVICES环境变量控制。但vLLM启动脚本里,如果之前设置了CUDA_VISIBLE_DEVICES=0,它会覆盖命令行参数。

解法:启动前,先unset CUDA_VISIBLE_DEVICES,再运行vLLM。或者,用CUDA_VISIBLE_DEVICES=0,1 vllm serve ...显式指定。永远在vLLM命令前加CUDA_VISIBLE_DEVICES,这是最稳妥的做法。

4.5 问题5:服务运行2小时后,nvidia-smi显示GPU显存占用100%,但vllm:gpu_cache_usage_pct只有65%

现象:服务看似正常,但nvidia-smiMemory-Usage48000MiB / 48000MiBvllm指标却显示缓存只用了65%,新请求全部超时。
排查路径

  1. nvidia-smi --query-compute-apps=pid,used_memory --format=csv,发现有一个python3进程占了40GB;
  2. ps aux | grep <pid>,发现是某个Worker进程卡死了,没释放显存。

根因:Worker进程异常退出(如Ctrl+C),但GPU显存没被CUDA Context自动回收。这是CUDA的已知行为,需要手动清理。

解法

# 杀死所有Python进程(谨慎!) pkill -f "python3.*worker" # 强制重置GPU sudo nvidia-smi --gpu-reset -i 0,1 # 或者重启vLLM和Worker ``

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询