File size: 15,165 Bytes
08ad55b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# Qwen3-235B-A22B MoE 推理优化阶段性总结

**对象模型**:Qwen3-235B-A22B-Instruct-2507(BF16,94 层,128 expert,top-k=8,GQA 64Q/4KV)
**硬件平台**:Ascend 910 初代 × 16 NPU(TP=16)
**软件路径**:纯 aclnn C++ EAGER(无图编译、无 PyTorch 依赖)
**报告周期**:2026-04-21 → 2026-04-22
**定稿日期**:2026-04-22

---

## 一 · 总体结论(一页速读)

### 1.1 质量保持前提下的 TG(最终口径)

| 场景(greedy, temperature=0) | TG | 相对起点 |
|---|---|---|
| 起点(未调优 aclnn EAGER) | 12 t/s | — |
| **通用推荐路径**(HCCL env + Fused RoPE + 小优化) | **27 t/s** | **+125%** |
| **创意生成(启用 PLD)** | **39 t/s** | **+225%** |

### 1.2 对标

| 路径 | TG | 性质 |
|---|---|---|
| ggml EAGER(参考) | ~13 t/s | 通用 |
| **本项目(aclnn EAGER + 优化)** | **27 / 39 t/s** | 通用 / 创意 |
| cann-recipes-infer(GE graph) | ~54 t/s | 工业基线(未超越) |

### 1.3 里程碑达成

-**MUST 25 t/s**:通用路径 27 t/s 稳定达成
- ⚠️ **Target 40 t/s**:仅创意 prompt + PLD 场景接近(median 41)
-**Stretch 54 t/s(追平 GE graph)**:未达成,硬件限制为主

---

## 二 · 关键优化(按可信贡献排序)

### 2.1 🥇 HCCL 环境变量调优 —— +89% TG(12 → 23 t/s)

**瓶颈定位**:Profile 显示每 token ~75 ms 中 HCCL AllReduce 占 ~47 ms(60%)。

**关键 env 组合**(固化在启动脚本):

```bash
HCCL_ALGO=level0:ring                 # 环状 topology,910×16 最优
HCCL_BUFFSIZE=200                      # sweet spot(100/400 都差)
HCCL_OP_EXPANSION_MODE=AIV             # 让 AI Vector cores 参与 reduce 调度
HCCL_OP_BASE_FFTS_MODE_ENABLE=1        # Fast Frequently-used Transfer Scheduling
TASK_QUEUE_ENABLE=2                    # 更激进异步任务入队
```

**阶梯实测**:

| 叠加项 | TG |
|---|---|
| baseline (ring + buffsize=200) | 12.20 t/s |
| + OP_EXPANSION=AIV | 17.74 t/s (+45%) |
| + FFTS=1 | 17.90 t/s (+47%) |
| + AIV + FFTS | 18.82 t/s (+54%) |
| + AIV + FFTS + TASK_QUEUE=2 | **23.10 t/s (+89%)** ⭐ |

**收获**:HCCL 不是黑盒;仅靠 env 调参翻倍 TG,零代码工程量。

**踩坑**- `HCCL_ALGO=level0:fullmesh` 会让 Qwen3-235B 输出乱码,`ring` 才正确
- `HCCL_OP_EXPANSION_MODE=AICPU` 启动直接崩(910 初代无实现)

---

### 2.2 🥈 Fused RoPE(`aclnnApplyRotaryPosEmbV2`)—— +17% TG(23 → 27 t/s,破 25 MUST)

**调用**```cpp
aclnnApplyRotaryPosEmbV2(q, k, cos, sin, layout=1, rotaryMode="half")
```

**替代**:原手写 HF-style RoPE(`neg + inplace_copy + mul + addcmul` × q, k = **8 launches/层**) → 融合为 **1 launch/层****规模收益**:每层省 7 launches × 94 层 = **658 kernel launches / token** ≈ 10 ms/token(按 15 µs/launch)。

