Files
llm-atlas/research/DEEPSEEK_ROUTING_TEMPLATE_AUDIT.md

15 KiB
Raw Permalink Blame History

DeepSeek-V2-Lite 路由的官方模板敏感性审计

状态:真实官方 BF16 权重、本机 RTX 5090、layer 0–6 连续 forward。 固定模型 revision:604d5664dddd88a0433dbae533b7fe9472482de0。 研究单位:同一批 128 条公开 source prompt 的三种输入协议。 结论身份:协议敏感性探针,不是能力评测、训练分布、线上流量或专家语义。

1. 这轮究竟要排除什么混杂因素

上一轮已经证明:自然长度不同会改变路由集中度,因此我们不能把所有域间差异都 解释成“内容语义”。

但即使内容和长度都固定,输入模型之前还有一层协议:

纯文本
  ↓ tokenizer
BOS + content

单轮对话
  ↓ official chat template + tokenizer
BOS + "User: " + content + "\n\n" + optional "Assistant:"

角色前缀会:

  1. 增加 wrapper token;
  2. 改变内容 token 的绝对位置;
  3. 为每个内容 token 增加可见的左侧上下文;
  4. 可能让第一个内容 token 在 BPE 边界处重新切分;
  5. 改变“整段输入统计”的分母。

所以本轮不是问“chat 模型强不强”,而是问:

同一段内容送入同一个 base checkpoint,仅改变官方输入包装时,前六个 MoE 层的路由统计会怎样变化?

2. 官方模板不是自行编写的教学字符串

固定 revision 的 tokenizer_config.json 给出:

{{ bos_token }}
{% for message in messages %}
  user      -> "User: " + content + "\n\n"
  assistant -> "Assistant: " + content + eos_token
  system    -> content + "\n\n"
{% endfor %}
{% if add_generation_prompt %}"Assistant:"{% endif %}

完整模板字符串 SHA-256:

8aeba567270fa9a8d5372caf4b04affea947c4421d96adc01e3ff94021b0ae8e

特殊 token:

角色 文本 ID
BOS <|begin▁of▁sentence|> 100000
EOS / PAD <|end▁of▁sentence|> 100001

实验通过 Transformers 4.41.2 的 apply_chat_template 生成字符串和 token IDs; 随后再次对渲染字符串编码,并要求两条路径的 token IDs 完全一致。

3. 三个条件如何构成“实验 + 负对照”

每条 source prompt 都渲染三遍:

RAW
[BOS] [content]

USER
[BOS] [User:] [content] [\n\n]

GENERATION
[BOS] [User:] [content] [\n\n] [Assistant:]

比较分两种:

3.1 RAW → USER

这是主要敏感性实验。

  • 内容字符前缀相同;
  • checkpoint、权重、batch、层、dtype 相同;
  • 改变的是左侧角色前缀和尾部换行;
  • 尾部换行不能反向影响更早的内容 token;
  • User: 前缀可以通过因果注意力影响后续内容 token。

它能描述“输入协议敏感性”,但不能单独证明训练中的角色语义或能力变化。

3.2 USER → GENERATION

这是因果负对照。

GENERATION 只在 USER 末尾追加 Assistant:。在严格因果 attention 中, 未来 suffix 不应改变此前共享前缀的 hidden state 或路由。

因此它同时验证:

  • causal mask;
  • batching;
  • token prefix;
  • route 捕获;
  • 分析管线。

若这个负对照失败,RAW → USER 的任何解释都不可信。

4. 固定 cohort 与内容长度合同

四个公开域仍为:

域 来源 使用字段 数量
英文百科 WikiText-2 raw validation text 32
中文新闻 CLUE TNEWS public test sentence 32
Python 代码 OpenAI HumanEval prompt 32
小学数学 OpenAI GSM8K test question 32

答案不输入,HumanEval 代码不执行。

4.1 为什么固定 23 个 content tokens

上一轮 24-token matched cohort 的总长包括 BOS:

1 BOS + 23 source-content tokens = 24 raw input tokens

本轮直接把内容合同写清楚为 23 个 regular-tokenizer content tokens。 这样 RAW 条件固定为 24 tokens,同时保留足够多的 TNEWS 候选。

