Mellum2.1 在本地补全总断片 / 是上下文窗口还是请求超时?

文章导读
本地接 Mellum2.1 出现补全断片、只出半句,通常不是单一原因,而是三条线中的某一条被触发:上下文窗口被截断、请求超时提前中断、显存或批大小导致的排队与丢弃。判断的先后顺序建议是先对齐时间戳、再动配置——先在 IDE 插件侧记下断片时刻和当时的补全输入,再到模型服务日志里找同一时间戳的请求记录,确认服务是否收到、何时返回、返回了多少 token。只有这两侧对得上,才谈得上改上下文长度或超时参
📋 目录
  1. 壹 在 IDE 插件日志里标记断片发生的时间点
  2. 贰 到模型服务日志中对照同一时间戳的请求记录
  3. 叁 检查上下文窗口与 prompt 拼接的截断位置
  4. 肆 调整请求超时和批大小后重放同一段代码
  5. 伍 记录显存变化与补全连续性
A A

本地接 Mellum2.1 出现补全断片、只出半句,通常不是单一原因,而是三条线中的某一条被触发:上下文窗口被截断、请求超时提前中断、显存或批大小导致的排队与丢弃。判断的先后顺序建议是先对齐时间戳、再动配置——先在 IDE 插件侧记下断片时刻和当时的补全输入,再到模型服务日志里找同一时间戳的请求记录,确认服务是否收到、何时返回、返回了多少 token。只有这两侧对得上,才谈得上改上下文长度或超时参数;否则只调参数,很容易把问题挪到别处。

断片先分清是“没送到”还是“送多了被截”,再分清是“提前掐断”还是“排队超时”。做法是:IDE 日志记时间与输入,服务日志对同一时间戳看状态码、生成耗时和输出 token 数,两边一致后再改上下文长度、timeout、batch size,每次只改一项并复现同一段代码。边界在于:如果服务侧根本没有该时间戳的请求记录,问题在客户端或网络链路,改模型参数无效。

在 IDE 插件日志里标记断片发生的时间点

断片发生时先别急着重试,把现场记下来。IDE 插件的输出面板(常见命名是 Output、Log、日志)一般会有触发补全、请求发出、收到响应、超时或取消这几类字段,按插件不同叫法略有差异。

  • 触发字段:记录本地时间戳(尽量到毫秒)、文件路径、光标位置、是手动触发还是自动触发。
  • 输入字段:把当时那段代码原样复制一份,尤其是光标前的整段函数,后面要拿它重放。
  • 响应字段:记录返回内容的长度(字符数或行数)、是否带 timeout、cancelled、stream closed 之类的标记。

如果面板只显示结果不显示时间,可以打开插件的详细日志开关,或到插件日志文件目录里翻最新写入的那个文件。判断断片的直观标准是:返回内容明显短于该位置应有的补全长度,或在语句、表达式中间直接停住。把“输入片段 + 时间戳 + 返回长度”三样一起记到同一个文本文件里,作为后续对照的基准。

到模型服务日志中对照同一时间戳的请求记录

拿上一步的时间戳到模型服务日志里找。自建服务一般是推理框架或反向代理打的访问日志,能用得上的字段通常只有这几类:

[TIME] [PATH] /v1/completions [STATUS] 200 [LATENCY] ... [COMPLETION_TOKENS] ...
[TIME] [PATH] /v1/completions [STATUS] 499 [LATENCY] ... [ERROR] client closed request

对照要点有四个:同一时间戳是否存在对应请求;状态码是 200、4xx 还是 5xx;生成耗时是否已经接近客户端侧的超时阈值;输出 token 数是否明显小于预期。如果服务日志里找不到该时间点的请求,说明请求在客户端或网络层就没发出去,这时改模型参数没有意义。如果服务默认没开访问日志,先在启动参数里打开(多数推理框架有 `--log-requests` 或 access-log 一类的开关,按实际框架确认),再复现一次。

检查上下文窗口与 prompt 拼接的截断位置

服务日志里请求正常、返回 token 也不算少,但客户端拿到的补全仍然断,这时重点看上下文拼接。补全类请求一般由客户端拼 prompt:前缀行数 + 光标前内容 + 光标后内容,总长超过最大上下文就会被裁掉一截。

通用配置项大致是这样几项:

Mellum2.1 在本地补全总断片 / 是上下文窗口还是请求超时?
max_context_tokens: 4096      # 提交给模型的最大上下文,超过即触发截断
truncation: "middle"          # head / middle / tail,决定裁掉哪一段
prefix_lines: 40              # 光标前保留的行数
suffix_lines: 10              # 光标后保留的行数
max_tokens: 128               # 本次补全最多生成多少 token

用一段长函数做对比会更直观:把它完整放进编辑器,在函数中部触发补全,再把 max_context_tokens 调到明显更小的值触发一次。如果缩小后才断片、放大后恢复,基本可以判定是上下文被截断;如果两种设置都断在同一位置,则更可能是超时或资源问题,回到上一步的日志确认。截断策略也值得换一次(head / middle / tail),因为不同策略留下的代码位置不同,断片位置会跟着变,这本身就是一种佐证。

调整请求超时和批大小后重放同一段代码

这一步要区分“等服务等到超时被掐断”和“资源不足排队排掉了”。可以先准备一份通用配置骨架,按自己的框架替换键名:

request_timeout_ms: 8000   # 客户端等待补全返回的上限
retry: 0                   # 先不要开重试,避免掩盖现象
batch_size: 1              # 服务端并发批大小
max_batch_tokens: 2048     # 单批 token 上限
max_tokens: 128            # 单次补全输出上限

做法是先把 timeout 调大(例如在默认值基础上翻倍),保持 batch size 不变,重放同一段代码,看还断不断。若不断了,说明更接近超时问题,再考虑是否要降低单次 max_tokens 来缩短生成长度,或调整调度方式。若仍断,再把 batch size 降到 1 重放。每次只改一项,把改动值、重放时间、返回长度写在同一行记录里。另一种常见现象是:调大 timeout 后断片只是推迟出现,那往往提示服务端排队或资源紧张,而不是客户端等得不够久。

记录显存变化与补全连续性

在跑上述重放的同时,另开一个终端观察显存:

nvidia-smi `--query-gpu`=memory.used,memory.total,utilization.gpu \
  `--format`=csv -l 1

没有 N 卡时用同类工具,比如 rocm-smi,或直接用推理框架自带的显存统计。关注的不是平均值,而是补全发生前后的峰值:把显存峰值时间、batch_size、是否断片三者对齐。常见的对应关系是——batch_size 调大后显存峰值上行、断片同时变多,偏向资源不足导致请求被推迟或批被拆分;显存平稳而断片依旧,则回到超时和上下文截断两条线上继续查。

最后留一张简单的排查记录表,每行一次复现:时间戳、输入片段标识、客户端返回长度、服务端状态码与耗时、上下文与超时配置、显存峰值、是否断片。连续记几次之后,是上下文窗口、请求超时还是资源不足,基本能靠这张表分出来,而不必靠猜。