**关键认知纠偏**:之前因 `aclnnAddRmsNorm` 在 910 初代无 kernel,误以为"所有 fused op 都不可用"。实测证伪 —— **同一 family 的 fused op 要逐个验证****踩坑**- `layout=0`(BSND)报错 status=561002
- `layout=1`(SBND)才接受;`rotaryMode` 必须是 `"half"`
- 与手写 HF rotate_half 对比:rel=1.24e-3,前 4 值 bit-identical

---

### 2.3 🥉 PLD(Prompt Lookup Decoding)—— 创意场景 +45%(27 → 39 t/s)

**理论基础 — Decode 是 latency-bound**

| S(batch size) | forward ms | amortized ms/token |
|---|---|---|
| 1 | 47.62 | 47.62 |
| 2 | 43.51 | 21.76 |
| 4 | 35.82 | 8.96 |
| 8 | 39.08 | **4.89**(9.7× throughput) |

S=1 到 S=8 forward 时间几乎不变 → decode 不吃算力,完全被 HCCL + kernel launch 主导 → **一次 forward 吐出多个 token 几乎免费**

**机制**
```
1. n-gram 匹配:从生成 hist 里找 draft[K=10] 个候选 token(multi-level fallback)
2. decode_batch([cur_token, draft[0..K-1]], S=K+1)  ← 单次 batch forward
3. 按 argmax 匹配接受最长前缀 + 1 bonus token
4. rewind_cache(K - accept):回滚未接受 draft 在 KV cache 中的 past_len
```

**最关键正确性 bug**(调试 >5 小时):
- 初版沿用 prefill 的 `sparse_mode=3 + 2048×2048 causal mask` → FIAS 把 q[i] 解释为"只能看 kv[0..i]",**完全忽略 past_len** → 每个 batch 位置"忘记" past context → accept 率仅 8%
- 修复:专用 `[1, 1, S, past+S]` bool mask + `sparse_mode=0`;`mask[i,j]=1 iff j>past_len+i`
- 修复后 accept 率在创意场景可达 0.5-3.0

**调优参数**:
- `K=10` fixed(`bench_pld_k.sh` sweep 最稳定)
- `n-gram=1` + multi-level fallback
- `min_hist=20`(早期避免假阳性)
- **`auto-disable` 是反模式**:`T_batch(S=11)=42ms` ≈ `T_decode=47ms`,accept 阈值 = -0.1(任何非负 accept 都赚)→ 原 `accept<0.5 disable` 误杀大量合法场景

**适用边界(重点)**:

| Prompt 类型 | accept/K | 效果 | 推荐 |
|---|---|---|---|
| 创意生成(故事、长文、对话回答) | 0.5-3.0 | +30-70% TG,输出连贯 | ✅ 启用 |
| 结构化代码(多样性足够) | 2-4 | +40-80% TG | ⚠️ 小样本验证 |
| 事实问答("X 的首都") | 4-8 | 易进死循环 | ❌ 禁用 |
| 代码生成("写一个函数") | 5-9 | 几乎必进死循环 | ❌ 禁用 |

---

### 2.4 其他小优化(合计 ~+15%)

| 优化 | 机制 |
|---|---|
| RoPE cos/sin 预算 cache | 消除每层 host 计算 + H2D(1 次构建 max_seq × head_dim 大表,每层 view) |
| Device-side topk_w 归一化 | 消除每层 D2H/H2D(改 `reduce_sum + adds + cast + div` 全 device 完成) |
| Device-side MoE argsort finalize | 消除每层 `aclrtSynchronizeStream` 和 host sort(改 `aclnnArgsort × 2`:inv_fwd=argsort(topk_idx), fwd=argsort(inv_fwd)) |
| WorkspacePool(thread_local + retain-old) | 复用 aclnn workspace,避免每 op `aclrtMalloc + Free` |

---