4.2 字符前缀如何重建

BPE 对“完整字符串”和“截断后的字符串”可能在最后一个 token 发生不同合并。 因此不能粗暴地取完整编码的前 23 个 IDs 再假设可逆。

脚本采用:

  1. 读取 fast tokenizer offset mapping;
  2. 在目标 token 边界附近搜索字符终点;
  3. 重新编码字符前缀;
  4. 只接受恰好得到 23 tokens 的最长前缀。

选样排序固定为:

ascending SHA256(
  "llm-atlas-deepseek-routing-template-control-v1"
  | domain
  | source_id
)

候选与选中来源长度:

域 可用候选 选中 source tokens min / mean / max
英文百科 1,655 32 29 / 142.84 / 280
中文新闻 1,609 32 23 / 24.66 / 30
Python 代码 164 32 42 / 141.78 / 352
小学数学 1,319 32 31 / 59.88 / 131

TNEWS 在完成选中前有 1 条无法构造精确 23-token 字符前缀,脚本显式记录并继续按 哈希顺序选择下一条;其余域为 0。

5. 为什么必须分“整段输入”和“对齐内容”

若直接把 wrapper token 与 content token 全部求和,得到的是部署协议问题:

这一整段实际输入会把 token 路由到哪里?

若只研究内容,需要保证比较的是同一 token,而不是“都在内容字符范围内”就算相同。

本轮为三种条件中的每个 token 记录:

(相对内容字符起点, 相对内容字符终点, token ID)

然后只保留三个条件的精确交集。

5.1 Token 账

条件 输入 tokens 对齐内容 tokens 六层真实 top-6 routes
RAW 3,072 2,874 110,592
USER 3,642 2,874 131,112
GENERATION 3,898 2,874 140,328
合计 10,612 — 382,032

RAW 原始内容范围共有 2,944 tokens;由于 User: 后的 BPE 边界,70 个首 token 不能以相同 (span, token ID) 对齐,因此从三个条件中同时剔除。

按域的对齐覆盖:

域 aligned / RAW content 覆盖率
英文百科 706 / 736 95.9%
中文新闻 735 / 736 99.9%
Python 代码 733 / 736 99.6%
小学数学 700 / 736 95.1%

这意味着 content_only 比较中的每一条 route 都对应相同字符段与相同 token ID。

6. 统计合同

与长度实验相同,抽样单位仍是 source prompt,而不是 token。

每个 layer × scope × mode × domain:

  1. 32 条 prompt;
  2. 有放回重采样 2,000 次;
  3. before / after 使用完全相同的 prompt indices;
  4. 固定 seed 20260729;
  5. 报告 percentile 95% 区间;
  6. 不进行假设检验;
  7. 不做多重比较校正。

聚合口径:

  • prompt_balanced:每条 prompt 先归一,再让 32 条 prompt 等权;
  • token_weighted:先汇总选中 token routes,再整体归一。

主要正文使用 prompt_balanced,避免少量 tokenization 长度差异改变 prompt 权重。

7. 负对照:21,852 个共享前缀 token 全部 exact

每个 MoE 层:

Layer USER 共享前缀 tokens ordered top-6 exact 违规 prompt
L1 3,642 3,642 0
L2 3,642 3,642 0
L3 3,642 3,642 0
L4 3,642 3,642 0
L5 3,642 3,642 0
L6 3,642 3,642 0
合计 21,852 21,852 0

content_only 的 USER→GENERATION 在 6 层 × 4 域共 24 个比较中:

CV Δ = 0
total variation = 0
JSD = 0

但若看 full_input,因为 GENERATION 确实新增了 Assistant: token,prompt-balanced total variation 为 0.037–0.051,平均 0.0445。

这组结果正好说明:

suffix 没有改写过去;整段统计改变,是因为统计对象加入了新 token。

8. 主结果:同一个 User: 前缀在不同深度方向不同

以下使用:

  • scope:三个条件精确对齐的 content tokens;
  • mode:prompt-balanced;
  • Δ:CV(USER) − CV(RAW)。
