模型部署是个筐,什么都能往里装。从给一个分类模型写个接口,到把开源大模型包进一套企业级推理平台,表面上都是“把模型放到线上跑”,但背后的工程复杂度和技术栈完全不是一个量级。我早期做算法工程师时,最头疼的就是模型训练完了,交付给后端同学部署,对方问一句:这个模型怎么调?有没有API文档?显存占用多少?——全是答不上来的问题。后来自己跳到部署这条线,才把从单个模型服务到LLM推理平台这条路完整走了一遍,踩过不少坑,也摸索出一套可行的落地方案。这篇文章就基于“正式环境模型部署框架全景”这个主题,结合我实操中部署过的小模型和开源LLM经验,把整条链路拆开讲清楚:单模型服务怎么做、Docker容器化要注意什么、模型格式怎么选(GGUF、ONNX这些到底什么区别)、为什么最终要演进到GPU调度型的LLM推理平台,以及在这条路上你会遇到哪些高频坑。内容偏工程向,适合已经跑通过训练流程、准备涉足部署的算法工程师,也适合想在企业里落地一套私有化模型服务的后端或运维同学参考。
1. 整体思路拆解:为什么“只服务一个模型”和“搭建推理平台”是两种工程
先说结论:单模型服务和LLM推理平台之间的差距,不是“多部署几个模型”就能填上的。它背后是两套不同的设计哲学。
单模型服务解决的是“点”的问题。模型训好了,格式转好(比如ONNX或者TorchScript),用FastAPI或者Flask包一个接口,挂到一台GPU服务器上,发给调用方一个POST地址就算完事。这套方案的核心关键词是“直接”:链路短、问题定位快、资源占用可控。如果模型是几百万参数的图片分类器,或者一个几B的向量化模型,这种做法完全够用,甚至是最优解。
LLM推理平台解决的则是“面”的问题。它不仅要把多个LLM(比如Qwen、Llama、GLM的多个尺寸版本)跑起来,还要处理并发调度、显存复用、连续批处理(Continuous Batching)、Prefix Caching、多租户隔离,甚至还要接到Agent体系、RAG知识库、评测系统里面。这个时候,你需要的不是一个“接口”,而是一套“运行时基础设施”。最典型的变化是:你不再自己写model.predict()然后包一层HTTP,而是把模型交给vLLM/TensorRT-LLM这类推理引擎,再在其上叠加网关、路由、负载均衡、监控面板。
所以,当你准备设计部署架构时,第一件事不是选工具,而是回答一个问题:你是在交付一个“模型”,还是在运营一套“推理服务”?
这类比的场景很多——比如装修:“装个灯泡”和“给全屋做智能照明系统”都是用电,但前者换灯泡就行,后者需要布线、网关、App联动。如果你的目标只是给业务方一个模型调用入口,直接上重型推理平台反而会把自己绕晕,维护成本高得离谱。但如果你团队里已经有五六个模型、多个业务线共用同一批GPU,那就必须往推理平台的方向走。
从我的实际经历来看,很多团队犯的错误是:一上来就想搭一套Kubernetes + vLLM + 网关的大平台,结果模型推理大小、格式都没理顺,后面的调度策略、推理引擎版本、显存配置全都跟着变形。再就是反过来——模型已经多到没法管理了,还在一个个手写服务脚本,显存碎片化严重,GPU利用率低到发指。
正确的做法是分阶段演进:
| 阶段 | 形态 | 核心任务 | 技术栈关键词 |
|---|---|---|---|
| 第一阶段 | 单模型服务 | 把单个模型包成HTTP API | FastAPI、Flask、ONNX Runtime、TorchServe |
| 第二阶段 | 模型容器化 | 固化环境、可迁移部署 | Docker、Compose、NVIDIA Container Toolkit |
| 第三阶段 | 多模型托管 | 统一加载、切换、监控多个模型 | Triton、Ollama、GPUStack、ModelScope |
| 第四阶段 | LLM推理平台 | 并发调度、显存效率、多租户、可观测性 | vLLM、TensorRT-LLM、KServe、网关、Prometheus |
这篇文章重点讲第一到第三阶段怎么走,以及第四阶段的入口怎么迈——因为第四阶段是对前三阶段所有问题的一次汇总收敛,没有前面积累,直接跳过去大概率会在运维细节上崩盘。
2. 核心细节解析:模型格式、推理引擎与容器化三件套
2.1 模型格式:ONNX、TorchScript、GGUF 到底该怎么选
部署聊得再high,第一步永远是“模型怎么交出来”。但很多同学在这个环节就卡住了——torch.save()出来的.pth文件,后端拿到根本不知道怎么加载。这不怪后端,PyTorch的state_dict天生就不适合做线上推理交换格式,它强依赖模型定义代码和Python环境版本。
部署层常用的三套格式,各有各的适用面:
ONNX(Open Neural Network Exchange)是跨框架交换格式的老牌方案。它的优势是计算图完全静态化、可被ONNX Runtime深度优化,支持FP16量化、动态轴设置。图像模型、检测模型、结构化的非LLM模型,转ONNX基本是标配。转换时最常用的工具是torch.onnx.export(),需要你固定输入尺寸、指定dynamic_axes(允许batch维度变化时用),这些细节可以直接决定导出后能不能被对方顺利加载。
TorchScript是PyTorch自家生态的产物。用torch.jit.trace或torch.jit.script把模型编译成可脱离源码运行的图,好处是复用PyTorch的算子生态、不必额外装runtime,坏处是版本绑定较强——PyTorch小版本升级时ScriptModule的兼容性偶尔出问题。我的习惯是:如果上下游都是PyTorch生态,TorchScript优先;如果对方可能用服务端推理框架(Triton、TensorRT),一律先用ONNX中转。
GGUF是llama.cpp社区主导的格式,也是当前中小规模LLM本地部署事实标准。它把tokenizer、模型权重、rope参数、对话模板全部打包进一个文件,有极其细粒度的量化方案(Q4_K_M、Q5_0、Q8_0等)。跑GGUF最直接的工具是llama.cpp和基于它的Ollama。你可能会问:为什么LLM部署圈不流行ONNX?原因是大模型内部有大量动态shape操作(beam search、KV Cache分配),ONNX的静态图机制做起来非常啰嗦;而GGUF天生就为LLM transformer架构优化过,更有社区生态直接支持。
实操经验:转换模型时,优先看四件事——算子支持清单(比如torch.where在ONNX某些版本会炸)、动态维度(batch_size是否可调)、数值精度(FP16还是量化)、是否包含decode/generate循环(LLM里这层逻辑不归模型管,推理框架会自己实现)。
2.2 推理引擎和运行时:为什么“装个驱动”不够
模型格式确定之后,接下来是推理引擎。有人会在这一步偷懒:直接用PyTorch原生的torch.inference_mode做推理。单模型阶段这套能跑,但一上并发就完蛋——torch.compile和CUDA Graph只提升单次推理的速度,不解决GPU显存复用和并发调度问题,尤其是LLM这种“每个请求消耗KV Cache、并发度直接决定整卡吞吐”的场景。
真正到LLM阶段,得换推理引擎。当前主流的几个:
- vLLM:最受欢迎,核心特性是PagedAttention(按页管理KV Cache)、Continuous Batching(新请求不用等整批结束)、Prefix Caching(共享前缀时可以省一次预填充)。部署入口是
vllm serve或Python API,支持HuggingFace格式和AWQ/GPTQ量化格式,也支持加载GGUF(底层调llama.cpp后端)。 - TensorRT-LLM:NVIDIA官方出品,推理延迟最优,但要编译成TensorRT引擎(plan文件),模型版本换来换去非常痛苦,适合极端追求延迟的场景。
- llama.cpp:CPU/GPU通吃、极其轻量。树莓派、MacBook、老破旧服务器都能跑。带
server子命令,拉起HTTP服务。它的关键价值不在性能,而在“什么硬件都能跑”的兜底能力。 - Ollama:把llama.cpp封装成了一套类似Docker的体验——
ollama pull qwen2.5:7b、ollama run qwen2.5:7b就完事。你基本不用关心引擎细节,一个大模型就是“一个镜像”。对不同尺寸的模型切换也很方便。
部署GGUF时最常犯的错是:用HuggingFace的transformers直接加载GGUF量化文件。这俩不是一路的。transformers不直接支持GGUF,得先跑llama.cpp或者Ollama。也有人用ctransformers库,但维护状态很一般。最省心的路子:小模型/边缘设备用llama.cpp,有交互管理需求用Ollama,GPU资源充足、追求高吞吐直接用vLLM。
2.3 Docker与GPU透传:一次配置,终生受益
容器化是整个部署环节里“投入产出比”最高的一步。把模型、引擎、依赖库、启动脚本全部锁进镜像,你就把“为什么你机器能跑我机器不能跑”这种问题彻底锁死在门外。
关键不是用Docker,而是用NVIDIA Container Toolkit。装好之后,docker run --gpus all就能把宿主机的CUDA环境透传给容器,镜像里面不需要重复装驱动,只需要装CUDA runtime和cuDNN这类库层组件。
我的标准镜像骨架大致这样:
FROM nvidia/cuda:12.1.1-runtime-ubuntu22.04 RUN apt-get update && apt-get install -y --no-install-recommends \ python3.10 python3-pip curl && \ rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["python3", "server.py"]容器编排我用Docker Compose,而不是一上来就Kubernetes。Compose对单机的部署编排足够强大,声明式定义、一键启停、日志采集都有,配合resource限制字段还能给容器固定GPU显存。我自己踩过的坑提醒一条:Dockerfile里面千万不要COPY巨大的模型权重进镜像。模型权重单独放挂载目录,镜像只管代码和依赖,否则每次改一行代码都要重新推一个几个GB的镜像,CI会被你拖垮。
3. 实操过程:从手写单模型服务,到把 LLM 跑成全套推理服务
3.1 最小可用方案:一个模型的 HTTP 服务
先从我执行力最强的路径讲起:一个图片分类模型,怎么在半小时内变成一个能被业务调用的HTTPService。
首选FastAPI,比Flask好在自动生成OpenAPI文档、原生异步支持、pydantic做参数校验省心。模型加载用全局变量,不要每次请求都做初始化。核心的代码骨架:
from fastapi import FastAPI, UploadFile import onnxruntime as ort import numpy as np app = FastAPI() # 模型在模块加载时加载一次,进程内全局复用 session = ort.InferenceSession("model.onnx", providers=["CUDAExecutionProvider"]) @app.post("/predict") async def predict(file: UploadFile): data = await file.read() # 图像解码、预处理……省略 input_tensor = np.expand_dims(img, axis=0).astype(np.float32) outputs = session.run(None, {"input": input_tensor})[0] return {"class_id": int(outputs.argmax()), "prob": float(outputs.max())}这里有几个细节值得专门说:
一是生产环境一定要把预处理逻辑放进服务端。很多人在客户端做预处理,传灰度图给服务,结果上线后图片尺寸不同、色彩空间不一致,模型效果直接下滑。宁可在服务里加个PIL.Image转RGB、resize到目标尺寸,也不要把不确定性留给调用方。
二是并发模型下,ONNX Runtime的Session不是线程完全安全的。我的习惯是起多个Session实例放进一个池子,或者用GIL保护,避免一个Session被多线程乱打。FastAPI异步接口里如果调同步的session.run(),建议用run_in_executor丢到线程池去,否则同步阻塞会卡住整个事件循环。
三是失败时的响应规范。接口要同时返回成功和失败的结构化JSON,HTTP状态码不要一律200——模型推理出NaN了,直接返回204或500,不要给调用方“拿到了200但结果全是null”这种幻觉。
3.2 正式环境容器化:一套组合拳打到底
写完成功跑通的单模型服务,下一步固定环境。我直接给一套我一直在用的Compose配置,照着改就能用:
version: "3.8" services: model-api: build: . runtime: nvidia environment: - NVIDIA_VISIBLE_DEVICES=0 - CUDA_VISIBLE_DEVICES=0 ports: - "8000:8000" volumes: - /data/models:/app/models:ro deploy: resources: reservations: devices: - driver: nvidia device_ids: ["0"] capabilities: [gpu] restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 5s retries: 3重点解释几个我一路上踩过坑的地方:
runtime: nvidia和NVIDIA_VISIBLE_DEVICES=0组合,会把这套服务钉死在物理GPU 0号上。如果机器有多卡,这个约束必须显式设置。否则很多推理框架默认“看见所有卡”,第一块卡被占满烧红了,其他卡还空着。- 健康检查(healthcheck)非常重要。Kubernetes里面做存活探针的时候,调的就是你容器里的
/health端点。这个端点不要只返回"ok"——我的习惯是顺带检查GPU显存可用量、模型权重是否已加载(权重懒加载模式下第一次预热可能还没完成),返回完整状态JSON,方便排查到底挂在哪一层。 - 模型权重挂载成只读(
:ro),防止容器内误改模型文件。这招是防呆,但能救命。
容器化之后,服务的可迁移性立刻上一个台阶。我曾在20系显卡机器上跑的容器,原封不动搬到4090服务器上启动,除了驱动版本需要更新外,代码和镜像零改动。
3.3 进阶实操:用 Ollama + GGUF 把 LLM 部署成本压到最低
接下来是LLM部署。如果只是想先把一个大模型在正式环境跑起来,别自己写vLLM代码了,先用Ollama,五分钟内搞定。
Ollama的模型管理像极了Docker。先拉模型:
ollama pull qwen2.5:7b-instruct-q4_K_M模型跑起来:
ollama serve ollama run qwen2.5:7b-instruct-q4_K_Mollama serve拉起一个默认端口11434的HTTP服务,POST /api/generate就能对话。这里我给一条我的实操配置:如果是正式环境,为Ollama准备一个独立服务配置,在/etc/systemd/system/ollama.service里加环境变量:
[Service] Environment="OLLAMA_MAX_LOADED_MODELS=2" Environment="OLLAMA_KEEP_ALIVE=5m" Environment="OLLAMA_HOST=0.0.0.0"OLLAMA_MAX_LOADED_MODELS=2的意思是同时最多常驻两个模型在显存里,防止第三个模型加载时把前面全挤出去,频繁换载模型导致首token延迟飙升。OLLAMA_KEEP_ALIVE控制在最近请求结束后模型在显存中驻留的时间——设太短模型会被频繁卸载,太长则浪费显存,推荐5~15分钟。
此时你算一只脚踏入了LLM推理的门槛:模型是GGUF格式、推理引擎是llama.cpp、部署形态是容器化服务。这个方案的上限是单模型并发几十路请求,满足内部工具调用、小规模知识库问答完全够用。
3.4 进入平台模式:vLLM 的部署和显存参数调优
等并发和模型数量再往上涨,Ollama就得让位给vLLM。vLLM目前最值得从Ollama迁移的理由,就是显存效率——Continuous Batching能让单张80GB的A100压出两倍于llama.cpp的吞吐,人多的时候优势尤其明显。
vLLM启动的官方姿势是命令行:
vllm serve /data/models/Qwen2.5-7B-Instruct \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --served-model-name qwen \ --port 8000这个命令里的参数背后全是算力管理学问,拆开讲:
--gpu-memory-utilization 0.9表示vLLM最多可用GPU显存的90%。不要写0.99。推理框架预留一部分显存给CUDA kernal、激活值和碎片化内存损耗。我测试下来,写0.95以上在部分卡上会直接OOM,留点余量才是稳定选项。
--max-model-len 8192是模型上下文窗口长度。这个值决定KV Cache预留多大。如果你知识库问答、长文档总结场景多,要特别策划这个数。显存的计算公式大概是:KV Cache大小 ≈ 2(K和V各一份)× 层数 × 头数 × 头维度 × 序列长度 × batch大小 × 字节数。拿Qwen2.5-7B(28层、28个KV头、每头128维,FP16)算,8192长度下每条序列的KV Cache大概占:2×28×28×128×8192×2字节 = 约4.3GB。也就是说,光上下文就吃掉快4.3G,多路并发线性膨胀。
所以正式环境规划显存时,不要只看“模型权重占了7B×2字节=14GB”,要把并发数和上下文长度一起算进去。这也是为什么vLLM的PagedAttention聪明——它像操作系统管内存一样管KV Cache,哪些token的KV暂时没用可以换出,空闲页能复用给其他请求。
vLLM起来之后,你的形态已经接近一个最小推理平台:有推理引擎、有批量调度、有多模型同时加载的能力(多实例不同端口),再配合前面的Nginx做路由和负载均衡,一个断开业务链路的LLM网关雏形就出来了。
3.5 要不要上GPUStack这类GPU资源调度层
如果团队内GPU服务器不止一两台,模型服务分散在多台机器,这时资源统计和调度会变成噩梦。我自己经历过一种“爽两天、苦两周”的局面:训练任务占卡、推理服务缺卡,大家靠微信喊话协调GPU。想彻底解决,可以引入GPUStack这类GPU资源管理和推理服务编排工具,它把GPU池化,按需分配。
GPUStack在设计上是“面向AI Infra的轻量调度方案”,比Kubernetes上手门槛低非常多。最合适当下的场景是让它在Windows/Linux机器上先接管各路GPU资源,然后在上面发布Ollama或vLLM的服务。它的界面会直接列出GPU型号、显存占用、正在运行的推理任务,点一下就能部署或销毁模型服务。对于不熟Kubernetes的团队来说,这是从“人肉调度”到“平台调度”的最平滑跳板。
不过GPUStack不是银弹。它本身不解决LLM推理引擎层面的问题,比如显存优化、连续批处理,它只解决“这台推理服务跑在哪张卡上”的问题。所以我的建议和标题一致:先有局部最优的“单模型服务”,再叠加平台层做“管理调度”,顺序不能反。
4. 常见问题与排查技巧实录
这一段全是真金白银的实践沉淀。我在部署模型的路上踩过的坑,大部分集中在这几张表里。
4.1 高频错误速查表
| 现象 | 大概率原因 | 快速排查命令/方法 |
|---|---|---|
容器内import torch报CUDA错误 | 镜像没装对CUDA runtime,或宿主机驱动版本太旧 | 宿主机nvidia-smi看驱动CUDA版本,容器内python -c "import torch; print(torch.cuda.is_available())" |
| 模型推理第一句慢到爆炸 | 预填充+模型冷加载,没做预热 | 服务启动后先发一个哑请求预热,再做健康检查 |
| 并发请求后显存OOM | max-model-len或gpu-memory-utilization配得太满 | vLLM日志看KV Cache预留,用nvidia-smi观察显存波峰 |
| 多个模型互相挤掉线 | Ollama默认单模型常驻,无max_loaded_models配置 | 加OLLAMA_MAX_LOADED_MODELS环境变量,或迁移vLLM |
| 新模型接入业务,老模型效果倒退 | 模型混部时忘了隔离版本或环境 | 用不同容器/实例跑不同模型,模型版本通过路由头区分 |
| 请求偶发超时,概率无法复现 | 健康检查把容器误杀,重启期间拉长 | 调整/health检查频率和超时,把应用启动时间纳入重试窗口 |
| 张量并行数多加后延迟反而上升 | 小模型跨卡通信开销大于计算收益 | 把--tensor-parallel-size退回1,优先用数据并行(多实例负载均衡) |
4.2 显存问题的排查思路
显存不够是部署LLM最常用的问题。首先nvidia-smi看实际的占用,老被忽略的占用方包括:CUDA context(每进程固定占几百MB到GB不等)、推理框架的workspace、KV Cache预留、以及多进程加载模型导致权重重复拷贝。
排查显存问题时我的建议是:先关掉所有其他进程,用nvidia-smi --query-gpu=used_memory,total_memory,power.draw --format=csv -l 1观察五分钟的运行曲线,确认峰值到底发生在哪一阶段。如果峰值出现在请求涌入期,那就是KV Cache或并发batch的显存问题;如果一直顶满,可能是权重重复加载或容器间显存未隔离。给一个实用公式,估算7B模型的部署显存:7B权重FP16约14GB(Q4量化约4.5GB),8192长度上下文每请求约4.3GB KV Cache。部署一个7B模型做对话,单请求至少预留18~20GB显存;10并发则至少再翻倍。
4.3 Ollama部署后的可视化查询问题
热搜词里有一条“Ollama部署模型后如何可视化”,我也遇到过这类需求。Ollama本身没有图形界面,但可视化有两个实用的方向。
一个方向是为Ollama接上Open WebUI(原Ollama Web UI)。直接用docker run拉ghcr.io/open-webui/open-webui:main,把OLLAMA_BASE_URL指到Ollama服务地址,就得到一个带聊天界面、知识库上传、多模型切换的Web前端。这是给非技术人员演示LLM能力最高效的方案。
另一个方向是可视化调用链和资源指标,给运维同学看。在Ollama前面加一层Nginx,日志里记录请求路径、模型名、耗时;再用Prometheus抓Nginx的stub_status,配合Grafana出面板,能实时看到每台机器上模型请求量和显存使用。这个方法花半小时就能搭好,效果却和商业APM接近。
我在实际铺Open WebUI时踩过一个坑:容器里访问宿主机Ollama时,不能用localhost,必须用host.docker.internal。Docker默认网络隔离下,容器的localhost是容器自己。这个坑极其常见,排查时别浪费时间去改Ollama配置——只需把OLLAMA_BASE_URL换掉。
4.4 模型切换与兼容性问题
LLM的部署还有一个索引问题:模型版本在飞轮式更新,API的协议、参数名、tokenizer版本都可能不同。今天用Llama的chat template,明天换Qwen的<|im_start|>格式,如果推理层不做抽象,业务层代码就得跟模型绑死。
我的方案是加一个“模型网关”层。它本质上是一个代理服务,负责三件事:路由(把请求转到正确的推理实例)、协议转换(把多模型不同的prompt格式归一化成OpenAI格式)、降级策略(模型A挂了自动切模型B)。因为是代理,你可以用一个相对轻量级的框架——我在实际项目中用Go写过一个,网上也有成熟的开源项目直接改名就能用。网关内部记录每个上游的鉴权、超时参数、健康状态,业务方只需要知道一个统一的OpenAI兼容API地址就够了。
模型切换体验还能再往上做一层——把GGUF或Safetensors权重挂到对象存储上,部署平台按需拉取。这样换模型不再需要登录服务器操作文件,在管理界面上点击切换即可,配合版本校验,回滚也只是指回旧版本号。
5. 代码结构建议:把部署代码和训练代码拆干净
前面讲的都是工程操作,但有一条贯穿始终的架构原则,值得单独拿出来说:训练代码和部署代码必须分家。
很多算法工程师喜欢在部署目录里复用训练时的预处理方法类、模型定义类、甚至训练配置。上线初期图方便,等模型迭代两三版之后,你会在部署代码里看到训练时的数据增强、断点逻辑、分布式初始化——这些东西不但没用,还成了bug温床。
推荐的部署仓库结构大概是这样的:
model-deploy/ ├── api/ # 接口层,负责请求解析、响应返回 ├── core/ # 推理核心,模型加载、预处理、后处理 ├── engines/ # 引擎适配(onnxruntime / vllm / ollama wrapper) ├── schemas/ # 请求响应模型定义(pydantic) ├── configs/ # 模型配置、部署参数、环境变量模板 ├── docker/ # Dockerfile、Compose、构建脚本 ├── tests/ # 接口级测试、回归测试(pytest) └── monitoring/ # Prometheus指标、日志采集配置core目录下的推理逻辑应该与框架无关,只依赖模型输入输出;engines目录才是适配层。比如同样一个LLM问答接口,可以分别用Ollama和vLLM实现两个engine类,各自实现相同的generate(prompt, params)方法,切换引擎的时候业务代码零改动。这套抽象说实话不复杂,实际收益却大到离谱——模型升级、引擎换代、机器迁移时,你不需要改任何接口代码,只需要换引擎配置。
另外,模型特征的回归测试(pytest)必须纳入部署流水线。我在实际项目中深受其害:模型A迭代成模型B之后,原有的一批badcase突然全部回归。如果没有回归测试,这些case只能等线上用户反馈才会暴露。所以我在每个推理服务仓库里都固定放了一批标准输入输出对,每次模型更新都要跑一遍,输出的置信度或回复文本必须通过相似度阈值,否则阻断发布。
6. 踩坑实录:那些文档里不会写的事
最后分享几个我在多个项目里反复踩到的、难度不高但极其耗时的坑,给你提前打预防针。
第一个是CUDA版本隐性兼容问题。很多框架编译时针对特定CUDA版本,比如vLLM 0.5版本支持CUDA 12.1,但如果你服务器的驱动只支持12.0,装好之后一启动直接报CUDA driver version is insufficient。排查这个问题的第一步永远是nvidia-smi看驱动信息,再进容器看框架版本要求。不要上来就重新编译vLLM,那会花掉半天时间。
第二个是模型文件损坏。从镜像仓库拉大模型权重时,网络闪断可能导致权重不完整,但文件名一样,加载时不一定会立刻报错,而是推理结果逐渐出现诡异行为。古早的面试题“K8s里微服务怎么检查bad apple”,到这里就变成“怎么确认模型完整性”。解决办法是拉取后用SHA256校验一下(很多仓库会提供哈希值),或者用model_card元数据比对参数量与文件大小。我还会故意跑一条预热请求输入一个已知的样例输入,对比输出哈希是否与预期一致——这招比啥都管用。
第三个是日志与监控缺失导致现场难排查。很多团队部署完服务之后,控制台打的日志全是乱码,存到/var/log没人看。我的建议是至少把这三类指标全面接起来:请求量(QPS)、延迟分位数(p50/p95/p99)、错误率(按HTTP状态码分)。再加一个系统资源指标。不用上复杂的平台,Prometheus + Grafana足够,针对LLM再加显存利用率和吞吐。有了这套基础可观测性,排查问题的时间能压缩三分之二。
说到这,我还想提一个观点:模型部署的难点从来不在模型本身,而在模型所处的系统边界。你既要懂一点CUDA的底层逻辑,又要明白HTTP协议的调性,还要会写Dockerfile、能排Compose报错,充分地说明这是一个“全栈型”的脏活累活。但也是因为脏,才留给能坚持把细节抠完的人巨大的竞争力。
我自己刚入门时踩得最深的一个坑,是把精力全放在“如何把一个模型跑起来”上,忽视了“如何让它稳定跑住”。后来把一个模型从能跑升级到能扛(有监控、有预热、有优雅退出、有自动重启、有回归测试)之后,才发现正式环境部署真正值钱的,是那百分之二十的“韧性工程”。这也是为什么我到现在还强烈建议每一个做部署的团队,在跑新模型之前,先把“模型的健康检查、预热、超时、降级方案”四个基础模块补齐,这四样东西的优先级,完全可以排在复杂选型的前面。