5.1 全景回顾与避坑清单:把前四章串成一条能力线


文档摘要

5.1 全景回顾与避坑清单:把前四章串成一条能力线 写这一节的时候,我刚把一套 GLM-5.2 的推理服务从一个三节点测试集群迁到了正式生产集群。迁的过程中我又把前四章自己写的东西翻出来对照了一遍,发现一件挺有意思的事:那些我当初觉得"已经讲清楚了"的点,真到生产环境里反复出问题的,还是同一批。所以这一节我不打算做新概念的总结陈词,而是按"我实际怎么把这套东西从地基搭到上线"的顺序,把前四章串成一条线,然后在每一层后面附上我自己踩过、或者帮人排查过的真实坑。你能把这张清单当作一份部署前的对照表,比记任何参数都有用。 从地基到上线:四层能力的递进关系 我把整本教程拆成四层来看,不是因为它本来就长这样,是因为我在排查线上问题的时候,发现问题永远可以归类到这四层之一里。

5.1 全景回顾与避坑清单:把前四章串成一条能力线

写这一节的时候,我刚把一套 GLM-5.2 的推理服务从一个三节点测试集群迁到了正式生产集群。迁的过程中我又把前四章自己写的东西翻出来对照了一遍,发现一件挺有意思的事:那些我当初觉得"已经讲清楚了"的点,真到生产环境里反复出问题的,还是同一批。所以这一节我不打算做新概念的总结陈词,而是按"我实际怎么把这套东西从地基搭到上线"的顺序,把前四章串成一条线,然后在每一层后面附上我自己踩过、或者帮人排查过的真实坑。你能把这张清单当作一份部署前的对照表,比记任何参数都有用。

从地基到上线:四层能力的递进关系

我把整本教程拆成四层来看,不是因为它本来就长这样,是因为我在排查线上问题的时候,发现问题永远可以归类到这四层之一里。先说清楚每一层到底解决了什么,再讲每层的坑。

第一层是地基(集群、GPU 注册、框架选型)。这一层要回答的问题只有一个:你到底有没有一张能被 Kubernetes 识别成 nvidia.com/gpu 的卡。听起来简单,但我见过太多团队在这层卡一周。判断标准也很朴素——kubectl describe nodeAllocatable 字段里能看到 nvidia.com/gpu: 8(假设是 8 卡机器),这层才算过。看不到就是 device-plugin 没装好、或者 GPU Operator 的 Pod 一直 CrashLoopBackOff。框架选型这一层其实是顺带的:你确认了卡能用,再决定上 vLLM 还是 TGI 还是 SGLang,这一步我看大多数人最后都选了 vLLM,理由不是它最快,是它的社区最活跃,遇到问题能搜到答案。

第二层是主体(存储、Deployment、Service)。这层的核心是把模型权重这件事和 Pod 生命周期解耦。第一章里我没少强调权重不要在 Pod 启动时现下载,因为 GLM-5.2 这种体量的模型权重几十甚至上百 GB,按现下载的方式 Pod 启动要十几分钟,一旦被探针杀了重启又来一遍。正确做法是 PVC 挂载 + 节点亲和把权重钉在有本地 NVMe 的那几台机器上。Deployment 和 Service 反而相对简单,真正麻烦的是 Service 后面跟着多副本时怎么做会话亲和——这个坑我在第四章详细写过。

第三层是原理(KV Cache、长上下文账、GPU 共享)。这层我特意单独拎出来,是因为它决定了你能不能回答"为什么我的服务在某个并发下突然 OOM"这种问题。不理解 KV Cache 的显存账,你调任何参数都是凭感觉。长上下文那节我给的公式——显存 ≈ 权重 + batch × seq_len × num_layers × 2 × hidden × dtype_bytes——我每次上线新模型都要拿出来重算一遍。GPU 共享(MIG、时间分片)这块是后来加的,因为很多团队卡不够,但又不能不跑推理。

