Files
llm-atlas/research/DEEPSEEK_ROUTING_TEMPLATE_AUDIT.md

439 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DeepSeek-V2-Lite 路由的官方模板敏感性审计
> 状态:真实官方 BF16 权重、本机 RTX 5090、layer 0–6 连续 forward。
> 固定模型 revision:`604d5664dddd88a0433dbae533b7fe9472482de0`。
> 研究单位:同一批 128 条公开 source prompt 的三种输入协议。
> 结论身份:**协议敏感性探针**,不是能力评测、训练分布、线上流量或专家语义。
## 1. 这轮究竟要排除什么混杂因素
上一轮已经证明:自然长度不同会改变路由集中度,因此我们不能把所有域间差异都
解释成“内容语义”。
但即使内容和长度都固定,输入模型之前还有一层协议:
```text
纯文本
↓ 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` 给出:
```jinja
{{ 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:
```text
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 都渲染三遍:
```text
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:
```text
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 的最长前缀。
选样排序固定为:
```text
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 记录:
```text
(相对内容字符起点, 相对内容字符终点, 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 个比较中:
```text
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 集合不完全相同。
这是一个重要的测量层级区别:
```text
aggregate distribution close
≠
every token took the same route
```
相反,也不能从 token route 变化推出输出答案或任务分数必然变化;本实验没有执行完整模型生成。
## 12. 独立复跑
正式输出:
```text
src/data/deepseek-v2-lite-routing-template.json
src/data/deepseek-v2-lite-routing-template-repro.json
```
两份完整 JSON:
```text
SHA-256
da1f10333b2fa269e64f9716a0ca6c1a656d23d3e70b8a25d6e59f8a5b3bc1b9
```
`cmp` byte-exact。
复现命令:
```bash
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 小模型复现。