在本地跑零一万物这类模型,部署方式不是先挑框架再算资源,而是先把显存预算写下来,再看哪条路径能装下。结论通常不是唯一的:同一台机器,短上下文任务可以全精度单卡,长上下文任务可能只能用量化,显存更紧张时则要考虑把部分层放到 CPU 或内存上。拿不定主意的时候,先做的动作是填表和对日志,而不是反复装环境。
适合正准备在单机部署零一万物做推理、还在量化与 CPU 混合之间犹豫的人。先按目标任务估算权重显存与上下文显存,再按“单卡全精度 → 量化上卡 → 量化加部分卸载 → CPU 混合”的顺序逐级回退,每退一级都跑一次最小冒烟测试。能装下、能出字、首 token 时间可接受才继续;启动即分配失败或加载异常缓慢,说明这一步不成立。具体数值需要结合本机显卡、内存与框架版本确认。
先写下目标任务的输入输出长度
显存预算的第一行不是模型大小,而是任务形状。同一个模型,输入几百 token 的问答和输入上万 token 的文档摘要,占用差别很大,因为必须为上下文(KV cache)单独留空间。KV cache 会随输入长度、并发数和层数增长,装不下时往往表现为跑到一半才报显存分配失败,而不是一开始就崩。
估 token 数量不要凭感觉。可以先挑几条真实样本,贴进所用分词器或聊天模板里数一遍;按汉字字数粗估只是粗略口径,不同分词器差别不小,建议以分词器结果为准。把样本里最长的一条作为输入上限,输出长度按任务需要给一个上限,两者相加才是要预留的上下文长度。
填表模板可以先写成下面这样,再用真实任务替换占位内容:
任务名称: # 例:长文档问答
输入长度上限(tokens):
输出长度上限(tokens):
上下文预留(tokens): # 输入+输出,再留一部分余量
并发数: # 同时处理几条请求
模型参数量(B):
目标精度: # fp16 / bf16 / int8 / int4
显卡型号与数量:
单卡显存(GB):
可用内存(GB):
余量给多少没有统一答案,需要结合框架的调度方式和并发需求确认。只跑单条请求和要扛并发,对 KV cache 的要求不同,表里应该分开写两行。
按精度档位算权重占用
权重占用可以用一句话换算:参数量乘以每个参数占用的字节数。fp16 和 bf16 通常按 2 字节估算,fp32 按 4 字节,int8 按 1 字节,int4 按 0.5 字节左右。例如 7B 参数在 int4 下,权重部分大约落在 3.5GB 这一量级;同样参数量换成 fp16 就要翻好几倍。这只是换算思路,实际还要加上嵌入层、输出层、量化元数据和读取格式的开销,建议用磁盘上模型文件的实际大小做校核。
常见量化命名和实际位宽并不总是一一对应,看到名字别直接当成精确位宽:
- Q8_0 / INT8:接近 8 位,体积和精度都比较接近原模型。
- Q5_K_M、Q4_K_M、Q3_K、Q2_K:属于 k-quant 系列,名字里的数字是基准位宽,K 表示分组量化,M、S 表示混合比例,实际每参数占用通常略高于名字里的数字。
- AWQ、GPTQ 的 4bit 档:同样是 4 位权重,但走的是 GPU 推理路径,和 GGUF 的 Q4 不是同一种东西,别把两者的文件大小或显存占用直接对比。
换算完之后要记住,权重只占预算的一半,另一半是前面算出的上下文。两者相加再和单卡显存比较,才轮到判断走哪条路径。
对比单卡、量化与 CPU 混合三条路径
这三条路径不是三选一的优劣排序,而是资源不匹配时的回退顺序。
- 单卡全精度:成立条件是权重加上下文能装进单卡显存,且框架支持该精度的内核。适合显存充足、又想少折腾量化的场景。失败时通常在启动阶段就报显存分配失败,或加载完成后一处理长输入就崩。
- 量化上卡:成立条件是量化后的权重加上下文能装下,且所用框架支持该量化格式——GGUF 一般走本地 CPU/GPU 混合运行时,AWQ、GPTQ 走 GPU 推理框架。适合显存偏紧但仍想保留 GPU 速度的场景。失败时表现为量化内核不被支持、加载报错,或长上下文下依然显存不足。
- 量化加 CPU 混合:成立条件是内存足够放下被卸载的层,并且能接受速度明显下降。适合显存很小、只求把功能跑通的场景。失败时表现为卸载若干层后仍然分配失败,或速度慢到无法用于交互。需要说清楚,把层放到 CPU 或内存是让任务跑得起来的止血手段,不是性能优化方案。
回退顺序建议按“全精度单卡 → 量化上卡 → 量化加部分卸载 → 纯 CPU 混合”逐级往下退,每退一级都重新跑一次冒烟测试。不要一次改两个变量,比如同时改精度和卸载层数,否则出问题时分不清是精度导致的还是卸载导致的。
跑一次最小冒烟测试验证结论
冒烟测试的目标是用最小成本确认“这个组合装得下、能出字”。输入不用长,一句短问题就够,但要把上下文长度参数按预算表里的上限设置,否则测不出真实占用。启动命令骨架大致如下,参数名以所用框架为准:
# GPU 推理框架(示意,参数需按框架实际调整)
<launch_cmd> `--model` <模型路径> \
`--dtype` <fp16|bf16|int8> \
`--max-model-len` <上下文上限> \
`--gpu-memory-utilization` <0-1>
# 本地 GGUF 推理(示意)
<binary> -m <模型路径> \
-c <上下文长度> \
-ngl <放到 GPU 的层数> \
-p "<一句测试输入>"
启动日志里要盯两类信息:一是显存占用,多数框架会打印权重加载后的显存数字或 KV cache 预留情况;二是首次输出时间,也就是从发请求到第一个 token 出来的耗时。首次输出时间明显偏长时,先怀疑上下文设置过大或卸载层数过多,不要马上判定成显存不够。
区分显存不足和加载缓慢可以看阶段:显存不足通常在分配阶段就报内存错误,进程直接退出或抛异常;加载缓慢则是日志缓慢推进、显存占用逐步上升,最终仍能加载完成。前者要降精度或缩短上下文,后者要查磁盘读取速度和模型分片方式,处理方向相反,别混为一谈。
把选型依据记录成可复用配置
冒烟测试通过之后,把当时的判断依据写进一份带注释的配置,下次换机器或换任务时可以直接对照。需要记录的配置项至少包括:模型名称与参数量、量化格式与位宽、推理框架及版本、上下文长度上限、并发数、显卡型号与单卡显存、卸载层数、完整启动命令。
# 选型记录:注释里写清“为什么这么选”,下次直接比对
model: <模型名> # 参数量 <N>B
quant: <如 Q4_K_M> # 量化格式与基准位宽,注明文件名
framework: <框架名与版本> # 版本变化可能改变显存行为
max_context: <tokens> # 按任务最长输入+输出+余量填写
concurrency: <并发数> # 单请求与并发场景分开记
gpu: <型号 x 数量> # 单卡显存 <GB>
cpu_offload_layers: <层数> # 0 表示全部放在 GPU
start_cmd: <完整启动命令>
这份记录在几种情况下会失效,需要重新跑冒烟测试:换了显卡或显存容量、换了推理框架或其大版本、把上下文长度上限调大、换了量化格式或位宽、升级了显卡驱动或运行环境。任何一种变化都可能让原来的显存估算不再成立,重新填一次表,通常比出问题之后逐项排查更快。