Layer English Chinese Code Math
L1 0.633→0.593, −0.040 [−0.063, −0.017] 0.670→0.621, −0.049 [−0.068, −0.028] 0.901→0.882, −0.019 [−0.039, 0.001] 0.817→0.796, −0.021 [−0.049, 0.006]
L2 0.404→0.405, 0.001 [−0.014, 0.016] 0.383→0.353, −0.030 [−0.041, −0.016] 0.684→0.617, −0.067 [−0.084, −0.047] 0.453→0.455, 0.002 [−0.016, 0.016]
L3 0.405→0.422, 0.017 [−0.002, 0.033] 0.415→0.471, 0.057 [0.033, 0.075] 0.824→0.812, −0.011 [−0.023, 0.001] 0.541→0.547, 0.006 [−0.011, 0.023]
L4 0.530→0.555, 0.025 [−0.002, 0.049] 0.897→0.848, −0.048 [−0.067, −0.030] 1.064→1.038, −0.027 [−0.039, −0.016] 0.668→0.649, −0.020 [−0.040, 0.000]
L5 0.446→0.451, 0.005 [−0.015, 0.021] 0.478→0.504, 0.026 [0.006, 0.042] 0.808→0.850, 0.043 [0.032, 0.055] 0.770→0.779, 0.008 [−0.005, 0.021]
L6 0.502→0.562, 0.060 [0.036, 0.076] 0.509→0.539, 0.030 [0.009, 0.043] 0.851→0.909, 0.058 [0.046, 0.068] 0.738→0.775, 0.037 [0.013, 0.058]

8.1 初学者应该怎样读

CV 越高,64 个 routed experts 的份额越不平。

  • L1:英文、中文的对齐内容变得更平;
  • L2:中文、代码变得更平;
  • L3:中文反而更集中;
  • L4:中文、代码又变平;
  • L5:中文、代码更集中;
  • L6:四域全部更集中。

因此不应写成:

Chat template 会让路由更均衡。

也不应写成:

Chat template 会让路由更集中。

更准确的说法是:

在这个 23-token 固定探针中,角色前缀对路由集中度的影响随层和域改变方向; 低层与高层不能用一个单调结论概括。

9. “整段输入”会给出另一幅图

若把 BOS、User:、换行与 content 全部计算,Δ CV 为:

Layer English Chinese Code Math
L1 0.061 [0.041, 0.077] 0.093 [0.062, 0.112] 0.014 [−0.009, 0.035] 0.058 [0.029, 0.083]
L2 0.093 [0.069, 0.106] 0.138 [0.106, 0.151] 0.038 [0.020, 0.052] 0.098 [0.065, 0.122]
L3 0.100 [0.072, 0.110] 0.140 [0.096, 0.159] 0.078 [0.058, 0.092] 0.058 [0.031, 0.076]
L4 0.131 [0.093, 0.152] −0.012 [−0.033, 0.003] 0.051 [0.035, 0.063] 0.066 [0.042, 0.083]
L5 0.083 [0.050, 0.094] 0.036 [−0.003, 0.054] 0.092 [0.070, 0.112] −0.053 [−0.075, −0.036]
L6 0.134 [0.090, 0.155] 0.046 [0.000, 0.069] 0.103 [0.079, 0.118] 0.027 [−0.004, 0.047]

多数格子是正值,与对齐内容的混合方向明显不同。

原因不是统计错误,而是问题不同:

  • full_input 包含重复出现的 wrapper token;
  • content_only 只比较相同字符段和 token ID;
  • wrapper 自己可能有很强、很稳定的路由偏好;
  • wrapper 数量占短输入的比例不小。

这也是为什么评测协议、chat template 和 tokenizer 必须进入实验账本,而不能只写模型名。

10. 逐 token top-6 有多稳定

对 RAW 与 USER 中能按 (relative span, token ID) 对齐的内容 token,报告:

  • set:top-6 专家集合完全相同的比例;
  • J:top-6 专家集合的平均 Jaccard。
