🧠 第六章 语言模型训练:高效赋能业务场景
📄 一句话概括

片段按 400 token、窗口按 Ollama 常见的 2048 算: k=1 还剩 1298 给回答,k=2 只剩 898(不够 1024),k=3 只剩 498, k=5 直接是 −302——已经超窗口了。 提示词不是「写几句话」,是在分一个固定的预算。

📝 本节和前面几节的关系

上一节定了 k。这一节把提示词写出来——先得知道预算有多少,才知道能写多长。

一、提示词、上下文、输出共用一个预算

一次请求的 token 总量是固定的: 系统提示词 + 检索片段 + 用户问题 + 模型要生成的回答,全部加起来不能超过 num_ctx。 而很多人配的时候只盯着「提示词写了多长」,没注意片段才是那个吃预算的大头——一段 400 token, k=5 就是 2000。 Demo 按「系统提示词 300、问题 50、每段 400、建议给回答留 1024」算了一遍:

k 片段 num_ctx 2048 4096 8192
1 400 1298 3346 7442
2 800 898(不够) 2946 7042
3 1200 498(不够) 2546 6642
5 2000 −302(超了) 1746 5842
窗口按 2048 时,k=2 就已经不够了。而 2048 不是什么奇怪的值——
它正是 Ollama 那个 num_ctx 的常见默认。

二、num_ctx 是那个被忽略的旋钮

模型标称支持 128k,不代表它跑起来就用 128k。 Ollama 的 num_ctx 默认取的是保守值(老版本 2048,新版本有的 4096), 它跟模型的训练上下文长度是两件事。 这解释了一个很常见的困惑:「我明明给了它一份长文档,它却只答了前半部分。」不是模型不行, 是运行时窗口只有 2048,后面的内容根本没进去。 更麻烦的是它的表现:没有报错,没有警告,只有一个 finish_reason 是 length。 不懂这个字段的人,会以为是「模型不想答了」。 所以配知识库的顺序应该是: 先看 num_ctx 是多少 → 再定片段多大 → 再定 k → 最后写提示词。 反过来做, 写完的提示词可能一上线就被截掉一半。

三、提示词怎么排,才不被挤掉

片段一多,位置就变成了资源。三条排序原则: ① 硬约束放最前。 「只根据下面的材料回答,材料里没有就说不知道」这条必须在最前面, 它被截掉就等于没有。 ② 输出格式放最后。 模型对「最近的要求」更敏感,把 JSON 字段、字数限制放在问题后面, 遵循率会高一些。 ③ 材料放中间,并且编号。 编号是为了让模型能引用([1]、[2]),也方便你在出问题时对回原文。 还有一个反直觉的点:长提示词不等于好提示词。系统提示词写到 800 token, 就把能塞的片段从 3 段压到 1 段。先用 200 到 300 字把规则说清,剩下的预算留给材料。

四、一个能直接抄的模板

四段结构,按上面的顺序排:

你是客服助手。只根据【材料】回答,材料里没有就说「暂未查到」。

【材料】
[1] ……
[2] ……
[3] ……

【问题】
{用户的问题}

【要求】
1. 先给结论,再给依据;
2. 引用材料时标出编号,如 [1];
3. 不超过 150 字。

四段各管一件事: 身份与边界、材料、问题、格式。拆开写的好处是——出问题时你能立刻定位是哪一段没说清。 最后一条(字数上限)还有个副作用:它会压低 finish_reason=length 的概率。因为模型知道要短, 就不会撞到输出上限。 📋 实操清单:Page Assist 的设置里有一栏「系统提示词」, 把上面这段(去掉【材料】和【问题】部分)粘进去;检索片段由插件自动填在中间。 注意别把 {用户的问题} 也粘进去,那是占位符。

五、动手 Demo:窗口按 2048 算:k=3 只剩 498,回答直接被截断

固定系统提示词 300 token、问题 50 token、每段片段 400 token、答案建议留 1024 token, 算 k 取 1/2/3/5 时,在 num_ctx 为 2048、4096、8192 三种情况下分别还剩多少给输出。