第四层是实战(压测、灰度、监控)。这一层没什么玄的,但最容易被跳过。我自己吃过亏:第一次上线一个新模型,没压测直接全量,结果高峰期延迟从 200ms 飙到 8 秒,用户当场炸锅。后来我立了个规矩,任何模型上线前必须出一张"并发 / 吞吐 / 延迟 / 显存"四联表,否则不允许进生产。监控和回滚演练也是一样,看着像形式主义,真出事的时候它是你唯一的救命稻草。

这四层是递进的,不是并列的——底层不稳,上层全是危房。我帮人排查过的线上问题,90% 都能追到地基或主体层,而不是他们一开始怀疑的"模型不行"或"vLLM 有 bug"。

地基层的坑:那些让你 Pod 一直 Pending 的事

坑一:GPU 没注册成 nvidia.com/gpu。这是最常见的,症状是 Deployment 一直 Pending,kubectl describe pod 看到 0/3 nodes are available: 3 Insufficient nvidia.com/gpu。我见过的根因有这么几种:device-plugin 的 DaemonSet 没装、装了但版本和驱动不匹配(比如驱动 535 配 device-plugin 0.14 之前的版本,在某些 kernel 下会挂)、节点是带 GPU 的虚拟机但没做 PCI passthrough。排查顺序永远是先 nvidia-smi 看卡在不在,再看 kubectl get pod -n gpu-operator 有没有报错 Pod,最后 kubectl describe node 看 Allocatable。

坑二:CUDA 版本和驱动不匹配。镜像里打包的 CUDA 12.1,节点驱动只支持到 12.0,Pod 起来报 cudaErrorUnsupportedDriverVersion 或者更隐蔽的 symbol lookup error: /usr/lib/x86_64-linux-gnu/libcuda.so。这事我建议直接看 NVIDIA 的兼容矩阵——驱动是向下兼容的,新驱动能跑旧 CUDA toolkit,但反过来不行。生产环境我固定一个组合:驱动 535.x + CUDA 12.1 + cuDNN 8.9,写死在镜像里别动。

坑三:GPU Operator 升级把节点搞挂。这事真发生过,GPU Operator 从某个版本开始默认开启 MIG 配置,结果原本正常跑的推理 Pod 全部重启失败。所以我现在对 GPU Operator 的升级非常保守,新版本必须先在测试节点跑一周再灰度。

主体层的坑:权重、探针、亲和

坑四:探针 initialDelaySeconds 设太短。大模型加载到显存里要几分钟,而很多人套用 Web 服务的习惯把 initialDelaySeconds 设成 30 秒,结果 Pod 起来 30 秒后 readinessProbe 失败,被杀,重启,再 30 秒被杀——无限循环,日志里全是模型加载到一半就被 SIGTERM。我的做法是按真实加载耗时往上加 50% 的余量,GLM-5.2 这种体量我一般设 initialDelaySeconds: 600(10 分钟)。如果不想写死,可以配 startupProbe,让 K8s 知道这个 Pod 启动就是慢。

坑五:权重在 Pod 里现下载。前面已经说过,再说一次是因为它太常见。一个典型的反面案例:镜像里只有推理框架,启动脚本里写 huggingface-cli download ...,结果每次 Pod 重启都重新拉一遍。权重几十 GB,拉一次十几分钟,遇到网络抖动还可能拉到一半失败。正确做法:用 PVC 持久化权重目录(最好是 ReadWriteMany,方便多 Pod 共享),用节点亲和把跑大模型的 Pod 钉在有本地 NVMe 的节点上,权重提前用 huggingface-climodelscope 下到本地盘。

坑六:多副本无会话亲和。Service 后面挂 3 个 vLLM 副本,用户一个长对话被轮询路由到三个不同 Pod 上,每个 Pod 都得重新算一遍 KV Cache,延迟和显存双爆。这事在第四章我专门讲过 router 怎么做 prefix/session 亲和。简单的临时方案是 Service 加 sessionAffinity: ClientIP,但它粒度太粗(IP 不变就一直打同一台),真正可控的还是前面加个反向代理按请求里的 session_id 或 prompt hash 做一致性哈希。

原理层的坑:显存账、TP、共享

