feat: isolate DeepSeek role marker head token

This commit is contained in:
wuyang
2026-07-29 20:44:43 +08:00
parent 8ef2d33437
commit ea49ed0511
15 changed files with 3676450 additions and 33 deletions
@@ -0,0 +1,648 @@
# DeepSeek-V2-Lite 角色词头单 Token 控制审计
> 状态:真实官方权重执行(X)<br />
> 模型:`deepseek-ai/DeepSeek-V2-Lite` **base checkpoint**<br />
> revision:`604d5664dddd88a0433dbae533b7fe9472482de0`<br />
> 执行边界:layer 0–6;观测 MoE layer 1–6<br />
> 样本:WikiText-2 / TNEWS / HumanEval / GSM8K 各 32 条<br />
> 正式运行与独立复跑:byte-exact<br />
> 完整 JSON SHA-256:`9dc0e37fbce6581269428dcfcb84c7a17b466c5239c8171e6741f66d5eeb8caf`
## 0. 一句话先说结论
在固定 DeepSeek-V2-Lite base 权重、固定 128 个公开样本、固定重复词元历史、
固定官方 assistant EOS、固定冒号、固定长度、固定目标位置和固定八格 batch 时:
> 只把目标内容前面的 `User` 词头换成 `Assistant` 或 `x`,会让后续目标
> token 的专家路由发生清晰的直接变化;但是它没有在 24 个 layer×domain
> 中统一放大或削弱 system message 的路由作用。
目标内容、prompt-balanced 口径下,官方序列与两个前置干预之间的直接 TV 为:
```text
system off system on
User → Assistant .0222 .0218
User → x .0263 .0253
```
而四种角色词头条件下,system-edge TV 的 24 格平均为:
```text
官方 User: .03729
目标 User→Assistant .03705
目标 User→x .03754
后置 Assistant→User .03729
```
最后一行不是“小数恰好接近”,而是严格的 causal suffix 负对照:
> 把目标内容后 generation prompt 的 `Assistant` 换成 `User`,在六个 MoE
> 层、两个 system 水平的 34,488 个目标 layer×token 对上 ordered top-6
> 全部相同;目标 load、TV、JSD 与 system edge 也全部精确不变。
最准确的命名是:
> **固定协议中的角色词头单 input-ID 条件化。**
它不是“完整角色语义探针”,更不是 Chat 模型能力实验。
---
## 1. 为什么要从 EOS 继续拆到角色词头
上一轮已经固定重复词元历史、角色包装、长度和目标位置,只把历史 assistant
后的 EOS 换成三个普通单 token。结果说明 EOS identity 不是普通占位符。
但官方序列仍然是:
```text
Assistant: x <EOS> User: TARGET
```
所以即便 EOS 被替换,下一条 `User:` 仍然提供回合入口线索。上一轮不能回答:
1. `User:` 中的角色词头是否会条件化后续目标计算;
2. 这种变化是角色词头的直接作用,还是对 system message 效应的统一调制;
3. 同一个单 ID 干预如果发生在目标之后,目标路由是否按 causal mask 保持不变。
本轮把这三个问题放进同一个八格 batch。
---
## 2. 官方模板中的 `User:` 不是一个 token
固定 revision 的官方模板仍然是:
```jinja
{% if message['role'] == 'user' %}
{{ 'User: ' + message['content'] + '\n\n' }}
{% elif message['role'] == 'assistant' %}
{{ 'Assistant: ' + message['content'] + eos_token }}
{% elif message['role'] == 'system' %}
{{ message['content'] + '\n\n' }}
{% endif %}
```
固定 tokenizer 的真实 tokenization 是:
| 文本 | token IDs | token 数 | special |
|---|---|---:|---:|
| `User` | `[5726]` | 1 | 否 |
| `Assistant` | `[77398]` | 1 | 否 |
| `User:` | `[5726, 25]` | 2 | 否 |
| `Assistant:` | `[77398, 25]` | 2 | 否 |
| `x` | `[87]` | 1 | 否 |
| `<EOS>` | `[100001]` | 1 | 是 |
因此本实验只干预 `User:` / `Assistant:` 的**词头 ID**,冒号 ID `25`
始终保留。
这一区分很重要:
> `User → Assistant` 不是把完整 `User:` 换成完整 `Assistant:` 的字符串实验,
> 而是把两个 token 序列中的第一个 ID 替换掉。
官方模板可在
[固定 tokenizer_config](https://huggingface.co/deepseek-ai/DeepSeek-V2-Lite/blob/604d5664dddd88a0433dbae533b7fe9472482de0/tokenizer_config.json)
直接核查。
---
## 3. 四个角色词头水平
两个因子是:
- `S`:system 关闭 / 开启;
- `R`:角色词头控制。
四个 `R` 水平如下:
| 水平 | 单 ID 改动 | 相对目标 | 官方合法序列 |
|---|---|---|---:|
| `official` | 无 | — | 是 |
| `target_assistant` | 目标前 `User 5726 → Assistant 77398` | 前 | 否 |
| `target_x` | 目标前 `User 5726 → x 87` | 前 | 否 |
| `suffix_user` | 目标后 `Assistant 77398 → User 5726` | 后 | 否 |
四种序列可以直观看成:
```text
OFFICIAL
... Assistant: x <EOS> User: TARGET \n\n Assistant:
TARGET_ASSISTANT
... Assistant: x <EOS> Assistant: TARGET \n\n Assistant:
TARGET_X
... Assistant: x <EOS> x: TARGET \n\n Assistant:
SUFFIX_USER
... Assistant: x <EOS> User: TARGET \n\n User:
```
三个干预都在官方 tokenization 完成后修改 ID,因此它们是明确的
counterfactual token sequences,不是官方 `apply_chat_template` 会自然生成的
合法聊天。
---
## 4. 固定了什么
每个 source 的八格共同固定:
- 同一 DeepSeek-V2-Lite base checkpoint;
- 同一 system 文本;
- 同一 repeated-token user / assistant 历史;
- 同一官方 assistant EOS;
- 同一目标文本;
- 同一 canonical 23-token 截断合同;
- 同一冒号 token;
- 同一序列长度;
- 同一目标绝对位置;
- 同一 attention mask;
- 同一 right-padding;
- 同一次 32-row forward 的 batch shape。
正式 renderer 验证:
| 检查 | 结果 |
|---|---:|
| source prompts | 128 |
| system groups | 256 |
| 四格同长度 | 256 / 256 |
| 四格同目标 span | 256 / 256 |
| 官方序列零 ID 改动 | 256 / 256 |
| 三种反事实恰好一 ID 改动 | 768 / 768 |
| 前置单 ID 干预 | 512 |
| 后置单 ID 干预 | 256 |
---
## 5. 为什么必须放一个目标后的角色词头
如果只比较:
```text
User: TARGET
Assistant: TARGET
```
即使看到差异,也只能知道“前面改一个 ID 会影响后面”,却不知道:
- renderer 是否错误对齐目标;
- batch 中的其他条件是否让任意单 ID 改动都污染所有位置;
- causal mask 是否按预期工作;
- route 对齐代码是否把 suffix token 错算进目标。
因此加入:
```text
TARGET ... Assistant:
TARGET ... User:
```
改动发生在所有目标内容 token 之后。对 decoder-only causal mask 而言,过去
位置不能看见未来位置,所以目标路由应精确不变。
这不是统计上的“希望接近零”,而是一条确定性执行闸门。
---
## 6. 统计量仍然分三张账
### 6.1 直接替换距离
在 system 水平 `s` 下:
```text
TV(P(s, official), P(s, role_control))
```
回答:
> 只改这个角色词头,目标内容的聚合专家份额移动了多少?
### 6.2 system edge
角色水平 `r` 下:
```text
TV(P(S0, r), P(S1, r))
```
回答:
> 固定角色词头时,system 开关让目标路由移动了多少?
### 6.3 system-edge contrast
```text
TV_system(role_control) - TV_system(official)
```
回答:
> 替换角色词头是否改变 system effect 的**强度**?
直接替换距离非零,不自动推出 system-edge contrast 也应同方向。
---
## 7. 先看直接作用:前置角色词头确实改变后续路由
目标内容、prompt-balanced 聚合:
| 干预 | S0 平均 direct TV | S1 平均 direct TV | S1−S0 |
|---|---:|---:|---:|
| `User→Assistant` | .022243 | .021804 | -.000439 |
| `User→x` | .026305 | .025339 | -.000966 |
| 后置 `Assistant→User` | 0 | 0 | 0 |
两个前置词头替换都产生约 `.02–.03` 的直接 TV;但 S1−S0 的均值接近零。
逐层平均与逐 token top-6 set exact:
| 层 | U→A direct TV S0 / S1 | U→A set exact S0 / S1 | U→x direct TV S0 / S1 | U→x set exact S0 / S1 | suffix direct TV |
|---:|---:|---:|---:|---:|---:|
| L1 | 0.0267 / 0.0253 | 79.9% / 81.8% | 0.0275 / 0.0278 | 78.2% / 80.5% | 0 / 0 |
| L2 | 0.0187 / 0.0175 | 82.0% / 83.1% | 0.0214 / 0.0240 | 80.2% / 78.8% | 0 / 0 |
| L3 | 0.0175 / 0.0191 | 81.7% / 80.3% | 0.0235 / 0.0231 | 79.5% / 76.7% | 0 / 0 |
| L4 | 0.0209 / 0.0238 | 76.3% / 74.3% | 0.0260 / 0.0249 | 72.2% / 71.4% | 0 / 0 |
| L5 | 0.0229 / 0.0218 | 71.4% / 73.7% | 0.0266 / 0.0251 | 68.3% / 70.4% | 0 / 0 |
| L6 | 0.0267 / 0.0233 | 70.0% / 70.5% | 0.0329 / 0.0270 | 66.4% / 68.4% | 0 / 0 |
随着层数增加,直接替换后的 set exact 整体下降,说明一个前置 ID 的差异会沿
残差流累积;但这仍只是 route-set 稳定性,不是专家语义或任务质量。
---
## 8. 关键反例:system-edge 强度没有统一方向
目标内容、prompt-balanced 口径,24 个 layer×domain 的平均:
```text
official .037290
target_assistant .037045
target_x .037542
suffix_user .037290
```
相对官方的 contrast:
| contrast | 平均 ΔTV | 点估计正 / 负 | CI 负 / 跨零 / 正 |
|---|---:|---:|---:|
| `target_assistant − official` | -.000246 | 12 / 12 | 2 / 19 / 3 |
| `target_x − official` | +.000252 | 13 / 11 | 3 / 19 / 2 |
| `suffix_user − official` | 0 | 0 / 0 | 0 / 24 / 0 |
因此不允许写:
- “Assistant 词头让 system 更强”;
- “x 会屏蔽 system”;
- “角色标记统一提高路由稳定性”。
数据支持的是:
> 前置词头会直接改变后续路由,但它与 system message 的组合关系依赖层与域。
---
## 9. 全部 24 格
下表只使用目标内容、prompt-balanced 与共享 source bootstrap:
| 层 | 域 | 官方 TV | User→Assistant TV | Δ / 95% CI | User→x TV | Δ / 95% CI | suffix TV |
|---:|---|---:|---:|---|---:|---|---:|
| L1 | 英文 | 0.0559 | 0.0585 | +0.0026 / [-0.0033, +0.0068] | 0.0625 | +0.0066 / [-0.0002, +0.0117] | 0.0559 |
| L1 | 中文 | 0.0276 | 0.0251 | -0.0025 / [-0.0054, +0.0016] | 0.0245 | -0.0032 / [-0.0070, +0.0007] | 0.0276 |
| L1 | 代码 | 0.0571 | 0.0574 | +0.0003 / [-0.0027, +0.0059] | 0.0553 | -0.0018 / [-0.0054, +0.0027] | 0.0571 |
| L1 | 数学 | 0.0784 | 0.0769 | -0.0015 / [-0.0070, +0.0045] | 0.0889 | +0.0105 / [+0.0060, +0.0157] | 0.0784 |
| L2 | 英文 | 0.0293 | 0.0298 | +0.0004 / [-0.0036, +0.0049] | 0.0321 | +0.0028 / [-0.0008, +0.0066] | 0.0293 |
| L2 | 中文 | 0.0204 | 0.0188 | -0.0016 / [-0.0045, +0.0025] | 0.0199 | -0.0005 / [-0.0042, +0.0038] | 0.0204 |
| L2 | 代码 | 0.0331 | 0.0377 | +0.0046 / [+0.0005, +0.0082] | 0.0375 | +0.0043 / [+0.0002, +0.0084] | 0.0331 |
| L2 | 数学 | 0.0401 | 0.0364 | -0.0037 / [-0.0090, +0.0016] | 0.0346 | -0.0056 / [-0.0103, -0.0007] | 0.0401 |
| L3 | 英文 | 0.0312 | 0.0360 | +0.0047 / [-0.0009, +0.0076] | 0.0328 | +0.0016 / [-0.0043, +0.0060] | 0.0312 |
| L3 | 中文 | 0.0224 | 0.0188 | -0.0036 / [-0.0075, +0.0011] | 0.0234 | +0.0009 / [-0.0036, +0.0061] | 0.0224 |
| L3 | 代码 | 0.0411 | 0.0464 | +0.0053 / [+0.0012, +0.0098] | 0.0443 | +0.0032 / [-0.0021, +0.0070] | 0.0411 |
| L3 | 数学 | 0.0337 | 0.0343 | +0.0006 / [-0.0041, +0.0050] | 0.0309 | -0.0028 / [-0.0057, +0.0024] | 0.0337 |
| L4 | 英文 | 0.0326 | 0.0326 | -0.0000 / [-0.0057, +0.0052] | 0.0360 | +0.0033 / [-0.0021, +0.0088] | 0.0326 |
| L4 | 中文 | 0.0293 | 0.0250 | -0.0043 / [-0.0085, +0.0013] | 0.0281 | -0.0011 / [-0.0053, +0.0041] | 0.0293 |
| L4 | 代码 | 0.0390 | 0.0356 | -0.0034 / [-0.0079, +0.0030] | 0.0322 | -0.0069 / [-0.0107, -0.0015] | 0.0390 |
| L4 | 数学 | 0.0513 | 0.0449 | -0.0064 / [-0.0127, -0.0012] | 0.0494 | -0.0019 / [-0.0086, +0.0049] | 0.0513 |
| L5 | 英文 | 0.0335 | 0.0366 | +0.0031 / [-0.0036, +0.0083] | 0.0354 | +0.0019 / [-0.0038, +0.0083] | 0.0335 |
| L5 | 中文 | 0.0244 | 0.0254 | +0.0009 / [-0.0036, +0.0062] | 0.0231 | -0.0014 / [-0.0059, +0.0038] | 0.0244 |
| L5 | 代码 | 0.0386 | 0.0283 | -0.0104 / [-0.0144, -0.0036] | 0.0252 | -0.0135 / [-0.0173, -0.0061] | 0.0386 |
| L5 | 数学 | 0.0459 | 0.0424 | -0.0035 / [-0.0081, +0.0015] | 0.0472 | +0.0013 / [-0.0053, +0.0073] | 0.0459 |
| L6 | 英文 | 0.0303 | 0.0321 | +0.0019 / [-0.0038, +0.0083] | 0.0298 | -0.0005 / [-0.0073, +0.0058] | 0.0303 |
| L6 | 中文 | 0.0242 | 0.0281 | +0.0039 / [-0.0020, +0.0091] | 0.0245 | +0.0002 / [-0.0047, +0.0066] | 0.0242 |
| L6 | 代码 | 0.0316 | 0.0395 | +0.0080 / [+0.0031, +0.0127] | 0.0357 | +0.0042 / [-0.0020, +0.0107] | 0.0316 |
| L6 | 数学 | 0.0436 | 0.0424 | -0.0013 / [-0.0067, +0.0047] | 0.0479 | +0.0043 / [-0.0022, +0.0096] | 0.0436 |
仅 5 / 24 个 `User→Assistant` 区间、5 / 24 个 `User→x` 区间完全位于零
的一侧;正负方向同时存在。
---
## 10. causal suffix 负对照的完整账
目标后的 `Assistant→User`:
| 对象 | exact |
|---|---:|
| target ordered top-6 layer×token×system | 34,488 / 34,488 |
| target set top-6 layer×token×system | 34,488 / 34,488 |
| target route hash prompt×layer×system | 1,536 / 1,536 |
| target integer load prompt×layer×system | 1,536 / 1,536 |
| target direct TV | 0 |
| target direct JSD | 0 |
| target system-edge contrast | 0 |
| target ΔCV contrast | 0 |
完整输入则不同:
| 对象 | exact |
|---|---:|
| full route hash | 0 / 1,536 |
| full integer load | 6 / 1,536 |
| mean direct TV at S0 | .012134 |
| mean direct TV at S1 | .008710 |
这正是合理分账:
- 目标内容位于 edit 之前,所以不可见;
- suffix 自身和其后的冒号位于 edit 之后,所以完整输入可变。
---
## 11. 为什么完整输入不能代替目标内容
完整输入的 mean system-edge TV:
```text
official .138929
target_assistant .139118
target_x .140953
suffix_user .139329
```
其中 `target_x − official` 平均为 `+.002024`,19 / 24 点估计为正;
`suffix_user − official` 也不为零。
但完整输入混入:
- 被替换的角色词头;
- 历史包装;
- 目标前后的换行;
- generation prompt;
- suffix 本身。
所以它回答的是:
> 整条协议流量如何改变?
而不是:
> 相同目标内容如何被上下文条件化?
网站必须允许 scope 切换,但主结论只能来自 exact target content。
---
## 12. ΔCV 与 direct interaction 都不支持统一方向
目标内容、prompt-balanced:
| 对象 | 平均 | 点估计正 / 负 | CI 负 / 跨零 / 正 |
|---|---:|---:|---:|
| U→A 的 system-edge ΔCV | +.003138 | 16 / 8 | 4 / 16 / 4 |
| U→x 的 system-edge ΔCV | +.003260 | 15 / 9 | 4 / 15 / 5 |
| U→A direct TV 的 S1−S0 | -.000439 | 11 / 13 | 3 / 20 / 1 |
| U→x direct TV 的 S1−S0 | -.000966 | 10 / 14 | 5 / 17 / 2 |
| suffix 全部上述量 | 0 | 0 / 0 | 0 / 24 / 0 |
因此不写“角色词头让路由更均衡”或“system 会放大角色作用”。
---
## 13. 同 batch shape 仍不等于同 BF16 执行
本轮的官方条件与上一轮 EOS 条件:
- 256 / 256 messages hash 相同;
- 256 / 256 rendered text hash 相同;
- 256 / 256 token-ID hash 相同;
- 256 / 256 长度与目标 token 合同相同;
- batch 都是 32 rows。
但同行的其他反事实从:
```text
EOS / x / period / newline
```
变成:
```text
official / target_assistant / target_x / suffix_user
```
跨实验 official route exact:
| 层 | full hash | target hash | full load | target load |
|---:|---:|---:|---:|---:|
| L1 | 256 / 256 | 256 / 256 | 256 / 256 | 256 / 256 |
| L2 | 60 / 256 | 180 / 256 | 158 / 256 | 208 / 256 |
| L3 | 43 / 256 | 149 / 256 | 99 / 256 | 188 / 256 |
| L4 | 27 / 256 | 102 / 256 | 74 / 256 | 149 / 256 |
| L5 | 13 / 256 | 72 / 256 | 52 / 256 | 124 / 256 |
| L6 | 16 / 256 | 73 / 256 | 49 / 256 | 129 / 256 |
观察到的事实是:
> 第一 MoE 层完全相同;经过不同 companion rows 的 MoE 计算后,深层 BF16
> route hashes 开始分化。
一种合理但尚未直接验证的工程解释是:其他 rows 改变 expert grouping / microbatch
形状或低精度 kernel 数值路径,细小误差再通过深层 gate 放大。当前证据不能唯一定位
是哪一个 kernel。
因此正式结论继续限定为:
> **同一次八格 batch 内的配对对比。**
“相同 batch shape”不足以支持跨实验 route-byte equality;batch 内容也是执行合同。
---
## 14. 与一手文献如何连接
### 14.1 Hugging Face chat templates
[官方文档](https://huggingface.co/docs/transformers/en/chat_templating)明确把
messages 转换为模型实际读取的 token sequence,并强调不同 chat model 使用不同控制
token / 格式。
它支持:
- 必须检查最终 token IDs;
- 不能把统一的 `{role, content}` API 当成统一模型输入。
它不支持:
- DeepSeek 的 `User` ID 必然有跨模型通用语义。
### 14.2 LIMA
[LIMA](https://arxiv.org/abs/2305.11206)在监督数据中用 EOT 标记说话人边界,
说明 speaker / turn serialization 是 alignment 数据合同的一部分。
它支持研究问题的重要性,但不证明本 base checkpoint 的某个 route TV 是 EOT 语义。
### 14.3 URIAL
[The Unlocking Spell on Base LLMs / URIAL](https://proceedings.iclr.cc/paper_files/paper/2024/file/6bcbb4a501dbad0eba1b660c1a55318c-Paper-Conference.pdf)
展示 system prompt、风格化示例与固定格式可以显著改变 base model 的生成行为。
它支持:
- base model 也可能受结构化上下文条件化;
- “base ≠ 完全不响应模板”。
它不支持:
- 把本实验的路由差异命名为 alignment;
- 推出 DeepSeek-V2-Lite-Chat 的行为。
### 14.4 Prompt template 与安全/能力
[Keeping LLMs Aligned After Fine-tuning](https://proceedings.neurips.cc/paper_files/paper/2024/file/d6f034bb216b472fc7d32ec7aff20342-Paper-Conference.pdf)
在多个 chat model 上显示 fine-tuning / inference prompt template 会影响安全行为。
[State of What Art?](https://aclanthology.org/2024.tacl-1.52/)则在 6.5M
实例上展示单一 instruction template 会造成性能与排名脆弱性。
两者支持“模板不能当无关包装”,但测量对象是行为/评测,不是本模型的专家路由机制。
---
## 15. 因果阶梯
当前证据阶梯可以写成:
```text
一级:官方模板精确 tokenization
已完成
二级:同长度单 ID 干预
已完成
三级:目标后 suffix causal 负对照
已完成,34,488 / 34,488 ordered exact
四级:前置角色词头直接改变目标路由
已完成,direct TV ≈ .02–.03
五级:角色词头统一调制 system
证据反对;方向混合、均值近零
六级:完整角色协议
未完成;冒号、换行、EOS、词头组合尚未一起正交
七级:Chat/SFT 行为
未完成
八级:任务能力 / 安全 / 准确率
未完成
```
---
## 16. 不允许外推的结论
本实验不能证明:
- 模型“理解 User 和 Assistant 的社会角色”;
- `Assistant` 词头比 `User` 更强或更弱;
- 角色词头统一放大 system prompt;
- 某个专家是 user / assistant 专家;
- 路由变化等于回答质量变化;
- base checkpoint 与 Chat checkpoint 使用相同角色机制;
- 任意 chat template 都可用一个 token 代表;
- suffix edit 对生成结果没有影响;
- 完整 27 层与当前前六个 MoE 层相同;
- 跨 batch 或跨硬件的 route hash 必须相同。
---
## 17. 可复现运行
脚本:
```text
experiments/deepseek/v2_lite_routing_role_marker_head_control.py
```
正式命令:
```bash
PYTHONPATH=/tmp/deepseek-v2-lite-pydeps.6ZJAH3:/usr/lib/python3/dist-packages \
/home/wuyang/.pyenv/versions/navi-router-cu128/bin/python -B \
experiments/deepseek/v2_lite_routing_role_marker_head_control.py \
--artifact-dir /tmp/deepseek-v2-lite-artifacts.OEjfce \
--human-eval /tmp/llm-atlas-routing-corpus/human-eval/data/HumanEval.jsonl.gz \
--gsm8k /tmp/llm-atlas-routing-corpus/gsm8k/grade_school_math/data/test.jsonl \
--tnews /tmp/llm-atlas-routing-corpus/tnews/test.json \
--tnews-archive /tmp/llm-atlas-routing-corpus/tnews_public.zip \
--wikitext /tmp/llm-atlas-routing-corpus/wikitext-2-raw-v1-validation.parquet \
--output src/data/deepseek-v2-lite-routing-role-marker-head-control.json \
--per-domain 32 \
--content-tokens 23 \
--batch-prompts 4 \
--layers 7 \
--bootstrap 2000 \
--seed 20260729 \
--captured-at 2026-07-29T12:30:00+00:00
```
正式结果:
```text
src/data/deepseek-v2-lite-routing-role-marker-head-control.json
53,440,884 bytes
SHA-256 9dc0e37fbce6581269428dcfcb84c7a17b466c5239c8171e6741f66d5eeb8caf
```
独立复跑:
```text
src/data/deepseek-v2-lite-routing-role-marker-head-control-repro.json
53,440,884 bytes
SHA-256 9dc0e37fbce6581269428dcfcb84c7a17b466c5239c8171e6741f66d5eeb8caf
```
两份文件 `cmp` byte-exact。
---
## 18. 下一步
角色词头已经拆到单 ID,但完整角色协议仍包含:
```text
角色词头 + 冒号 + 空格/换行 + EOS/EOT + 训练身份
```
下一批优先级:
1. special-token family:BOS / EOS / 普通词头使用同长度家族对照;
2. 完整 `User:` / `Assistant:` 两 token 角色块,用等长两 token 控制;
3. V2-Lite-Chat checkpoint 的同构路由与生成行为;
4. target role swap 后的 next-token logits / generation / task score;
5. 固定整批 companion rows 的 dummy-shape 与 batch-content 对照;
6. 完整 27 层;
7. gate logits / margin,而不只看离散 top-6。
本轮最重要的收获不是一句“角色很重要”,而是一条更精确的分界:
> 一个前置角色词头 ID 足以改变后续路由;但它没有把 system 作用变成统一方向。
> 真正严格的 Chat 角色机制,需要把训练身份、完整控制 token 族与行为指标一起补上。