Layer English Chinese Code Math
L1 set 0.552 · J 0.854 set 0.600 · J 0.867 set 0.625 · J 0.867 set 0.459 · J 0.822
L2 set 0.606 · J 0.874 set 0.604 · J 0.869 set 0.562 · J 0.846 set 0.577 · J 0.860
L3 set 0.625 · J 0.881 set 0.634 · J 0.875 set 0.570 · J 0.864 set 0.644 · J 0.890
L4 set 0.572 · J 0.865 set 0.590 · J 0.869 set 0.562 · J 0.831 set 0.593 · J 0.873
L5 set 0.540 · J 0.849 set 0.550 · J 0.841 set 0.551 · J 0.824 set 0.610 · J 0.872
L6 set 0.581 · J 0.859 set 0.558 · J 0.839 set 0.529 · J 0.814 set 0.544 · J 0.846

两个数字可以同时成立:

  • 只有约 46%–64% 的 token 保持完全相同的 top-6 集合;
  • 但平均 Jaccard 仍为 0.814–0.890。

直觉上,这说明很多变化不是“六个专家全部换掉”,而是 top-6 边缘的一两个专家发生替换。 它仍不能告诉我们专家“负责什么语义”。

11. 分布距离不大,不等于逐 token 没变化

RAW→USER 的对齐内容、prompt-balanced:

  • total variation 点估计约为 0.024–0.067;
  • JSD 点估计约为 0.001–0.008 nats。

聚合分布看起来接近,但上一节仍有大量 token 的 top-6 集合不完全相同。

这是一个重要的测量层级区别:

aggregate distribution close
    ≠
every token took the same route

相反,也不能从 token route 变化推出输出答案或任务分数必然变化;本实验没有执行完整模型生成。

12. 独立复跑

正式输出:

src/data/deepseek-v2-lite-routing-template.json
src/data/deepseek-v2-lite-routing-template-repro.json

两份完整 JSON:

SHA-256
da1f10333b2fa269e64f9716a0ca6c1a656d23d3e70b8a25d6e59f8a5b3bc1b9

cmp byte-exact。

复现命令:

PYTHONPATH=/path/to/transformers-4.41.2-deps:/usr/lib/python3/dist-packages \
python -B experiments/deepseek/v2_lite_routing_template_probe.py \
  --artifact-dir /path/to/deepseek-v2-lite \
  --human-eval /path/to/HumanEval.jsonl.gz \
  --gsm8k /path/to/gsm8k/test.jsonl \
  --tnews /path/to/tnews/test.json \
  --tnews-archive /path/to/tnews_public.zip \
  --wikitext /path/to/wikitext-validation.parquet \
  --output /path/to/template-probe.json \
  --per-domain 32 \
  --content-tokens 23 \
  --batch-prompts 8 \
  --layers 7 \
  --bootstrap 2000 \
  --seed 20260729 \
  --captured-at 2026-07-29T08:30:00+00:00

Jinja2 是 apply_chat_template 的运行依赖;正式环境记录为 3.1.6。

13. 可以说什么,不能说什么

可以说

  • 固定官方 revision 的 chat template 会改变输入 token 序列;
  • 相同字符段、相同 token ID 的内容路由对角色前缀敏感;
  • 敏感性随层和域改变方向;
  • 追加在未来的 Assistant: 不改变此前共享前缀,六层 21,852 / 21,852 exact;
  • wrapper-inclusive 与 aligned-content 统计回答不同问题;
  • 正式运行与独立复跑 byte-exact。

不能说

  • User: 专门激活某类“对话专家”;
  • top-6 ID 的变化等于专家语义变化;
  • 路由更平或更集中等于模型能力更强;
  • 这个 base checkpoint 的 route probe 等于 chat model 的线上流量;
  • 前六层等于完整 27 层;
  • 23-token 前缀代表长对话;
  • 不生成答案的实验可以给出任务准确率结论;
  • 24 个 layer×domain 区间是已校正的显著性检验。

14. 下一道闸门

  1. 下载并执行完整 layer 0–26,检查“低层变平、高层变集中”的模式是否延续;
  2. 加入 system role、few-shot turn 与换行边界的正交扰动;
  3. 把模板敏感性接到完整生成与输出指标,仍保持 route / output 两张账;
  4. 在支持环境执行 FlashMLA optimized kernel;
  5. 继续 FP8 / pipeline traces 与 R1-like 小模型复现。