xianglarry Claude Opus 4.7 (1M context) commited on
Commit
08ad55b
·
1 Parent(s): 4b9fefd

Add Chinese optimization summary + draft-model speculative spec docs

Browse files

- docs/optimization-summary-zh.md / .pdf: 阶段性优化总结 covering key
optimizations (HCCL env / Fused RoPE / PLD guard), PLD correctness
boundary (feedback-loop failure mode), honest per-prompt-class TG,
and project-level lessons (e.g., accept>5/K is a red flag, not a win).
- docs/next-steps-draft-model-speculative.md: execution spec for the
top-priority future direction — Qwen3-0.6B draft model speculative
decoding, with M1-M4 milestones, correctness protocol (greedy token
equivalence to verifier-only baseline), risk mitigation, and
checkpoint decision criteria.
- README: link to both docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

README.md CHANGED
@@ -333,6 +333,13 @@ Not recommended:
333
 
334
  ---
335
 
 
 
 
 
 
 
 
336
  ## License
337
 
338
  Apache License 2.0 — see `LICENSE`.
 
333
 
334
  ---
335
 
336
+ ## Documentation
337
+
338
+ - [`docs/optimization-summary-zh.md`](docs/optimization-summary-zh.md) — 阶段性优化总结(中文):关键优化原因、PLD 正确性边界、项目级教训
339
+ - [`docs/next-steps-draft-model-speculative.md`](docs/next-steps-draft-model-speculative.md) — Draft Model Speculative Decoding(Qwen3-0.6B)执行规格:M1-M4 里程碑、正确性测试协议、风险兜底
340
+
341
+ ---
342
+
343
  ## License
344
 
345
  Apache License 2.0 — see `LICENSE`.
