04 · 开发心得(14 条可迁移结论,每条挂数据或代码锚点)
只收"有单一数据/单段代码能一击支撑"的结论。出处一律给到
文件 + 行/章节;标 本报告计算 的是我从台账数字算出的派生量。延迟/精度/管线的原始数字见本目录01/02/03三章。
结论速查
| # | 一句话结论 | 一击支撑 |
|---|---|---|
| 1 | 泛化重构可以零性能损失,但前提是dispatch 结果不变(编译期常量折叠) | 6 shape 中 ≥4.3ms 的 3 个偏差 ≤1.10% |
| 2 | "全零但 isfinite"是测试盲区:数值锚必须对零敏感 | 725cf45d 2P/11F,探针 absmax 0.0 vs 0.096 |
| 3 | 新 MTE 原语先写 3 行探针再全量替换 | DataCopyPad 在 dav_m200 静默不写数据 |
| 4 | UB 借缓冲必须先做生命周期表推演 | 1 行换缓冲修掉"head 序号 ≥8 全垃圾" |
| 5 | 跨 commit 控制变量(同测试 × 不同构建)是定位引入点最快的路 | 改 ASCEND_CUSTOM_OPP_PATH 不重编译,4 列矩阵一天跑完 |
| 6 | "补齐走 Cube 比回退 Vector 快得多"这条第一性假设值一个 commit | 该规则在 a9c66ddb 已写下,6.32x 同日兑现 |
| 7 | 编译期特化档位与 launch 开销是单向恶化,注释里的论证要预期被打脸 | 3 kernel → 4 kernel,初版注释原话可查 |
| 8 | review 的正确姿势是"策略函数 + 值域映射",不是再加一个 if | +138/−205 净 −67 行,支持域 3 点 → 3 区间 |
| 9 | 合规成本沿"代码内 suppress → 中央白名单 → 行内 NOLINT"三级曲线走 | RGDR 快照 NOLINT 0 → 11 处 |
| 10 | "先写死跑通、review 时泛化"是这个仓库的真实工作流 | 两版都有完整价值:没有写死版就没有泛化依据 |
| 11 | 按不变式修复,不按症状打补丁 | 8 处栅栏 vs "测试只复现得了 1-2 处" |
| 12 | 正确的同步不花钱 | 最坏单 shape 退化 +0.41%(绝对 18µs) |
| 13 | 优化会迁移瓶颈,所以必须重测,不能沿用上一轮的瓶颈模型 | Scalar 0.674 vs Cube 0.013 |
| 14 | 阈值型结论必须带口径(同日/跨日、绝对/相对) | 同一构建跨天自漂移达 6.36%/8.20% |
1. 泛化重构可以零性能损失——前提是这个重构不改变 dispatch 结果
- 数据:
PLAN.md §中间 commit 实测矩阵 L131-137的 4×6 延迟矩阵(a9c66ddb 写死三桶 vs 725cf45d/69300ffc 桶策略化)逐 shape 重算(本报告计算,见01-延迟台账.md §6):≥4.3ms 的 3 个 shape 最大偏差 1.10%,4 个 ≥1.13ms 的 shape 最大 1.65%。文档结论"六 shape 差 <1.5%"在亚毫秒 shape 上被自身表格推翻(−7.78%/−7.80%,绝对 66~73µs),但方向性结论成立。 - 机理:
docs/PR-1135-d0906c3e/10-Commit演进史.md §9.2——SelectCubeDk是纯 host 侧逻辑,模板参数kSpecializedDk在编译期常量折叠,"写死"与"映射到同一常量"生成相同机器码;device 侧逐指令等价。 - 代码锚点:
src/d0906c3e/op_kernel/chunk_gated_delta_rule.h:74(template <typename inType, typename outType, uint32_t kSpecializedDk>)、:91(alignK_ = (kSpecializedDk != 0 && tilingData->dk <= kSpecializedDk) ? kSpecializedDk : naturalAlignK)、:258-259(if constexpr (kSpecializedDk != 0)单分支 +realK_ <= kSpecializedDk && chunkLen == 64 && vStepAligned_ == kSpecializedDk)——桶宽是类型级常量,host 怎么算出这个常量对 device 代码零影响。 - 迁移动作:判断一次"结构性重构会不会掉性能",只需回答一个问题:编译期可见的常量与分支集合有没有变。有变(例如新增档位)→ 去看第 7 条;没变 → 性能结论按噪声地板读。
2. "全零但 isfinite"是测试盲区,数值锚必须非平凡
- 数据:725cf45d 构建 2 passed / 11 failed / 13 skipped(
PLAN.md L131),11 个 fail = 9 组 tail(cosine=0.0/nrmse=1.0)+test_accuracy_beta_zero(out_diff≈0.50)+test_accuracy_recurrent_reference(cosine=0.0);而test_shapes_and_dtypes与test_optional_g通过——"它们只断言torch.isfinite,全零是有限的,溜过去了"(docs/PR-1135-d0906c3e/10 §9.3 L303-308)。 - 决定性锚点:
/tmp/probe725.py输出725cf45d → 0.0 / 0.0,对照组a9c66ddb/69300ffc/d0906c3e → ~0.096 / ~0.023(docs/PR-1135-d0906c3e/02-实验复现.md §6.4 L212)。同一个 shape、同一份随机输入,absmax 这一个数就把"静默不写数据"暴露出来(比值 ∞,本报告计算)。 - 迁移动作:任何 AscendC 算子的正确性套件里,至少留一条非零量级断言(absmax 下界、与非零参考的 cosine、或对全零显式
assert not allclose(0))。"输出是有限值"不构成正确性证据;文档沉淀为铁律:docs/PR-1135-d0906c3e/10 §9.6 第 1 条。
3. 新 MTE 原语先写 3 行探针,再谈全量替换
- 事件:725cf45d 用
DataCopyPad统一了 K/Q 装载(四处各写一份的 strided DataCopy 全删),结果在 310P(dav_m200)上 MTE2 静默不写数据:UB 先被Duplicate清零、DataCopyPad不落数、后续 Cast 读到的全是 0 → 全链路输出全零(docs/PR-1135-d0906c3e/10 §9.3 L312-316)。这不是精度退化,是"API 文档说支持、芯片代际半支持"。 - 代码锚点(三代对照,本仓库可核):
src/a9c66ddb/op_kernel/chunk_gated_delta_rule.h中DataCopyPad出现 0 次(本地 grep,docs 同一口径见§9.3 L320)→ 初版从不用它;725cf45d 首次引入即翻车;69300ffc 重写为Duplicate 清零 + 标准 DataCopy 分块 + 标量尾巴,终态注释直接写明来源:src/d0906c3e/op_kernel/chunk_gated_delta_rule.h:274-278("Copy a row-major GM slab to a zero-padded UB matrix using dav-2002 supported primitives … fill at most 15 tail elements through Scalar"),函数体在:272。 - 成本对比(docs 原话):"一条 3 行的 GM→UB 探针就能测出来,比'重构后全套挂'的返工便宜得多——作者在 69300ffc 补的正是这一课"(
docs/PR-1135-d0906c3e/10 §9.6 第 3 条)。 - 迁移动作:换 DMA/装载原语时,先在最小 shape 上写"填 1 个已知 pattern → DMA → 读回比对"的探针,再动主路径。探针本身可复用到 §2 的 absmax 断言里。
4. UB 借缓冲:先做生命周期表推演,再动 ReinterpretCast
- 事件:!1135 的
LoadChunkCoefficients把 beta 板 DMA 进 UB 时借用了chunkVFp32,随后在同一块内存上做 fp16→fp32 的原地加宽 Cast——Vector 管按 32B repeat 批量推进时,fp32 写窗口是 fp16 读窗口的 2 倍宽且前向延伸,第 2 个 repeat 起未读的 beta 全被自己上一 repeat 的输出覆盖 → beta 板每行 Hv 个 head,head 序号 ≥8 的列全变垃圾,GQA 模型(Hv≥8)普遍中招(docs/PR-1158-88d8b965/00 §1 L16)。 - 代码锚点(1 行修复,两版快照对照):修复前
src/d0906c3e/op_kernel/chunk_gated_delta_rule.h:819LocalTensor<inType> betaLocal = chunkVFp32.template ReinterpretCast<inType>();(紧接着:827的Cast(chunkVFp32, betaLocal, …)→ 源与目的同一块); 修复后src/88d8b965/op_kernel/chunk_gated_delta_rule.h:820换成chunkAttnOutFp32,并多一行注释 "Keep the FP16 source separate because an in-place widening Cast overwrites unread beta values."(本地 diff:仅 1 行改动 + 1 行注释,与 docs"kernel.h 1 行"一致)。 - 为什么 1 行也值得独立成 PR:!1135 的测试 shape 没触达损坏区(head 列 ≥8 的错误在小容差组合下漏网),所以本 PR 同时加 87 行测试把"宽 Hv / 多种 GQA 比例"变成测试变量;缓冲重叠 bug 的暴露依赖 shape 触达损坏区——不是"全错"而是"特定列错",随机小 shape 极易漏测(
docs/PR-1158-88d8b965/00 §2 L20-22)。 - 迁移动作:借缓冲的唯一安全前提是"被借区间的读在写开始前全部完成",这要用生命周期表(谁在哪一 phase 最后读它)推演,不能靠"看起来此刻空闲"。可验证的推演手段:把修复 revert 回去、看哪些用例从过变挂(现成流程
docs/PR-1158-88d8b965/02 §4 L36-58)。
5. 跨 commit 的控制变量实验是定位"哪一版引入的"最快手段
- 做法:同一份测试文件 × 不同构建。checkout 的 commit 决定测试文件版本,但本实验统一用工作树(96ea3f55 时点,覆盖最全:9 组 dk 边界 + CPU 递推双断言)的测试跑所有构建;切换被测 vendor 只改
ASCEND_CUSTOM_OPP_PATH,不重新编译(docs/PR-1135-d0906c3e/02 §6.2 L143-170、§5 第 2/4 条;env 模板PLAN.md §Step A+B L62-75)。 - 战果(全部三条结论都出自这一张矩阵):① 泛化零性能损失;② 725cf45d 是全零坏版本、69300ffc 收敛;③ L0C 间歇崩溃初版即有(
PLAN.md §中间 commit 实测矩阵 三条结论 L139-142)。docs 把它列为方法论第 2 条:"本节全部结论……都来自'同一测试文件 × 不同构建'的矩阵"(docs/PR-1135-d0906c3e/10 §9.6 第 2 条)。 - 为什么快:4 个构建 × (正确性 + perf) 的机时代价只有编译是重复的,而 310P 单 commit 编译约 3~5 分钟(
docs/PR-406-00e39ab5/00 §4.2)、正确性约 2~6 分钟(docs/PR-1135-d0906c3e/02 §3、PLAN.md L69)→ 瓶颈在编译不在测量;/tmp/build_one.sh+/tmp/test_one.sh两个脚本即可批量化(docs/PR-1135-d0906c3e/02 §6.1-6.2)。 - 两个必踩的坑(已记录):perf 部分务必带
-v,否则崩溃时看不出挂在哪个 shape(同文§6.2 L165);崩溃后设备会脏,下一条用例的数据生成算子连带报 2 个 errors(docs/PR-1135-d0906c3e/10 §9.5 L340-341)→ "首跑崩、复跑过"必须当两件事记录。
6. "补齐走 Cube 比回退 Vector 快得多"——一条第一性假设撑起整个 PR 的方向
- 原始判断:
SolveVStep增加preferredVStep参数,Dv<128时优先把 V tile 补到 128 而不是退回更小的 Vector tile;docs 明确记为"这条 8/13 就写下的判断,是整个 PR 的第一性假设"(docs/PR-1135-d0906c3e/10 §1.1 L35-36)。终态代码里它变成preferredVStep = cubeDk(src/d0906c3e/op_host/chunk_gated_delta_rule_tiling.cpp:296-299,注释 "Keep V and padded K on the same regular Cube tile. This enables the fused path for every Dk in the 64/80/96/128 ranges")。 - 收益兑现:写有该假设的 a9c66ddb 当天就把收益吃满——同 shape
1×16×128×128×128从 7.273ms 降到 1.151ms = 6.3189x,占终态总收益的 99.69%(本报告计算,见01-延迟台账.md §5;PLAN.md L82、L133)。 - 代价也被量化了:一旦补齐补到更宽的桶,UB 预算立刻上升——
dk=80在 725cf45d 落进 96 桶,tbuf 从 125312 → 153088 字节(+22.17%)、vStep 80 → 96(+20%,本报告计算;docs/PR-1135-d0906c3e/02 §6.3)。 - 诚实的反例:唯一"不需要补齐"的 S1(Dk=Dv=64)加速比最低(1.35x,
PLAN.md L99)→ "补齐 vs 回退"只决定走不走 Cube 这条路,不决定小 shape 上固定成本的占比。别把 6.3x 归功于补齐技巧本身。 - 迁移动作:硬件单元切换类优化,先把"回退路径慢多少"量成一条可核的比值(这里是 Vector 管 4096 次软件点积 vs Cube 12.6µs,
docs/PR-1135-d0906c3e/00 §1 L23、11 §1.3),再决定要不要为补齐付 UB。
7. 编译期特化档位与 kernel launch 开销是单向恶化的
- 初版的论证(原文可查):
src/a9c66ddb/op_host/chunk_gated_delta_rule_tiling.cpp:311-314"Keep exactly three compiled kernels: key 0 is the dense Dk=80 Cube path, key 2 is the fixed Dk=128 path, and key 1 handles both Dk=64 and generic shapes. Avoiding a fourth kernel also avoids a measurable launch overhead on small generic shapes." - 被打脸的过程:69300ffc 为了把 Dk=80 这个真实生态 shape 拉回专用快路径,被迫把 tilingKey 从三分改四分(0=64/1=80/2=96/3=128)——"直接推翻初版 a9c66ddb '只编三个 kernel 省 launch 开销' 的注释"(
docs/PR-1135-d0906c3e/10 §5.1 L196-199)。终态代码:src/d0906c3e/op_host/chunk_gated_delta_rule_tiling.cpp:323-333("Keep four compiled kernels…")与入口src/d0906c3e/op_kernel/chunk_gated_delta_rule.cpp:33-49(4 个TILING_KEY_IS分支)。 - 可观测痕迹:崩溃 kernel 名后缀从 3 桶版的
_2变成 4 桶版的_3(docs/PR-1135-d0906c3e/02 §6.5 L222-225)——kernel 个数是能在设备侧被读出来的。注意_2在两版里指不同 kernel(3 桶版_2=128 桶,4 桶版_2=96 桶),跨版本引用必须带构建号(02-精度台账.md §6)。 - 本报告的边界提醒:docs 说"档位加了就只能付 kernel 数的成本",但 09-14 矩阵里 4 桶版相对 3 桶版的最差偏差只有 1.65%(且落在亚毫秒 shape 的噪声地板内,
01-延迟台账.md §6/§8)→ 第 4 个 kernel 的 launch 代价在现有数据里无法分离。这条只能作为"预期会付、量级未知"的教训,不是实测结论。 - 迁移动作:写"只编 N 个 kernel 省开销"这类注释时,同时写清"若新增一档会付什么";
tilingKey的数量和含义要作为可回归项进测试(这里靠崩溃 kernel 后缀间接暴露)。
8. review 的正确姿势:策略函数 + 值域映射,而不是再加一个 if
- 规模:725cf45d 是 +138/−205(净 −67 行),功能不变、结构可扩展(
docs/PR-1135-d0906c3e/10 §3 L121)。 - 做法三步(同文 §3.1-3.2):① 枚举变策略——
SelectCubeDk(dk)把dk ∈ {64,128,80}等值匹配换成≤64 / ≤80 / ≤96 / ≤128区间映射,超出末档时走函数末尾的return 0进 fallback;终态代码src/d0906c3e/op_host/chunk_gated_delta_rule_tiling.cpp:212-226(含注释"this is a shape-range policy rather than a one-off specialization for a benchmark dimension")。② 语义统一——alignK_ = cubeDk(非CeilAlign(dk,16))、stateStrideK_ = alignK_,于是ComputeTmpBuffBytes签名里的dk参数被删(少一个耦合变量)。两版快照可直接核对:初版src/a9c66ddb/op_host/chunk_gated_delta_rule_tiling.cpp:135的形参含uint32_t dk,终态src/d0906c3e/op_host/chunk_gated_delta_rule_tiling.cpp:138-139已无该形参;初版src/a9c66ddb/op_kernel/chunk_gated_delta_rule.h:91是stateStrideK_ = Ceil(dk, FP32_NUM_PER_BLOCK) * FP32_NUM_PER_BLOCK(与 alignK_ 各算各的),终态src/d0906c3e/op_kernel/chunk_gated_delta_rule.h:90-92变成naturalAlignK → alignK_ = (kSpecializedDk != 0 && dk <= kSpecializedDk) ? kSpecializedDk : naturalAlignK; stateStrideK_ = alignK_;——"两个语义统一"正是删参数的前提。③ dispatch 折叠——六处kSpecializedDk == 64/128/80三连分支折成if constexpr (kSpecializedDk != 0)一处,快路径判定从"=="变"<="。 - 一击数据:
grep -c "kSpecializedDk == 80"在src/a9c66ddb/op_kernel/chunk_gated_delta_rule.h= 16 行(docs §1.3 记为"9 处"),在终态src/d0906c3e/…= 0。每一处都是"加一档要改 N 处"的税——这是"再加一个 if"最直接的代价图像(两处口径并列,见02-精度台账.md §8)。 - 配套义务:泛化会把从未跑过的 shape 送进快路径,所以测试结构必须同 commit 跟着走:
test_non_multiple_reduce_width从 dk=80 单点 → 6 组(63/65/80/95/97/127,§3.5)→ 9 组"每桶边界 ±1"(79/80/81 三连,§5.3),中间还夹一个 7bbe0e01 专门加 CPU 递推双断言(§4)。 - 迁移动作:reviewer 说"别写死"时,正确的翻译不是多写一个
if,而是把常量换成策略函数、把等值换成值域、让所有下游(对齐/步长/预算/测试)跟着一个 commit 对齐(§3.5 末"本 commit 的方法论")。
9. 合规成本曲线:代码内 suppress → 中央白名单 → 行内 NOLINT
- 三段路径(同一 PR 内走完):
- 代码内 suppress(9ed2dd8e):6 个 Cube 函数 + 3 个 Load/Mmad helper 各加
// cppcheck-suppress unusedPrivateFunction、5 个超长函数加// #lizard forgives,理由各写一句("splitting the fused Cube/Vector sequence would break its explicit event pipeline")——无理由会被打回(docs/PR-1135-d0906c3e/10 §2 L103-107、§7 表末行)。 - 中央白名单(725cf45d):
filter_cppcheck.txt收编unusedPrivateFunction、whitelizard.txt新增 Scene2 行(file:function1,function2,...),代码内 suppress 全部删除;前提是折叠后函数签名稳定(同文 §3.4 L163-167)。本地可核:终态src/d0906c3e/op_kernel/*.h/.cpp里cppcheck-suppress/#lizard forgives均为 0(本地 grep)。 - 白名单自身还要维护:6a3813c3 把变长的
LoadPaddedRows加进列表;773ee9e3 又发现 lizard 对类成员函数要求全限定名ChunkGatedDeltaRule::LoadPaddedRows,写裸函数名匹配不上(同文 §6 L221-227)。→ 两个 1 行 commit 是"用集中机制"的后续税。
- 代码内 suppress(9ed2dd8e):6 个 Cube 函数 + 3 个 Load/Mmad helper 各加
- cpplint 没有白名单机制 → 只能行内 NOLINT:本仓库快照给的量化证据是 RGDR 两个时点的对比——
src/f3f5e676/(初版)6 个文件里NOLINT0 处,src/96ea3f55/(终态)同批文件 11 处(5×NOLINT(build/include_subdir)+ 6×NOLINT(build/namespaces),本地 grep)。docs 对应记录:"op_host/recurrent_gated_delta_rule_tiling.cpp 逻辑一致(ab45f0dc 重排为 NOLINT 版)"(docs/PR-1140-96ea3f55/02 §6 L84)。CGDR 侧同样稳定在 11 处(各快照同数)。 - 迁移动作:投华为仓库的 device 代码,把合规按"能不能集中表达"分层——能集中(cppcheck/lizard)就进白名单文件并保证签名稳定,不能集中(cpplint 的头文件包含与命名空间)就老老实实行内 NOLINT;
#lizard forgives必须带理由。
10. "先写死跑通、review 时泛化"是这个仓库的真实工作流
- docs 原话:"先写死跑通、review 时泛化是这个仓库的现实工作流——a9c66ddb 的三桶写死换来 8/13 一天交出全部机制,725cf45d 再花一个 commit 还债。两次提交都有完整价值:没有写死版就没有泛化方向的判断依据"(
docs/PR-1135-d0906c3e/10 §7 结论 1 L239-241)。 - 支撑数据:① 时间线——5 天 7 个 commit = 1 功能 + 1 review 重构 + 1 泛化 + 3 合规 + 1 测试加固(同文 §0 L19);② 机制集中度——初版 a9c66ddb 一天交出模板分桶、GM 暂存、补偿分裂、六处 Cube 函数全部设计骨架(§1 L25-27,行号表 L48-69);③ 收益集中度——性能收益 99.69% 在这一天兑现(本报告计算,
01-延迟台账.md §5),后续 commit 的贡献是正确性/泛化/合规。 - 反面教训(同一工作流的代价):写死版自带的是"没改测试"的债——
git diff 00e39ab5 a9c66ddb -- test_chunk_gated_delta_rule.py为空,对非 benchmark shape 的覆盖为零(§1.4 L96-99);泛化版又踩进 DataCopyPad(第 3 条)。→ 若走这条路,测试加固必须和泛化同批落地,否则 CI 拦不住(§9.3"为什么 CI 没拦住")。
11. 按不变式修复,不按症状打补丁
- 不变式:所有 L0C→UB 的结果搬运由 M 管发起 DataCopy,紧跟的 Vector 读取必须有 M↔V 双向事件栅栏;
PipeBarrier<PIPE_ALL>只约束发射顺序、不保证执行完成(docs/PR-1174-048d0427/00 §1 L17)。 - 为什么不能按症状:"竞争是概率性的,测试只复现得了其中 1-2 处" → 只补被复现的那一处就是假的修好了(同文 §1 L19,原话"按不变式修复,不按症状打补丁")。做法是新增
CopyL0CToUb封装并把 8 处 L0C 输出点全部替换。 - 代码锚点(可核):
src/048d0427/op_kernel/chunk_gated_delta_rule.h:116-126cpp本地 grep:event_t mToV = …FetchEventID(HardEvent::M_V); event_t vToM = …FetchEventID(HardEvent::V_M); SetFlag<HardEvent::M_V>(mToV); WaitFlag<HardEvent::M_V>(mToV); DataCopy(dst, src, copyParams, copyEnhanced); SetFlag<HardEvent::V_M>(vToM); WaitFlag<HardEvent::V_M>(vToM);CopyL0CToUb在 048d0427 出现 9 次 = 1 定义(:116)+ 8 调用(:511/:1183/:1298/:1734/:1823/:1909/:2058/:2271),在 d0906c3e 出现 0 次 → 与 docs"8 处替换"完全一致。 - 症状侧的证据链也要求"按不变式":崩溃在 a9c66ddb → 69300ffc → d0906c3e 全程同形态(同一 shape 簇、~1/3 概率、同一硬件报错
L0C read/write conflict (vec read & cube write same address)),docs 据此判定"!1135 自带、非某 commit 新引入"(docs/PR-1135-d0906c3e/10 §9.5 L335-356、PLAN.md L142)。
12. 正确的同步不花钱
- 机理:栅栏等待的事件本来就会发生(V 管反正要等数据、M 管反正要等 Mmad 完成),补栅栏只是把隐式依赖显式化(
docs/PR-1174-048d0427/00 §2 L29)。 - 数据:048d0427 vs d0906c3e 同日逐 shape 差值 = −7.54% / −0.87% / −1.44% / +0.41% / +0.13% / −8.43%(本报告计算,
01-延迟台账.md §7)→ 唯一变慢的是 18µs 和 11µs;表面的 −7.5%/−8.4% 经跨天对照判定为 d0906c3e 当日离群(同 build 跨天自漂移 S6 达 −8.20%,01-延迟台账.md §8)。代价规模:净 +11 行(1 封装 + 8 处替换)。 - 对照:不正确的同步很贵。同一个 PR 里,缺栅栏换来的是间歇崩溃(~1/3 概率、
runtime 507015、设备要重启自愈),而补栅栏的实测价格是 0.41%(docs/PR-1135-d0906c3e/02-实验复现.md:93、docs/PR-1174-048d0427/00 §2)。 - 迁移动作:跨管访问的栅栏要成对写(M→V 与 V→M),且用"不变式清单"驱动而不是"哪里崩补哪里";同步点写进封装函数里,让下一个调用者无法漏掉(这正是
CopyL0CToUb的封装价值,也见第 11 条)。
13. 优化会迁移瓶颈,所以必须重测
- 数据:Cube 化之后,
1×16×128×128×128单 kernel 的管线占比是 Scalar 651.7µs/0.674、Vector 295.4/0.306、MTE2 125.9/0.131、MTE3 49.2/0.051、Cube 12.6/0.013、MTE1 10.1/0.010(docs/PR-1135-d0906c3e/11-流水线剖析msprof实战.md §1.3)。六处 Cube 函数省下的 Vector 工作量转移到了调度/标量侧,Cube 本身几乎空闲(1.3%)→ "继续优化方向不在 Cube,在标量循环批量化"(同文 §1.3 结论 1)。 - 本报告计算:Scalar 651.7µs 比 Vector+MTE2+MTE3 之和(470.5µs)还多 38.5%;Vector/Scalar = 0.4533;Scalar+Vector = 947.1µs = 99.17% 的 955µs(六行 µs 求和 1144.9、ratio 求和 1.185 > 1 → 各管忙时相互重叠,不能当切分读)。
- 迁移动作:每完成一次"换硬件单元"级别的优化,就要重跑一次分管线采集,并把新瓶颈写进设计文档;旧 PR 的瓶颈描述(!1108 的"3 万条 Axpy + 4096 次软件点积压在 Vector 管",
docs/PR-1135-d0906c3e/00 §1 L23)在 !1135 之后已经失效。 - 仍未做的验证:泛化重构前后
scalar_ratio是否持平(docs 自己标注的延伸实验,本次未做,docs/PR-1135-d0906c3e/11 §3 L139-140)。
14. 阈值型结论必须带口径,否则会被自己的表格推翻
- 两个例子:① "泛化零性能损失(六 shape 差 <1.5%)"(
PLAN.md L140、docs/PR-1135-d0906c3e/10 §9.2)——按PLAN.md L132-137逐 shape 重算,a9c66ddb→725cf45d 有 3 格超过 1.5%(−7.78%/−7.80%/−1.65%);结论在 ≥1.13ms 的 4 个 shape 上成立、在两个亚毫秒 shape 上不成立(本报告计算,01-延迟台账.md §6)。② "048d0427 性能与 d0906c3e 相同"(PLAN.md L111)——同一段数据里 S6 差 1.597ms(−8.43%),docs 用"持平"更准(docs/PR-1174-048d0427/00 §2)。 - 为什么必须带口径:同一构建跨两次测量(09-08 vs 09-14)的自漂移是 −6.36% / −1.65% / −4.45% / −0.34% / −0.10% / −8.20%(本报告计算,
01-延迟台账.md §8)→ 绝对地板约 40~75µs(亚毫秒 shape)与 1.55ms(t2048)。任何不注明"同日/跨日、相对/绝对"的百分比结论都可能只是噪声。 - 迁移动作:写性能结论时固定三件事——测量日、对照 build、绝对差;报告"零变化"时给"最坏单 shape 退化 X%(绝对 Y µs)",不要给"相同"。这条同样是本目录三章的写法约定。