从单模型到LLM推理平台:vLLM、Triton与K8s架构演进实战
1. 从单模型到推理平台部署这件事到底在解决什么问题模型部署这个词听起来像是运维的活儿但真正做过一轮完整上线的人都知道它其实是算法、工程、基础设施三条线的交汇点。我最早接触模型部署的时候想法特别朴素把训练好的权重文件丢到一台带显卡的机器上写个 Flask 接口包一层能返回结果就算完事。这套做法在单模型、低并发、内部使用的场景下确实能跑但只要业务方开始问“能不能同时支持三个模型”“QPS 能不能上到 200”“显存不够了怎么办”这套土办法就会瞬间崩塌。所以这篇文章想聊的不是某一个工具的安装教程而是从单模型服务一路演进到 LLM 推理平台这条路上每一步到底在解决什么问题、为什么需要引入新的组件、这些组件之间的边界在哪里。核心关键词会围绕模型部署、LLM、vLLM、Triton、K8s展开但我会尽量把每个技术选型背后的“为什么”讲清楚而不是罗列一堆命令让你照抄。先说清楚这篇文章适合谁看。如果你现在还在用python app.py起一个模型服务想搞清楚下一步该往哪走那这篇对你有用如果你已经在用 vLLM 或 Triton 做推理但说不清楚为什么要在它们之上再套一层 K8s那这篇也能帮你把架构逻辑理顺如果你只是想知道“vLLM 是什么”“Triton 和 vLLM 到底啥关系”我也会用生活化的类比把它讲明白。整篇内容会按照单模型服务 → 推理引擎选型 → 服务编排框架 → 容器化与集群调度 → 平台化能力这条主线推进每一层都会给出可落地的配置思路和踩坑经验。需要提前说明的是下面涉及的具体版本号、参数值都是基于我在实际项目中验证过的常见实践不同硬件、不同模型规模下最优解会有差异你需要根据自己的场景做调整。我不会给你一个“万能配置”因为那种东西不存在。2. 单模型服务的起点与它的天花板2.1 最朴素的部署方式长什么样单模型服务最典型的形态就是一个 Python 进程加载一份权重对外暴露一个 HTTP 接口。用 FastAPI 或者 Flask 都行核心逻辑就三步启动时把模型 load 进显存收到请求时做预处理推理完把结果返回。这种模式我称之为“手工作坊式部署”它的优点是直观、调试方便、出问题容易定位缺点是几乎没有任何工程化能力。我拿一个实际例子来说明。假设你有一个 7B 的对话模型用 transformers 库直接加载代码大概是这样from fastapi import FastAPI from transformers import AutoModelForCausalLM, AutoTokenizer import torch app FastAPI() model_path /models/qwen-7b-chat tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) app.post(/generate) def generate(prompt: str, max_new_tokens: int 512): inputs tokenizer(prompt, return_tensorspt).to(cuda) with torch.no_grad(): outputs model.generate(**inputs, max_new_tokensmax_new_tokens) return {text: tokenizer.decode(outputs[0], skip_special_tokensTrue)}这段代码能跑但它有几个致命问题。第一没有并发处理能力FastAPI 默认是单 worker一个请求进来会把整个进程占住第二个请求只能排队。第二没有显存管理模型加载后显存就固定占着多个请求同时进来很容易 OOM。第三没有批处理每个请求单独推理GPU 利用率极低一张 A100 跑 7B 模型可能只能撑住个位数 QPS。第四没有健康检查和优雅退出进程挂了就是挂了没有任何自愈能力。2.2 单模型服务的三个硬伤我把单模型服务的问题归纳成三个硬伤理解了这三个硬伤你就能明白后面为什么要引入那么多组件。硬伤一吞吐量上不去。大模型推理的本质是矩阵运算GPU 最擅长的就是并行计算。但单请求推理时GPU 大部分时间在等数据搬运算力利用率可能只有 10% 到 20%。解决办法是批处理把多个请求拼成一个 batch 一起送进 GPU这样算力利用率能拉到 60% 以上。但批处理有个矛盾不同请求的生成长度不一样如果等所有请求都生成完再返回短请求就要被长请求拖死。这就是后来continuous batching连续批处理要解决的问题也是 vLLM 这类推理引擎的核心价值之一。硬伤二显存利用率低。transformers 默认的 KV Cache 分配方式是预分配一整块连续显存按最大序列长度来算。比如你设 max_seq_len 是 4096那每个请求都要预留 4096 长度的 KV Cache哪怕实际只用了 100 个 token。这导致显存浪费极其严重一张 80G 的 A100 可能只能同时服务十几个请求。vLLM 提出的PagedAttention就是借鉴操作系统虚拟内存分页的思路把 KV Cache 切成固定大小的 block按需分配显存利用率能提升好几倍。硬伤三没有服务治理。单进程服务没有副本、没有负载均衡、没有熔断降级、没有灰度发布。业务量一上来你只能手动多起几个进程然后用 Nginx 做轮询。但模型服务不像普通 Web 服务每个副本都要占一份显存一台机器能起的副本数是有物理上限的。这时候就需要一个能感知 GPU 资源的调度系统也就是后面要讲的 K8s。提示如果你现在的场景是内部工具、日调用量几百次、对延迟不敏感那单模型服务完全够用不要为了“技术先进”去过度设计。架构演进的前提是业务压力真的到了否则就是给自己找麻烦。2.3 从单模型到多模型的第一个分叉口当业务方提出“我要同时上线对话模型、embedding 模型、重排序模型”的时候你就面临第一个架构决策是每个模型起一个独立服务还是用一个框架统一管理独立服务的做法是每个模型一个进程、一个端口好处是隔离性好一个模型崩了不影响其他模型坏处是资源浪费每个进程都要占一份基础显存而且调用方要记住一堆端口。统一管理的做法是用一个推理服务框架把多个模型挂载到同一个进程或同一组进程下好处是资源复用、接口统一坏处是框架本身有学习成本而且模型之间可能互相影响。我的经验是模型数量少于 3 个、且模型之间没有依赖关系时独立服务更简单一旦模型数量超过 5 个或者需要做模型版本管理、A/B 测试就必须上统一框架。这个分叉口的选择直接决定了你后面是走向 Triton 这条路还是走向 vLLM 自研网关这条路。3. 推理引擎选型vLLM、Triton 与它们的边界3.1 vLLM 到底解决了什么问题vLLM 是一个专门为 LLM 推理设计的引擎它的核心卖点就是前面提到的 PagedAttention 和 continuous batching。我用一个生活化的类比来解释 PagedAttention传统的 KV Cache 分配就像你去餐厅吃饭服务员不管你几个人都给你留一张十人大桌哪怕你只有两个人。PagedAttention 则是把桌子拆成一个个小格子你几个人就占几个格子剩下的格子还能给别人用。这样一来同样大小的餐厅显存能同时接待的客人请求就多了好几倍。continuous batching 解决的是另一个问题。传统批处理就像公交车必须等所有人都上车了才发车到站了所有人一起下。continuous batching 则像地铁随时有人上车随时有人下车不用等。具体到推理场景就是当一个请求生成结束后立刻把新的请求塞进这个 batch 的空位而不是等整个 batch 都结束。这两个机制叠加让 vLLM 的吞吐量相比朴素 transformers 实现能提升 10 倍以上。vLLM 的部署方式很直接官方提供了 OpenAI 兼容的 API Serverpython -m vllm.entrypoints.openai.api_server \ --model /models/qwen-7b-chat \ --tensor-parallel-size 1 \ --max-model-len 4096 \ --gpu-memory-utilization 0.9 \ --port 8000这里有几个参数值得展开说。tensor-parallel-size是张量并行度简单说就是你把模型切到几张卡上跑。7B 模型单卡 24G 显存够用设 1 就行70B 模型至少需要 4 张 A100 80G设 4。gpu-memory-utilization是显存占用比例默认 0.9意思是留 10% 给其他进程。这个值设太高容易 OOM设太低浪费显存我一般设 0.85 到 0.92 之间具体看机器上还有没有其他进程。max-model-len是最大序列长度这个值直接决定 KV Cache 的 block 数量设太大浪费显存设太小长文本请求会报错需要根据业务实际输入长度来定。3.2 Triton 的定位和它跟 vLLM 的关系很多人搞不清楚 Triton 和 vLLM 的关系以为它们是竞品其实不是。Triton 是 NVIDIA 推出的推理服务框架它的定位是多模型、多框架的统一服务层。你可以把 Triton 理解成一个“模型服务容器”它支持 TensorFlow、PyTorch、ONNX、TensorRT 等多种后端也支持 Python 自定义后端。vLLM 则是一个专门的 LLM 推理引擎它只做 LLM 这一件事但做得足够深。它们的关系可以这样理解Triton 是餐厅的前厅经理负责接待客人、安排座位、调度后厨vLLM 是专门做川菜的大厨只负责把川菜做好。你可以让 Triton 同时管理川菜大厨vLLM、粤菜大厨TensorRT、面点师ONNX客人点什么菜Triton 就派给对应的厨师。如果你只做川菜那直接找大厨也行不一定需要前厅经理。在实际项目中什么时候需要 Triton我的判断标准是当你需要在一个服务里同时提供多种类型的模型推理时Triton 的价值才体现出来。比如你有一个推荐系统需要同时跑 embedding 模型、排序模型、LLM 生成模型那用 Triton 统一管理就很合适。如果你只做 LLM 推理那直接用 vLLM 的 API Server 更简单少一层抽象少一层故障点。Triton 集成 vLLM 的方式是通过 Python backend你需要写一个model.py实现execute方法在里面调用 vLLM 的LLM类。这种集成方式比直接用 vLLM API Server 复杂但换来的是 Triton 的动态批处理、模型版本管理、指标监控等能力。如果你的团队已经有 Triton 的运维经验那集成 vLLM 是顺理成章的如果从零开始我建议先用 vLLM 原生 API Server等有多模型统一管理需求时再考虑 Triton。3.3 推理引擎选型的决策表为了让你更直观地做选择我把常见场景和推荐方案整理成一张表场景特征推荐方案理由单模型、内部使用、QPS 10transformers FastAPI简单直接调试方便单模型、生产环境、QPS 50vLLM API Server吞吐量高OpenAI 兼容多模型、同构框架Triton 对应 backend统一管理版本控制多模型、含 LLM 和非 LLMTriton vLLM Python backend兼顾统一管理和 LLM 性能需要极致低延迟TensorRT-LLM编译优化延迟最低边缘设备、资源受限llama.cpp / Ollama量化支持好CPU 也能跑这张表里的 QPS 阈值只是粗略参考实际要看模型大小和硬件配置。比如 0.5B 的小模型单卡 vLLM 跑到几百 QPS 都有可能70B 的大模型单卡可能只有个位数 QPS。注意选型时不要只看吞吐量还要看首 token 延迟和生成速度这两个指标。有些场景用户对首 token 延迟极其敏感比如对话有些场景更看重整体吞吐比如离线批量生成。vLLM 在吞吐上优势明显但在极端低延迟场景下TensorRT-LLM 可能更合适。4. 容器化与 K8s 调度让模型服务真正跑在生产环境4.1 为什么模型服务需要 K8s单机跑 vLLM 能解决推理性能问题但解决不了资源调度和高可用问题。我举个实际场景你有三台 GPU 服务器一台 A100 80G两台 A100 40G现在要部署 7B、13B、70B 三个模型。7B 可以放 40G 卡上13B 需要 80G 卡或者两张 40G 卡做张量并行70B 需要至少 4 张卡。如果手动分配你得记住哪个模型在哪台机器上、哪个端口、显存还剩多少。一旦某台机器挂了你得手动把模型迁到其他机器上还要改调用方的配置。K8s 解决的就是这个问题。它把 GPU 当成一种可调度资源你只需要在部署描述里声明“我需要 1 张 GPU、24G 显存”K8s 会自动帮你找合适的节点。节点挂了K8s 会自动把 Pod 调度到其他健康节点上。需要扩容改一下副本数就行。这套机制在普通 Web 服务上已经很成熟但 GPU 调度有几个特殊点需要处理。第一个特殊点是GPU 资源声明。K8s 原生不支持 GPU需要安装 NVIDIA Device Plugin安装后节点上的 GPU 会以nvidia.com/gpu这种资源名暴露出来。你的 Pod 里这样声明resources: limits: nvidia.com/gpu: 1第二个特殊点是显存感知调度。K8s 默认只认 GPU 数量不认显存大小。也就是说一个需要 40G 显存的 Pod 可能被调度到只有 24G 显存的卡上然后启动时 OOM。解决办法是用GPU 共享或者MIGMulti-Instance GPU技术把一张物理卡切成多个逻辑卡每个逻辑卡有独立的显存配额。A100 支持 MIG可以切成 7 个 10G 的实例消费级卡不支持 MIG只能用时间片共享这种共享对 LLM 推理不太友好因为显存是硬隔离的。第三个特殊点是模型加载时间。LLM 权重文件动辄几十 G从镜像里加载或者从网络存储拉取都要好几分钟。如果每次 Pod 重启都重新拉模型那滚动更新会非常慢。常见的做法是用PVCPersistent Volume Claim把模型文件挂载到 Pod 里模型文件只拉一次后续 Pod 直接挂载。更激进的做法是用模型缓存机制比如把模型预热到节点本地 SSD 上Pod 启动时直接从本地加载。4.2 一个可落地的 vLLM on K8s 部署方案下面我给一个实际验证过的部署方案用 Deployment 管理 vLLM Pod用 Service 暴露服务用 PVC 挂载模型。首先是 PVC 定义假设你已经把模型文件放到了 NFS 或对象存储挂载的 PV 上apiVersion: v1 kind: PersistentVolumeClaim metadata: name: model-pvc namespace: llm-serving spec: accessModes: - ReadOnlyMany resources: requests: storage: 200Gi storageClassName: nfs-storage然后是 DeploymentapiVersion: apps/v1 kind: Deployment metadata: name: vllm-qwen-7b namespace: llm-serving spec: replicas: 1 selector: matchLabels: app: vllm-qwen-7b template: metadata: labels: app: vllm-qwen-7b spec: containers: - name: vllm image: vllm/vllm-openai:v0.6.3 command: [python, -m, vllm.entrypoints.openai.api_server] args: - --model/models/qwen-7b-chat - --tensor-parallel-size1 - --max-model-len4096 - --gpu-memory-utilization0.9 - --port8000 ports: - containerPort: 8000 resources: limits: nvidia.com/gpu: 1 volumeMounts: - name: model-volume mountPath: /models readOnly: true readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 120 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 180 periodSeconds: 30 volumes: - name: model-volume persistentVolumeClaim: claimName: model-pvc这里有几个细节值得展开。readinessProbe的initialDelaySeconds设了 120 秒因为 vLLM 加载 7B 模型大概需要 1 到 2 分钟如果设太短Pod 还没加载完就被判定为不健康会反复重启。livenessProbe的延迟设得更长因为存活检查一旦失败会触发重启必须确保模型完全加载后再开始检查。gpu-memory-utilization设 0.9 是留 10% 给 CUDA context 和其他开销如果你的机器上还有其他进程占显存这个值要调低。Service 的定义就比较标准apiVersion: v1 kind: Service metadata: name: vllm-qwen-7b-svc namespace: llm-serving spec: selector: app: vllm-qwen-7b ports: - port: 8000 targetPort: 8000 type: ClusterIP如果集群外需要访问可以用 Ingress 或者 NodePort。生产环境我推荐用 Ingress 内部网关不要直接暴露 NodePort因为模型服务通常需要鉴权、限流、审计这些能力这些应该在网关层做而不是让每个模型服务自己实现。4.3 K8s 部署模型服务的常见坑我在 K8s 上部署模型服务踩过的坑比在单机上多得多。这里挑几个最典型的说说。坑一GPU 节点没有打标签Pod 被调度到 CPU 节点。K8s 默认调度器不知道哪些节点有 GPU如果你不给 GPU 节点打标签Pod 可能被调度到没有 GPU 的节点上然后启动失败。解决办法是给 GPU 节点打标签然后在 Pod 里用nodeSelector或affinity指定kubectl label nodes gpu-node-1 acceleratornvidia-a100nodeSelector: accelerator: nvidia-a100坑二多个 Pod 争抢同一张 GPU。如果你在一台 8 卡机器上部署了 8 个单卡 Pod但某个 Pod 的gpu-memory-utilization设成了 0.95那它可能把整张卡的显存都占了导致其他 Pod 启动失败。解决办法是给每个 Pod 设置合理的显存上限或者用 MIG 做硬隔离。我一般会在节点上预留 1 到 2 张卡给系统组件不让业务 Pod 占用。坑三模型文件挂载失败导致 Pod 一直 CrashLoopBackOff。这个问题的根源通常是 PVC 的 access mode 不对。模型文件是只读的应该用ReadOnlyMany但有些存储插件不支持ReadOnlyMany只能用ReadWriteMany。另外NFS 挂载在大文件读取时性能可能很差加载 70B 模型可能要十几分钟。如果对加载速度有要求建议用本地 SSD 做缓存或者用支持高性能读的分布式存储。坑四滚动更新时服务中断。默认的 Deployment 滚动更新策略是先起新 Pod 再停旧 Pod但模型服务启动慢新 Pod 可能几分钟都起不来这期间如果旧 Pod 被停了服务就中断了。解决办法是设置maxUnavailable: 0和maxSurge: 1确保新 Pod 完全就绪后再停旧 Pod。同时要设置合理的terminationGracePeriodSeconds给旧 Pod 足够时间处理完存量请求。提示K8s 的initialDelaySeconds和模型加载时间强相关不同模型差异很大。7B 模型可能 1 分钟70B 模型可能 5 分钟以上。建议先用kubectl logs观察实际加载时间再设置探针参数不要拍脑袋填。5. 平台化能力从能跑到好用还差什么5.1 模型网关统一入口与流量治理当你的集群里跑了十几个模型服务每个服务一个 Service调用方要记住一堆地址这时候就需要一个模型网关。网关的作用是把所有模型服务聚合成一个统一入口调用方只需要知道模型名称不需要知道背后的服务地址。网关还负责鉴权、限流、计费、日志、路由这些横切关注点。我见过很多团队用 Nginx 或 Envoy 做网关这没问题但模型网关有一些特殊需求。比如按 token 计费需要解析请求和响应里的 token 数量按模型路由需要根据请求里的 model 字段转发到不同的后端流式响应需要支持 SSEServer-Sent Events长连接。这些需求用通用网关实现起来比较别扭所以后来出现了专门的大模型网关比如 One API、Higress AI 网关等。如果你不想引入新组件用 Envoy 加自定义 filter 也能实现但开发维护成本不低。我的建议是模型数量少于 5 个时直接用 K8s Service Ingress 就够了超过 5 个或者需要计费、多租户隔离再考虑上专门的模型网关。5.2 可观测性指标、日志与追踪模型服务上线后最怕的就是“用户说慢但你不知道慢在哪”。可观测性三件套——指标、日志、追踪——在模型服务场景下有特殊的采集需求。指标方面除了常规的 QPS、延迟、错误率模型服务还要关注GPU 利用率、显存占用、KV Cache 命中率、批处理大小。vLLM 原生暴露了 Prometheus 指标路径是/metrics你可以用 Prometheus 抓取然后在 Grafana 上做面板。我常用的几个关键指标是vllm:gpu_cache_usage_percKV Cache 使用率、vllm:num_requests_running正在处理的请求数、vllm:time_to_first_token_seconds首 token 延迟。这几个指标能帮你判断显存是否吃紧、是否需要扩容。日志方面模型服务的日志量很大尤其是开启 debug 级别后每个请求的输入输出都会打出来。生产环境建议只记录元数据请求 ID、模型名、token 数、延迟不要记录完整的 prompt 和 response一是隐私问题二是日志量太大。如果需要排查问题可以用采样或者按请求 ID 临时开启详细日志。追踪方面模型服务通常处于调用链的末端上游可能是业务系统、Agent 框架、RAG 管道。用 OpenTelemetry 做全链路追踪能帮你定位是检索慢、还是模型推理慢、还是网络传输慢。vLLM 本身不直接支持 OTel但你可以在网关层做埋点把请求 ID 透传到 vLLM 的日志里实现关联。5.3 弹性伸缩什么时候该扩什么时候不该扩模型服务的弹性伸缩比普通 Web 服务复杂得多因为模型加载慢、显存占用大、扩容成本高。普通 Web 服务扩容新 Pod 几秒钟就起来了模型服务扩容新 Pod 可能要几分钟才能就绪而且新 Pod 会占一份显存如果节点显存不够扩了也起不来。所以模型服务的弹性伸缩策略要更保守。我一般用HPAHorizontal Pod Autoscaler基于自定义指标做扩容指标选vllm:num_requests_waiting排队请求数而不是 CPU 利用率。因为模型服务的瓶颈是 GPU 和显存CPU 利用率通常很低用 CPU 做指标根本不准。当排队请求数持续超过阈值比如 5超过 2 分钟才触发扩容缩容则要更慢比如排队数为 0 持续 10 分钟才缩。另外缩容时要确保没有正在处理的请求。vLLM 的优雅退出需要时间如果直接 kill Pod正在生成的请求会中断。K8s 的terminationGracePeriodSeconds要设得足够长同时 vLLM 要支持优雅退出新版本已经支持。如果业务对中断零容忍那缩容策略要更保守甚至可以考虑不自动缩容只手动缩。5.4 多模型共存的资源隔离策略一个平台上跑多个模型资源隔离是绕不开的问题。最彻底的隔离是物理隔离每个模型独占一台机器或一张卡互不影响但成本最高。其次是MIG 隔离一张 A100 切成多个实例每个实例有独立的显存和计算单元隔离性接近物理隔离但只有 A100、H100 等高端卡支持。再次是时间片共享多个模型共用一张卡通过 CUDA 的时间片调度轮流使用隔离性最差一个模型把显存占满其他模型就崩了。我的经验是核心业务模型用物理隔离或 MIG边缘业务模型用共享。比如对话主模型用独占卡embedding 模型和重排序模型可以共享一张卡。共享时一定要设置显存上限vLLM 的gpu-memory-utilization就是干这个的但要注意这个参数是进程级的如果多个进程共卡每个进程都要设小一点加起来不超过 1。还有一个容易被忽略的点是网络带宽隔离。模型服务加载权重、传输请求响应都要走网络如果多个模型服务共用一张网卡大模型的权重加载可能把带宽占满导致其他模型服务响应变慢。解决办法是给关键模型服务做 QoS 限流或者用 SR-IOV 做网卡虚拟化。这个在中小规模集群里不常见但规模上去后一定会遇到。6. 常见问题与排查技巧实录6.1 模型加载失败类问题问题vLLM 启动时报CUDA out of memory但显存明明够。这种情况通常是gpu-memory-utilization设太高或者机器上有其他进程占着显存。排查步骤先用nvidia-smi看显存占用确认没有僵尸进程然后把gpu-memory-utilization从 0.9 降到 0.8 试试如果还不行检查模型是否比预期大比如你以为加载的是 7B实际是 13B。还有一个隐蔽原因是CUDA context 占用每个进程初始化 CUDA 时会占几百 MB 显存如果机器上跑了很多小进程累积起来也不少。问题模型加载到一半卡住日志没有报错。这通常是存储读取慢导致的。如果模型文件在 NFS 上大文件读取可能超时。排查方法在节点上手动dd读一下模型文件看读取速度。如果速度只有几十 MB/s那加载 70B 模型约 140GB要几十分钟。解决办法是把模型文件预热到本地 SSD或者换用高性能分布式存储。6.2 推理性能类问题问题QPS 上不去GPU 利用率却很低。这是典型的批处理没生效。排查步骤先看 vLLM 日志里的running requests数量如果一直是 1说明请求没有并发进来可能是上游网关做了串行处理或者客户端是同步调用。再看gpu_cache_usage_perc如果很低说明 KV Cache 没吃满可以适当增大max-model-len或max-num-seqs。如果这两个都正常但 GPU 利用率还是低那可能是模型太小GPU 算力过剩这种情况可以考虑把多个小模型合并到一张卡上。问题首 token 延迟很高但生成速度正常。首 token 延迟高通常是prefill 阶段慢原因可能是输入 prompt 太长或者 batch 里混了长 prompt 请求。解决办法是开启chunked prefillvLLM 支持把长 prompt 分块处理避免阻塞短请求。另外可以设置prefill 优先级让短请求优先处理。如果业务允许还可以对超长 prompt 做截断或摘要。6.3 K8s 调度类问题问题Pod 一直 Pending事件显示Insufficient nvidia.com/gpu。这说明集群里没有可用的 GPU 资源。排查步骤kubectl describe node看 GPU 节点的资源分配情况确认是否所有 GPU 都被占用了。如果确实占满了要么扩容节点要么把一些低优先级的 Pod 缩容。还有一种可能是 GPU 节点被打了 taintPod 没有对应的 toleration导致无法调度。问题Pod 启动后一直不 Ready但日志显示模型已加载完成。这通常是探针配置问题。检查readinessProbe的路径是否正确vLLM 的健康检查路径是/health不是/healthz或/。另外检查端口是否匹配vLLM 默认端口是 8000如果你改了--port探针端口也要改。还有一种可能是探针的initialDelaySeconds太短模型还没加载完就开始检查导致一直失败。6.4 常见问题速查表现象可能原因排查方法解决方案启动 OOM显存不足或参数过大nvidia-smi 看占用降低 gpu-memory-utilization加载卡住存储读取慢dd 测试读取速度预热到本地 SSDQPS 低批处理未生效看 running requests检查上游并发配置首 token 慢prefill 阻塞看请求长度分布开启 chunked prefillPod PendingGPU 资源不足describe node扩容或缩容Pod 不 Ready探针配置错误检查路径和端口修正探针参数滚动更新中断新 Pod 启动慢看更新策略maxUnavailable 设 0多模型互相影响资源未隔离看显存和带宽占用MIG 隔离或限流注意排查问题时先看指标再看日志。指标能告诉你“哪里不对”日志能告诉你“为什么不对”。很多新手一上来就翻日志结果被海量日志淹没效率很低。正确的顺序是Grafana 看异常指标 → 定位到具体 Pod → 看该 Pod 的关键日志 → 复现问题。6.5 几个我踩过的坑和对应的经验第一个坑是镜像版本不匹配。vLLM 的镜像版本和 CUDA 版本、PyTorch 版本强相关用错版本会导致各种奇怪的错误。我现在的做法是生产环境固定一个验证过的镜像 tag不要用latest也不要在生产环境随便升级。升级前先在测试环境跑一轮压测确认没问题再上。第二个坑是模型文件权限问题。用 PVC 挂载模型文件时如果文件权限是 root 所有而容器里跑的是非 root 用户就会读不到文件。解决办法是在 PVC 里设置fsGroup或者在构建镜像时把模型文件复制进去并设置正确权限。我一般用fsGroup方案更灵活。第三个坑是K8s 节点时钟不同步。模型服务的日志时间戳如果和网关不一致排查问题时会很痛苦。K8s 节点默认用 UTC 时间如果你的业务系统用本地时间日志对不上。解决办法是统一时区或者在日志采集层做时间转换。这个坑不常遇到但遇到一次就够你查半天。第四个坑是GPU 驱动版本和 CUDA 版本不匹配。节点上的 NVIDIA 驱动版本决定了支持的 CUDA 最高版本如果容器里的 CUDA 版本高于驱动支持的版本就会报错。排查方法是nvidia-smi看驱动版本然后对照 CUDA 兼容性表。生产环境建议锁定驱动版本不要随便升级。7. 架构演进路线与个人经验总结7.1 一条务实的演进路线如果你现在还在单模型服务阶段我建议的演进路线是这样的第一阶段单模型 vLLM。把 transformers 换成 vLLM吞吐量立刻提升一个数量级。这个阶段不需要 K8s单机跑就行重点是调好gpu-memory-utilization和max-model-len这两个参数。第二阶段多模型 Docker Compose。模型数量多了之后用 Docker Compose 管理多个 vLLM 容器每个容器一个模型端口区分。这个阶段重点是做好端口规划和显存分配避免容器之间抢显存。第三阶段K8s 集群 基础调度。当机器数量超过 3 台或者需要高可用时上 K8s。重点是配好 GPU Device Plugin、节点标签、探针参数。这个阶段不要追求大而全先把基本调度跑通。第四阶段模型网关 可观测性。模型数量超过 5 个后加网关统一入口加 Prometheus Grafana 做监控。这个阶段重点是建立运维规范比如模型上线流程、版本管理、回滚机制。第五阶段平台化 弹性伸缩。当业务量波动大、需要自动化运维时上 HPA、模型缓存、多租户隔离。这个阶段是长期演进不要一步到位根据实际痛点逐步补齐。7.2 一些反直觉的经验最后分享几个我在实际项目中总结的、可能和主流观点不太一样的经验。经验一不是所有模型都值得上 vLLM。如果你的模型是 BERT 类的 embedding 模型或者小规模的分类模型用 vLLM 反而更慢因为 vLLM 的调度开销比直接推理还大。这类模型用 Triton 的 ONNX backend 或者 TensorRT 更合适。vLLM 的优势只在自回归生成模型上体现。经验二K8s 不是银弹小规模场景用 Docker Compose 更省心。我见过一些团队只有两台 GPU 机器、三个模型硬上 K8s结果运维成本比收益还高。K8s 的价值在规模规模不到它就是负担。判断标准很简单如果你手动管理容器的时间超过每周 2 小时那就值得上 K8s否则再等等。经验三模型加载时间比推理时间更值得优化。很多人花大量时间调推理参数却忽略了模型加载时间。一个 70B 模型从 NFS 加载要 10 分钟从本地 SSD 加载只要 1 分钟这 9 分钟的差距在滚动更新、故障恢复时是致命的。我的做法是所有生产模型都预热到节点本地 SSD用 DaemonSet 做预热任务Pod 启动时直接从本地加载。经验四监控指标不要贪多抓住三个核心就够。我见过有人给模型服务配了几十个监控指标结果告警天天响运维疲于奔命。我的经验是核心指标就三个排队请求数判断是否需要扩容、首 token 延迟判断用户体验、显存使用率判断是否要 OOM。其他指标作为辅助不要设告警。经验五模型版本管理要用镜像 tag不要用文件路径。我早期用文件路径区分模型版本比如/models/qwen-7b-v1、/models/qwen-7b-v2结果经常搞混。后来改成用镜像 tag 区分每个版本的模型打一个独立镜像部署时指定 tag回滚时切 tag 就行。这样版本管理清晰而且镜像可以推到镜像仓库多集群共享。这套架构我从单模型一路搭到平台化前后花了大概一年半时间中间踩的坑不计其数。现在回头看最大的体会是架构演进要跟着业务压力走不要提前优化。每个阶段解决当前最痛的问题就行不要想着一步到位。你现在觉得复杂的方案等业务量到了那个程度自然就理解了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →