本地部署 Mellum2.1 做代码补全时,显存吃紧往往不是单一原因:同一份权重,量化档不同、上下文长度不同、服务端批大小不同、编辑器触发频率不同,显存峰值和补全延迟都会明显变化。比较稳的排查顺序是——先确认加载的到底是哪一档精度,再记下加载完成后的空闲显存,然后单独压上下文长度、单独压批大小与并发数,最后回到 IDE 插件侧收触发频率与在途请求数。每轮只改一个变量,改完重放同一段输入再对比。
判断方向:把显存问题拆成权重、KV 缓存、批处理三块分别定位,比笼统调低上下文更有效。操作上,先读模型目录与启动参数确认量化档,再用 nvidia-smi 和启动日志拿到本机实际占用,之后依次单变量调整上下文长度、批大小、并发数,最后在 IDE 侧限制触发频率与在途请求数。边界:单张消费级显卡同时跑长上下文和高并发通常不现实,具体上限必须靠本机观察值确定,不能照搬别人的配置。
查看模型文件与加载参数确认权重精度
显存排查第一步是搞清楚自己加载的是什么。模型目录里一般有 config.json 和权重文件,文件名可能带 fp16、bf16、int8、int4、awq、gptq、gguf 之类的标识;权重分片则通过 index json 描述清单。先看这些,再去看启动参数,避免把「量化档选错」误判成「上下文太长」。
- 模型目录:config.json 中的层数、隐藏维度、注意力头数、max_position_embeddings,以及权重文件命名里的量化标识。
- 加载命令:`--dtype`、`--quantization`、`--load-format`、`--max-model-len`、-ngl 之类参数,决定了实际加载精度和卸载层数。
- 服务配置:max_model_len、gpu_memory_utilization、max_num_seqs 等启动项,会被服务写进启动日志,可对照确认。
把结果填进下面这张估算表,再看本机读数是否吻合。表格只是估算框架,不是承诺值。
| 占用项 | 决定因素 | 估算方式 | 怎么确认 |
|---|---|---|---|
| 模型权重 | 参数量 × 每参数字节数 | fp16/bf16 约 2 字节/参数,int8 约 1 字节,int4 约 0.5 字节,含缩放参数会略多 | 模型目录中权重文件合计大小、启动日志加载行 |
| KV 缓存 | 层数、KV 头数、head_dim、上下文长度、批大小、缓存精度 | 2 × 层数 × KV 头数 × head_dim × 序列长度 × 批大小 × 每元素字节数 | 服务配置项,加启动日志中 KV cache 相关信息 |
| 运行时开销 | 框架、CUDA context、临时缓冲区、显存碎片 | 难以精确预估,用空闲显存差值反推 | nvidia-smi 启动前后对比 |
| 批处理余量 | batch size、并发请求数、提示长度分布 | 批越大,KV 缓存近似线性增长 | 逐项调参时对比显存曲线 |
在推理服务启动日志里记录显存峰值
先拿到两个基线值:服务进程启动前的显存(含桌面或其他进程占用),以及加载完成、没有任何请求时的显存。两者差值大致就是权重加运行时开销。用一条采样命令盯着看更直观。
nvidia-smi `--query-gpu`=index,memory.used,memory.total `--format`=csv -l 1
# 另开终端启动推理服务,观察数值跳变
# 需要留档时重定向到文件
nvidia-smi `--query-gpu`=index,memory.used `--format`=csv -l 1 > gpu_mem.log
服务侧日志同时留意这几类行:权重加载大小、KV cache 分配、显存利用率预分配、批处理配置。有的后端会打印「model weights take」「KV cache size」这类字样,能直接对着上面的估算表核对。
记录方式建议固定为四个点:启动前、加载完成、空闲等待半分钟、第一次请求返回后。只写实际读到的数,不要拿估算值反推填表。如果加载完成后空闲显存已经接近容量上限,说明权重档位或上下文配置本身偏高,此时不要急着加并发。
用固定代码片段测不同上下文长度的延迟
准备一段固定输入,比如一个几十行的函数或类,把它截成短、中、长三档:短档几百 token,中档一两千 token,长档接近你配置的 max_model_len 的一半以上。三次请求使用同一个 max_tokens 和 temperature,最好关闭采样随机性。
# payload_short.json / payload_mid.json / payload_long.json 只有 prompt 长度不同
nvidia-smi `--query-gpu`=memory.used `--format`=csv,noheader
curl -s http://127.0.0.1:8000/v1/completions \
-H 'Content-Type: application/json' \
-d @payload_long.json \
-w 'total=%{time_total}s\n' -o resp.json
nvidia-smi `--query-gpu`=memory.used `--format`=csv,noheader
观察点有三个:生成总耗时、显存增量、首包时间。如果服务支持流式返回,用 curl -N 看第一个数据块到达的时间,这比总耗时更能反映补全体感。返回体里若带 usage 字段,优先按它记录的 prompt_tokens 和 completion_tokens 归档,比按字符数估算更接近实际。
这一轮结论只对你当前的机器和量化档成立。如果长档显存逼近上限、耗时也明显抬升,先把服务端 max_model_len 或 IDE 侧允许的最大上下文调小,而不是继续加并发。
调整批大小和并发数观察显存与补全延迟
不同推理后端的字段名不一样,先在本端启动参数或配置文件里找到对应项:批大小(batch size / max_num_seqs / max_batch_size)、最大并发请求数(max_concurrent_requests / 队列长度)、单请求超时(timeout / request_timeout),以及单批 token 上限。下面只是字段含义示意,名称按你的后端替换。
max_num_seqs: 8 # 同时处理的序列数,直接影响 KV 缓存
max_num_batched_tokens: 2048 # 单批 token 上限,影响预填充阶段的峰值
max_concurrent_requests: 4 # 允许进入排队的请求数
request_timeout: 30s # 超时后释放,避免请求长期占位
gpu_memory_utilization: 0.85 # 给碎片和临时缓冲留余量
测试方法:固定 prompt 和上下文长度,先把并发设为 1、批大小取小值,重启服务等显存稳定,再逐步提高批大小,记录稳定显存和每次请求耗时;然后固定批大小,逐步提高并发,观察日志里是否出现排队等待时间。每次只改一项,改完重启服务再测,否则无法判断是哪一项造成的变化。
风险边界:提高批大小通常能提升整体吞吐,但补全请求对首字延迟敏感。如果在途请求已经排到编辑器等待上限之后,继续加并发只会让补全变成迟到结果。超时值要和服务端排队上限对应,设得太短会误杀正常的长上下文请求。
在 IDE 插件侧限制触发频率并重测
编辑器连续按键会产生大量补全请求,本地服务很容易进入排队状态,表现就是「显存明明够,补全却越来越慢」。先在插件设置里找这几类项:触发延迟或 debounce、是否每次按键都触发、自动补全总开关、单次请求超时、最大在途请求数、是否在注释和字符串内触发。
# 通用配置骨架,字段名按实际插件设置项替换
trigger_delay_ms: 300 # 停止输入后再发请求,常用起点
max_inflight_requests: 1 # 在途只保留一个,新请求取消旧的
request_timeout_ms: 1500 # 超过就放弃,不排队等
max_context_tokens: 2048 # 与本地服务的 max_model_len 保持一致
enable_in_comments: false # 注释和字符串内可单独关掉,减少无效请求
改完重放同一段输入:在同一个文件的同一位置,连续输入相同内容,记录补全出现时间,同时看服务端日志里的请求条数和排队情况。判断标准是触发次数下降、在途请求不再堆积、显存曲线趋于平稳,而不是某一次请求的偶发快慢。
顺序上建议先服务端再 IDE 端:量化档、上下文长度、批大小、并发数定下来之后,再用插件侧配置去收触发频率。反过来调,容易把服务端配置不当的问题掩盖掉。全部调完再回看一次 nvidia-smi 的空闲值和峰值,给长文件和大上下文留出余量,具体留多少以本机观察结果为准。