## 三 · 正确性修复(无正确输出,性能无意义)

| # | Bug | 修复 | 根因 |
|---|---|---|---|
| 1 | MoE 权重 rel=94.6% | `aclnnInplaceCopy` 后立即 `aclrtSynchronizeStream` | 局部 `DeviceBuffer` 在 lambda 返回时析构释放设备内存,但 permute kernel 尚未执行 |
| 2 | TP=16 输出 "CZHJZFROJF00" 乱码 | GQA KV 头分片:每 rank 1 个 KV 头,按 `kv_head_idx = rank / (tp/num_kv)` 切 | FIAS 看到 Hq=Hkv=4 会假设 **1:1 mapping**,而实际 4 个 Q 应**全部**共享同一 KV head |
| 3 | FIAS decode 模式 shape mismatch | decode 用 `sparse_mode=0 + mask=nullptr`;prefill 用 `sparse_mode=3 + 2048 mask` | sparse_mode=3 要求 q.S == kv.S,decode 时 q.S=1 ≠ kv.S 崩 |
| 4 | FIAS q/out 别名数据竞争 rel=0.18 | 分配独立 `attn_out_scratch` 缓冲区 | FIAS kernel 同时读 q 写 out,同一块内存 → 数据竞争 |
| 5 | `aclnnMoeFinalizeRoutingV2` rel=0.9-1.0 | 自实现 device-side:`argsort × 2` + `IndexSelect` + 广播 Mul + `ReduceSum` | V3 routing 与 V2 finalize 对 `expanded_row_idx` 语义不兼容 |
| 6 | PLD batch decode accept 率仅 8% | 专用 `[1, 1, S, past+S]` bool mask + `sparse_mode=0` | sparse_mode=3 忽略 past_len,每 batch 位置"失忆" |
| 7 | 多轮对话 UTF-8 截断 → JSON 失败 | `utf8_trim_incomplete()` 回溯末尾 ≤4 字节丢弃不完整序列 | `n_predict` 可能在多字节 codepoint 中间截断 |

---

## 四 · 重大翻车与修正(PLD 正确性)

### 4.1 问题

早期曾宣传"PLD mean 82.94 / peak 177.40 t/s,超越 GE graph 1.54× / 3.3×"。这一口径**已撤回**。

**翻车证据**(实测对照):

| Prompt | Baseline | PLD K=10 | accept/K | 正确性 |
|---|---|---|---|---|
| "The capital of France is" | "Paris. It is known for…" | **"Paris. The capital of Paris is the city of Paris…"** × N | 8.20 | ❌ 死循环 |
| "Write a long Python function…" | 正常代码 | **"function function function…"** × 100+ | ~9 | ❌ 死循环 |
| "Once upon a time…" | 故事 A | 故事 B(Goldilocks,连贯但不同) | 0.6-2.5 | ✅ 可接受 |

### 4.2 根因:正反馈循环

```
模型 temperature=0 下有轻微重复倾向
  → n-gram 在 hist 里匹配到重复 → draft[K] 全是同一 token
  → batch verify 时 past 已含重复,attention 对这些 token 的 logits 偏高
  → accept 率飙到 5-9/K
  → hist 里重复模式更密集 → 下一轮循环更紧
  → 最终完全 "W W W W …" 死循环输出
```

### 4.3 认知纠偏

| 旧认知 | 新认知 |
|---|---|
| accept 高 = 加速好消息 | **accept > 5/K 持续 = degeneration loop 征兆** |
| peak 177 t/s 是硬件极限展示 | peak 177 t/s = 输出死循环 token 时的 TG,不是可用推理 |
| 10-run 统计越大越好 | 10-run 混合了正常和损坏 run,不可直接引用 |
| 质量和速度可以分开报告 | **性能数字必须与正确性绑定** |

### 4.4 为什么 K sweep 仍然"显示 K=10 最稳"

