默认上下文会悄悄截断,而且不报错
ollama show 里看到的 context length 是模型训练时的上限,运行时真正用多少由 options.num_ctx 决定,Ollama 默认往往只给 2048 或 4096。你把一万字文档贴进去,界面上一切正常,实际超出部分被直接丢掉,模型只基于开头作答,答案通顺但漏了后半本——这类问题最难查,因为没有任何报错。凡是长文档任务,第一件事就是显式设 num_ctx。
curl http://localhost:11434/api/chat -d '{
"model": "qwen2.5:7b",
"messages": [{"role": "user", "content": "总结下面这份文档 ..."}],
"options": {"num_ctx": 8192, "temperature": 0},
"stream": false
}'
但 num_ctx 不是越大越好。KV cache 显存近似等于 2 × 层数 × kv_heads × head_dim × ctx × 每元素字节数,7B 级模型在 fp16、8k 上下文下大约 1 到 2 GB,翻到 32k 就是 4 到 8 GB。8 GB 显卡跑 7B 开 16k 很容易 OOM,而且 Ollama 显存不够时不会报错,会把层静默挪到内存,token 速度从 30 掉到 3。改完 num_ctx 一定用 ollama ps 看 PROCESSOR 那一列是不是 100% GPU。
长文档的四条路线,优先选第一条
- RAG 优先:能检索就别全塞。只把相关片段拼进提示词,成本、延迟、召回都更好,这也是上下文窗口存在感最弱的做法。
- map-reduce:每段先独立摘要,再把摘要合并成总摘要,适合"总结全书"这类不需要交叉引用的任务,可并行。
- refine:逐段读,边读边把已有结论和新段落一起传给模型更新,顺序执行,适合要贯穿线索的场景,但慢且容易累积偏差。
- 目录导航:先给模型一份目录,让它指出该看哪几章,再定点取章节全文,比无脑塞整本强得多。
还有个反直觉的结论:很多标称 128k 的模型存在 lost in the middle,关键信息放在长上下文的中间位置时召回明显低于放首尾。所以约束、输出格式、最关键的那段材料,尽量放在提示词的开头和结尾,别埋在中间。
按 token 算账,别按字数拍脑袋
按 token 计费的 API,输入长度直接乘钱;本地推理则乘 prefill 延迟,随长度近似线性增长。中文的 token 效率比英文低,同样一段字,中文 token 数往往更多,估预算前先量一下。
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
text = open("doc.md", encoding="utf-8").read()
print(len(enc.encode(text)), "tokens")
ollama ps
nvidia-smi --query-gpu=memory.used,memory.total --format=csv
多轮对话也别把历史原样全带上,几轮之后同样会触发截断却毫无提示。做法是设一个 token 预算,超了就把早期历史压成摘要,或直接滑动窗口只保留最近 N 轮加一段会话摘要。