xuan-luo's picture
|
download
raw
20 kB
# Parallel DMTD:训练与测试逻辑
四个模型使用相同的基础结构:
```text
Qwen3-4B
├── 36 个 Transformer layers
├── Parallel layers: P28 = layers[0:28]
├── Sequential layers: S8 = layers[28:36]
├── Cycle length: τ = 4
└── MASK token id: = 151660
```
四个模型由两个正交选项组成:
| 模型 | 当前 cycle 内 P28 shadow attention | 历史 cycle 表示 |
| --- | --- | --- |
| `Causal-Parallel-Norefresh` | Causal | 保留历史 shadow KV |
| `Causal-Parallel-Refresh` | Causal | 使用历史 real KV,不保留 shadow KV |
| `Bidirectional-Parallel-Norefresh` | Bidirectional | 保留历史 shadow KV |
| `Bidirectional-Parallel-Refresh` | Bidirectional | 使用历史 real KV,不保留 shadow KV |
后文使用:
- `x_c`:当前 cycle 的第一个真实 token,即 cycle head;
- `M`:MASK token;
- `s_0,s_1,s_2,s_3`:当前 cycle 经过 P28 后得到的四个最终 shadow hidden states;
- `C_P[l]`:第 `l` 个 parallel layer 的 KV cache,`l=0,...,27`
- `C_S[l]`:第 `l` 个 sequential layer 的 KV cache,`l=28,...,35`
---
# 一、训练逻辑
## 1. 共同训练逻辑
### 1.1 Cycle 划分
训练时将序列按照 `τ=4` 划分:
```text
真实序列:
[x0, x1, x2, x3] [x4, x5, x6, x7] ...
cycle id:
[0, 0, 0, 0 ] [1, 1, 1, 1 ] ...
```
cycle head 由以下规则确定:
```python
is_cycle_head = position_ids % mtp_horizon == 0
cycle_id = position_ids // mtp_horizon
```
每个 cycle 的 shadow 输入为:
```text
[真实 cycle head, MASK, MASK, MASK]
```
因此完整 shadow 序列为:
```text
[x0, M, M, M] [x4, M, M, M] ...
```
### 1.2 P28 输出与真实 embedding 合并
P28 处理 shadow 序列,产生每个位置的 parallel hidden:
\[
h_i^{parallel}=P28(shadow)_i
\]
S8 的输入为:
\[
h_i^{S8}=Embed(x_i)+h_i^{parallel}
\]
两个分量的含义不同:
- `Embed(x_i)` 是当前位置的真实 token;
- `h_i^{parallel}` 是 cycle 开始时可以提前计算的 shadow/lookahead 表示。
例如一个 cycle:
```text
P28([x_c,M,M,M]) → [s_0,s_1,s_2,s_3]
S8 的训练输入:
embed(x_c) + s_0
embed(x_{c+1}) + s_1
embed(x_{c+2}) + s_2
embed(x_{c+3}) + s_3
```
训练时真实序列全部已知,因此可以在同一个 teacher-forced forward 中计算四个位置的
S8 输入。
### 1.3 Loss
四个模型都使用普通 next-token causal language modeling loss:
\[
\mathcal{L}
=-\sum_i \log p(x_{i+1}\mid x_{\le i},h_i^{parallel})
\]
没有额外 LM head,也没有额外 multi-token loss。区别只在 P28 shadow representation
如何构造。
### 1.4 Packed sequence
使用 packing 时:
- 每个 document 的 `position_ids` 从 0 重新开始;
- cycle 不能跨越 document boundary;
- real 和 shadow attention 都不能跨 document;
- 同一 batch 应具有兼容的 cycle layout。
---
## 2. Causal-Parallel-Norefresh 训练
Shadow 序列:
```text
[x0,M,M,M] [x4,M,M,M] [x8,M,M,M] ...
```
整个 shadow 序列通过 P28,使用标准 causal attention。
对于位置 `i`
```text
shadow position i 只能看到 shadow positions <= i
```
因此当前 cycle 可以看到历史 cycle 的 shadow 表示:
```text
历史:
[x0,M,M,M] [x4,M,M,M]
当前:
[x8,M,M,M]
```
历史非 head 位置始终是 MASK representation,不会被真实 token 替换。
训练过程:
```text
shadow_ids
└── P28 causal shadow forward
└── parallel_hidden
inputs_embeds + parallel_hidden
└── S8 causal forward
└── LM head + next-token loss
```
Norefresh 的关键训练语义是:
> 后续 cycle 会读取之前 cycle 的 shadow K/V,因此历史 shadow representation 是模型
> 上下文的一部分。
---
## 3. Causal-Parallel-Refresh 训练
训练时同时运行共享权重的 real branch 和 shadow branch。
### 3.1 Real branch
真实序列:
```text
[x0,x1,x2,x3] [x4,x5,x6,x7] ...
```
通过 P28 标准 causal forward。每个 parallel layer 都产生自己的 real K/V:
```text
Layer 0 : K_real[0], V_real[0]
Layer 1 : K_real[1], V_real[1]
...
Layer 27: K_real[27], V_real[27]
```
Real branch 的最终 hidden states 不直接进入 S8。它只为 shadow branch 提供历史 cycle
的真实 K/V。
### 3.2 Shadow branch
Shadow 序列:
```text
[x0,M,M,M] [x4,M,M,M] ...
```
在每个 P28 layer,shadow query 读取:
1. 严格更早 cycle 的 real K/V;
2. 当前 cycle 的 shadow K/V;
3. 当前 cycle 内遵守 causal 顺序。
可见性为:
```python
real_visible = real_kv and real_cycle < query_cycle
shadow_visible = shadow_kv and shadow_cycle == query_cycle and shadow_position <= query_position
visible = real_visible or shadow_visible
```
历史模式为:
```text
[real,real,real,real] ... [head,MASK,MASK,MASK]
```
即:
```text
[1111] [1111] [1000]
```
这在一次 teacher-forced forward 中模拟测试时:
1. 上一 cycle 已完成;
2. 上一 cycle 用真实 token causal P28 forward 完成 refresh;
3. 当前 cycle 再执行 shadow forward。
Refresh 训练的关键语义是:
> Shadow branch 只使用当前 cycle 的临时 shadow K/V;跨 cycle 历史使用 real K/V,
> 不使用历史 shadow K/V。
---
## 4. Bidirectional-Parallel-Norefresh 训练
它与 `Causal-Parallel-Norefresh` 的区别仅在当前 cycle 内的 shadow attention。
Attention 规则:
```python
same_cycle = query_cycle == key_cycle
causal_history = key_position <= query_position
visible = same_cycle or causal_history
```
含义:
- 跨 cycle 保持 causal;
- 当前 cycle 的四个 shadow 槽位完全双向可见;
- 历史 cycle 仍然是 `[head,MASK,MASK,MASK]` shadow representation。
当前 cycle:
```text
[x_c,M_1,M_2,M_3]
```
其内部 attention 为:
```text
x_c ↔ M_1 ↔ M_2 ↔ M_3
```
这里双向可见的只是 MASK/shadow representation,不包含未来真实 token。
Norefresh 训练仍然要求历史 shadow representation 被后续 cycle 使用。
---
## 5. Bidirectional-Parallel-Refresh 训练
它结合:
1. 历史 cycle 使用 real K/V;
2. 当前 cycle 的 shadow block 使用 bidirectional attention。
每个 P28 layer 的可见性为:
```python
real_visible = real_kv and real_cycle < query_cycle
shadow_visible = shadow_kv and shadow_cycle == query_cycle
visible = real_visible or shadow_visible
```
因此:
```text
历史 cycle:
[real,real,real,real] [real,real,real,real]
当前 cycle:
[head,MASK,MASK,MASK],四个位置双向可见
```
Real branch 仍然只提供每层历史 K/V;最终送入 S8 的是 shadow branch 最后一层输出:
```text
parallel_hidden = shadow_hidden_after_layer_27
h_S8 = inputs_embeds + parallel_hidden
```
Refresh 训练不要求历史 shadow KV 跨 cycle 保留。
---
# 二、测试逻辑
## 6. 共同测试结构
测试仍然使用 DMTD-style cycled decoding。
每个 cycle 的第一个真实 token 已知后:
1. 根据模型类型处理历史 P28 KV;
2. 一次性计算当前 cycle 的四个 shadow hidden;
3. 保存最终 `[s_0,s_1,s_2,s_3]`
4. cycle 内逐 token 运行 S8;
5. S8 每一步使用一个真实 token embedding 和对应 shadow hidden。
当前 cycle:
```text
P28([x_c,M,M,M]) → [s_0,s_1,s_2,s_3]
embed(x_c) + s_0 → S8 → x_{c+1}
embed(x_{c+1}) + s_1 → S8 → x_{c+2}
embed(x_{c+2}) + s_2 → S8 → x_{c+3}
embed(x_{c+3}) + s_3 → S8 → x_{c+4}
```
`x_{c+4}` 是下一个 cycle head。
### 6.1 S8 cache:四个模型完全相同
后 8 层中的每一层都保存自己的 real KV:
```text
C_S[28], C_S[29], ..., C_S[35]
```
每处理一个真实 token:
```text
Layer 28 append 当前 token 的 K/V
Layer 29 append 当前 token 的 K/V
...
Layer 35 append 当前 token 的 K/V
```
S8 cache:
- 每个真实 token 增加一个位置;
- 始终是 dense real-token history;
- 永远不保存 MASK;
- 永远不 refresh;
- 永远不 crop。
### 6.2 当前 cycle shadow hidden
每个 cycle 只运行一次:
```text
[x_c,M,M,M] → P28
```
只需要为 cycle 内 S8 decoding 持久保存:
```text
[s_0,s_1,s_2,s_3]
```
这四个 hidden 是 Layer 27 输出后的最终 parallel hidden。
P28 每一层在计算当前 block attention 时仍然需要当前 block 的临时 Q/K/V,但是否将
这些 K/V 写入跨 cycle cache,取决于 Refresh 或 Norefresh。
---
## 7. Refresh 测试逻辑
适用于:
- `Causal-Parallel-Refresh`
- `Bidirectional-Parallel-Refresh`
### 7.1 Persistent P28 cache
Refresh 模型持久保存 28 组 real KV cache:
```text
C_P[0], C_P[1], ..., C_P[27]
```
其中每一组都只包含已经 refresh 的真实 token K/V。
例如进入位置 `8~11` 的 cycle 前,P28 real cache 只包含位置 `0~3`
```text
Layer 0 : [R0 R1 R2 R3]
Layer 1 : [R0 R1 R2 R3]
...
Layer 27: [R0 R1 R2 R3]
```
上一 cycle 的真实 token `x4,x5,x6,x7` 已经生成,但还未进入 P28 persistent cache。
### 7.2 Refresh 上一 cycle
将:
```text
[x4,x5,x6,x7]
```
作为一个真实 block,通过全部 28 个 parallel layers,使用标准 causal attention。
在每一层分别追加该层产生的 real K/V:
```text
Layer 0 append K_real[0](x4:x7), V_real[0](x4:x7)
Layer 1 append K_real[1](x4:x7), V_real[1](x4:x7)
...
Layer 27 append K_real[27](x4:x7), V_real[27](x4:x7)
```
完成后:
```text
Layer 0 : [R0 ... R7]
Layer 1 : [R0 ... R7]
...
Layer 27: [R0 ... R7]
```
因为 Refresh 模式从未将上一 cycle 的 shadow K/V 写入 persistent cache,所以:
- 不需要 crop shadow KV;
- 不需要替换 shadow KV;
- 直接 append real KV 即可。
### 7.3 将 refresh 与当前 shadow 合并为一次 P28 forward
上面的“refresh 上一 cycle”和“forward 当前 shadow block”描述的是两个逻辑阶段。
实际编写 inference 时,应将它们合并成一次带定制 attention mask 的 P28 forward,
避免先后启动两次 4-token forward。
在稳态 cycle 中,P28 一次接收 8 个 token slots:
```text
P28 input:
[x4,x5,x6,x7 | x8,MASK9,MASK10,MASK11]
└ refresh real ┘ └── current shadow ──┘
4 tokens 4 tokens
```
所以 cycle 第一次 forward 的计算粒度是:
```text
Layers 0~27 (P28): 8 个 token slots
Layers 28~35 (S8): 仅当前 cycle head x8,共 1 个 token
```
不能把 8 个 token 全部送入 S8。前四个 token 仅用于生成 P28 real KV,当前 shadow
四槽仅用于生成 `[s8,s9,s10,s11]`;随后只有:
```text
embed(x8) + s8
```
进入 S8,产生 `x9`
对于每一个 parallel layer `l=0,...,27`,一次性计算这 8 个位置的 Q/K/V,并使用以下
block attention mask。
#### Refresh real queries:前四个位置
```text
query x4 只能看到 persistent real history 和 x4
query x5 只能看到 persistent real history 和 x4:x5
query x6 只能看到 persistent real history 和 x4:x6
query x7 只能看到 persistent real history 和 x4:x7
```
它们使用标准 causal attention,并且绝不能看到当前 cycle 的 shadow 位置 `8~11`
#### Current shadow queries:后四个位置
所有当前 shadow query 都可以看到:
```text
1. P28 persistent real history;
2. 本次 forward 中刚计算的 refresh real positions x4:x7;
3. 当前 cycle 的 local shadow block。
```
当前 shadow block 内部的可见性由模型决定:
```text
Causal-Parallel-Refresh:
当前四槽内部使用 causal mask
Bidirectional-Parallel-Refresh:
当前四槽内部完全双向可见
```
`Bidirectional-Parallel-Refresh` 为例,单层 mask 的逻辑结构是:
```text
Keys →
Queries ↓ old real cache refresh real 4 current shadow 4
refresh real 4 causal causal ×
current shadow 4 ✓ ✓ bidirectional
```
其中 `×` 表示 refresh real queries 禁止读取当前 shadow。
每层 forward 后必须拆分 KV:
```python
real_kv = kv[:, :, :4, :] # x4:x7
shadow_kv = kv[:, :, 4:8, :] # x8:MASK11
append_to_persistent_cache(real_kv)
discard_after_this_layer(shadow_kv)
```
也就是说:
- 前四个 refresh real K/V 写入该层 `C_P[l]`
- 后四个 current shadow K/V 只参与本层 attention,不写入 persistent cache;
- real hidden 和 shadow hidden 都传给下一个 P28 layer;
- Layer 27 完成后,只持久保存最后四个 shadow outputs:
`[s8,s9,s10,s11]`
因此,合并 forward 不改变 Refresh 的 cache 语义:
```text
P28 persistent cache 只增加 4 个 real KV positions,
不会增加当前 cycle 的 4 个 shadow KV positions。
```
第一个生成 cycle 是特殊情况:没有上一生成 cycle 可以 refresh,因此第一次 P28
forward 只有当前 `[head,MASK,MASK,MASK]` 四个 shadow slots。之后的稳态 cycle 才使用
上述 8-token P28 forward。
### 7.4 Forward 当前 shadow block
构造:
```text
[x8,MASK9,MASK10,MASK11]
```
依次通过 Layer 0 到 Layer 27。
对于每一个 parallel layer `l`
```text
Persistent history:
C_P[l] = real K/V positions 0~7
Current local block:
K_shadow[l], V_shadow[l] positions 8~11
```
当前层 attention 使用:
```text
[persistent real history ; current temporary shadow block]
```
当前层计算结束后:
- shadow K/V 不写入 `C_P[l]`
- shadow K/V 只在当前层 forward 中临时存在;
- 当前层输出 hidden 传给下一层。
完成 Layer 27 后,只保存:
```text
[s8,s9,s10,s11]
```
除本次合并 forward 中已经追加的四个 refresh real positions 外,当前 shadow block
不会继续改变 P28 persistent cache:
```text
Layer 0 : [R0 ... R7]
...
Layer 27: [R0 ... R7]
```
### 7.5 Causal Refresh 与 Bidirectional Refresh
`Causal-Parallel-Refresh` 当前 local shadow block:
```text
s8 看到 shadow positions <= 8
s9 看到 shadow positions <= 9
s10 看到 shadow positions <= 10
s11 看到 shadow positions <= 11
```
`Bidirectional-Parallel-Refresh` 当前 local shadow block:
```text
s8,s9,s10,s11 相互全部可见
```
两者都读取相同的 persistent real P28 history,且都不保存当前 shadow KV。
### 7.6 Cycle 内 S8 decoding
```text
embed(x8) + s8 → S8 → x9
embed(x9) + s9 → S8 → x10
embed(x10) + s10 → S8 → x11
embed(x11) + s11 → S8 → x12
```
S8 的每个 Layer 28~35 分别追加真实位置 `8~11` 的 K/V。
Cycle 结束后:
```text
P28 persistent cache:
每层只包含 real positions 0~7
S8 persistent cache:
每层包含 real positions 0~11
Saved shadow hidden:
cycle 结束后可以释放
```
下一个 cycle 以 `x12` 开始时:
1. 将真实 `[x8,x9,x10,x11]` causal forward P28;
2. P28 每层 append real KV positions `8~11`
3. 再计算 `[x12,MASK13,MASK14,MASK15]` 的当前 shadow hidden;
4. 当前 shadow K/V 仍然不持久保存。
Refresh 模式的核心不变量:
> P28 persistent cache 永远只包含 real KV;当前 cycle shadow KV 永远是临时值。
---
## 8. Norefresh 测试逻辑
适用于:
- `Causal-Parallel-Norefresh`
- `Bidirectional-Parallel-Norefresh`
### 8.1 Persistent P28 cache
Norefresh 不会在下一 cycle 使用真实 token 重算 P28,因此历史 context 只能来自之前
cycle 的 shadow representation。
所以必须持久保存 28 组 shadow KV cache:
```text
C_P[0], C_P[1], ..., C_P[27]
```
每个 parallel layer 都保存自己的 shadow K/V,不能只保存 Layer 27。
例如进入位置 `8~11` 的 cycle 前:
```text
Layer 0 : [S0 S1 S2 S3 | S4 S5 S6 S7]
Layer 1 : [S0 S1 S2 S3 | S4 S5 S6 S7]
...
Layer 27: [S0 S1 S2 S3 | S4 S5 S6 S7]
```
这里每个 `S` 都是相应 layer 在之前 shadow forward 中产生的 K/V。
### 8.2 Forward 当前 shadow block
构造:
```text
[x8,MASK9,MASK10,MASK11]
```
对于每一个 parallel layer `l`
```text
Persistent history:
C_P[l] = previous cycles' shadow K/V
Current local block:
K_shadow[l], V_shadow[l] positions 8~11
```
当前层 attention 使用历史 shadow cache 和当前 local block。计算完成后,将当前 block 的
四个 shadow K/V 追加到该层 persistent cache:
```text
Layer 0 append S8:S11
Layer 1 append S8:S11
...
Layer 27 append S8:S11
```
因此 forward 后:
```text
Layer 0 : [S0 ... S11]
Layer 1 : [S0 ... S11]
...
Layer 27: [S0 ... S11]
```
同时保存 Layer 27 最终输出:
```text
[s8,s9,s10,s11]
```
区别是:
- 28 层 shadow K/V 用于后续 cycle 的 P28 attention;
- 最终四个 shadow hidden 用于当前 cycle 的 S8 decoding。
两者用途不同,Norefresh 中都需要保存。
### 8.3 Causal Norefresh 与 Bidirectional Norefresh
`Causal-Parallel-Norefresh`
- 历史 shadow cache 全部可因果读取;
- 当前 cycle 内也是 causal。
`Bidirectional-Parallel-Norefresh`
- 历史 cycle 作为已有 shadow cache 被当前 cycle 读取;
- 当前 cycle 的四个 local shadow 槽位双向可见;
- 当前 cycle 计算出的 bidirectional shadow K/V 会被持久保存;
- 下一 cycle 会读取这些历史 shadow K/V。
### 8.4 Cycle 内 S8 decoding
与 Refresh 完全相同:
```text
embed(x8) + s8 → S8 → x9
embed(x9) + s9 → S8 → x10
embed(x10) + s10 → S8 → x11
embed(x11) + s11 → S8 → x12
```
Cycle 结束后:
```text
P28 persistent cache:
每层包含 shadow positions 0~11
S8 persistent cache:
每层包含 real positions 0~11
Saved shadow hidden:
cycle 结束后可以释放
```
下一个 cycle 直接计算:
```text
[x12,MASK13,MASK14,MASK15] → P28
```
它会读取已经保存的历史 shadow KV,不执行 real causal refill。
Norefresh 模式的核心不变量:
> P28 persistent cache 保存所有历史 shadow KV;这些 KV 是后续 cycle 的 P28 context。
---
## 9. Refresh 与 Norefresh 的 cache 对比
### Refresh
```text
P28 cache:
28 层,每层只保存 real KV
每个 cycle 开始时,将上一 cycle 的真实 token causal forward P28 后 append
当前 cycle shadow KV 不写入 cache
Current cycle:
只保存 P28 最终输出的 4 个 shadow hidden
S8 cache:
8 层,每层保存所有真实 token KV
```
### Norefresh
```text
P28 cache:
28 层,每层保存历史 shadow KV
每个 cycle 的 shadow forward 都向每层 append 4 个 shadow KV
不执行 real causal refill
Current cycle:
保存 P28 最终输出的 4 个 shadow hidden
S8 cache:
8 层,每层保存所有真实 token KV
```
最关键的区别:
```text
Refresh:
shadow KV 是当前 P28 forward 的临时张量
跨 cycle 保存的是 real KV
Norefresh:
shadow KV 是后续 cycle 的历史 context
必须跨 cycle 保存
```
---
## 10. 一个完整的 Bidirectional-Parallel-Refresh cycle
假设:
- 上一 cycle:`x4,x5,x6,x7`
- 当前 cycle:位置 `8~11`
- 当前 head:`x8`
### Step A:Refresh
```text
[x4,x5,x6,x7] → causal P28
```
每个 Layer 0~27 append 自己的 real K/V:
```text
C_P[0] append R4:R7
C_P[1] append R4:R7
...
C_P[27] append R4:R7
```
### Step B:Bidirectional shadow forward
```text
[x8,M9,M10,M11] → bidirectional-within-cycle P28
```
每层:
```text
attention history = 该层 persistent real K/V
attention current = 该层 temporary shadow K/V
```
当前四个 shadow 位置双向可见,但 temporary shadow K/V 不写入 persistent cache。
最终只保存:
```text
[s8,s9,s10,s11]
```
### Step C:Sequential decoding
```text
embed(x8) + s8 → S8 → x9
embed(x9) + s9 → S8 → x10
embed(x10) + s10 → S8 → x11
embed(x11) + s11 → S8 → x12
```
每次 S8 forward 都向 Layer 28~35 的 cache 各追加一个真实 K/V。
### Step D:进入下一 cycle
释放:
```text
[s8,s9,s10,s11]
```
然后:
```text
[x8,x9,x10,x11] → causal P28 refresh
[x12,M13,M14,M15] → bidirectional shadow P28
```
整个过程中,`S8:S11` 的 bidirectional shadow K/V 从未写入 persistent P28 cache。

Xet Storage Details

Size:
20 kB
·
Xet hash:
ff5f3aeceac632100defcfae4c1a3f323190688b16e440ab253bae5c680931ca

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.