因为 sweep 只测 TG 数字,没测输出正确性。
**K=10 最稳 = 最容易触发 feedback loop** —— 在相同 prompt/seed 下,K=10 能把 baseline 轻度重复倾向最快放大成死循环,于是统计上"3/3 runs 100+ t/s" 其实是"3/3 run 都成功进入死循环"。

**教训**:benchmark 脚本必须包含输出抽查,纯数字不够。

---

## 五 · 推荐推理路径

### 5.1 生产默认(所有 prompt 安全)

```bash
./scripts/tp_launch.sh 16 ./build/qwen3-aclnn-cli \
    --model-dir /path/to/Qwen3-235B-A22B-Instruct-2507-BF16 \
    --prompt "<任意 prompt>" --n-predict 200 \
    --temperature 0 --no-stream
# 期望: ~27 t/s, 所有 prompt 输出正确
```

### 5.2 创意生成(可选 PLD,需人工核验输出)

```bash
./scripts/tp_launch.sh 16 ./build/qwen3-aclnn-cli ... \
    --prompt "Once upon a time, in a small village" \
    --n-predict 200 --pld --temperature 0
# 期望: 30-50 t/s, accept 0.5-3, 连贯故事输出
```

### 5.3 禁用 PLD 的场景

- 事实性问答("Who is the CEO of X")
- 代码生成("Write a function…")
- 数学步骤(固定模板)
- 会话中已观察到 accept > 5/K 时

---

## 六 · 单层 forward 数据流(优化后)

```
x_in [S, D=4096]

┌── Attention 分支 ──┐
│  RmsNorm(input_layernorm)
│  linear_hf q_proj/k_proj/v_proj → q, k, v
│    (TP=16: Q=4h×128=512, KV=1h×128=128)
│  Per-head RmsNorm q_norm, k_norm
│  Fused RoPE: aclnnApplyRotaryPosEmbV2 (layout=1, half)   ★ 优化
│  Append K, V to layer cache at past_len..past_len+S-1
│  Mask 选择:
│    - prefill (past=0, S>1):  2048×2048 causal + sparse_mode=3
│    - decode (S=1):            mask=nullptr + sparse_mode=0
│    - batch decode (PLD):      [1,1,S,past+S] + sparse_mode=0  ★ 关键修复
│  FIAS(q, k_cache, v_cache, mask)
│  o_proj linear_hf → partial
│  HCCL AllReduce (ring + AIV + FFTS)  ★ 优化
└─────────────────┘
  ↓ residual add
┌── MoE 分支 ──┐
│  RmsNorm(post_attention_layernorm)
│  linear_hf router → logits [S, 128]
│  moe_gating_topk_softmax → topk_w, topk_idx
│  Device-side normalize                                    ★ 优化
│  moe_init_routing_v3 (counts + rowIdxType=1)
│  grouped_matmul_v4 (gate/up/down)
│    silu(gate) * up → act; act @ w_down
│  Device-side argsort × 2(代替 host sort sync)           ★ 优化
│  IndexSelect → packed
│  Broadcast mul with topk_w, ReduceSum axis=1
│  HCCL AllReduce
└────────────┘
  ↓ residual add
x_out
```

---

## 七 · 未来方向(优先级重排)

| # | 方向 | 预期收益 | 工程量 | 优先级 |
|---|---|---|---|---|
| 1 | **PLD degeneration 检测**(draft 连续同 token / accept 饱和 → fallback single decode) | 让 PLD 在事实/代码场景可用 | 0.5-1 周 | ⭐⭐⭐ |
| 2 | Benchmark 脚本加输出正确性抽查 | 避免再误报 | 0.5 天 | ⭐⭐⭐ |
| 3 | Draft model speculative decoding(Qwen3-0.6B on spare NPU) | 更稳 accept,避免 n-gram 正反馈 | 1-2 周 | ⭐⭐⭐ |
| 4 | Tree attention(K-draft tree 多分支) | peak +20-30% | 2-3 周 | ⭐⭐ |
| 5 | 真·GE IR 图编译 | +10-30%(受 910 融合算子缺失限制) | 4-6 周 | ⭐ |
| 6 | 迁移 910B/A2/A3 硬件 | 200-500+ t/s | 需新硬件 | n/a |

