ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ollama 回答总在 4096 字被截断:num_ctx 改了却不生效的排查路径

Ollama 回答总在 4096 字被截断:num_ctx 改了却不生效的排查路径

本文摘要:大模型回答总在 4096 处截断,改了num_ctx却不生效。用curl对照请求定位参数覆盖层级,追查谁在覆盖它。

一、问题与结论

环境是个人工作站上ollama serve(默认端口11434)加一个 Open WebUI 前端,跑qwen2.5:7b这类 GGUF 量化模型。让模型写长文,回答总在约 4096 token 处停住,不报错、不提示。你已在Modelfile里写了PARAMETER num_ctx 8192并用ollama create重建,ollama show也能看到这一行,但走 Open WebUI 对话仍被截断。

结论先给三条:

  1. num_ctx有三层生效位置:请求级options>ModelfilePARAMETER> 内置默认值。改Modelfile只改了中间层,任何一端传了请求级参数都会盖掉它。
  2. 先分清"上下文不够"和"生成长度不够"。后者由num_predict卡住,此时调num_ctx不会有任何变化。
  3. 截断是静默的,只有响应里的done_reason、eval_count能佐证到底卡在哪一层。

二、排查与选择依据

结论:按"单请求对照 → 单入口对照 → 跨入口对照"三层推进,每层只变一个变量。

第一步:确认模型里到底有没有num_ctx

ollama show qwen2.5:8k--modelfile

看输出里有没有PARAMETER num_ctx 8192。没有,说明用的是内置默认值;有,进入第二步。

第二步:同模型、同提示词,只变options

curl-shttp://localhost:11434/api/generate-d'{ "model": "qwen2.5:8k", "prompt": "写一篇约3000字的技术短文,主题是GPU显存管理", "stream": false }'|jq'{eval_count, done_reason}'

再发一次,请求体里加"options": {"num_ctx": 8192},同样取eval_count与done_reason对照:

  • 两次都截断且done_reason为length:先查num_predict,这不是num_ctx的问题。
  • 默认请求截断、带options的不截断:Modelfile层没被应用,或某处用请求级参数覆盖了它。

第三步:跨入口对照

把第二步的请求原样通过 Open WebUI 发一次。若 curl 不截断而 Open WebUI 截断,问题在前端请求体。用OLLAMA_DEBUG=1 ollama serve重启服务,观察它实际收到的 JSON,重点看options字段和model名是否与重建后的模型一致。同时确认 Open WebUI 是否提供了会话参数设置入口——它可能按请求塞入了自己的上下文或生成长度配置。

替代方案与取舍

做法选择条件代价边界
本文路径:分层对照定位参数覆盖单机、多入口(curl/ Open WebUI)混用需逐层发请求,排查耗时多实例、多卡调度问题不适用
绕过前端直接调 API程序化调用、批量任务要精确控参放弃现成对话界面,需自建或写脚本不解决前端体验需求
改num_predict而非num_ctxdone_reason明确为length无法扩大可容纳的输入输入本身超长时不解决问题

不该用本文方案的情形:截断点与 token 计数无关(如前端主动终止流)、或问题发生在模型 chat template 层面——这两类即使把num_ctx调大也不会有变化。

三、关键原理

参数覆盖是一条单向优先链:API 请求options覆盖ModelfilePARAMETER,后者覆盖内置默认值。因此"改了没用"通常不是参数写错,而是改错了层级——中间层被更高层覆盖,或中间层压根没被加载。

代价在于显存。num_ctx直接决定 KV cache 的槽位数量,KV cache 随上下文长度近似线性增长,层数、KV 头数越多,单份 cache 越大。把 4096 提到 8192,等于给同一份模型多留一倍的 cache 空间;多模型并发拉起时这部分会先撞显存上限,触发 Ollama 把旧模型挤出。

ollamapsnvidia-smi --query-gpu=memory.used,memory.total--format=csv,noheader

ollama ps的 CONTEXT 列应显示新值(以下输出为未验证示例,实际数值取决于模型与硬件):

NAME PROCESSOR CONTEXT UNTIL qwen2.5:8k 100% GPU 8192 4 minutes from now

若 CONTEXT 仍是 4096,说明当前驻留的是旧模型版本;若nvidia-smi显示显存吃紧,说明 8192 在这张卡上已经接近可用上限。

四、可运行示例

环境:Linux + NVIDIA 单卡,Ollama 已安装并能ollama pull qwen2.5:7b,默认监听11434。

cat>Modelfile<<'EOF' FROM qwen2.5:7b PARAMETER num_ctx 8192 EOFollama create qwen2.5:8k-fModelfile ollama show qwen2.5:8k--modelfile

预期输出:Modelfile段落中出现PARAMETER num_ctx 8192。

随后发两条对照请求(不带 / 带options.num_ctx),取eval_count与done_reason。预期输出:带options的那条eval_count明显更高且done_reason为stop。实际输出:本文未在真实环境执行,两组数值与截断位置均标为未验证,需按第二节的判定规则自行读数。

常见失败及修复:ollama create后仍按旧名qwen2.5:7b调用,拿到的还是默认参数——用ollama list确认新模型名并改调用侧;Open WebUI 模型列表是缓存的,刷新或重启服务后再试。

五、验证结果与边界

判据只看两个字段:done_reason为length说明是生成上限,为stop说明模型自己收尾,两者都不是就去看eval_count是否已达num_ctx上限。ollama ps的 CONTEXT 列与请求里的options一致,是"参数真的进了服务"的第一手证据。

边界要说清:num_ctx调大换的是更长的可容纳输入与输出,付出的是显存和首 token 延迟,多模型并发场景下更容易把别的模型挤下卡。以下情形不建议沿用这条路径——任务本身可以分段处理(拆成多次短调用更省显存)、需要高吞吐批处理(该换专门的推理服务端)、或前端已固化了参数下发逻辑且无法修改(此时应去前端侧配置,而不是在 Ollama 里反复改)。

参考资料

  • ollama/ollama (GitHub)
  • open-webui/open-webui (GitHub)
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进