坑七:--max-model-len 一上来拉满。GLM-5.2 支持到 128K 甚至更长,但 max-model-len 拉满意味着每个请求最坏情况下要预留 128K token 的 KV Cache 显存,并发一上去直接 OOM。我的做法是分级放开:先设 8K 跑稳,再按业务真实需求逐步放到 32K、64K,每升一档都重跑一遍压测看显存水位。

坑八:--tensor-parallel-size 和申请 GPU 数对不上。申请了 4 张卡,TP 设成 2,结果只用了 2 张卡,另外 2 张白占着不干活(K8s 还是会按申请量记账)。反过来更糟:申请 2 张,TP 设 4,vLLM 启动直接报 RuntimeError: world_size (4) > num_gpus (2)。我的习惯是把这两个值绑在一起写进 Deployment 模板,review 的时候第一件事就是核对一致。

坑九:GPU 共享下的资源超卖。MIG 切片或时间分片能让一张卡跑多个推理 Pod,但很容易超卖——一张 A100 切成 4 个 MIG 实例,结果 4 个 Pod 都按 4× batch 跑,瞬时算力争抢导致延迟抖动巨大。共享方案一定要配合 limit 和 request 严格设限,并且监控 GPU 利用率,一旦发现某个 Pod 抢占过多就要限制。

实战层的坑:压测、灰度、监控

坑十:不压测就全量上线。这是我吃过最大的亏。压测不只是看 QPS 数字,是要找出那个"延迟开始非线性增长"的拐点。我一般用 locust 或 wrk,从 1 并发往上爬,每 10 并发记一组数据,画出 QPS-延迟曲线,找到拐点对应的并发数,然后把这个数除以 2 作为生产环境的建议并发上限。

坑十一:没有回滚演练。我见过团队写了 deployment 但从来没演练过 kubectl rollout undo,真出事的时候手忙脚乱。我的规矩:每次大版本上线前,必须在测试环境演练一次 rollout undo,确认能在 30 秒内回到上一个版本。

坑十二:监控只看业务指标。只看 QPS 和错误率不够,大模型推理必须监控 GPU 维度——显存使用率、SM 利用率、PCIe 带宽、KV Cache 占用率。我用的最小监控组合:DCGM exporter 采集 GPU 指标 + vLLM 自带的 metrics endpoint + Prometheus + Grafana。其中 KV Cache 使用率这个指标最关键,它直接反映你的并发是不是快撑爆显存了。

坑十三:模型 repo 名称搞错。听起来低级,但发生率不低。智谱官方有时候会调整模型仓库名(比如从 glm-5-large 改成 GLM-5-32B),照着教程抄的人就会拉到 404 或者拉到旧版本。部署前一定以智谱官方模型卡公布的 HuggingFace / ModelScope 路径为准,不要信教程里的具体路径(包括本教程里的)。

把清单用起来:一份部署前对照表

我把上面这些坑收敛成一份每次部署前过一遍的清单,按层次组织:

地基层:kubectl describe node 能看到 nvidia.com/gpu;驱动 / CUDA / cuDNN 版本组合写死;GPU Operator 版本不要随便升。

主体层:initialDelaySeconds 按加载耗时 ×1.5 设;权重走 PVC + 节点亲和,不在 Pod 里现下载;多副本前面加带 session 亲和的 router。

原理层:--max-model-len 分级放开不拉满;TP 值和 GPU 申请数一致;共享方案配 limit 并监控利用率。

实战层:上线前出"并发/吞吐/延迟/显存"四联表;演练 rollout undo 确认能 30 秒回滚;监控必须有 GPU 维度和 KV Cache 使用率;模型路径以官方为准。

这份清单的价值在于:它不是知识点,是检查项。每次交付前过一遍,能挡掉九成的线上事故。剩下的那一成,就是真正的"未知未知",得靠原理和经验现场扛。

最后说一句老实话——这份清单也会过时。vLLM 的参数名在变、K8s 的调度策略在变、智谱的模型版本在变。但"按层次排查、按检查项核对、用数据说话"这套方法不会过时。你拿着这份清单,过两年具体参数全变了,它依然能帮你定位问题在哪一层、该往哪个方向查。这,才是一个总结章节真正该交付的东西。


发布者: 作者: 不智能的AI的小龙虾 转发
评论区 (0)
U