手上有一张卡,正确的顺序不是先挑量化档位,而是先把这张卡扣掉系统与显示占用后真正能交给模型的显存算出来,再除以每参数字节,得到这张卡能承载多少参数,最后才反过来决定 FP16、8bit 还是 4bit,以及上下文长度开多大。先下完权重再试,最大的浪费不是带宽,而是把“参数量放不下”和“上下文开太大”混在一起排查,最后不知道是哪一项超的。
判断方向:显存占用不等于模型文件大小,权重、KV cache、框架固定开销是三笔独立的账。操作上,先按“可用显存 ÷ 每参数字节”筛掉明显放不下的量化档位,再用只加载不推理的启动命令核对峰值,最后从 2048 起逐档抬高上下文长度,记录每档峰值和是否 OOM。适用场景是单卡本地跑通验证;多卡、张量并行、MoE 激活值波动大时,需要结合环境重新确认。风险边界:算出来的只是容量上限,吞吐与首字延迟不在这一步的结论范围内。
把权重显存、KV cache 和框架开销拆成三笔账
第一笔是权重。算法只有一行:权重字节数 ≈ 参数量 × 每参数字节。参数量取模型 config 里的字段或权重文件索引里的张量总数,不要用“文件多大就占多少显存”去估,因为磁盘上的 4bit 文件是按块压缩存储的,加载后还要展开成运行时张量,两者不是一回事。算完建议再留一成左右的余量,量化权重里的 scale、zero-point 以及未量化的 embedding、lm_head 通常按高精度存放,会让实际占用高于纯乘法的结果。
第二笔是 KV cache,它不随模型大小固定,而是随上下文长度和并发数线性增长。可以按下面的骨架估:
KV 字节数 ≈ 2 × 层数 × KV头数 × head_dim × 序列长度 × 并发数 × 每元素字节数
# 2 表示 K 和 V 各一份
# KV头数取 config 中的 num_key_value_heads(GQA 模型小于注意力头数)
# 每元素字节数:FP16 为 2,FP8 KV 为 1
第三笔是框架自身的固定占用,包括 CUDA context、通信缓冲区、计算图捕获等。观察方法比计算公式可靠:先用一个极小的模型或空载进程跑一次,用 nvidia-smi 读空闲基线;再启动真正的模型,看启动日志里权重占用、非 torch 占用、剩余可分配给 KV cache 的显存通常是分开打印的,按日志字段名逐项对齐,差额就是框架开销。三笔账相加仍小于显存总量,才有继续调档位的必要。
按卡上可用显存倒推能承受的量化档位
先算可用显存:可用显存 = 显卡总显存 − 系统与显示占用 − 框架预留。带桌面的机器上,显示输出和驱动会吃掉一部分,建议先跑 nvidia-smi 看空闲时的 used 值,把它当作已占用,而不是直接从标称容量开始算。再把剩下的显存除以每参数字节:
| 量化档位 | 每参数字节(建议取值) | 适用判断 |
|---|---|---|
| FP16 / BF16 | 2 | 可用显存充足,且对精度敏感时优先 |
| 8bit(INT8 / FP8) | 约 1 | 需要保留较多上下文或并发时的折中 |
| 4bit(AWQ / GPTQ 等) | 约 0.5,实际建议按 0.55~0.6 估 | 显存紧张、且能接受精度取舍时 |
举例说明用法而非给结论:若某卡可用显存为 M,用 4bit 时先用 M ÷ 0.6 得到参数量的宽松上限,再拿模型真实参数量去比。参数量超过上限就直接放弃这一档,不要指望靠缩上下文救回来,因为权重是固定成本。只有权重放得下、留有余量时,才进入下一步谈上下文长度。
写一条只加载模型、不发起推理的启动命令
目的是用最小成本验证前面的估算,所以进程起来后不要发任何请求,让它停在加载完成状态即可。下面给通用骨架,参数名按你实际使用的推理框架替换,路径和量化方式按权重实际情况填写:
# 通用骨架一:服务型推理框架
python -m <框架入口> \
`--model` /path/to/kimi-k3-weights \
`--quantization` <awq|gptq|fp8|none> \
`--max-model-len` 2048 \
`--max-num-seqs` 1 \
`--gpu-memory-utilization` 0.90
# 通用骨架二:GGUF 本地加载
./llama-server -m /path/to/model.gguf \
-ngl 99 \
-c 2048 \
-b 512
启动后另开一个终端读峰值,注意 memory.used 是一直在变的,用采样方式记录最大值:
nvidia-smi `--query-gpu`=memory.used,memory.total `--format`=csv -l 1
# 或持续观察
watch -n 1 nvidia-smi
把加载完成稳定后的 used 值记下来,它是“权重 + 框架开销 + 最小上下文 KV cache”的合计。若这个值已经接近显存总量,说明后续没有抬高上下文的余地;若明显低于,差值就是可以分配给 KV cache 的空间。
把上下文长度从最小值逐档加到目标值
不要一次把 max-model-len 设成目标值再试,那样只知道失败不知道边界在哪。建议从 2048 起,按 4096、8192、16384 逐档翻倍,每一档都完整重启进程,不要让上一次的显存残留影响读数。每档记录三件事:加载完成后的稳定显存、短请求一次后的显存、以及进程是否因 OOM 退出。
# 每档的执行顺序
1) 修改 `--max-model-len` 为目标档位
2) 重启进程,等待加载完成日志
3) 记录 nvidia-smi 的 used 稳定值
4) 发一条极短请求,再记录一次 used
5) 出现 OOM 或进程退出,该档记为不可用,回退上一档
两个容易忽略的点:一是 KV cache 通常在启动时就按 max-model-len 预分配,所以加载后的显存已经把上下文成本算进去了,不需要真的跑满长文本才能看出超没超;二是并发数 `--max-num-seqs` 会成倍放大 KV cache,验证阶段建议固定为 1,否则测出来的上限会被并发吃掉,无法归因。
把测试结果整理成一张选型表并标注不可行组合
零散数字没法复用,把上面几轮结果填进同一张矩阵表,行是显存规格,列是量化档位,格子里填“可用 + 实测能开到的最大上下文”,不可行的组合直接写不可行并注明原因(权重放不下 / KV cache 不足 / 框架开销过大)。
| 可用显存 | FP16 | 8bit | 4bit |
|---|---|---|---|
| 24 GB | 待测:多为不可行 | 待测:需缩上下文 | 待测:上下文余量小 |
| 48 GB | 待测 | 待测 | 待测:通常有余量 |
| 80 GB | 待测 | 待测 | 待测 |
表格里的格子必须来自你自己的逐档测试,不要抄别人的数字,因为显存占用和驱动版本、框架版本、是否量化 embedding、KV cache 的数据类型都相关。填完之后,跑不动的组合按下面的顺序降级,每一步只动一个变量:
- 先减并发:把
`--max-num-seqs`降到 1,确认是不是并发把 KV cache 撑爆了。 - 再缩上下文:把 max-model-len 退回到上一档可用值,这是最温和的降级。
- 最后降精度:从 FP16 换 8bit 再换 4bit,同时重新跑一遍逐档测试,因为换了量化方式后可用上下文上限会变。
- 以上都不可行时,只能换更小的模型规格或改用多卡,不要靠系统 swap 或磁盘缓存硬顶,那只会让推理慢到不可用。
验证方式很简单:任何一次配置变更,都以“重启进程 + 读稳定显存 + 发一条短请求不报错”三步确认,三件事都通过,这才算这个组合在这张卡上可用。