1
DCU技术达人
海光 DCU 单卡 64GB 显存实测——vLLM 占到 57GB 不是泄漏,是预算
22小时前
22
## 1. 起因与测量口径 一次部署记录引出本篇。目标是在海光 DCU 单卡上起一个 32B 推理服务,事前估算为权重 15GB 出头、卡 64GB,判断余量充足。服务起来后 `rocm-smi` 读到的占用是 57GB。最初按显存泄漏排查,翻完日志后结论修正:这是 vLLM 的设计行为——它不是"用多少占多少",而是"说好多少就占多少"。 自此固定了一个流程:任何模型上卡前先算一遍显存账。本篇整理这套账的算法与实测数据。测量环境:海光 DCU K100_AI,单卡 HBM 64GB;vLLM 主机与容器两种部署,TP=1/2/4;实测模型 Qwen3-0.6B(bf16/float32)与 DeepSeek-R1-Distill-Qwen-32B(GPTQ int4、BF16)。数据来源两处:`rocm-smi` 的稳态读数,vLLM 启动日志的加载报告。口径说明:文中"每卡总占用"一律取服务加载完成后的稳态值,不采信启动阶段的瞬时峰值;权重列以加载日志的明确报告为准,不用估算值。 第一个要校准的数是卡的可用总量。K100_AI 标称 64GB,`rocm-smi --showmeminfo vram` 实读 **65520 MiB,约 63.98 GiB**。做预算时按 63.98 GiB 计,不按 64 算——几 GB 的差在关键场景就是够与不够的差别。 ## 2. 实测数据 以下四组均为实跑数据,非推算值: | 模型 | 精度 | 并行方式 | 每卡权重 | 每卡总占用 | 备注 | | --- | --- | --- | --- | --- | --- | | Qwen3-0.6B | bf16 | TP=1(vLLM) | 1.12 GiB | 很小 | 单卡轻松 | | Qwen3-0.6B | float32 | TP=1(自建推理栈) | ~3.2GB | ~3.2GB+ | 分类服务,不经 vLLM | | DeepSeek-R1-Distill-32B | GPTQ int4 | TP=2(容器内 vLLM) | ~4.5GB | **~55GB** | 权重共 9.11GB | | DeepSeek-R1-Distill-32B | BF16 | TP=4(vLLM) | 15.4GB | **~57GB** | 权重 8 shards 共 ~65GB,实测 ~19 tok/s(eager) | 表中最值得注意的对照是后两行:int4 方案每卡权重不到 5GB,总占用 55GB;BF16 方案每卡权重 15.4GB,总占用 57GB。权重相差三倍,总占用几乎持平。 数据出处说明,便于复现:权重列取自加载日志——int4 那次日志明确报告权重共 9.11GB;BF16 那次为 8 个分片、每个约 8.78GB,合计约 65GB。总占用列取自 `rocm-smi` 稳态值。BF16 那组附带测了速度:eager 模式约 19 tok/s,单路不快,但速度是另一笔账,本篇只记显存。 ## 3. 占用构成与预算机制 把总占用拆开,结构是: ``` 总占用 ≈ 权重 + KV cache 池 + 编译/激活开销 ``` 关键在第二项:KV cache 池由 vLLM 在启动时**一次性预分配**,大小默认按 `gpu_memory_utilization=0.9` 计算。只要这个参数不动,无论权重是 4.5GB 还是 15.4GB,池子都会把剩余显存吃掉绝大部分。于是第 2 节那对"权重差三倍、占用几乎相同"的数字得到解释——**占用反映的是被允许使用的量,不是被需要的量**。 把参数摆进公式: ``` KV cache 池 ≈ gpu_memory_utilization × 总显存 − 权重 − 激活开销 ``` 实测归纳的取值建议: | 场景 | 建议值 | 理由 | | --- | --- | --- | | 独占一卡,追求最大并发 | 0.9(默认) | 池子越大并发越高 | | 卡上可能有其他任务 | 0.8 | 给别人留量,也给自己留退路 | | 多卡 TP 且某卡被共用 | 按**最紧张那张卡**算 | TP 下池子每卡各建一份 | | 自建推理栈 | 不适用 | 无池子概念,按需分配 | 一个需要纠正的理解:0.9 不是"OOM 时自动收敛的上限保护"。它是启动时的预分配,启动那一刻就划走。调高它不会让推理变快,只会让池子变大;只有确实需要更多并发上下文时才值得动。 多卡 TP 场景另有一点容易误读:池子不是在多张卡上划一大块共享空间,而是**每张卡各建一份**。因此第 3 节表里"某卡被共用"的行才要求按最紧张的那张卡算预算——共用卡上留出的余量,是给别的任务用的,不是给池子用的。 ## 4. 两个边界案例 32B 的两条边界单独记录,选型时直接引用: - **32B BF16 单卡**:纯权重约 62GB,逼近 63.98 GiB 总量且未计 KV cache。结论:装不下,必须 TP。 - **32B int4 TP=1**:权重约 18 GiB,单卡装得下,但实测中撞上反量化 kernel 的数值问题(输出异常)。量化 kernel 的平台适配属于深水区,本篇只记结论:装得下不等于跑得对,上线前必须先验证数值正确性。 ## 5. 编译开销与激活开销 公式第三项在实测中最常被低估,拆成两半记录。 **编译开销**:一次性,发生在启动阶段。vLLM 为 kernel 做编译与图捕获,峰值出现在启动时刻,运行后基本不涨。它的风险位置在时间轴上:与权重加载、池子预分配挤在同一个窗口里抢显存。预算算得太满时,先出事的往往不是运行中,而是启动直接失败。 **激活开销**:持续波动,随负载走——输入越长、batch 越大、输出越长,峰值越高。它有一个隐蔽性质:峰值不实时体现在占用读数上。池子早已划走,激活在池外余量里呼吸,平时看 `rocm-smi` 一片平稳,余量被顶穿时直接 OOM,中间没有渐进警告。"启动正常、运行中 OOM"因此是显存问题里最常见的失效形态:启动时的静态核算兜不住运行时的动态峰值。 第 2 节那行 float32 的 0.6B 走的是自建推理栈,不经 vLLM,约 3.2GB 占用中权重占大头,其余是框架运行时开销。这类栈没有预分配池,占用随用随涨。两类运行时的显存行为对照: | | vLLM(池化预分配) | 自建推理栈(按需分配) | | --- | --- | --- | | 占用曲线 | 启动即到顶,此后平稳 | 随负载爬坡 | | 占用大的含义 | 池子大,并发高 | 模型大或负载高 | | 主要风险 | 运行中 OOM(激活顶穿余量) | 高峰期占用失控 | | 适合 | 高并发在线服务 | 轻量单一功能的常驻服务 | 看监控前先确认服务属于哪一类。拿池化一派的"占用高"去诊断按需一派的问题,方向从第一步就错了。 ## 6. KV cache 的估算法与校准源 评估"给定并发与上下文长度,池子够不够",手算公式如下: ``` 每 token KV 字节数 = 2(K 与 V 两份) × 层数 × KV 头数 × 每头维度 × 每元素字节数 总需求 = 每 token 字节数 × 并发路数 × 单路上下文长度 ``` 以 32B 量级模型为例(64 层、8 个 KV 头、head_dim 128、bf16 两字节): ``` 每 token ≈ 2 × 64 × 8 × 128 × 2 = 262144 字节 ≈ 256 KiB 单路 4096 上下文 ≈ 1 GiB TP=4 时按卡数摊:每卡每路 ≈ 256 KiB ``` 需要明确这个手算的定位:**只用于建立直觉,不做上线依据**。层数、头数、GQA 压缩比、每元素字节数,任何一项记错,结果差几倍,且这类错误不报错,只表现为"说好的并发上限没出现"。 上线依据是 vLLM 启动日志里的两行实测值: ``` GPU KV cache size: XXXXX tokens Maximum concurrency for XXXX token tokens per request: XX.XXx ``` 前者是池子实际能容纳的 token 数,后者是给定单路长度时能支撑的并发路数。以这两个数为准,手算只用于在看懂它们之前不慌。 ## 7. 三个复现过的异常 ### 7.1 刚释放就启动,可用显存只剩个位数 上一个服务刚杀掉立刻起新服务,vLLM 启动时报告可用显存仅约 4.8 GiB(总量 63.98 GiB),而 `rocm-smi` 里已无进程。复测确认这是驱动侧的延迟释放:池子回收有几十秒滞后,属账未结清而非泄漏。操作上等几十秒再重跑 `rocm-smi --showmeminfo vram`,确认可用量回到 60GB 以上再启动;赶时间则换一张空闲卡。 ### 7.2 utilization 拉到 0.95,启动正常、运行中崩 0.9 改 0.95 后启动日志一切正常,池子确实变大;运行一段时间后在推理中途报显存不足退出。原因在 utilization 的扣减口径:它对激活与输出 buffer 的估算偏乐观,真实峰值激活出现在长输入、大 batch 的运行时刻,启动时留的余量兜不住。结论是不赌这 0.05——0.9 到 0.95 之间约 3GB 不是白给的量,是激活和输出 buffer 的安全垫。确实需要更多 KV cache 时,正确路径是加卡或换量化,不是刮掉这层垫子。 ### 7.3 TP 下各卡占用不均 TP=4 起服务,`rocm-smi` 显示三张卡 57GB、一张 49GB。排查按两步走:第一步核对权重 shard 是否均匀——TP 按卡切分权重,正常时每卡权重应几乎一致(实测 32B BF16 TP=4 为每卡 15.4GB);权重一致而占用不均,第二步排查那张"多出来"的卡是否被其他任务蹭用。对应两种改法:权重不均查并行配置与加载日志;权重均匀则用 `fuser -v /dev/kfd /dev/dri/*` 找出来路不明的进程,该挪的挪、该清的清。 ## 8. 选型结论 把全篇实测收敛为一张 64GB 单卡的能力表: | 模型规模 | 64GB 单卡结论 | 说明 | | --- | --- | --- | | 0.6B bf16 | 放心跑 | 权重 1.12 GiB,其余全给 KV cache,并发拉满 | | 8B bf16 | 放心跑 | 权重约 16GB,剩余约 47GB 给 KV cache 与并发 | | 32B int4 | 有条件 | 权重 ~18 GiB 装得下,先确认量化 kernel 在本平台数值正确 | | 32B bf16 | 单卡不行 | 纯权重 62GB,必须 TP≥2 | 收束成三句话:vLLM 时代的显存占用是预算概念而非需求概念,`gpu_memory_utilization` 定池子、权重只是先扣的一块;账本三个核对点——权重看精度与并行度、KV cache 看启动日志实测值、utilization 默认 0.9 不赌 0.95。顺序上坚持显存账先于模型选型:先算权重、估 KV、看并发,再定模型与卡数;反过来先看榜单再算账,通常停在"装是装下了,跑是跑不动"。
全部评论(0)
只看楼主
暂无数据
--
楼主
Level.2
0 帖子
楼主热帖
暂无数据