sys_tok, q_tok, need = 300, 50, 1024
per = 400                        # 每段约 400 token
for k in (1, 2, 3, 5):
    for win in (2048, 4096, 8192):
        left = win - sys_tok - q_tok - k * per
        print(k, win, left, u'不够' if left < need else u'够')

实跑输出:

--------------------------------------------------------------------
上下文窗口按 2048 算:k=3 只剩 498,回答直接被截断
--------------------------------------------------------------------
  系统提示词               300 token
  用户问题                 50 token
  每个片段                 约 400 token
  答案建议预留             1024 token

  k   片段   num_ctx 2048   4096   8192
  ------------------------------------
  1    400           1298   3346   7442
  2    800       898 不够   2946   7042
  3   1200       498 不够   2546   6642
  5   2000      -302 不够   1746   5842

  看第一列:**窗口按 2048 算,k=3 就只剩 498 token 给回答。**
  而一段像样的答案要 500 到 1000 token。
  这不是假设。Ollama 的 `num_ctx` 默认取的是保守值(老版本 2048,新版本有的 4096),
  **它不是模型的训练上下文长度。**一个标称支持 128k 的模型,跑起来照样只用 2048。
  被截断的表现很迷惑:模型「答到一半没了」,日志里没有报错,只有一个 `finish_reason` 是 length。
  三条修法,按代价从低到高:
  **① 调大 num_ctx;② 片段切到 300 到 500 token;③ k 定在 3 左右。**

表里第一列最能说明问题: 2048 窗口下,k=2 只剩 898,k=3 只剩 498。而一段像样的答案要 500 到 1000 token。 k=5 那格是 −302,负数。 意思是光片段就已经装不下了,问题本身都可能被截断——这时候模型答非所问,你却在怀疑检索。 第二列 4096 宽松一些:k=3 还剩 2546,够写一段长答案。 所以「调大 num_ctx」是这套配置里性价比最高的一次改动,它不需要重新标数据、不需要换模型。 第三列 8192 基本随便配。 但窗口越大,显存和首字延迟也越高, 不是无脑拉满就好——按「k×片段 + 1024」倒推需要多大窗口,才是正解。 要说明:这里的 400 token 每段是假设值,实际取决于你的切块参数。 把你自己库里的平均段落长度代进去,这张表就是你的预算表。

30 秒实验

  1. 把每段从 400 token 改成 300,看 2048 窗口下 k 最大能到几。 片段切小一点,能塞的段数就多一点——这是不改窗口也能救的办法。
  2. 把系统提示词从 300 改成 800(写了一大段人设),看 4096 下的 k=3 还够不够。 长提示词是直接从片段预算里抢的。
  3. 把「建议给回答留 1024」改成 512,再看哪些格子由「不够」翻成「够」。 这一改的代价是长回答会被截断——它只是把问题往后推。

常见误区

  1. 「提示词写得越详细越好」。系统提示词每多 100 token,就少塞四分之一个片段。 规则说清就行,预算留给材料。
  2. 「模型支持 128k 就能塞 128k」。运行时吃的是 num_ctx,默认值往往只有 2048 或 4096。
  3. 「答到一半停了是模型不行」。 先看 finish_reason 是不是 length,那是窗口不够,不是模型不想答。
自测三问(点开对答案) 1. 一次请求的 token 预算由哪几部分组成? —— 系统提示词 + 检索片段 + 用户问题 + 模型生成的回答,总和不能超过 num_ctx。 2. 为什么 num_ctx 比模型标称的上下文长度更重要? —— 因为运行时实际用的是 num_ctx。模型标称 128k,num_ctx 设成 2048,就只按 2048 算。 3. 提示词里为什么硬约束要放最前面? —— 因为它一旦被截掉就等于没有。放最前面能在截断时保住最关键的那条规则。

提示词写好了,接下来是最后一件事:怎么知道它到底行不行。下一节给一份两列指标的测试法。

相关笔记