---

## 八 · 项目级教训(写给未来自己)

1. **高 accept 不等于成功**:在 speculative decoding 类优化中,accept rate 过高是异常信号而非加速胜利
2. **性能宣传必须绑定正确性**:只报 TG 数字不验证输出,是工程伦理失守
3. **用户的"是否正确"是终极 benchmark**:一句追问击穿所有纯数字统计
4. **Fused op 要逐个验证**:不要因一个算子不可用就否定整个 family(`AddRmsNorm` 不可用 ≠ `ApplyRotaryPosEmbV2` 不可用)
5. **Adaptive 不总比 fixed 好**:当成本函数近似常数(如 T_batch ≈ T_decode),fixed 参数更稳
6. **Benchmark 必须包含正确性抽查**:纯数字 sweep 会把灾难当胜利
7. **异步模型下 host-side free 要谨慎**:aclnn kernel 异步执行,任何 DeviceBuffer/workspace 释放必须确保 in-flight kernel 已完成
8. **HCCL 不是黑盒**:AIV / FFTS 等 env 在 910 初代有明显效果,值得花时间 sweep
9. **文档与实际行为可能不符**`aclnnApplyRotaryPosEmbV2``layout=0` 官方没标注不可用,靠测试枚举才发现
10. **GE 图编译不是银弹**:910 初代缺融合算子,图编译的"融合红利"大幅缩水;盲目投入不值

---

## 九 · 性能演进时间线

| 阶段 | 日期 | 关键动作 | 通用 TG | PLD 创意 TG |
|---|---|---|---|---|
| 起点 | 04-21 晨 | 端到端跑通(TP=16) | 12 t/s | — |
| HCCL ring+buffsize | 04-21 下午 | 基础 HCCL 参数 | 13.8 t/s | — |
| HCCL env 深挖 | 04-21 晚 | + AIV + FFTS + TASK_QUEUE=2 | 23 t/s | — |
| Fused RoPE | 04-21 夜 | + aclnnApplyRotaryPosEmbV2 | **27 t/s** ✅ MUST | — |
| PLD 初版 | 04-21 夜 | causal-with-past mask bug | 27 t/s | ~30 t/s 混合 |
| PLD 调参 | 04-21 夜 | K=10, multi-level, min_hist=20 | 27 t/s | 宣传 82.94 ⚠️ |
| **正确性修正** | 04-22 | 发现 feedback loop,撤回宣传 | **27 t/s** | **39 t/s(仅创意)** |

---

## 十 · 结论

纯 aclnn EAGER 路径在 Ascend 910 初代 × 16 NPU 上,通过 **HCCL env 调参 + Fused RoPE + 小优化**,将 Qwen3-235B-A22B BF16 推理从 12 t/s 提升到 **27 t/s**(通用、所有 prompt 正确)。
启用 PLD 后在**创意生成**场景可达 **39 t/s**(+45%),但在事实/代码场景会触发 feedback loop 死循环,**必须禁用****未达成 cann-recipes-infer GE graph 54 t/s 基线**。差距主要来自:
- 910 初代缺失关键融合算子(`MatmulAllReduce``GroupedMatmulAllReduce``AddRmsNorm`- EAGER 路径 HCCL + kernel launch 占 75% 时间,真计算仅 12%
- 图编译风格的 stream-capture API(`aclmdlRI`)不提供加速(POC 证伪)

后续最高优先级工作是 **PLD degeneration 检测**,让加速路径在事实/代码场景也安全可用。

---

*本阶段性总结基于 2026-04-22 实测与代码快照。*