先回答判断方向:本地把 Hy3 起起来,卡点几乎都在权重占用上。权重占用由参数量和量化位宽两个数决定,先把这两个数落到纸上,再去调上下文和并发,试错次数会少很多;反过来先改配置后看日志,很容易把显存不足和配置写错混在一起。
先算权重占用上限再选量化档位,是本地部署 Hy3 比较省事的路径:用 参数量 × 位宽 ÷ 8 估出权重 GB,和 nvidia-smi 的 free 显存做减法,剩下的才留给 KV cache 与运行时开销。表内数值要按实际模型文件核对,位宽是名义值,真实占用会随权重格式、是否分片、上下文长度和并发数浮动;本页给的是可自填的骨架,不是某张卡上的固定结论。
用 nvidia-smi 和显存预算表算出权重占用上限
权重占用的通用算式是:权重占用(GB)≈ 参数量(以十亿计)× 量化位宽(bit)÷ 8。例如按 16 bit 存,占用大致是参数量数值的两倍;换成 8 bit 减半,4 bit 再减半。这个算式给的是下限,实际会略高,因为还有 embedding、归一化层和框架自身开销。先把总参数量和激活参数量分开记录:总参数量决定权重能占多大显存,激活参数量更多影响每次前向的计算量和部分激活显存,两者不能混成一格填。
先填这张表,再决定要不要试:
| 项目 | 填写值 | 来源/算法 | 说明 |
|---|---|---|---|
| 总参数量(B) | ___ | 模型 config | 权重占用的主体,按全部参数算 |
| 激活参数量(B) | ___ | 模型 config | 影响前向计算与部分激活显存 |
| 量化位宽(bit) | ___ | 启动配置 | 16 / 8 / 4 等,属名义值 |
| 权重占用(GB) | ___ | 总参数量 × 位宽 ÷ 8 | 是下限,实际略高于此值 |
| 显卡可用显存(GB) | ___ | nvidia-smi 的 free | 需扣掉其他进程已占用 |
| 余额(GB) | ___ | 可用显存 − 权重占用 | 为负就不要试这一档 |
读 nvidia-smi 时重点看 free 而不是 total:total 是卡的总容量,used 里包含别人的进程和桌面环境占用,只有 free 才是你能拿到的。可以先看一次总量,再在启动服务的过程中用轮询观察变化:
nvidia-smi `--query-gpu`=memory.total,memory.used,memory.free `--format`=csv
nvidia-smi -l 1 # 启动服务前后各观察一段时间,看 used 增量落在哪一步
如果 used 在加载权重阶段就开始持续攀升并接近 total,说明档位选高了;如果权重阶段很平、到请求进来才涨,问题多半在 KV cache 和并发。把这一步的读数填进上表再往下走。
在配置里固定量化档位与最大上下文长度
量化档位、最大序列长度、批大小三项互相牵制,只动其中一项通常看不到稳定结果。量化档位决定权重占用,最大序列长度和批大小共同决定 KV cache 占用,而 KV cache 是按并发份数复制的。下面是一份通用配置骨架,字段名以你实际使用的框架为准:
model_path: /path/to/hy3 # 占位,换成实际权重目录
quantization: <none|int8|int4> # 字段名以实际框架为准
dtype: <bfloat16|float16> # 与量化档位可能互斥,二选一
max_model_len: <4096> # 最大序列长度,占位
max_num_seqs: <1> # 并发/批大小,先给最小
gpu_memory_utilization: <0.9> # 允许框架占用的显存比例,占位
取舍方向可以这样定:位宽往下降,权重能塞进显存但输出质量可能变差,且低比特通常要配套特定权重量化格式,不是所有权重文件都能直接用;最大序列长度往下降,KV cache 线性变小,代价是长文本会被截断;并发数往下降,每份 KV cache 变少,代价是同时处理请求数变少。保守做法是首次启动把并发设成 1、长度设成一个你能接受的短值,先确认能起来,再逐项往上加。
起服务后从日志确认权重加载与显存分配阶段
服务起不来时,先分清失败停在哪一类,再决定下一步动作。日志关键行大致分三类:
- 权重加载类,形如
Loading weights ...、Loading checkpoint shards 1/2、文件路径或格式不匹配的报错。这类失败说明权重没读进来,多与路径、分片文件缺失、量化格式与实际权重不匹配有关,下一步是核对目录和权重文件清单。 - 显存分配类,形如
CUDA out of memory、KV cache 块数分配失败、gpu_memory_utilization相关提示。这类失败说明权重之后的空间不够,下一步是回到预算表,按第 5 节的顺序退让。 - 首 token 生成类,形如
Engine started、prefill/decode计时、请求级报错。进程已经起来、权重也加载完了,问题在请求侧,下一步是检查请求字段、输入长度和超时设置。
三类日志的处理方向不同:加载类改配置没用,先查文件;分配类改文件没用,先改档位和长度;请求类说明前两步已经过了,别再去动量化。把日志最后一段的报错行原样对照这三类,基本能定位卡点。
用一条最短请求验证服务真的能出结果
进程活着不代表能跑通,权重加载完成也不代表能出 token。先发一条最小请求,地址、端口和字段名以实际服务为准:
curl -s `--max-time` 60 http://127.0.0.1:<port>/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"<model-name>","messages":[{"role":"user","content":"你好"}],"max_tokens":16,"stream":false}'
期望返回里 choices[0].message.content 有一段非空文本,usage 里能看到输入输出 token 数。如果返回的是空内容或没有 choices 字段,说明服务通了但生成环节有问题。超时判定用 `--max-time` 卡住,同时另开一个终端跑 nvidia-smi -l 1:请求发出后 used 明显上升、请求结束回落,说明走的是正常路径;如果请求期间 used 不变,多半请求没进到模型。
显存仍然不够时按顺序退让的三步
按下面的顺序退让,每一步只动一个变量,改完重跑第 4 节那条最短请求:
- 降量化档位。从高比特降到低比特,权重占用按比例下降,通常是最直接的一步。代价是输出质量可能下降,而且低比特权重需要对应格式的文件,不是改个字段就能生效,改完要重新确认生成结果是否还能接受。
- 降最大上下文长度。KV cache 随序列长度增长,把
max_model_len调小能腾出空间。代价是超过长度的输入会被截断或直接拒绝,适合先验证服务能起来,长文本需求后面再单独评估。 - 降并发。把
max_num_seqs或批大小压到 1,每份 KV cache 只保留一份。代价是同时能处理的请求变少,排队时间可能变长,但单请求的显存占用会更可控。
如果三步走完余额仍然为负,说明这张卡在当前模型规模下确实放不下,此时再考虑换卡或换更小的模型,比继续调参数更省时间。