一张消费级显卡能不能跑 Index-Translate 的本地翻译,先看三个变量:参数量、权重精度、批大小;输入长度和它们一起决定 KV cache 与中间激活的开销。可操作的判断路径是:先从模型卡和配置文件读出参数量和精度,按公式手算一个下限,再用 nvidia-smi 看清真正可用的显存,最后用最小脚本按长度阶梯实测峰值;不够时按“精度→批量→输入长度”的顺序逐级往下调。
先读 config.json 里的 torch_dtype 和权重目录大小,按“参数量 × 每参数字节数 + 余量”手算权重占用;再用 nvidia-smi 确认扣除显示与常驻进程后还剩多少;最后跑长度阶梯脚本记录峰值。手算值只当下限参考,以实测为准。显存不足时先降精度,再降批量,最后才动输入长度。
从模型卡和配置文件读出参数量与权重精度
估算的第一组输入只有两样:模型有多少参数、每个参数占几个字节。这两项通常能在权重目录的配置文件里直接读到,不需要先把整个模型跑起来。
- config.json:torch_dtype 或 dtype 表示权重精度(float32、float16、bfloat16 等);architectures 说明结构;num_hidden_layers、hidden_size、num_attention_heads 可用来核对规模档位。
- 量化相关字段:load_in_8bit、load_in_4bit、quantization_config,表示权重已按 8bit 或 4bit 存放。
- 权重文件本身:safetensors 或 bin 分片加起来的总大小。GGUF、CTranslate2 这类格式常把精度写进文件名,例如 q4_k_m、q8_0、int8、float16,看到标识就不必再猜。
- generation_config.json:max_new_tokens、num_beams 等生成参数,它们不占权重显存,但会影响推理时的缓存与激活开销。
如果配置里没有精度字段、权重又不是自描述格式,就用文件大小反推规模:
du -sh ./model_dir
du -sh ./model_dir/*.safetensors
拿权重文件总字节数除以每参数字节数(fp16、bfloat16 按 2,int8 按 1,4bit 按 0.5 再留一点量化元数据余量),得到的是参数量级,用来对应模型卡上的 1B、3B、7B 档位。注意目录里还可能有词表、tokenizer、残留的优化器状态等非推理权重文件,反推前先排除,否则会高估显卡需求。
按权重占用加推理开销做第一轮手算
手算的目的不是求精确值,而是在下载和部署之前先排掉“根本放不下”的档位。算式骨架是:权重显存约等于参数量乘以每参数字节数,再乘一个很小的对齐缓冲系数,然后单独加上中间激活与运行时开销。
权重显存(GB) = 参数量(个) × 每参数字节(B) / 1024^3
每参数字节参考:
float32 4
float16 / bfloat16 2
int8 1
4bit 约 0.5
KV cache(GB) = 2 × 层数 × KV头数 × 头维度 × 每元素字节 × 序列长度 × 批大小 / 1024^3
运行时固定开销:CUDA context、框架与算子缓存,通常几百 MB 量级
代入时用变量而不是记结论。假设参数量为 N 个、精度 16 位,权重就是 N×2 字节的量级;换成 4bit 大致降到四分之一,但量化库自身仍有开销。举个代入示例(不是实测值):7B 按 fp16 算,权重约 14GB 量级,再加 KV cache 和框架开销,单卡 16GB 往往只能勉强跑短输入;换成 4bit 后权重降到 4GB 量级,余量才够长输入。真实数字以你自己代进去的结果为准。
把手算过程整理成固定几列,方便和后面的实测对照:参数量、权重精度、每参数字节、权重显存、当前可用显存、留白余量、初步判断。这几列填完后,能不能跑哪一档模型就有了第一轮答案。
用命令行查看显卡当前占用与可用显存
显卡总显存和这次推理真正能用的显存不是一回事。先看总量、已用和进程占用:
nvidia-smi
nvidia-smi `--query-gpu`=memory.total,memory.used,memory.free `--format`=csv
nvidia-smi `--query-compute-apps`=pid,process_name,used_memory `--format`=csv
nvidia-smi 顶部的 Total、Used、Free 是全局视角;下面的 Processes 段能看出显存被哪个进程占着,重点确认有没有上一次没退干净的 Python 进程或残留服务还在吃显存。要读的字段就是总量、已用量、每进程占用量这三类。
必须为系统和显示留出空间:桌面环境、浏览器、输入法之类都会占显存,驱动和 CUDA context 还要额外一块;机器接了显示器或跑图形界面时这部分更明显。所以判断基准通常要低于 free 值,不要按 free 数值顶格配置。也可以在 Python 里查一次真实空闲:
import torch
free_b, total_b = torch.cuda.mem_get_info()
print(free_b / 1024**3, total_b / 1024**3)
如果服务是通过容器或 systemd 启动,还要确认容器有没有做显存限制、是否有多个实例共享同一张卡。拿“手算权重显存 + 余量”和这里的可用显存相比,够不够就有一个初步结论。
写一个最小推理脚本做长度阶梯测试
手算和实测之间的偏差主要来自激活与缓存的真实占用,用长度阶梯测试可以把这部分量出来。脚本骨架如下,函数名与加载方式以 Index-Translate 实际使用的仓库为准:
import torch
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
path = './model_dir'
tok = AutoTokenizer.from_pretrained(path)
model = AutoModelForSeq2SeqLM.from_pretrained(
path, torch_dtype=torch.float16, device_map='auto').eval()
for n in [16, 64, 128, 256, 512]:
ids = tok(' '.join(['test'] * n), return_tensors='pt').to(model.device)
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
with torch.inference_mode():
model.generate(**ids, max_new_tokens=64, num_beams=1)
peak = torch.cuda.max_memory_allocated() / 1024**3
print('tokens', ids.input_ids.shape[-1], 'peak_gb', round(peak, 2))
要点是控制变量:每一轮只改输入长度,max_new_tokens、num_beams、批大小保持一致;批大小先固定为 1,得到的峰值是单条请求的下限。如果服务要并发,再把批大小调到 2、4 各测一轮,看峰值随批大小的增长斜率。
结果记成表:输入长度、批大小、dtype、是否量化、峰值显存、是否成功、单次耗时。换配置时这张表就是对照依据。
按精度、批量、输入长度顺序逐级降配
出现显存不足报错时,按下面的顺序逐级降,每一步只动一个变量,并记录观察项:
- 降精度:把 torch_dtype 从 float32 换成 float16 或 bfloat16,或启用 int8、4bit 量化。观察项是加载阶段能否成功、峰值显存降到多少、译文质量是否可接受。这一步主要压的是权重静态占用,通常最直接。
- 降批量:把批大小和并发降到 1,关掉服务里的动态批处理。观察项是峰值显存与单次耗时变化;KV cache 和激活随批大小增长,长输入场景下这一步效果更明显。
- 降输入长度:把长文档按句或按段切分,并限制单次 max_new_tokens。观察项是切分后的译文在衔接处是否通顺,因为这一步直接作用于翻译质量。
每降一步都重跑一次长度阶梯脚本,看峰值是否落进可用显存范围内。记下“从哪一步开始不再出现显存不足报错”,以及当时对应的精度、批量、长度配置,作为这台机器的可用配置。
风险边界:量化到 4bit 后,部分翻译模型会出现术语漂移、数字或格式错乱,关键内容建议人工抽检;把输入切短会割裂上下文,代词和指代关系容易出错。这些是显存换效果的取舍,不是没有代价的优化。