Skip to content

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 静默不写数据
4UB 借缓冲必须先做生命周期表推演1 行换缓冲修掉"head 序号 ≥8 全垃圾"
5跨 commit 控制变量(同测试 × 不同构建)是定位引入点最快的路改 ASCEND_CUSTOM_OPP_PATH 不重编译,4 列矩阵一天跑完
6"补齐走 Cube 比回退 Vector 快得多"这条第一性假设值一个 commit该规则在 a9c66ddb 已写下,6.32x 同日兑现
7编译期特化档位与 launch 开销是单向恶化,注释里的论证要预期被打脸3 kernel → 4 kernel,初版注释原话可查
8review 的正确姿势是"策略函数 + 值域映射",不是再加一个 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 内走完):
    1. 代码内 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 表末行)。
    2. 中央白名单(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)。
    3. 白名单自身还要维护:6a3813c3 把变长的 LoadPaddedRows 加进列表;773ee9e3 又发现 lizard 对类成员函数要求全限定名 ChunkGatedDeltaRule::LoadPaddedRows,写裸函数名匹配不上(同文 §6 L221-227)。→ 两个 1 行 commit 是"用集中机制"的后续税。
  • cpplint 没有白名单机制 → 只能行内 NOLINT:本仓库快照给的量化证据是 RGDR 两个时点的对比——src/f3f5e676/(初版)6 个文件里 NOLINT 0 处,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-126
    cpp
    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);
    本地 grep: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)",不要给"相同"。这条同样是本目录三章的写法约定。