docs/next-steps-draft-model-speculative.md ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Draft Model Speculative Decoding — 执行规格 v1
2
+
3
+ **目标模型**:Qwen3-0.6B(draft)+ Qwen3-235B-A22B(verifier,当前主模型)
4
+ **平台**:Ascend 910 × 16 NPU
5
+ **预期收益**:全 prompt 类型 +50~100% TG(目标 40-50 t/s 稳定)
6
+ **工程量**:1-2 周
7
+
8
+ ---
9
+
10
+ ## 0 · 为什么换 draft model
11
+
12
+ | 指标 | PLD (n-gram) | Draft Model (0.6B) |
13
+ |---|---|---|
14
+ | Accept rate 均值 | 0.3-1.5 (低) | 3-5 (预期) |
15
+ | 事实/代码 prompt | 需 guard 丢弃大量 draft → 几乎失效 | 语义理解 → 稳定 accept |
16
+ | 正反馈循环风险 | 高(n-gram 放大重复) | 低(小模型有自己的分布,不会完美复制主模型重复) |
17
+ | 内存占用 | ~0 | ~1.2GB BF16 |
18
+ | 计算成本 | ~0 | 每 draft step ~10-15ms |
19
+
20
+ n-gram PLD 已到方法论天花板;accept=0.3-1.5 意味着平均每次 verify 只赚 1-2 个 token,
21
+ 且 guard 在事实/代码场景丢弃几乎所有 draft。
22
+
23
+ ---
24
+
25
+ ## 1 · 硬件 / 资源规划
26
+
27
+ ### 1.1 NPU 分配决策
28
+
29
+ **选项 A**(推荐):主模型 TP=16 不变,draft 模型**时分复用 device 0**
30
+ - 优点:无需改动主模型 sharding
31
+ - draft decode ~10ms,主模型 decode ~35ms → 串行总 45ms 仍然值得(vs pure decode 35ms)
32
+ 因为每次 draft 后的 batch verify 摊销到 3-5 token → 50/5 = 10ms/token
33
+
34
+ **选项 B**:主模型 TP=15,draft 独占 device 15
35
+ - 优点:draft / 主完全并行
36
+ - 缺点:主模型所有 sharding 常量需重算 `64/15` 不整除问题严重
37
+
38
+ **选项 C**:主模型 TP=8(仅用 device 0-7),draft 独占 device 8
39
+ - 优点:配置简洁
40
+ - 缺点:主模型从 TP=16 降到 TP=8,每 rank 权重加倍,可能 OOM(Qwen3-235B BF16 约 470GB / 8 = 58GB/rank,910 单卡 64GB 勉强够)
41
+
42
+ **→ 选 A**。draft decode 可重叠的时机有限,但工程风险最低。
43
+
44
+ ### 1.2 权重下载 / 存储
45
+ - HF:`Qwen/Qwen3-0.6B`(约 1.2GB BF16)
46
+ - 位置:`/run/modelscope/Qwen3-0.6B-BF16/`(与主模型同目录结构)
47
+ - tokenizer **完全相同**(共享 Qwen3 vocab.bin)—— 这是选 0.6B 的关键优势
48
+
49
+ ---
50
+
51
+ ## 2 · 架构设计
52
+
53
+ ### 2.1 模块
54
+
55
+ ```
56
+ include/
57
+ ├── draft_runner.h ← 新增:TP=1 Runner 特化,device 独占
58
+ ├── draft_engine.h ← 新增:Qwen3 28 层 attn+MLP forward (非 MoE)
59
+ ├── draft_model_config.h ← 新增:Qwen3-0.6B hparams
60
+ src/
61
+ ├── draft_runner.cpp ← 简化 runner.cpp,去 TP/HCCL
62
+ ├── main_cli.cpp ← 新增 --draft-model-dir flag + spec decode loop
63
+ ```
64
+
65
+ ### 2.2 主循环流程(替换现有 PLD 分支)
66
+
67
+ ```
68
+ while (!eos && decoded < n_predict) {
69
+ // Phase 1: draft 模型连续 decode K 步
70
+ draft_tokens = []
71
+ draft_runner.set_cursor(main_runner.past_len + 1) // 同步位置
72
+ cur = next_id
73
+ for k in 0..K-1:
74
+ d_logits = draft_runner.decode(cur) // ~10 ms (device 0 独占)
75
+ cur = argmax(d_logits)
76
+ draft_tokens.append(cur)
77
+ if is_eos(cur): break
78
+
79
+ // Phase 2: 主模型 batch verify (K+1 位置)
80
+ batch_input = [next_id] + draft_tokens
81
+ batch_logits = main_runner.decode_batch(batch_input) // ~42 ms
82
+
83
+ // Phase 3: 接受最长前缀 + 1 bonus
84
+ accept = 0
85
+ for i in 0..K-1:
86
+ if argmax(batch_logits[i]) == draft_tokens[i]:
87
+ accept++
88
+ else:
89
+ next_id = argmax(batch_logits[i]); break
90
+ if accept == K:
91
+ next_id = argmax(batch_logits[K]) // bonus
92
+
93
+ // Phase 4: rewind 两个模型的 KV cache
94
+ main_runner.rewind(K - accept)
95
+ draft_runner.rewind(K - accept + (accept == K ? 0 : 1)) // draft 多走了一步
96
+
97
+ // Phase 5: emit accepted + bonus
98
+ for tok in draft_tokens[:accept] + [next_id]:
99
+ emit(tok); hist.append(tok)
100
+ }
101
+ ```
102
+
103
+ ### 2.3 关键 invariant
104
+
105
+ | invariant | 保障 |
106
+ |---|---|
107
+ | draft 与 main 看同一 token 序列 | 每次 decode 前 `draft.position = main.past_len`,rewind 同步 |
108
+ | device 0 时序:draft decode 不冲突主模型 | 主模型 rank 0 在 HCCL 等待期间 device 0 空闲 → draft 用这段时间 |
109
+ | KV cache 独立 | draft 与 main 各有自己的 KV cache,大小分别按 0.6B / 235B |
110
+
111
+ ### 2.4 device 0 争用分析
112
+
113
+ 主模型每 decode step ~35ms,其中:
114
+ - rank 0 的 local compute ~10ms
115
+ - HCCL AllReduce 等待 ~22ms(device idle from rank 0 POV)
116
+ - final logits ~3ms
117
+
118
+ **理论上 draft decode 可在 HCCL 等待期间占用 device 0**。但实际:
119
+ - HCCL 等待期间 rank 0 的 stream 是被 HCCL 占用的(不是 idle)
120
+ - draft 需要独立的 stream 才能真正并行
121
+ - 若用 `aclrtCreateStream` 分出第二条 stream,draft 和主的 HCCL 共享 AI Core 资源 → 可能互相干扰
122
+
123
+ **保守估算**:串行执行 draft (K=4 步约 40ms) + verify (42ms) = 82ms per spec cycle,产出 3-5 token → amortized 16-27 ms/token → 37-60 t/s(对比当前 ~27 t/s 单 decode)。
124
+
125
+ ---
126
+
127
+ ## 3 · 实施步骤(按依赖排序)
128
+
129
+ ### M1 · draft_model 独立推理(3 天���
130
+ - [ ] `draft_model_config.h`:Qwen3-0.6B hparams(28 层,16 Q head,8 KV head,hidden=1024, ...)
131
+ - [ ] `draft_engine.h`:simplified attention + MLP(SwiGLU),无 MoE
132
+ - [ ] `draft_runner.cpp`:单 device,无 HCCL,复用 RoPE cache / workspace pool
133
+ - [ ] `test_draft_standalone.cpp`:与 HF Transformers 参考比对 rel ≤ 1e-3
134
+ - [ ] `test_draft_consistency.cpp`:同 prompt 多次 argmax 确定(greedy 应稳定)
135
+
136
+ **验证点**:draft 单模型 decode ≥ 80 t/s(TP=1,0.6B 应该很快)
137
+
138
+ ### M2 · 协同控制(3 天)
139
+ - [ ] `main_cli.cpp` 新增 `--draft-model-dir` flag
140
+ - [ ] 主/draft cursor 同步 wrapper
141
+ - [ ] draft.rewind() 实现(沿用 main 的 rewind_cache 模式)
142
+ - [ ] 串行版 spec decode loop(不追求重叠)
143
+ - [ ] **正确性验证**:--draft 路径与 --pld 关闭路径对相同 prompt 应产生**完全相同**的 token 序列(greedy temp=0)
144
+ - 这是黄金标准:任何偏差都是 bug
145
+ - 用 `test_spec_correctness.sh` 自动回归
146
+
147
+ **验证点**:5 个 prompt(故事/代码/事实/数学/对话)输出与 baseline 逐 token 一致
148
+
149
+ ### M3 · 性能测量与调优(3 天)
150
+ - [ ] K sweep:K ∈ {2, 4, 6, 8},找 best accept × throughput
151
+ - [ ] 与 PLD+guard 对比:同 prompt 3 runs
152
+ - [ ] 文档更新:诚实对比表
153
+
154
+ **验证点**:
155
+ - 所有 prompt OK verdict(bench_pld_safe.sh 分类器不报 degraded)
156
+ - mean TG 比 base +40% 以上
157
+
158
+ ### M4(可选)· 并行化 / 异步 draft(2-3 天)
159
+ - [ ] 第二条 aclrtStream 用于 draft
160
+ - [ ] 尝试 draft decode 与主 verify 的下一轮重叠
161
+ - [ ] 若测不出显著增益则砍掉,保持串行
162
+
163
+ ---
164
+
165
+ ## 4 · 正确性测试计划
166
+
167
+ **绝对底线**:greedy (temp=0) 下,启用 draft 的输出必须与不启用**逐 token bit-identical**。
168
+ 理由:spec decoding 的正确性定义就是 "输出等价于 verifier 单独贪心解码"。
169
+
170
+ ### 4.1 回归测试套件
171
+
172
+ ```bash
173
+ # test_spec_correctness.sh
174
+ for prompt in "Paris" "Python function" "Once upon" "2+2" "长句测试":
175
+ out_base = run --no-pld --temperature 0
176
+ out_draft = run --draft-model-dir --temperature 0
177
+ diff out_base out_draft # must be identical
178
+ ```
179
+
180
+ ### 4.2 压力测试
181
+ - 长 prompt (>1K token) 稳定性
182
+ - 多轮对话下 cache rewind 正确
183
+ - EOS 在 draft 和 main 不一致时的处理
184
+
185
+ ### 4.3 benchmark 诚实性
186
+ - 继续用 `bench_pld_safe.sh` + 新增 `--draft` 模式列
187
+ - OK/degraded 分组统计;degraded = fatal
188
+
189
+ ---
190
+
191
+ ## 5 · 风险与兜底
192
+
193
+ | 风险 | 概率 | 兜底 |
194
+ |---|---|---|
195
+ | device 0 独占争用让 draft 延迟 >15ms | 中 | 保持串行执行,不追求重叠 |
196
+ | draft accept rate 不如预期(<3) | 中 | K 调小至 2-3,仍有 +20-30% 收益 |
197
+ | rewind 导致 KV cache 污染 | 高 | 严格测试 rewind 正确性;失败就回滚到"不 rewind,允许小概率错" |
198
+ | 910 初代 0.6B 权重加载失败 | 低 | 已验证 Qwen3-235B BF16 可加载,0.6B 结构子集 |
199
+ | 与 PLD guard 机制冲突 | 低 | draft 替代 PLD,同时不开启两个路径 |
200
+
201
+ ---
202
+
203
+ ## 6 · 时间表(单人 1 周密集 / 2 周分散)
204
+
205
+ | 天 | 任务 | 产出 |
206
+ |---|---|---|
207
+ | D1-D3 | M1 draft_standalone | test_draft_standalone PASS |
208
+ | D4-D6 | M2 协同 + 正确性 | test_spec_correctness 5/5 PASS |
209
+ | D7-D8 | M3 性能调优 | K sweep + 对比表 |
210
+ | D9-D10 | M4 可选重叠 + 文档 | 最终报告 |
211
+
212
+ ---
213
+
214
+ ## 7 · 决策 checkpoint
215
+
216
+ **第 3 天末**:若 draft 单模型 decode < 60 t/s(远不如预期),暂停,重新评估 0.6B 是否太慢 / 切换到 Qwen3-1.7B / 或放弃路径。
217
+
218
+ **第 6 天末**:若 5/5 prompt 正确性测试过不了,停下查 bug,不急于测性能。
219
+
220
+ **第 8 天末**:若 mean TG 提升 < 20% vs 当前 PLD+guard,判 ROI 不足,回滚保留 PLD+guard 为主路径。
221
+
222
+ ---
223
+
224
+ *此规格服务于下一轮会话,作为单人专职工作的路线图。*
docs/optimization-summary-zh.md ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Qwen3-235B-A22B MoE 推理优化阶段性总结
2
+
3
+ **对象模型**:Qwen3-235B-A22B-Instruct-2507(BF16,94 层,128 expert,top-k=8,GQA 64Q/4KV)
4
+ **硬件平台**:Ascend 910 初代 × 16 NPU(TP=16)
5
+ **软件路径**:纯 aclnn C++ EAGER(无图编译、无 PyTorch 依赖)
6
+ **报告周期**:2026-04-21 → 2026-04-22
7
+ **定稿日期**:2026-04-22
8
+
9
+ ---
10
+
11
+ ## 一 · 总体结论(一页速读)
12
+
13
+ ### 1.1 质量保持前提下的 TG(最终口径)
14
+
15
+ | 场景(greedy, temperature=0) | TG | 相对起点 |
16
+ |---|---|---|
17
+ | 起点(未调优 aclnn EAGER) | 12 t/s | — |
18
+ | **通用推荐路径**(HCCL env + Fused RoPE + 小优化) | **27 t/s** | **+125%** |
19
+ | **创意生成(启用 PLD)** | **39 t/s** | **+225%** |
20
+
21
+ ### 1.2 对标
22
+
23
+ | 路径 | TG | 性质 |
24
+ |---|---|---|
25
+ | ggml EAGER(参考) | ~13 t/s | 通用 |
26
+ | **本项目(aclnn EAGER + 优化)** | **27 / 39 t/s** | 通用 / 创意 |
27
+ | cann-recipes-infer(GE graph) | ~54 t/s | 工业基线(未超越) |
28
+
29
+ ### 1.3 里程碑达成
30
+
31
+ - ✅ **MUST 25 t/s**:通用路径 27 t/s 稳定达成
32
+ - ⚠️ **Target 40 t/s**:仅创意 prompt + PLD 场景接近(median 41)
33
+ - ❌ **Stretch 54 t/s(追平 GE graph)**:未达成,硬件限制为主
34
+
35
+ ---
36
+
37
+ ## 二 · 关键优化(按可信贡献排序)
38
+
39
+ ### 2.1 🥇 HCCL 环境变量调优 —— +89% TG(12 → 23 t/s)
40
+
41
+ **瓶颈定位**:Profile 显示每 token ~75 ms 中 HCCL AllReduce 占 ~47 ms(60%)。
42
+
43
+ **关键 env 组合**(固化在启动脚本):
44
+
45
+ ```bash
46
+ HCCL_ALGO=level0:ring # 环状 topology,910×16 最优
47
+ HCCL_BUFFSIZE=200 # sweet spot(100/400 都差)
48
+ HCCL_OP_EXPANSION_MODE=AIV # 让 AI Vector cores 参与 reduce 调度
49
+ HCCL_OP_BASE_FFTS_MODE_ENABLE=1 # Fast Frequently-used Transfer Scheduling
50
+ TASK_QUEUE_ENABLE=2 # 更激进异步任务入队
51
+ ```
52
+
53
+ **阶梯实测**:
54
+
55
+ | 叠加项 | TG |
56
+ |---|---|
57
+ | baseline (ring + buffsize=200) | 12.20 t/s |
58
+ | + OP_EXPANSION=AIV | 17.74 t/s (+45%) |
59
+ | + FFTS=1 | 17.90 t/s (+47%) |
60
+ | + AIV + FFTS | 18.82 t/s (+54%) |
61
+ | + AIV + FFTS + TASK_QUEUE=2 | **23.10 t/s (+89%)** ⭐ |
62
+
63
+ **收获**:HCCL 不是黑盒;仅靠 env 调参翻倍 TG,零代码工程量。
64
+
65
+ **踩坑**:
66
+ - `HCCL_ALGO=level0:fullmesh` 会让 Qwen3-235B 输出乱码,`ring` 才正确
67
+ - `HCCL_OP_EXPANSION_MODE=AICPU` 启动直接崩(910 初代无实现)
68
+
69
+ ---
70
+
71
+ ### 2.2 🥈 Fused RoPE(`aclnnApplyRotaryPosEmbV2`)—— +17% TG(23 → 27 t/s,破 25 MUST)
72
+
73
+ **调用**:
74
+ ```cpp
75
+ aclnnApplyRotaryPosEmbV2(q, k, cos, sin, layout=1, rotaryMode="half")
76
+ ```
77
+
78
+ **替代**:原手写 HF-style RoPE(`neg + inplace_copy + mul + addcmul` × q, k = **8 launches/层**) → 融合为 **1 launch/层**。
79
+
80
+ **规模收益**:每层省 7 launches × 94 层 = **658 kernel launches / token** ≈ 10 ms/token(按 15 µs/launch)。
81
+
82
+ **关键认知纠偏**:之前因 `aclnnAddRmsNorm` 在 910 初代无 kernel,误以为"所有 fused op 都不可用"。实测证伪 —— **同一 family 的 fused op 要逐个验证**。
83
+
84
+ **踩坑**:
85
+ - `layout=0`(BSND)报错 status=561002
86
+ - `layout=1`(SBND)才接受;`rotaryMode` 必须是 `"half"`
87
+ - 与手写 HF rotate_half 对比:rel=1.24e-3,前 4 值 bit-identical
88
+
89
+ ---
90
+
91
+ ### 2.3 🥉 PLD(Prompt Lookup Decoding)—— 创意场景 +45%(27 → 39 t/s)
92
+
93
+ **理论基础 — Decode 是 latency-bound**:
94
+
95
+ | S(batch size) | forward ms | amortized ms/token |
96
+ |---|---|---|
97
+ | 1 | 47.62 | 47.62 |
98
+ | 2 | 43.51 | 21.76 |
99
+ | 4 | 35.82 | 8.96 |
100
+ | 8 | 39.08 | **4.89**(9.7× throughput) |
101
+
102
+ S=1 到 S=8 forward 时间几乎不变 → decode 不吃算力,完全被 HCCL + kernel launch 主导 → **一次 forward 吐出多个 token 几乎免费**。
103
+
104
+ **机制**:
105
+ ```
106
+ 1. n-gram 匹配:从生成 hist 里找 draft[K=10] 个候选 token(multi-level fallback)
107
+ 2. decode_batch([cur_token, draft[0..K-1]], S=K+1) ← 单次 batch forward
108
+ 3. 按 argmax 匹配接受最长前缀 + 1 bonus token
109
+ 4. rewind_cache(K - accept):回滚未接受 draft 在 KV cache 中的 past_len
110
+ ```
111
+
112
+ **最关键正确性 bug**(调试 >5 小时):
113
+ - 初版沿用 prefill 的 `sparse_mode=3 + 2048×2048 causal mask` → FIAS 把 q[i] 解释为"只能看 kv[0..i]",**完全忽略 past_len** → 每个 batch 位置"忘记" past context → accept 率仅 8%
114
+ - 修复:专用 `[1, 1, S, past+S]` bool mask + `sparse_mode=0`;`mask[i,j]=1 iff j>past_len+i`
115
+ - 修复后 accept 率在创意场景可达 0.5-3.0
116
+
117
+ **调优参数**:
118
+ - `K=10` fixed(`bench_pld_k.sh` sweep 最稳定)
119
+ - `n-gram=1` + multi-level fallback
120
+ - `min_hist=20`(早期避免假阳性)
121
+ - **`auto-disable` 是反模式**:`T_batch(S=11)=42ms` ≈ `T_decode=47ms`,accept 阈值 = -0.1(任何非负 accept 都赚)→ 原 `accept<0.5 disable` 误杀大量合法场景
122
+
123
+ **适用边界(重点)**:
124
+
125
+ | Prompt 类型 | accept/K | 效果 | 推荐 |
126
+ |---|---|---|---|
127
+ | ���意生成(故事、长文、对话回答) | 0.5-3.0 | +30-70% TG,输出连贯 | ✅ 启用 |
128
+ | 结构化代码(多样性足够) | 2-4 | +40-80% TG | ⚠️ 小样本验证 |
129
+ | 事实问答("X 的首都") | 4-8 | 易进死循环 | ❌ 禁用 |
130
+ | 代码生成("写一个函数") | 5-9 | 几乎必进死循环 | ❌ 禁用 |
131
+
132
+ ---
133
+
134
+ ### 2.4 其他小优化(合计 ~+15%)
135
+
136
+ | 优化 | 机制 |
137
+ |---|---|
138
+ | RoPE cos/sin 预算 cache | 消除每层 host 计算 + H2D(1 次构建 max_seq × head_dim 大表,每层 view) |
139
+ | Device-side topk_w 归一化 | 消除每层 D2H/H2D(改 `reduce_sum + adds + cast + div` 全 device 完成) |
140
+ | Device-side MoE argsort finalize | 消除每层 `aclrtSynchronizeStream` 和 host sort(改 `aclnnArgsort × 2`:inv_fwd=argsort(topk_idx), fwd=argsort(inv_fwd)) |
141
+ | WorkspacePool(thread_local + retain-old) | 复用 aclnn workspace,避免每 op `aclrtMalloc + Free` |
142
+
143
+ ---
144
+
145
+ ## 三 · 正确性修复(无正确输出,性能无意义)
146
+
147
+ | # | Bug | 修复 | 根因 |
148
+ |---|---|---|---|
149
+ | 1 | MoE 权重 rel=94.6% | `aclnnInplaceCopy` 后立即 `aclrtSynchronizeStream` | 局部 `DeviceBuffer` 在 lambda 返回时析构释放设备内存,但 permute kernel 尚未执行 |
150
+ | 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 |
151
+ | 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 崩 |
152
+ | 4 | FIAS q/out 别名数据竞争 rel=0.18 | 分配独立 `attn_out_scratch` 缓冲区 | FIAS kernel 同时读 q 写 out,同一块内存 → 数据竞争 |
153
+ | 5 | `aclnnMoeFinalizeRoutingV2` rel=0.9-1.0 | 自实现 device-side:`argsort × 2` + `IndexSelect` + 广播 Mul + `ReduceSum` | V3 routing 与 V2 finalize 对 `expanded_row_idx` 语义不兼容 |
154
+ | 6 | PLD batch decode accept 率仅 8% | 专用 `[1, 1, S, past+S]` bool mask + `sparse_mode=0` | sparse_mode=3 忽略 past_len,每 batch 位置"失忆" |
155
+ | 7 | 多轮对话 UTF-8 截断 → JSON 失败 | `utf8_trim_incomplete()` 回溯末尾 ≤4 字节丢弃不完整序列 | `n_predict` 可能在多字节 codepoint 中间截断 |
156
+
157
+ ---
158
+
159
+ ## 四 · 重大翻车与修正(PLD 正确性)
160
+
161
+ ### 4.1 问题
162
+
163
+ 早期曾宣传"PLD mean 82.94 / peak 177.40 t/s,超越 GE graph 1.54× / 3.3×"。这一口径**已撤回**。
164
+
165
+ **翻车证据**(实测对照):
166
+
167
+ | Prompt | Baseline | PLD K=10 | accept/K | 正确性 |
168
+ |---|---|---|---|---|
169
+ | "The capital of France is" | "Paris. It is known for…" | **"Paris. The capital of Paris is the city of Paris…"** × N | 8.20 | ❌ 死循环 |
170
+ | "Write a long Python function…" | 正常代码 | **"function function function…"** × 100+ | ~9 | ❌ 死循环 |
171
+ | "Once upon a time…" | 故事 A | 故事 B(Goldilocks,连贯但不同) | 0.6-2.5 | ✅ 可接受 |
172
+
173
+ ### 4.2 根因:正反馈循环
174
+
175
+ ```
176
+ 模型 temperature=0 下有轻微重复倾向
177
+ → n-gram 在 hist 里匹配到重复 → draft[K] 全是同一 token
178
+ → batch verify 时 past 已含重复,attention 对这些 token 的 logits 偏高
179
+ → accept 率飙到 5-9/K
180
+ → hist 里重复模式更密集 → 下一轮循环更紧
181
+ → 最终完全 "W W W W …" 死循环输出
182
+ ```
183
+
184
+ ### 4.3 认知纠偏
185
+
186
+ | 旧认知 | 新认知 |
187
+ |---|---|
188
+ | accept 高 = 加速好消息 | **accept > 5/K 持续 = degeneration loop 征兆** |
189
+ | peak 177 t/s 是硬件极限展示 | peak 177 t/s = 输出死循环 token 时的 TG,不是可用推理 |
190
+ | 10-run 统计越大越好 | 10-run 混合了正常和损坏 run,不可直接引用 |
191
+ | 质量和速度可以分开报告 | **性能数字必须与正确性绑定** |
192
+
193
+ ### 4.4 为什么 K sweep 仍然"显示 K=10 最稳"
194
+
195
+ 因为 sweep 只测 TG 数字,没测输出正确性。
196
+ **K=10 最稳 = 最容易触发 feedback loop** —— 在相同 prompt/seed 下,K=10 能把 baseline 轻度重复倾向最快放大成死循环,于是统计上"3/3 runs 100+ t/s" 其实是"3/3 run 都成功进入死循环"。
197
+
198
+ **教训**:benchmark 脚本必须包含输出抽查,纯数字不够。
199
+
200
+ ---
201
+
202
+ ## 五 · 推荐推理路径
203
+
204
+ ### 5.1 生产默认(所有 prompt 安全)
205
+
206
+ ```bash
207
+ ./scripts/tp_launch.sh 16 ./build/qwen3-aclnn-cli \
208
+ --model-dir /path/to/Qwen3-235B-A22B-Instruct-2507-BF16 \
209
+ --prompt "<任意 prompt>" --n-predict 200 \
210
+ --temperature 0 --no-stream
211
+ # 期望: ~27 t/s, 所有 prompt 输出正确
212
+ ```
213
+
214
+ ### 5.2 创意生成(可选 PLD,需人工核验输出)
215
+
216
+ ```bash
217
+ ./scripts/tp_launch.sh 16 ./build/qwen3-aclnn-cli ... \
218
+ --prompt "Once upon a time, in a small village" \
219
+ --n-predict 200 --pld --temperature 0
220
+ # 期望: 30-50 t/s, accept 0.5-3, 连贯故事输出
221
+ ```
222
+
223
+ ### 5.3 禁用 PLD 的场景
224
+
225
+ - 事实性问答("Who is the CEO of X")
226
+ - 代码生成("Write a function…"��
227
+ - 数学步骤(固定模板)
228
+ - 会话中已观察到 accept > 5/K 时
229
+
230
+ ---
231
+
232
+ ## 六 · 单层 forward 数据流(优化后)
233
+
234
+ ```
235
+ x_in [S, D=4096]
236
+
237
+ ┌── Attention 分支 ──┐
238
+ │ RmsNorm(input_layernorm)
239
+ │ linear_hf q_proj/k_proj/v_proj → q, k, v
240
+ │ (TP=16: Q=4h×128=512, KV=1h×128=128)
241
+ │ Per-head RmsNorm q_norm, k_norm
242
+ │ Fused RoPE: aclnnApplyRotaryPosEmbV2 (layout=1, half) ★ 优化
243
+ │ Append K, V to layer cache at past_len..past_len+S-1
244
+ │ Mask 选择:
245
+ │ - prefill (past=0, S>1): 2048×2048 causal + sparse_mode=3
246
+ │ - decode (S=1): mask=nullptr + sparse_mode=0
247
+ │ - batch decode (PLD): [1,1,S,past+S] + sparse_mode=0 ★ 关键修复
248
+ │ FIAS(q, k_cache, v_cache, mask)
249
+ │ o_proj linear_hf → partial
250
+ │ HCCL AllReduce (ring + AIV + FFTS) ★ 优化
251
+ └─────────────────┘
252
+ ↓ residual add
253
+ ┌── MoE 分支 ──┐
254
+ │ RmsNorm(post_attention_layernorm)
255
+ │ linear_hf router → logits [S, 128]
256
+ │ moe_gating_topk_softmax → topk_w, topk_idx
257
+ │ Device-side normalize ★ 优化
258
+ │ moe_init_routing_v3 (counts + rowIdxType=1)
259
+ │ grouped_matmul_v4 (gate/up/down)
260
+ │ silu(gate) * up → act; act @ w_down
261
+ │ Device-side argsort × 2(代替 host sort sync) ★ 优化
262
+ │ IndexSelect → packed
263
+ │ Broadcast mul with topk_w, ReduceSum axis=1
264
+ │ HCCL AllReduce
265
+ └────────────┘
266
+ ↓ residual add
267
+ x_out
268
+ ```
269
+
270
+ ---
271
+
272
+ ## 七 · 未来方向(优先级重排)
273
+
274
+ | # | 方向 | 预期收益 | 工程量 | 优先级 |
275
+ |---|---|---|---|---|
276
+ | 1 | **PLD degeneration 检测**(draft 连续同 token / accept 饱和 → fallback single decode) | 让 PLD 在事实/代码场景可用 | 0.5-1 周 | ⭐⭐⭐ |
277
+ | 2 | Benchmark 脚本加输出正确性抽查 | 避免再误报 | 0.5 天 | ⭐⭐⭐ |
278
+ | 3 | Draft model speculative decoding(Qwen3-0.6B on spare NPU) | 更稳 accept,避免 n-gram 正反馈 | 1-2 周 | ⭐⭐⭐ |
279
+ | 4 | Tree attention(K-draft tree 多分支) | peak +20-30% | 2-3 周 | ⭐⭐ |
280
+ | 5 | 真·GE IR 图编译 | +10-30%(受 910 融合算子缺失限制) | 4-6 周 | ⭐ |
281
+ | 6 | 迁移 910B/A2/A3 硬件 | 200-500+ t/s | 需新硬件 | n/a |
282
+
283
+ ---
284
+
285
+ ## 八 · 项目级教训(写给未来自己)
286
+
287
+ 1. **高 accept 不等于成功**:在 speculative decoding 类优化中,accept rate 过高是异常信号而非加速胜利
288
+ 2. **性能宣传必须绑定正确性**:只报 TG 数字不验证输出,是工程伦理失守
289
+ 3. **用户的"是否正确"是终极 benchmark**:一句追问击穿所有纯数字统计
290
+ 4. **Fused op 要逐个验证**:不要因一个算子不可用就否定整个 family(`AddRmsNorm` 不可用 ≠ `ApplyRotaryPosEmbV2` 不可用)
291
+ 5. **Adaptive 不总比 fixed 好**:当成本函数近似常数(如 T_batch ≈ T_decode),fixed 参数更稳
292
+ 6. **Benchmark 必须包含正确性抽查**:纯数字 sweep 会把灾难当胜利
293
+ 7. **异步模型下 host-side free 要谨慎**:aclnn kernel 异步执行,任何 DeviceBuffer/workspace 释放必须确保 in-flight kernel 已完成
294
+ 8. **HCCL 不是黑盒**:AIV / FFTS 等 env 在 910 初代有明显效果,值得花时间 sweep
295
+ 9. **文档与实际行为可能不符**:`aclnnApplyRotaryPosEmbV2` 的 `layout=0` 官方没标注不可用,靠测试枚举才发现
296
+ 10. **GE 图编译不是银弹**:910 初代缺融合算子,图编译的"融合红利"大幅缩水;盲目投入不值
297
+
298
+ ---
299
+
300
+ ## 九 · 性能演进时间线
301
+
302
+ | 阶段 | 日期 | 关键动作 | 通用 TG | PLD 创意 TG |
303
+ |---|---|---|---|---|
304
+ | 起点 | 04-21 晨 | 端到端跑通(TP=16) | 12 t/s | — |
305
+ | HCCL ring+buffsize | 04-21 下午 | 基础 HCCL 参数 | 13.8 t/s | — |
306
+ | HCCL env 深挖 | 04-21 晚 | + AIV + FFTS + TASK_QUEUE=2 | 23 t/s | — |
307
+ | Fused RoPE | 04-21 夜 | + aclnnApplyRotaryPosEmbV2 | **27 t/s** ✅ MUST | — |
308
+ | PLD 初版 | 04-21 夜 | causal-with-past mask bug | 27 t/s | ~30 t/s 混合 |
309
+ | PLD 调参 | 04-21 夜 | K=10, multi-level, min_hist=20 | 27 t/s | 宣传 82.94 ⚠️ |
310
+ | **正确性修正** | 04-22 | 发现 feedback loop,撤回宣传 | **27 t/s** | **39 t/s(仅创意)** |
311
+
312
+ ---
313
+
314
+ ## 十 · 结论
315
+
316
+ 纯 aclnn EAGER 路径在 Ascend 910 初代 × 16 NPU 上,通过 **HCCL env 调参 + Fused RoPE + 小优化**,将 Qwen3-235B-A22B BF16 推理从 12 t/s 提升到 **27 t/s**(通用、所有 prompt 正确)。
317
+ 启用 PLD 后在**创意生成**场景可达 **39 t/s**(+45%),但在事实/代码场景会触发 feedback loop 死循环,**必须禁用**。
318
+
319
+ **未达成 cann-recipes-infer GE graph 54 t/s 基线**。差距主要来自:
320
+ - 910 初代缺失关键融合算子(`MatmulAllReduce`、`GroupedMatmulAllReduce`、`AddRmsNorm`)
321
+ - EAGER 路径 HCCL + kernel launch 占 75% 时间,真计算仅 12%
322
+ - 图编译风格的 stream-capture API(`aclmdlRI`)不提供加速(POC 证伪)
323
+
324
+ 后续最高优先级工作是 **PLD degeneration 检测**,让加速路径在事实/代码场景也安全可用。
325
+
326
+ ---
327
+
328
+ *本阶段性总结基于 2026-04-22 实测与代码快照。*