| @@ -0,0 +1,215 @@ | |||
| 1 | +# mamba3_siso_bwd Triton-Ascend 实践说明 | ||
| 2 | + | ||
| 3 | +## 算子功能 | ||
| 4 | + | ||
| 5 | +`mamba3_siso_bwd` 迁移 Mamba3 SISO backward 的三个核心算子: | ||
| 6 | + | ||
| 7 | +1. **`compute_dqktheta`** — 通过 rotary embeddings 和 biases 计算 dQ、dK、dAngles、dScale、dGamma、dQ_bias、dK_bias 梯度。 | ||
| 8 | +2. **`compute_ddt_dtrap_dinput_states`** — 从 dScale 和 dGamma 计算 dDT 和 dTrap 梯度,并可选地计算 input state 梯度。 | ||
| 9 | + | ||
| 10 | +三个 Triton kernel 对应: | ||
| 11 | + | ||
| 12 | +| Kernel | 功能 | Grid | | ||
| 13 | +| --- | --- | --- | | ||
| 14 | +| `mamba3_siso_bwd_kernel_rotary_bias_angles` | d Rotary+Bias+Angles kernel | `(nchunks, batch)` | | ||
| 15 | +| `mamba3_siso_bwd_kernel_dk_state_post` | dK_state post-process kernel (via atomic add) | `(nheads, batch)` | | ||
| 16 | +| `mamba3_siso_bwd_kernel_ddt_dtrap_dinput_states` | dDT, dTrap, dInput States kernel | `(nheads, batch)` | | ||
| 17 | + | ||
| 18 | +输入布局(`compute_dqktheta`): | ||
| 19 | + | ||
| 20 | +- `q/k`: `[B, S, Hq, K]` | ||
| 21 | +- `scale/gamma/dqk`: `[B, H, S]` | ||
| 22 | +- `q_bias/k_bias`: `[H, K]` | ||
| 23 | +- `angles`: `[B, S, H, A]` | ||
| 24 | +- `dq_in/dk_in`: `[B, S, H, K]` | ||
| 25 | + | ||
| 26 | +输入布局(`compute_ddt_dtrap_dinput_states`): | ||
| 27 | + | ||
| 28 | +- `dscale/dgamma/dt/trap`: `[B, H, S]` | ||
| 29 | + | ||
| 30 | +约束:`H % Hq == 0`,`A <= K / 2`,`chunk_size` 为 64。 | ||
| 31 | + | ||
| 32 | +## 交付文件 | ||
| 33 | + | ||
| 34 | +| 文件 | 作用 | | ||
| 35 | +| --- | --- | | ||
| 36 | +| `mindspeed_ops/api/triton/mamba3_siso_bwd.py` | 公开 API,负责 arch32 guard、参数转发和返回结果。 | | ||
| 37 | +| `mindspeed_ops/arch32/triton/mamba3/mamba3_siso_bwd_impl.py` | Triton-Ascend 生产实现,包含三个 kernel 和三个 host 函数。 | | ||
| 38 | +| `mindspeed_ops/arch32/triton/mamba3/mamba3_utils.py` | Triton JIT helper,提供 rotary 小角度 sin/cos 多项式、sigmoid、silu。 | | ||
| 39 | +| `tests/unit_tests/triton/test_mamba3_siso_bwd.py` | pytest 精度单测,覆盖 compute_dqktheta 和 compute_ddt_dtrap_dinput_states。 | | ||
| 40 | +| `tests/atk_tests/triton/mamba3_siso_bwd/generate_mamba3_siso_bwd.py` | ATK case generator,从 PR 内参数空间生成 case。 | | ||
| 41 | +| `tests/atk_tests/triton/mamba3_siso_bwd/mamba3_siso_bwd.yaml` | ATK 配置,声明输入、dtype、范围、baseline API 与 Triton API。 | | ||
| 42 | +| `tests/atk_tests/triton/mamba3_siso_bwd/triton_mamba3_siso_bwd.py` | ATK wrapper,测试侧 torch reference 与 candidate API 注册。 | | ||
| 43 | +| `tests/atk_tests/triton/mamba3_siso_bwd/reference_impl.py` | PR 内 torch reference,仅用于精度测试和自证脚本。 | | ||
| 44 | +| `tests/atk_tests/triton/mamba3_siso_bwd/self_check_mamba3_siso_bwd.py` | 自包含自证报告脚本,包含文件范围、静态 no-fallback、live 精度。 | | ||
| 45 | +| `docs/triton/mamba3_siso_bwd.md` | 本说明文档。 | | ||
| 46 | + | ||
| 47 | +## 迁移要点 | ||
| 48 | + | ||
| 49 | +- 将 CUDA/PTX `cos.approx.f32`、`sin.approx.f32` 替换为 Triton JIT 内可编译的多项式近似(`cos_approx`/`sin_approx`)。 | ||
| 50 | +- 移除 `assert z.is_cuda` 等 CUDA 专属断言,适配 NPU 设备。 | ||
| 51 | +- 移除 `triton.Config` 中的 `maxnreg` 参数,因为 NPU Triton 不支持该参数。 | ||
| 52 | +- 将 3D tensor 操作(`tl.reshape`/`tl.split`/`tl.join`)替换为 even/odd 列索引方式,避免 NPU 编译错误(`cannot align 2 axis for memref.alloc`)。 | ||
| 53 | +- 在 rotary embedding 的 GQA reduction 中使用分开的 even/odd 累加器,避免 3D 内存对齐问题。 | ||
| 54 | +- `mamba3_siso_bwd_kernel_dk_state_post` 不使用 autotune(使用 `tl.atomic_add` 写入 dK 和 dAngles,autotune 会导致多次覆写)。 | ||
| 55 | +- arch35 当前 fail loudly;不会静默切到 reference、CPU、vendor whole-op 或其它 backend fallback。 | ||
| 56 | + | ||
| 57 | +## 评审术语说明 | ||
| 58 | + | ||
| 59 | +- `candidate`:本 PR 提交的待评审实现,也就是生产 API 最终调用的 Triton-Ascend kernel。 | ||
| 60 | +- `reference`:只用于测试对比的正确性基准。本 PR 内的 reference 是 `tests/atk_tests/triton/mamba3_siso_bwd/reference_impl.py`,生产 API 不引用它。 | ||
| 61 | +- `fallback`:候选算子失败或变慢时,生产路径偷偷改用 torch、torch_npu 高层等价算子、CPU、上游 reference、vendor whole-op 或其它 backend 的实现。本 PR 禁止生产路径 fallback。 | ||
| 62 | +- `whole-op`:一个库函数直接完成整个算子功能。生产路径可以使用 torch/Triton 做张量、device 和 launch 管理,但不能调用现成 whole-op 顶替本 Triton 实现。 | ||
| 63 | +- `arch32/arch35`:MindSpeed-Ops 内的架构目录。当前 PR 交付 arch32 路径;arch35 没有交付实现,所以会明确报不支持,而不是静默切换到其它实现。 | ||
| 64 | +- `ATK`:仓内已有的自动化测试配置体系。本 PR 的 ATK 文件用于生成和执行测试,不属于生产调用路径。 | ||
| 65 | +- `GQA`:Grouped Query Attention,即 `nheads` 是 `nheads_qk` 的整数倍,多个 head 共享同一组 Q/K。 | ||
| 66 | + | ||
| 67 | +## 评审人自证命令 | ||
| 68 | + | ||
| 69 | +在 MindSpeed-Ops 仓库根目录执行: | ||
| 70 | + | ||
| 71 | +```shell | ||
| 72 | +python tests/atk_tests/triton/mamba3_siso_bwd/self_check_mamba3_siso_bwd.py \ | ||
| 73 | + --output /tmp/mamba3_siso_bwd_self_check.md | ||
| 74 | +``` | ||
| 75 | + | ||
| 76 | +这个脚本只依赖 PR 内文件和当前 MindSpeed-Ops 运行环境,不读取外部评测目录。它会生成 Markdown 报告并返回退出码:通过返回 `0`,失败返回非零。 | ||
| 77 | + | ||
| 78 | +快速检查可先跑两条代表性 case: | ||
| 79 | + | ||
| 80 | +```shell | ||
| 81 | +python tests/atk_tests/triton/mamba3_siso_bwd/self_check_mamba3_siso_bwd.py \ | ||
| 82 | + --quick \ | ||
| 83 | + --output /tmp/mamba3_siso_bwd_quick_self_check.md | ||
| 84 | +``` | ||
| 85 | + | ||
| 86 | +单元测试命令: | ||
| 87 | + | ||
| 88 | +```shell | ||
| 89 | +pytest -q tests/unit_tests/triton/test_mamba3_siso_bwd.py | ||
| 90 | +``` | ||
| 91 | + | ||
| 92 | +ATK 配置入口: | ||
| 93 | + | ||
| 94 | +```text | ||
| 95 | +tests/atk_tests/triton/mamba3_siso_bwd/mamba3_siso_bwd.yaml | ||
| 96 | +``` | ||
| 97 | + | ||
| 98 | +## 已完成自证报告 | ||
| 99 | + | ||
| 100 | +本节是当前 PR 内容在本机 Ascend NPU 上已经跑出的完整自证结果,不是运行说明的占位。 | ||
| 101 | +复核命令仍保留在上一节,供评审人需要时重新生成同格式报告。 | ||
| 102 | + | ||
| 103 | +执行命令: | ||
| 104 | + | ||
| 105 | +```shell | ||
| 106 | +python tests/atk_tests/triton/mamba3_siso_bwd/self_check_mamba3_siso_bwd.py \ | ||
| 107 | + --output /tmp/mamba3_siso_bwd_self_check_full.md | ||
| 108 | +``` | ||
| 109 | + | ||
| 110 | +执行时间:2026-07-03 00:12:00 +08:00。执行结果:退出码 `0`。 | ||
| 111 | + | ||
| 112 | +### 0. 运行与结论 | ||
| 113 | + | ||
| 114 | +- overall: PASS | ||
| 115 | +- mode: `full` | ||
| 116 | +- static_no_fallback: PASS | ||
| 117 | +- live_cases: PASS | ||
| 118 | +- repo_root: `<MindSpeed-Ops repository root>` | ||
| 119 | +- external_workspace_required: `no` | ||
| 120 | + | ||
| 121 | +### 1. 提交范围 | ||
| 122 | + | ||
| 123 | +| 文件 | 类型 | 用途 | 审查项 | 存在 | | ||
| 124 | +| --- | --- | --- | --- | --- | | ||
| 125 | +| `mindspeed_ops/api/triton/mamba3_siso_bwd.py` | production api | 公开 Python API;做 arch32 guard、参数转发和结果返回。 | 不能实现 reference 计算,不能调用 torch/torch_npu/vendor whole-op fallback。 | yes | | ||
| 126 | +| `mindspeed_ops/arch32/triton/mamba3/mamba3_siso_bwd_impl.py` | production kernel | Triton-Ascend kernel 和 host dispatch;包含 rotary_bias_angles、dk_state_post、ddt_dtrap_dinput_states 三个 kernel。 | 生产计算必须进入本文件内 Triton kernel;dispatch 只能按运行时 shape/dtype/metadata。 | yes | | ||
| 127 | +| `mindspeed_ops/arch32/triton/mamba3/mamba3_utils.py` | production helper | Triton JIT helper:rotary 小角度 sin/cos 多项式、sigmoid、silu。 | 仅提供 JIT helper,不读取外部 reference、case、golden 或上游包。 | yes | | ||
| 128 | +| `tests/unit_tests/triton/test_mamba3_siso_bwd.py` | unit test | pytest 精度单测,覆盖 compute_dqktheta 和 compute_ddt_dtrap_dinput_states 两条生产路径。 | 测试侧可调用 torch reference;生产入口不能反向依赖测试代码。 | yes | | ||
| 129 | +| `tests/atk_tests/triton/mamba3_siso_bwd/generate_mamba3_siso_bwd.py` | ATK generator | ATK 用例生成器;从提交内参数空间生成 shape/dtype/attr。 | 不读取外部 task cases、answer key 或私有 workload 文件。 | yes | | ||
| 130 | +| `tests/atk_tests/triton/mamba3_siso_bwd/mamba3_siso_bwd.yaml` | ATK config | ATK 算子配置,声明输入、dtype、取值范围、baseline API 与 Triton API。 | 配置中的有效范围要与本交付支持边界一致。 | yes | | ||
| 131 | +| `tests/atk_tests/triton/mamba3_siso_bwd/triton_mamba3_siso_bwd.py` | ATK wrapper | ATK baseline/candidate wrapper;baseline 仅用于测试侧对比。 | candidate wrapper 必须调用 `mindspeed_ops.api.triton.mamba3_siso_bwd`。 | yes | | ||
| 132 | +| `tests/atk_tests/triton/mamba3_siso_bwd/reference_impl.py` | test reference | 提交内 torch reference,用于 UT/ATK/自证脚本精度校验。 | 只能被测试和自证脚本引用,生产路径不能 import 它。 | yes | | ||
| 133 | +| `tests/atk_tests/triton/mamba3_siso_bwd/self_check_mamba3_siso_bwd.py` | self-check script | 一键生成 Markdown/JSON 自证报告,包含文件范围、静态 no-fallback、live 精度。 | 只依赖 PR 内文件和当前 MindSpeed-Ops 运行环境;不读取外部评测目录。 | yes | | ||
| 134 | +| `docs/triton/mamba3_siso_bwd.md` | documentation | 中文实践说明和审查说明,记录支持边界、验证命令和证据口径。 | 文档声明必须能由本脚本或仓内测试复核。 | yes | | ||
| 135 | + | ||
| 136 | +### 2. 无 fallback 静态检查 | ||
| 137 | + | ||
| 138 | +允许的生产依赖边界: | ||
| 139 | + | ||
| 140 | +| import/text | 用途 | | ||
| 141 | +| --- | --- | | ||
| 142 | +| `torch` | tensor/runtime API for input metadata and device checks | | ||
| 143 | +| `torch_npu` | runtime availability probe only; no torch_npu whole-op call is allowed | | ||
| 144 | +| `triton` | Triton-Ascend JIT and launch API | | ||
| 145 | +| `triton.language` | Triton language primitives | | ||
| 146 | + | ||
| 147 | +生产文件 import 扫描: | ||
| 148 | + | ||
| 149 | +- `mindspeed_ops/api/triton/mamba3_siso_bwd.py` imports: `__future__`, `typing`, `torch`, `mindspeed_ops.api.triton.utils`, `mindspeed_ops.utils`, `mindspeed_ops.arch32.triton.mamba3.mamba3_siso_bwd_impl`, `mindspeed_ops.arch32.triton.mamba3.mamba3_siso_bwd_impl` | ||
| 150 | +- `mindspeed_ops/arch32/triton/mamba3/mamba3_siso_bwd_impl.py` imports: `typing`, `torch`, `torch_npu`, `triton`, `triton.language`, `mindspeed_ops.arch32.triton.mamba3.mamba3_utils` | ||
| 151 | +- `mindspeed_ops/arch32/triton/mamba3/mamba3_utils.py` imports: `triton`, `triton.language` | ||
| 152 | + | ||
| 153 | +- PASS: production files do not import or reference torch_npu high-level ops, golden/reference code, upstream runtime packages, peer code, or other backend fallback. | ||
| 154 | + | ||
| 155 | +### 3. live 精度 | ||
| 156 | + | ||
| 157 | +accuracy pass rule: `max_abs <= abs_tol OR relative_rmse <= relative_rmse_tol`, abs_tol=`0.001`, relative_rmse_tol=`0.001` | ||
| 158 | + | ||
| 159 | +| case | dtype | shape | tensor | max_abs | rel_rmse | MARE | MERE | RMSE | result | | ||
| 160 | +| ---: | --- | --- | --- | ---: | ---: | ---: | ---: | ---: | --- | | ||
| 161 | +| 1 | float16 | `B1_S32_Hq1_H1_K64_CS64` | dq | 0.0000608861 | 0.0002071885 | 0.0004769560 | 0.0001783582 | 0.0000237140 | PASS | | ||
| 162 | +| 1 | float16 | `B1_S32_Hq1_H1_K64_CS64` | dk | 0.0000610352 | 0.0002124990 | 0.0019322314 | 0.0001810303 | 0.0000192188 | PASS | | ||
| 163 | +| 1 | float16 | `B1_S32_Hq1_H1_K64_CS64` | dangles | 0.0000001490 | 0.0000001062 | 0.0000044614 | 0.0000002889 | 0.0000000627 | PASS | | ||
| 164 | +| 1 | float16 | `B1_S32_Hq1_H1_K64_CS64` | dscale | 0.0000001490 | 0.0000001185 | 0.0000053441 | 0.0000003990 | 0.0000000592 | PASS | | ||
| 165 | +| 1 | float16 | `B1_S32_Hq1_H1_K64_CS64` | dgamma | 0.0000000075 | 0.0000000737 | 0.0000712978 | 0.0000001770 | 0.0000000018 | PASS | | ||
| 166 | +| 2 | float16 | `B1_S64_Hq1_H2_K64_CS64` | dq | 0.0001219809 | 0.0002143254 | 0.0004760298 | 0.0001779745 | 0.0000349229 | PASS | | ||
| 167 | +| 2 | float16 | `B1_S64_Hq1_H2_K64_CS64` | dk | 0.0001215041 | 0.0002045313 | 0.0023482803 | 0.0001753308 | 0.0000255590 | PASS | | ||
| 168 | +| 2 | float16 | `B1_S64_Hq1_H2_K64_CS64` | dangles | 0.0000004768 | 0.0000001480 | 0.0000028696 | 0.0000002554 | 0.0000001300 | PASS | | ||
| 169 | +| 2 | float16 | `B1_S64_Hq1_H2_K64_CS64` | dscale | 0.0000004768 | 0.0000001556 | 0.0000050795 | 0.0000002916 | 0.0000001024 | PASS | | ||
| 170 | +| 2 | float16 | `B1_S64_Hq1_H2_K64_CS64` | dgamma | 0.0000000149 | 0.0000000756 | 0.0000484480 | 0.0000001264 | 0.0000000018 | PASS | | ||
| 171 | +| 3 | float16 | `B2_S64_Hq2_H4_K64_CS32` | dq | 0.0001221001 | 0.0002118507 | 0.0047994906 | 0.0001779644 | 0.0000343607 | PASS | | ||
| 172 | +| 3 | float16 | `B2_S64_Hq2_H4_K64_CS32` | dk | 0.0001219809 | 0.0002095905 | 0.0009686586 | 0.0001767072 | 0.0000262123 | PASS | | ||
| 173 | +| 3 | float16 | `B2_S64_Hq2_H4_K64_CS32` | dangles | 0.0000007153 | 0.0000001452 | 0.0000493808 | 0.0000005875 | 0.0000001792 | PASS | | ||
| 174 | +| 3 | float16 | `B2_S64_Hq2_H4_K64_CS32` | dscale | 0.0000004768 | 0.0000001503 | 0.0000156663 | 0.0000004670 | 0.0000001486 | PASS | | ||
| 175 | +| 3 | float16 | `B2_S64_Hq2_H4_K64_CS32` | dgamma | 0.0000000149 | 0.0000000751 | 0.0004196171 | 0.0000001859 | 0.0000000019 | PASS | | ||
| 176 | +| 5 | bfloat16 | `B1_S96_Hq1_H2_K64_CS64` | dq | 0.0009753108 | 0.0017151781 | 0.0038860423 | 0.0014231033 | 0.0002790484 | PASS | | ||
| 177 | +| 5 | bfloat16 | `B1_S96_Hq1_H2_K64_CS64` | dk | 0.0009760261 | 0.0016716727 | 0.0038712594 | 0.0014198506 | 0.0002089125 | PASS | | ||
| 178 | +| 5 | bfloat16 | `B1_S96_Hq1_H2_K64_CS64` | dangles | 0.0000004768 | 0.0000001638 | 0.0000111523 | 0.0000004928 | 0.0000001713 | PASS | | ||
| 179 | +| 5 | bfloat16 | `B1_S96_Hq1_H2_K64_CS64` | dscale | 0.0000004172 | 0.0000001620 | 0.0000065263 | 0.0000003077 | 0.0000001403 | PASS | | ||
| 180 | +| 5 | bfloat16 | `B1_S96_Hq1_H2_K64_CS64` | dgamma | 0.0000000149 | 0.0000000768 | 0.0000585953 | 0.0000001356 | 0.0000000019 | PASS | | ||
| 181 | +| 6 | bfloat16 | `B2_S96_Hq1_H2_K64_CS64` | dq | 0.0009765029 | 0.0016663175 | 0.0038535503 | 0.0014037021 | 0.0002714253 | PASS | | ||
| 182 | +| 6 | bfloat16 | `B2_S96_Hq1_H2_K64_CS64` | dk | 0.0009760857 | 0.0016691156 | 0.0038709389 | 0.0013973597 | 0.0002083580 | PASS | | ||
| 183 | +| 6 | bfloat16 | `B2_S96_Hq1_H2_K64_CS64` | dangles | 0.0000007451 | 0.0000001430 | 0.0000181485 | 0.0000007031 | 0.0000002289 | PASS | | ||
| 184 | +| 6 | bfloat16 | `B2_S96_Hq1_H2_K64_CS64` | dscale | 0.0000007153 | 0.0000001719 | 0.0000036239 | 0.0000004094 | 0.0000002161 | PASS | | ||
| 185 | +| 6 | bfloat16 | `B2_S96_Hq1_H2_K64_CS64` | dgamma | 0.0000000149 | 0.0000000767 | 0.0009313169 | 0.0000002564 | 0.0000000019 | PASS | | ||
| 186 | +| 7 | float16 | `B1_S128_Hq2_H4_K64_CS64` | dq | 0.0001220107 | 0.0002117362 | 0.0014773572 | 0.0001783168 | 0.0000342835 | PASS | | ||
| 187 | +| 7 | float16 | `B1_S128_Hq2_H4_K64_CS64` | dk | 0.0001219511 | 0.0002058915 | 0.0038244731 | 0.0001760291 | 0.0000258792 | PASS | | ||
| 188 | +| 7 | float16 | `B1_S128_Hq2_H4_K64_CS64` | dangles | 0.0000009537 | 0.0000001636 | 0.0004074033 | 0.0000020912 | 0.0000002190 | PASS | | ||
| 189 | +| 7 | float16 | `B1_S128_Hq2_H4_K64_CS64` | dscale | 0.0000007153 | 0.0000001603 | 0.0000094319 | 0.0000003863 | 0.0000001688 | PASS | | ||
| 190 | +| 7 | float16 | `B1_S128_Hq2_H4_K64_CS64` | dgamma | 0.0000000149 | 0.0000000776 | 0.0001984816 | 0.0000001512 | 0.0000000019 | PASS | | ||
| 191 | +| 101 | float16 | `B1_H1_S32` | dDT | 0.0000000149 | 0.0000000297 | 0.0000001294 | 0.0000001294 | 0.0000000044 | PASS | | ||
| 192 | +| 101 | float16 | `B1_H1_S32` | dTrap | 0.0000000002 | 0.0000000211 | 0.0000000990 | 0.0000000990 | 0.0000000001 | PASS | | ||
| 193 | +| 102 | float16 | `B1_H2_S64` | dDT | 0.0000000298 | 0.0000000394 | 0.0000001195 | 0.0000001195 | 0.0000000056 | PASS | | ||
| 194 | +| 102 | float16 | `B1_H2_S64` | dTrap | 0.0000000005 | 0.0000000545 | 0.0000001615 | 0.0000001615 | 0.0000000001 | PASS | | ||
| 195 | +| 103 | float16 | `B2_H2_S64` | dDT | 0.0000000298 | 0.0000000368 | 0.0000001622 | 0.0000001622 | 0.0000000059 | PASS | | ||
| 196 | +| 103 | float16 | `B2_H2_S64` | dTrap | 0.0000000009 | 0.0000000468 | 0.0000001557 | 0.0000001557 | 0.0000000001 | PASS | | ||
| 197 | +| 104 | bfloat16 | `B1_H4_S96` | dDT | 0.0000000298 | 0.0000000433 | 0.0000001861 | 0.0000001861 | 0.0000000067 | PASS | | ||
| 198 | +| 104 | bfloat16 | `B1_H4_S96` | dTrap | 0.0000000005 | 0.0000000391 | 0.0000002167 | 0.0000002167 | 0.0000000001 | PASS | | ||
| 199 | +| 105 | float16 | `B2_H4_S128` | dDT | 0.0000000298 | 0.0000000390 | 0.0000001735 | 0.0000001735 | 0.0000000061 | PASS | | ||
| 200 | +| 105 | float16 | `B2_H4_S128` | dTrap | 0.0000000009 | 0.0000000473 | 0.0000001871 | 0.0000001871 | 0.0000000001 | PASS | | ||
| 201 | + | ||
| 202 | +### 4. 已知边界 | ||
| 203 | + | ||
| 204 | +- arch: arch32 production path; arch35 fails loudly | ||
| 205 | +- operator_scope: dense non-varlen backward path (dqktheta, ddt_dtrap) | ||
| 206 | +- unsupported_scope: varlen, state-return, full backward combined | ||
| 207 | +- known_limitation: headdim_qk=128 triggers NPU UB overflow (loop unrolling doubles buffer usage); current PR covers headdim_qk=64 only | ||
| 208 | + | ||
| 209 | +## 支持边界 | ||
| 210 | + | ||
| 211 | +- 支持 arch32 Triton-Ascend 路径。 | ||
| 212 | +- 支持 dense、non-varlen backward 路径(dqktheta、ddt_dtrap)。 | ||
| 213 | +- 当前 PR 不覆盖 varlen、state-return、full backward combined 路径。 | ||
| 214 | +- headdim_qk=128 在 NPU 上因 UB 溢出(loop unrolling 翻倍 buffer 占用)暂不支持,当前 PR 覆盖 headdim_qk=64。 | ||
| 215 | +- 测试侧 torch reference 只存在于 `tests/atk_tests/.../reference_impl.py`,生产 API 不引用测试代码。 | ||
| @@ -0,0 +1,66 @@ | |||
| 1 | +# Copyright (c) 2026, Huawei Technologies Co., Ltd. All rights reserved. | ||
| 2 | +# Copyright (c) 2025, Dao AI Lab, Goombalab | ||
| 3 | + | ||
| 4 | +from __future__ import annotations | ||
| 5 | + | ||
| 6 | +from typing import Optional, Tuple | ||
| 7 | + | ||
| 8 | +import torch | ||
| 9 | + | ||
| 10 | +from mindspeed_ops.api.triton.utils import input_guard | ||
| 11 | +from mindspeed_ops.utils import is_arch35 | ||
| 12 | + | ||
| 13 | +__all__ = ["compute_dqktheta", "compute_ddt_dtrap_dinput_states"] | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | +def compute_dqktheta( | ||
| 18 | + q: torch.Tensor, | ||
| 19 | + k: torch.Tensor, | ||
| 20 | + scale: torch.Tensor, | ||
| 21 | + gamma: torch.Tensor, | ||
| 22 | + q_bias: torch.Tensor, | ||
| 23 | + k_bias: torch.Tensor, | ||
| 24 | + angles: torch.Tensor, | ||
| 25 | + dq_in: torch.Tensor, | ||
| 26 | + dk_in: torch.Tensor, | ||
| 27 | + dqk: torch.Tensor, | ||
| 28 | + d_ok_state: Optional[torch.Tensor] = None, | ||
| 29 | + chunk_size: int = 64, | ||
| 30 | + Cu_Seqlens: Optional[torch.Tensor] = None, | ||
| 31 | +) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: | ||
| 32 | + """Compute gradients through rotary embeddings and biases for Mamba-3 backward pass.""" | ||
| 33 | + if is_arch35(): | ||
| 34 | + raise NotImplementedError("mamba3_siso_bwd is currently implemented for arch32 only") | ||
| 35 | + | ||
| 36 | + from mindspeed_ops.arch32.triton.mamba3.mamba3_siso_bwd_impl import compute_dqktheta as _impl | ||
| 37 | + | ||
| 38 | + return _impl( | ||
| 39 | + q=q, k=k, scale=scale, gamma=gamma, q_bias=q_bias, k_bias=k_bias, | ||
| 40 | + angles=angles, dq_in=dq_in, dk_in=dk_in, dqk=dqk, | ||
| 41 | + d_ok_state=d_ok_state, chunk_size=chunk_size, Cu_Seqlens=Cu_Seqlens, | ||
| 42 | + ) | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + | ||
| 46 | +def compute_ddt_dtrap_dinput_states( | ||
| 47 | + dscale: torch.Tensor, | ||
| 48 | + dgamma: torch.Tensor, | ||
| 49 | + dt: torch.Tensor, | ||
| 50 | + trap: torch.Tensor, | ||
| 51 | + d_issm_state: Optional[torch.Tensor] = None, | ||
| 52 | + input_k_state: Optional[torch.Tensor] = None, | ||
| 53 | + input_v_state: Optional[torch.Tensor] = None, | ||
| 54 | + Cu_Seqlens: Optional[torch.Tensor] = None, | ||
| 55 | +) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor], Optional[torch.Tensor], Optional[torch.Tensor]]: | ||
| 56 | + """Compute dDT, dTrap from dScale/dGamma, and optionally input state gradients.""" | ||
| 57 | + if is_arch35(): | ||
| 58 | + raise NotImplementedError("mamba3_siso_bwd is currently implemented for arch32 only") | ||
| 59 | + | ||
| 60 | + from mindspeed_ops.arch32.triton.mamba3.mamba3_siso_bwd_impl import compute_ddt_dtrap_dinput_states as _impl | ||
| 61 | + | ||
| 62 | + return _impl( | ||
| 63 | + dscale=dscale, dgamma=dgamma, dt=dt, trap=trap, | ||
| 64 | + d_issm_state=d_issm_state, input_k_state=input_k_state, | ||
| 65 | + input_v_state=input_v_state, Cu_Seqlens=Cu_Seqlens, | ||
| 66 | + ) | ||
| @@ -0,0 +1,789 @@ | |||
| 1 | +""" | ||
| 2 | +Mamba-3 Backward Pass Triton Kernels for Ascend NPU. | ||
| 3 | + | ||
| 4 | +Copyright (c) 2026, Dao AI Lab, Goombalab | ||
| 5 | +Copyright (c) 2026, Huawei Technologies Co., Ltd. All rights reserved. | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +from typing import Optional, Tuple | ||
| 9 | + | ||
| 10 | +import torch | ||
| 11 | +import torch_npu | ||
| 12 | + | ||
| 13 | +import triton | ||
| 14 | +import triton.language as tl | ||
| 15 | +from mindspeed_ops.arch32.triton.mamba3.mamba3_utils import cos_approx, sin_approx, sigmoid_approx | ||
| 16 | + | ||
| 17 | + | ||
| 18 | +# ============================================================================= | ||
| 19 | +# d Rotary+Bias Kernel | ||
| 20 | +# ============================================================================= | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + configs=[ | ||
| 25 | + triton.Config({"multibuffer": False}, num_stages=1, num_warps=2) | ||
| 26 | + ], | ||
| 27 | + key=["CHUNK_SIZE", "BLOCK_HEADDIM_QK", "HEADDIM_QK", "GQA_RATIO"] | ||
| 28 | +) | ||
| 29 | + | ||
| 30 | +def mamba3_siso_bwd_kernel_rotary_bias_angles( | ||
| 31 | + # Input tensors | ||
| 32 | + Q, K, Scale, Gamma, Q_bias, K_bias, Angles, dQ_in, dK_in, dQK, | ||
| 33 | + # Output tensors | ||
| 34 | + dQ, dK, dAngles, dScale, dGamma, dQ_bias, dK_bias, | ||
| 35 | + # Strides for inputs ------------------------------------------------------- | ||
| 36 | + # Q: (batch, seqlen, nheads_qk, headdim_qk) | ||
| 37 | + stride_q_batch, stride_q_seqlen, stride_q_head, stride_q_qkdim, | ||
| 38 | + # K: (batch, seqlen, nheads_qk, headdim_qk) | ||
| 39 | + stride_k_batch, stride_k_seqlen, stride_k_head, stride_k_qkdim, | ||
| 40 | + # Scale: (batch, nheads, seqlen) | ||
| 41 | + stride_scale_batch, stride_scale_head, stride_scale_seqlen, | ||
| 42 | + # Gamma: (batch, nheads, seqlen) | ||
| 43 | + stride_gamma_batch, stride_gamma_head, stride_gamma_seqlen, | ||
| 44 | + # Q_bias: (nheads, headdim_qk) | ||
| 45 | + stride_q_bias_head, stride_q_bias_qkdim, | ||
| 46 | + # K_bias: (nheads, headdim_qk) | ||
| 47 | + stride_k_bias_head, stride_k_bias_qkdim, | ||
| 48 | + # Angles: (batch, seqlen, nheads, headdim_qk/2) | ||
| 49 | + stride_angles_batch, stride_angles_seqlen, stride_angles_head, stride_angles_qkdim, | ||
| 50 | + # dQ_in: (batch, seqlen, nheads, headdim_qk) | ||
| 51 | + stride_dq_in_batch, stride_dq_in_seqlen, stride_dq_in_head, stride_dq_in_qkdim, | ||
| 52 | + # dK_in: (batch, seqlen, nheads, headdim_qk) | ||
| 53 | + stride_dk_in_batch, stride_dk_in_seqlen, stride_dk_in_head, stride_dk_in_qkdim, | ||
| 54 | + # dQK: (batch, nheads, seqlen) | ||
| 55 | + stride_dqk_batch, stride_dqk_head, stride_dqk_seqlen, | ||
| 56 | + # Strides for outputs ------------------------------------------------------ | ||
| 57 | + # dQ: (batch, seqlen, nheads_qk, headdim_qk) | ||
| 58 | + stride_dq_batch, stride_dq_seqlen, stride_dq_head, stride_dq_qkdim, | ||
| 59 | + # dK: (batch, seqlen, nheads_qk, headdim_qk) | ||
| 60 | + stride_dk_batch, stride_dk_seqlen, stride_dk_head, stride_dk_qkdim, | ||
| 61 | + # dAngles: (batch, seqlen, nheads, headdim_qk/2) | ||
| 62 | + stride_dangles_batch, stride_dangles_seqlen, stride_dangles_head, stride_dangles_qkdim, | ||
| 63 | + # dScale: (batch, nheads, n_qk_blocks, seqlen) | ||
| 64 | + stride_dscale_batch, stride_dscale_head, stride_dscale_nqkchunks, stride_dscale_seqlen, | ||
| 65 | + # dGamma: (batch, nheads, n_qk_blocks, seqlen) | ||
| 66 | + stride_dgamma_batch, stride_dgamma_head, stride_dgamma_nqkchunks, stride_dgamma_seqlen, | ||
| 67 | + # dQ_bias: (batch, nchunks, nheads, headdim_qk) | ||
| 68 | + stride_dq_bias_batch, stride_dq_bias_nchunks, stride_dq_bias_head, stride_dq_bias_qkdim, | ||
| 69 | + # dK_bias: (batch, nchunks, nheads, headdim_qk) | ||
| 70 | + stride_dk_bias_batch, stride_dk_bias_nchunks, stride_dk_bias_head, stride_dk_bias_qkdim, | ||
| 71 | + # ---- sizes ---- | ||
| 72 | + seqlen, nheads_qk, nheads, headdim_qk, headdim_angles, | ||
| 73 | + CHUNK_SIZE: tl.constexpr, | ||
| 74 | + HEADDIM_QK: tl.constexpr, | ||
| 75 | + BLOCK_HEADDIM_QK: tl.constexpr, | ||
| 76 | + GQA_RATIO: tl.constexpr, | ||
| 77 | +): | ||
| 78 | + """ | ||
| 79 | + Grid: (nchunks, batch) | ||
| 80 | + Each program processes one (batch, chunk) pair. | ||
| 81 | + """ | ||
| 82 | + pid_nchunk = tl.program_id(0) | ||
| 83 | + pid_batch = tl.program_id(1) | ||
| 84 | + | ||
| 85 | + # Base offsets for inputs | ||
| 86 | + q_offset_base = pid_batch * stride_q_batch | ||
| 87 | + k_offset_base = pid_batch * stride_k_batch | ||
| 88 | + scale_offset_base = pid_batch * stride_scale_batch | ||
| 89 | + gamma_offset_base = pid_batch * stride_gamma_batch | ||
| 90 | + angle_offset_base = pid_batch * stride_angles_batch | ||
| 91 | + dq_in_offset_base = pid_batch * stride_dq_in_batch | ||
| 92 | + dk_in_offset_base = pid_batch * stride_dk_in_batch | ||
| 93 | + dqk_offset_base = pid_batch * stride_dqk_batch | ||
| 94 | + | ||
| 95 | + # Base offsets for outputs | ||
| 96 | + dq_offset_base = pid_batch * stride_dq_batch | ||
| 97 | + dk_offset_base = pid_batch * stride_dk_batch | ||
| 98 | + dangle_offset_base = pid_batch * stride_dangles_batch | ||
| 99 | + dscale_offset_base = pid_batch * stride_dscale_batch | ||
| 100 | + dgamma_offset_base = pid_batch * stride_dgamma_batch | ||
| 101 | + dq_bias_offset_base = pid_batch * stride_dq_bias_batch + pid_nchunk * stride_dq_bias_nchunks | ||
| 102 | + dk_bias_offset_base = pid_batch * stride_dk_bias_batch + pid_nchunk * stride_dk_bias_nchunks | ||
| 103 | + | ||
| 104 | + num_nheads_qk = HEADDIM_QK // BLOCK_HEADDIM_QK | ||
| 105 | + for nhead_qk_id in range(num_nheads_qk): | ||
| 106 | + offs_s = tl.arange(0, CHUNK_SIZE) + pid_nchunk * CHUNK_SIZE | ||
| 107 | + offs_dr = tl.arange(0, BLOCK_HEADDIM_QK // 2) + nhead_qk_id * (BLOCK_HEADDIM_QK // 2) | ||
| 108 | + # Even/odd column offsets for rotary pairs (avoids tl.split/tl.join for NPU) | ||
| 109 | + offs_d_even = tl.arange(0, BLOCK_HEADDIM_QK // 2) * 2 + nhead_qk_id * BLOCK_HEADDIM_QK | ||
| 110 | + offs_d_odd = tl.arange(0, BLOCK_HEADDIM_QK // 2) * 2 + 1 + nhead_qk_id * BLOCK_HEADDIM_QK | ||
| 111 | + | ||
| 112 | + for qk_head_idx in range(nheads_qk): | ||
| 113 | + q_offset = q_offset_base + qk_head_idx * stride_q_head | ||
| 114 | + k_offset = k_offset_base + qk_head_idx * stride_k_head | ||
| 115 | + # Even/odd pointers for Q, K (load directly to avoid slice indexing on NPU) | ||
| 116 | + q_ptrs_even = Q + q_offset + offs_s[:, None] * stride_q_seqlen + offs_d_even[None, :] * stride_q_qkdim | ||
| 117 | + q_ptrs_odd = Q + q_offset + offs_s[:, None] * stride_q_seqlen + offs_d_odd[None, :] * stride_q_qkdim | ||
| 118 | + k_ptrs_even = K + k_offset + offs_s[:, None] * stride_k_seqlen + offs_d_even[None, :] * stride_k_qkdim | ||
| 119 | + k_ptrs_odd = K + k_offset + offs_s[:, None] * stride_k_seqlen + offs_d_odd[None, :] * stride_k_qkdim | ||
| 120 | + | ||
| 121 | + mask_2d_even = (offs_s[:, None] < seqlen) & (offs_d_even[None, :] < headdim_qk) | ||
| 122 | + mask_2d_odd = (offs_s[:, None] < seqlen) & (offs_d_odd[None, :] < headdim_qk) | ||
| 123 | + | ||
| 124 | + # Separate even/odd accumulators for GQA reduction | ||
| 125 | + dq_acc_even = tl.zeros((CHUNK_SIZE, BLOCK_HEADDIM_QK // 2), dtype=tl.float32) | ||
| 126 | + dq_acc_odd = tl.zeros((CHUNK_SIZE, BLOCK_HEADDIM_QK // 2), dtype=tl.float32) | ||
| 127 | + dk_acc_even = tl.zeros((CHUNK_SIZE, BLOCK_HEADDIM_QK // 2), dtype=tl.float32) | ||
| 128 | + dk_acc_odd = tl.zeros((CHUNK_SIZE, BLOCK_HEADDIM_QK // 2), dtype=tl.float32) | ||
| 129 | + | ||
| 130 | + for gqa_idx in range(GQA_RATIO): | ||
| 131 | + nhead_idx = qk_head_idx * GQA_RATIO + gqa_idx | ||
| 132 | + | ||
| 133 | + # Bias for this head (even/odd columns loaded separately) | ||
| 134 | + q_bias_even = tl.load( | ||
| 135 | + Q_bias + nhead_idx * stride_q_bias_head + offs_d_even * stride_q_bias_qkdim, | ||
| 136 | + mask=offs_d_even < headdim_qk, other=0.0).to(tl.float32) | ||
| 137 | + q_bias_odd = tl.load( | ||
| 138 | + Q_bias + nhead_idx * stride_q_bias_head + offs_d_odd * stride_q_bias_qkdim, | ||
| 139 | + mask=offs_d_odd < headdim_qk, other=0.0).to(tl.float32) | ||
| 140 | + k_bias_even = tl.load( | ||
| 141 | + K_bias + nhead_idx * stride_k_bias_head + offs_d_even * stride_k_bias_qkdim, | ||
| 142 | + mask=offs_d_even < headdim_qk, other=0.0).to(tl.float32) | ||
| 143 | + k_bias_odd = tl.load( | ||
| 144 | + K_bias + nhead_idx * stride_k_bias_head + offs_d_odd * stride_k_bias_qkdim, | ||
| 145 | + mask=offs_d_odd < headdim_qk, other=0.0).to(tl.float32) | ||
| 146 | + | ||
| 147 | + # Q + bias, K + bias (even/odd columns loaded directly) | ||
| 148 | + q0_even = tl.load(q_ptrs_even, mask=mask_2d_even, other=0.0) | ||
| 149 | + q0_odd = tl.load(q_ptrs_odd, mask=mask_2d_odd, other=0.0) | ||
| 150 | + k0_even = tl.load(k_ptrs_even, mask=mask_2d_even, other=0.0) | ||
| 151 | + k0_odd = tl.load(k_ptrs_odd, mask=mask_2d_odd, other=0.0) | ||
| 152 | + Q_wb_even = q0_even + q_bias_even[None, :] | ||
| 153 | + Q_wb_odd = q0_odd + q_bias_odd[None, :] | ||
| 154 | + K_wb_even = k0_even + k_bias_even[None, :] | ||
| 155 | + K_wb_odd = k0_odd + k_bias_odd[None, :] | ||
| 156 | + | ||
| 157 | + # dQK | ||
| 158 | + dqk_offset = dqk_offset_base + nhead_idx * stride_dqk_head | ||
| 159 | + dqk = tl.load(dQK + dqk_offset + offs_s * stride_dqk_seqlen, mask=offs_s < seqlen, other=0.0) | ||
| 160 | + | ||
| 161 | + # Scale, Gamma | ||
| 162 | + scale_offset = scale_offset_base + nhead_idx * stride_scale_head | ||
| 163 | + gamma_offset = gamma_offset_base + nhead_idx * stride_gamma_head | ||
| 164 | + scale = tl.load(Scale + scale_offset + offs_s * stride_scale_seqlen, mask=offs_s < seqlen, other=0.0).to(tl.float32) | ||
| 165 | + gamma = tl.load(Gamma + gamma_offset + offs_s * stride_gamma_seqlen, mask=offs_s < seqlen, other=0.0).to(tl.float32) | ||
| 166 | + | ||
| 167 | + # Angles | ||
| 168 | + angle_offset = angle_offset_base + nhead_idx * stride_angles_head | ||
| 169 | + theta = tl.load( | ||
| 170 | + Angles + angle_offset + offs_s[:, None] * stride_angles_seqlen + offs_dr[None, :] * stride_angles_qkdim, | ||
| 171 | + mask=(offs_dr[None, :] < headdim_angles) & (offs_s[:, None] < seqlen), | ||
| 172 | + other=0.0).to(tl.float32) | ||
| 173 | + | ||
| 174 | + # dQ_in, dK_in (even/odd columns loaded directly) | ||
| 175 | + dq_in_offset = dq_in_offset_base + nhead_idx * stride_dq_in_head | ||
| 176 | + dk_in_offset = dk_in_offset_base + nhead_idx * stride_dk_in_head | ||
| 177 | + dQ_in_even = tl.load(dQ_in + dq_in_offset + offs_s[:, None] * stride_dq_in_seqlen + offs_d_even[None, :] * stride_dq_in_qkdim, | ||
| 178 | + mask=mask_2d_even, other=0.0) | ||
| 179 | + dQ_in_odd = tl.load(dQ_in + dq_in_offset + offs_s[:, None] * stride_dq_in_seqlen + offs_d_odd[None, :] * stride_dq_in_qkdim, | ||
| 180 | + mask=mask_2d_odd, other=0.0) | ||
| 181 | + dK_in_even = tl.load(dK_in + dk_in_offset + offs_s[:, None] * stride_dk_in_seqlen + offs_d_even[None, :] * stride_dk_in_qkdim, | ||
| 182 | + mask=mask_2d_even, other=0.0) | ||
| 183 | + dK_in_odd = tl.load(dK_in + dk_in_offset + offs_s[:, None] * stride_dk_in_seqlen + offs_d_odd[None, :] * stride_dk_in_qkdim, | ||
| 184 | + mask=mask_2d_odd, other=0.0) | ||
| 185 | + | ||
| 186 | + # dGamma = dQK * (Q_wbias · K_wbias), dot via even/odd sums | ||
| 187 | + QK_dot = tl.sum(Q_wb_even * K_wb_even, axis=1) + tl.sum(Q_wb_odd * K_wb_odd, axis=1) | ||
| 188 | + d_gamma = dqk * QK_dot | ||
| 189 | + dgamma_store_offset = dgamma_offset_base + nhead_idx * stride_dgamma_head | ||
| 190 | + tl.store( | ||
| 191 | + dGamma + dgamma_store_offset + offs_s * stride_dgamma_seqlen + nhead_qk_id * stride_dgamma_nqkchunks, | ||
| 192 | + d_gamma, mask=offs_s < seqlen) | ||
| 193 | + | ||
| 194 | + # cos/sin | ||
| 195 | + cos_angle = cos_approx(theta.to(tl.float32)) | ||
| 196 | + sin_angle = sin_approx(theta.to(tl.float32)) | ||
| 197 | + | ||
| 198 | + # dScale = sum(dK_in * K_rot) using even/odd | ||
| 199 | + K_rot_even = K_wb_even * cos_angle - K_wb_odd * sin_angle | ||
| 200 | + K_rot_odd = K_wb_even * sin_angle + K_wb_odd * cos_angle | ||
| 201 | + | ||
| 202 | + dscale_val = tl.sum(dK_in_even * K_rot_even, axis=1) + tl.sum(dK_in_odd * K_rot_odd, axis=1) | ||
| 203 | + dscale_store_offset = dscale_offset_base + nhead_idx * stride_dscale_head | ||
| 204 | + tl.store( | ||
| 205 | + dScale + dscale_store_offset + offs_s * stride_dscale_seqlen + nhead_qk_id * stride_dscale_nqkchunks, | ||
| 206 | + dscale_val, mask=offs_s < seqlen) | ||
| 207 | + | ||
| 208 | + # Inverse rotary on even/odd halves (scale applied directly) | ||
| 209 | + dK_in_scaled_even = dK_in_even * scale[:, None] | ||
| 210 | + dK_in_scaled_odd = dK_in_odd * scale[:, None] | ||
| 211 | + | ||
| 212 | + dq0 = dQ_in_even * cos_angle + dQ_in_odd * sin_angle | ||
| 213 | + dq1 = -dQ_in_even * sin_angle + dQ_in_odd * cos_angle | ||
| 214 | + dk0 = dK_in_scaled_even * cos_angle + dK_in_scaled_odd * sin_angle | ||
| 215 | + dk1 = -dK_in_scaled_even * sin_angle + dK_in_scaled_odd * cos_angle | ||
| 216 | + | ||
| 217 | + # Add dQK path | ||
| 218 | + dqk_scaled = (dqk * gamma)[:, None] | ||
| 219 | + dQ_pre_even = dq0 + dqk_scaled * K_wb_even | ||
| 220 | + dQ_pre_odd = dq1 + dqk_scaled * K_wb_odd | ||
| 221 | + dK_pre_even = dk0 + dqk_scaled * Q_wb_even | ||
| 222 | + dK_pre_odd = dk1 + dqk_scaled * Q_wb_odd | ||
| 223 | + | ||
| 224 | + # Accumulate for GQA reduction | ||
| 225 | + dq_acc_even += dQ_pre_even | ||
| 226 | + dq_acc_odd += dQ_pre_odd | ||
| 227 | + dk_acc_even += dK_pre_even | ||
| 228 | + dk_acc_odd += dK_pre_odd | ||
| 229 | + | ||
| 230 | + # Store bias gradients (even/odd separately) | ||
| 231 | + dq_bias_out_even = tl.sum(dQ_pre_even, axis=0) | ||
| 232 | + dq_bias_out_odd = tl.sum(dQ_pre_odd, axis=0) | ||
| 233 | + dk_bias_out_even = tl.sum(dK_pre_even, axis=0) | ||
| 234 | + dk_bias_out_odd = tl.sum(dK_pre_odd, axis=0) | ||
| 235 | + dq_bias_store_offset = dq_bias_offset_base + nhead_idx * stride_dq_bias_head | ||
| 236 | + dk_bias_store_offset = dk_bias_offset_base + nhead_idx * stride_dk_bias_head | ||
| 237 | + tl.store(dQ_bias + dq_bias_store_offset + offs_d_even * stride_dq_bias_qkdim, dq_bias_out_even, mask=offs_d_even < headdim_qk) | ||
| 238 | + tl.store(dQ_bias + dq_bias_store_offset + offs_d_odd * stride_dq_bias_qkdim, dq_bias_out_odd, mask=offs_d_odd < headdim_qk) | ||
| 239 | + tl.store(dK_bias + dk_bias_store_offset + offs_d_even * stride_dk_bias_qkdim, dk_bias_out_even, mask=offs_d_even < headdim_qk) | ||
| 240 | + tl.store(dK_bias + dk_bias_store_offset + offs_d_odd * stride_dk_bias_qkdim, dk_bias_out_odd, mask=offs_d_odd < headdim_qk) | ||
| 241 | + | ||
| 242 | + # dAngles | ||
| 243 | + dtheta_q = dQ_in_even * (-Q_wb_even * sin_angle - Q_wb_odd * cos_angle) + dQ_in_odd * (Q_wb_even * cos_angle - Q_wb_odd * sin_angle) | ||
| 244 | + dtheta_k = dK_in_scaled_even * (-K_wb_even * sin_angle - K_wb_odd * cos_angle) + dK_in_scaled_odd * (K_wb_even * cos_angle - K_wb_odd * sin_angle) | ||
| 245 | + dtheta = dtheta_q + dtheta_k | ||
| 246 | + | ||
| 247 | + dangle_store_offset = dangle_offset_base + nhead_idx * stride_dangles_head | ||
| 248 | + tl.store( | ||
| 249 | + dAngles + dangle_store_offset + offs_s[:, None] * stride_dangles_seqlen + offs_dr[None, :] * stride_dangles_qkdim, | ||
| 250 | + dtheta, mask=(offs_dr[None, :] < headdim_angles) & (offs_s[:, None] < seqlen)) | ||
| 251 | + | ||
| 252 | + # End of GQA group: store accumulated dQ, dK using even/odd offsets | ||
| 253 | + dq_off = dq_offset_base + qk_head_idx * stride_dq_head | ||
| 254 | + dk_off = dk_offset_base + qk_head_idx * stride_dk_head | ||
| 255 | + tl.store(dQ + dq_off + offs_s[:, None] * stride_dq_seqlen + offs_d_even[None, :] * stride_dq_qkdim, | ||
| 256 | + dq_acc_even, mask=(offs_s[:, None] < seqlen) & (offs_d_even[None, :] < headdim_qk)) | ||
| 257 | + tl.store(dQ + dq_off + offs_s[:, None] * stride_dq_seqlen + offs_d_odd[None, :] * stride_dq_qkdim, | ||
| 258 | + dq_acc_odd, mask=(offs_s[:, None] < seqlen) & (offs_d_odd[None, :] < headdim_qk)) | ||
| 259 | + tl.store(dK + dk_off + offs_s[:, None] * stride_dk_seqlen + offs_d_even[None, :] * stride_dk_qkdim, | ||
| 260 | + dk_acc_even, mask=(offs_s[:, None] < seqlen) & (offs_d_even[None, :] < headdim_qk)) | ||
| 261 | + tl.store(dK + dk_off + offs_s[:, None] * stride_dk_seqlen + offs_d_odd[None, :] * stride_dk_qkdim, | ||
| 262 | + dk_acc_odd, mask=(offs_s[:, None] < seqlen) & (offs_d_odd[None, :] < headdim_qk)) | ||
| 263 | + | ||
| 264 | + | ||
| 265 | +# NOTE: Do not autotune this kernel. It overwrites dK, dK_bias, dAngles via atomic adds | ||
| 266 | +# and autotuning will lead to multiple overwrites. | ||
| 267 | + | ||
| 268 | +def mamba3_siso_bwd_kernel_dk_state_post( | ||
| 269 | + # Inputs tensors | ||
| 270 | + dK_State, Angles, K, K_bias, Cu_Seqlens, | ||
| 271 | + # Outputs tensors | ||
| 272 | + dK, dK_bias, dAngles, | ||
| 273 | + # Strides for dK_State: (num_sequences, nheads, headdim_qk) | ||
| 274 | + stride_dk_state_batch, stride_dk_state_head, stride_dk_state_qkdim, | ||
| 275 | + # Strides for Angles: (batch, seqlen, nheads, headdim_angles) | ||
| 276 | + stride_angles_batch, stride_angles_seqlen, stride_angles_head, stride_angles_qkdim, | ||
| 277 | + # Strides for K: (batch, seqlen, nheads_qk, headdim_qk) | ||
| 278 | + stride_k_batch, stride_k_seqlen, stride_k_head, stride_k_qkdim, | ||
| 279 | + # Strides for K_bias: (nheads, headdim_qk) | ||
| 280 | + stride_k_bias_head, stride_k_bias_qkdim, | ||
| 281 | + # Strides for Cu_Seqlens: (num_sequences + 1,) | ||
| 282 | + stride_cu_seqlen, | ||
| 283 | + # Strides for dK: (batch, seqlen, nheads_qk, headdim_qk) | ||
| 284 | + stride_dk_batch, stride_dk_seqlen, stride_dk_head, stride_dk_qkdim, | ||
| 285 | + # Strides for dK_bias: (nheads, headdim_qk) | ||
| 286 | + stride_dk_bias_head, stride_dk_bias_qkdim, | ||
| 287 | + # Strides for dAngles: (batch, seqlen, nheads, headdim_angles) | ||
| 288 | + stride_dangles_batch, stride_dangles_seqlen, stride_dangles_head, stride_dangles_qkdim, | ||
| 289 | + # Dimensions | ||
| 290 | + seqlen, headdim_qk, headdim_angles, | ||
| 291 | + HEADDIM_QK: tl.constexpr, | ||
| 292 | + GQA_RATIO: tl.constexpr, | ||
| 293 | + IS_VARLEN: tl.constexpr, | ||
| 294 | +): | ||
| 295 | + """ | ||
| 296 | + Post-kernel for d_ok_state contributions. | ||
| 297 | + Grid: (nheads, batch) | ||
| 298 | + """ | ||
| 299 | + pid_head = tl.program_id(0) | ||
| 300 | + pid_batch = tl.program_id(1) | ||
| 301 | + | ||
| 302 | + if IS_VARLEN: | ||
| 303 | + pid_seq = pid_batch | ||
| 304 | + pid_batch = 0 | ||
| 305 | + cu_seqlen = tl.load(Cu_Seqlens + (pid_seq + 1) * stride_cu_seqlen).to(tl.int32) | ||
| 306 | + last_pos = cu_seqlen - 1 | ||
| 307 | + else: | ||
| 308 | + pid_seq = 0 | ||
| 309 | + last_pos = seqlen - 1 | ||
| 310 | + | ||
| 311 | + qk_head_idx = pid_head // GQA_RATIO | ||
| 312 | + offs_dr = tl.arange(0, HEADDIM_QK // 2) | ||
| 313 | + # Even/odd offsets to split rotary pairs without tl.split/tl.join on NPU | ||
| 314 | + offs_d_even = offs_dr * 2 | ||
| 315 | + offs_d_odd = offs_dr * 2 + 1 | ||
| 316 | + | ||
| 317 | + # Load dK_State even/odd columns | ||
| 318 | + dk_state_base = dK_State + (pid_batch + pid_seq) * stride_dk_state_batch + pid_head * stride_dk_state_head | ||
| 319 | + dk_state_r0 = tl.load(dk_state_base + offs_d_even * stride_dk_state_qkdim, mask=offs_d_even < headdim_qk, other=0.0).to(tl.float32) | ||
| 320 | + dk_state_r1 = tl.load(dk_state_base + offs_d_odd * stride_dk_state_qkdim, mask=offs_d_odd < headdim_qk, other=0.0).to(tl.float32) | ||
| 321 | + | ||
| 322 | + # Load angles at last position | ||
| 323 | + angles_base = Angles + pid_batch * stride_angles_batch + last_pos * stride_angles_seqlen + pid_head * stride_angles_head | ||
| 324 | + angles_val = tl.load(angles_base + offs_dr * stride_angles_qkdim, mask=offs_dr < headdim_angles, other=0.0).to(tl.float32) | ||
| 325 | + | ||
| 326 | + cos_ang = cos_approx(angles_val) | ||
| 327 | + sin_ang = sin_approx(angles_val) | ||
| 328 | + | ||
| 329 | + # Inverse rotary: dk_rotated (even/odd halves) | ||
| 330 | + dk0 = dk_state_r0 * cos_ang + dk_state_r1 * sin_ang | ||
| 331 | + dk1 = -dk_state_r0 * sin_ang + dk_state_r1 * cos_ang | ||
| 332 | + | ||
| 333 | + # 1. Accumulate to dK (GQA reduction via atomic) - even/odd columns | ||
| 334 | + dk_base = dK + pid_batch * stride_dk_batch + last_pos * stride_dk_seqlen + qk_head_idx * stride_dk_head | ||
| 335 | + tl.atomic_add(dk_base + offs_d_even * stride_dk_qkdim, dk0, mask=offs_d_even < headdim_qk) | ||
| 336 | + tl.atomic_add(dk_base + offs_d_odd * stride_dk_qkdim, dk1, mask=offs_d_odd < headdim_qk) | ||
| 337 | + | ||
| 338 | + # 2. Accumulate to dK_bias (batch reduction via atomic) - even/odd columns | ||
| 339 | + dk_bias_base = dK_bias + pid_head * stride_dk_bias_head | ||
| 340 | + tl.atomic_add(dk_bias_base + offs_d_even * stride_dk_bias_qkdim, dk0, mask=offs_d_even < headdim_qk) | ||
| 341 | + tl.atomic_add(dk_bias_base + offs_d_odd * stride_dk_bias_qkdim, dk1, mask=offs_d_odd < headdim_qk) | ||
| 342 | + | ||
| 343 | + # 3. Compute dAngles | ||
| 344 | + k_base = K + pid_batch * stride_k_batch + last_pos * stride_k_seqlen + qk_head_idx * stride_k_head | ||
| 345 | + k_r0 = tl.load(k_base + offs_d_even * stride_k_qkdim, mask=offs_d_even < headdim_qk, other=0.0).to(tl.float32) | ||
| 346 | + k_r1 = tl.load(k_base + offs_d_odd * stride_k_qkdim, mask=offs_d_odd < headdim_qk, other=0.0).to(tl.float32) | ||
| 347 | + | ||
| 348 | + k_bias_base = K_bias + pid_head * stride_k_bias_head | ||
| 349 | + kb_r0 = tl.load(k_bias_base + offs_d_even * stride_k_bias_qkdim, mask=offs_d_even < headdim_qk, other=0.0).to(tl.float32) | ||
| 350 | + kb_r1 = tl.load(k_bias_base + offs_d_odd * stride_k_bias_qkdim, mask=offs_d_odd < headdim_qk, other=0.0).to(tl.float32) | ||
| 351 | + | ||
| 352 | + K_wbias_r0 = k_r0 + kb_r0 | ||
| 353 | + K_wbias_r1 = k_r1 + kb_r1 | ||
| 354 | + | ||
| 355 | + dtheta_k = (dk_state_r0 * (-K_wbias_r0 * sin_ang - K_wbias_r1 * cos_ang) + | ||
| 356 | + dk_state_r1 * (K_wbias_r0 * cos_ang - K_wbias_r1 * sin_ang)) | ||
| 357 | + | ||
| 358 | + da_base = dAngles + pid_batch * stride_dangles_batch + last_pos * stride_dangles_seqlen + pid_head * stride_dangles_head | ||
| 359 | + tl.atomic_add(da_base + offs_dr * stride_dangles_qkdim, dtheta_k, mask=offs_dr < headdim_angles) | ||
| 360 | + | ||
| 361 | + | ||
| 362 | +# ============================================================================= | ||
| 363 | +# dDT, dTrap, and dInput States Kernel | ||
| 364 | +# ============================================================================= | ||
| 365 | + | ||
| 366 | + configs=[ | ||
| 367 | + triton.Config({"CHUNK_SIZE": 64, "multibuffer": False}, num_stages=1, num_warps=2) | ||
| 368 | + ], | ||
| 369 | + key=["HEADDIM_V", "HEADDIM_QK", "HAS_INPUT_STATE", "IS_VARLEN"] | ||
| 370 | +) | ||
| 371 | + | ||
| 372 | +def mamba3_siso_bwd_kernel_ddt_dtrap_dinput_states( | ||
| 373 | + # Input tensors | ||
| 374 | + dScale, dGamma, DT, Trap, | ||
| 375 | + d_ISSM_State, Input_K_State, Input_V_State, Cu_Seqlens, | ||
| 376 | + # Output tensors | ||
| 377 | + dDT, dTrap, | ||
| 378 | + dInput_SSM_State, dInput_K_State, dInput_V_State, | ||
| 379 | + # Strides for dScale: (batch, nheads, seqlen) | ||
| 380 | + stride_dscale_batch, stride_dscale_head, stride_dscale_seqlen, | ||
| 381 | + # Strides for dGamma: (batch, nheads, seqlen) | ||
| 382 | + stride_dgamma_batch, stride_dgamma_head, stride_dgamma_seqlen, | ||
| 383 | + # Strides for DT: (batch, nheads, seqlen) | ||
| 384 | + stride_dt_batch, stride_dt_head, stride_dt_seqlen, | ||
| 385 | + # Strides for Trap: (batch, nheads, seqlen) | ||
| 386 | + stride_trap_batch, stride_trap_head, stride_trap_seqlen, | ||
| 387 | + # Strides for d_ISSM_State: (num_sequences, nheads, headdim_v, headdim_qk) | ||
| 388 | + stride_d_issm_state_batch, stride_d_issm_state_head, stride_d_issm_state_vdim, stride_d_issm_state_qkdim, | ||
| 389 | + # Strides for Input_K_State: (num_sequences, nheads, headdim_qk) | ||
| 390 | + stride_input_k_state_batch, stride_input_k_state_head, stride_input_k_state_qkdim, | ||
| 391 | + # Strides for Input_V_State: (num_sequences, nheads, headdim_v) | ||
| 392 | + stride_input_v_state_batch, stride_input_v_state_head, stride_input_v_state_vdim, | ||
| 393 | + # Stride for Cu_Seqlens | ||
| 394 | + stride_cu_seqlen, | ||
| 395 | + # Strides for dDT: (batch, nheads, seqlen) | ||
| 396 | + stride_ddt_batch, stride_ddt_head, stride_ddt_seqlen, | ||
| 397 | + # Strides for dTrap: (batch, nheads, seqlen) | ||
| 398 | + stride_dtrap_batch, stride_dtrap_head, stride_dtrap_seqlen, | ||
| 399 | + # Strides for dInput_SSM_State: (num_sequences, nheads, headdim_v, headdim_qk) | ||
| 400 | + stride_dinput_ssm_state_batch, stride_dinput_ssm_state_head, stride_dinput_ssm_state_vdim, stride_dinput_ssm_state_qkdim, | ||
| 401 | + # Strides for dInput_K_State: (num_sequences, nheads, headdim_qk) | ||
| 402 | + stride_dinput_k_state_batch, stride_dinput_k_state_head, stride_dinput_k_state_qkdim, | ||
| 403 | + # Strides for dInput_V_State: (num_sequences, nheads, headdim_v) | ||
| 404 | + stride_dinput_v_state_batch, stride_dinput_v_state_head, stride_dinput_v_state_vdim, | ||
| 405 | + # Dimensions | ||
| 406 | + seqlen, headdim_v, headdim_qk, | ||
| 407 | + # Compile-time constants | ||
| 408 | + CHUNK_SIZE: tl.constexpr, | ||
| 409 | + HEADDIM_V: tl.constexpr, | ||
| 410 | + HEADDIM_QK: tl.constexpr, | ||
| 411 | + HAS_INPUT_STATE: tl.constexpr, | ||
| 412 | + IS_VARLEN: tl.constexpr, | ||
| 413 | +): | ||
| 414 | + """ | ||
| 415 | + Backward kernel for computing dDT, dTrap, and input state gradients. | ||
| 416 | + """ | ||
| 417 | + pid_head = tl.program_id(0) | ||
| 418 | + pid_batch = tl.program_id(1) | ||
| 419 | + | ||
| 420 | + if IS_VARLEN: | ||
| 421 | + pid_seq = pid_batch | ||
| 422 | + pid_batch = 0 | ||
| 423 | + cu_seqlen = tl.load(Cu_Seqlens + pid_seq * stride_cu_seqlen).to(tl.int32) | ||
| 424 | + cu_seqlen_next = tl.load(Cu_Seqlens + (pid_seq + 1) * stride_cu_seqlen).to(tl.int32) | ||
| 425 | + seqlen = cu_seqlen_next - cu_seqlen | ||
| 426 | + else: | ||
| 427 | + pid_seq = 0 | ||
| 428 | + cu_seqlen = 0 | ||
| 429 | + | ||
| 430 | + # Pointer Offsets | ||
| 431 | + dscale_offset = pid_batch * stride_dscale_batch + pid_head * stride_dscale_head + IS_VARLEN * cu_seqlen * stride_dscale_seqlen | ||
| 432 | + dgamma_offset = pid_batch * stride_dgamma_batch + pid_head * stride_dgamma_head + IS_VARLEN * cu_seqlen * stride_dgamma_seqlen | ||
| 433 | + dt_offset = pid_batch * stride_dt_batch + pid_head * stride_dt_head + IS_VARLEN * cu_seqlen * stride_dt_seqlen | ||
| 434 | + trap_offset = pid_batch * stride_trap_batch + pid_head * stride_trap_head + IS_VARLEN * cu_seqlen * stride_trap_seqlen | ||
| 435 | + ddt_offset = pid_batch * stride_ddt_batch + pid_head * stride_ddt_head + IS_VARLEN * cu_seqlen * stride_ddt_seqlen | ||
| 436 | + dtrap_offset = pid_batch * stride_dtrap_batch + pid_head * stride_dtrap_head + IS_VARLEN * cu_seqlen * stride_dtrap_seqlen | ||
| 437 | + | ||
| 438 | + # Part 1: dDT and dTrap | ||
| 439 | + num_chunks = tl.cdiv(seqlen, CHUNK_SIZE) | ||
| 440 | + | ||
| 441 | + for chunk_idx in range(num_chunks): | ||
| 442 | + offs_s = chunk_idx * CHUNK_SIZE + tl.arange(0, CHUNK_SIZE) | ||
| 443 | + mask = offs_s < seqlen | ||
| 444 | + | ||
| 445 | + dscale_t = tl.load(dScale + dscale_offset + offs_s * stride_dscale_seqlen, mask=mask, other=0.0) | ||
| 446 | + dgamma_t = tl.load(dGamma + dgamma_offset + offs_s * stride_dgamma_seqlen, mask=mask, other=0.0) | ||
| 447 | + trap_presig_t = tl.load(Trap + trap_offset + offs_s * stride_trap_seqlen, mask=mask, other=0.0).to(tl.float32) | ||
| 448 | + trap_t = sigmoid_approx(trap_presig_t) | ||
| 449 | + dt_t = tl.load(DT + dt_offset + offs_s * stride_dt_seqlen, mask=mask, other=0.0) | ||
| 450 | + | ||
| 451 | + offs_s_prev = offs_s - 1 | ||
| 452 | + mask_prev = (offs_s_prev >= 0) & (offs_s_prev < seqlen) | ||
| 453 | + dscale_prev = tl.load( | ||
| 454 | + dScale + dscale_offset + offs_s_prev * stride_dscale_seqlen, | ||
| 455 | + mask=mask_prev, | ||
| 456 | + other=0.0 | ||
| 457 | + ) | ||
| 458 | + | ||
| 459 | + ddt_t = (dgamma_t + dscale_t) * trap_t + dscale_prev * (1.0 - trap_t) | ||
| 460 | + dtrap_t = (dgamma_t + dscale_t) * dt_t - dscale_prev * dt_t | ||
| 461 | + dtrap_presig_t = dtrap_t * trap_t * (1.0 - trap_t) | ||
| 462 | + | ||
| 463 | + tl.store(dDT + ddt_offset + offs_s * stride_ddt_seqlen, ddt_t, mask=mask) | ||
| 464 | + tl.store(dTrap + dtrap_offset + offs_s * stride_dtrap_seqlen, dtrap_presig_t, mask=mask) | ||
| 465 | + | ||
| 466 | + # Part 2: Input State Gradients | ||
| 467 | + if HAS_INPUT_STATE: | ||
| 468 | + d_issm_offset = (pid_batch + pid_seq) * stride_d_issm_state_batch + pid_head * stride_d_issm_state_head | ||
| 469 | + input_k_offset = (pid_batch + pid_seq) * stride_input_k_state_batch + pid_head * stride_input_k_state_head | ||
| 470 | + input_v_offset = (pid_batch + pid_seq) * stride_input_v_state_batch + pid_head * stride_input_v_state_head | ||
| 471 | + dinput_ssm_offset = (pid_batch + pid_seq) * stride_dinput_ssm_state_batch + pid_head * stride_dinput_ssm_state_head | ||
| 472 | + dinput_k_offset = (pid_batch + pid_seq) * stride_dinput_k_state_batch + pid_head * stride_dinput_k_state_head | ||
| 473 | + dinput_v_offset = (pid_batch + pid_seq) * stride_dinput_v_state_batch + pid_head * stride_dinput_v_state_head | ||
| 474 | + | ||
| 475 | + dt_0 = tl.load(DT + dt_offset).to(tl.float32) | ||
| 476 | + trap_presig_0 = tl.load(Trap + trap_offset).to(tl.float32) | ||
| 477 | + trap_0 = sigmoid_approx(trap_presig_0) | ||
| 478 | + scalar = dt_0 * (1.0 - trap_0) | ||
| 479 | + | ||
| 480 | + offs_v = tl.arange(0, HEADDIM_V) | ||
| 481 | + offs_qk = tl.arange(0, HEADDIM_QK) | ||
| 482 | + | ||
| 483 | + input_k = tl.load( | ||
| 484 | + Input_K_State + input_k_offset + offs_qk * stride_input_k_state_qkdim, | ||
| 485 | + mask=offs_qk < headdim_qk, other=0.0).to(tl.float32) | ||
| 486 | + input_v = tl.load( | ||
| 487 | + Input_V_State + input_v_offset + offs_v * stride_input_v_state_vdim, | ||
| 488 | + mask=offs_v < headdim_v, other=0.0).to(tl.float32) | ||
| 489 | + | ||
| 490 | + d_issm = tl.load( | ||
| 491 | + d_ISSM_State + d_issm_offset + | ||
| 492 | + offs_v[:, None] * stride_d_issm_state_vdim + | ||
| 493 | + offs_qk[None, :] * stride_d_issm_state_qkdim, | ||
| 494 | + mask=(offs_v[:, None] < headdim_v) & (offs_qk[None, :] < headdim_qk), | ||
| 495 | + other=0.0).to(tl.float32) | ||
| 496 | + | ||
| 497 | + tl.store( | ||
| 498 | + dInput_SSM_State + dinput_ssm_offset + | ||
| 499 | + offs_v[:, None] * stride_dinput_ssm_state_vdim + | ||
| 500 | + offs_qk[None, :] * stride_dinput_ssm_state_qkdim, | ||
| 501 | + d_issm, | ||
| 502 | + mask=(offs_v[:, None] < headdim_v) & (offs_qk[None, :] < headdim_qk), | ||
| 503 | + ) | ||
| 504 | + | ||
| 505 | + outer_product = input_v[:, None] * input_k[None, :] | ||
| 506 | + d_scalar = tl.sum(d_issm * outer_product) | ||
| 507 | + | ||
| 508 | + dinput_v = tl.sum(d_issm * input_k[None, :], axis=1) * scalar | ||
| 509 | + dinput_k = tl.sum(d_issm * input_v[:, None], axis=0) * scalar | ||
| 510 | + | ||
| 511 | + tl.store(dInput_V_State + dinput_v_offset + offs_v * stride_dinput_v_state_vdim, dinput_v, mask=offs_v < headdim_v) | ||
| 512 | + tl.store(dInput_K_State + dinput_k_offset + offs_qk * stride_dinput_k_state_qkdim, dinput_k, mask=offs_qk < headdim_qk) | ||
| 513 | + | ||
| 514 | + ddt_0_contrib = d_scalar * (1.0 - trap_0) | ||
| 515 | + dtrap_0_contrib = d_scalar * (-dt_0) | ||
| 516 | + dtrap_0_presig_contrib = dtrap_0_contrib * trap_0 * (1.0 - trap_0) | ||
| 517 | + | ||
| 518 | + tl.atomic_add(dDT + ddt_offset, ddt_0_contrib) | ||
| 519 | + tl.atomic_add(dTrap + dtrap_offset, dtrap_0_presig_contrib) | ||
| 520 | + | ||
| 521 | + | ||
| 522 | +# ============================================================================= | ||
| 523 | +# Host Functions | ||
| 524 | +# ============================================================================= | ||
| 525 | +def compute_dqktheta( | ||
| 526 | + q: torch.Tensor, | ||
| 527 | + k: torch.Tensor, | ||
| 528 | + scale: torch.Tensor, | ||
| 529 | + gamma: torch.Tensor, | ||
| 530 | + q_bias: torch.Tensor, | ||
| 531 | + k_bias: torch.Tensor, | ||
| 532 | + angles: torch.Tensor, | ||
| 533 | + dq_in: torch.Tensor, | ||
| 534 | + dk_in: torch.Tensor, | ||
| 535 | + dqk: torch.Tensor, | ||
| 536 | + d_ok_state: Optional[torch.Tensor] = None, | ||
| 537 | + chunk_size: int = 64, | ||
| 538 | + Cu_Seqlens: Optional[torch.Tensor] = None, | ||
| 539 | +) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: | ||
| 540 | + """Compute gradients through rotary embeddings and biases for Mamba-3 backward pass.""" | ||
| 541 | + batch, seqlen, nheads_qk, headdim_qk = q.shape | ||
| 542 | + assert q.shape == k.shape | ||
| 543 | + | ||
| 544 | + nheads = scale.shape[1] | ||
| 545 | + nchunks = triton.cdiv(seqlen, chunk_size) | ||
| 546 | + GQA_RATIO = nheads // nheads_qk | ||
| 547 | + | ||
| 548 | + assert scale.shape == (batch, nheads, seqlen) | ||
| 549 | + assert gamma.shape == (batch, nheads, seqlen) | ||
| 550 | + assert q_bias.shape == (nheads, headdim_qk) | ||
| 551 | + assert k_bias.shape == (nheads, headdim_qk) | ||
| 552 | + headdim_angles = angles.shape[-1] | ||
| 553 | + assert angles.shape == (batch, seqlen, nheads, headdim_angles) | ||
| 554 | + assert dq_in.shape == (batch, seqlen, nheads, headdim_qk) | ||
| 555 | + assert dk_in.shape == (batch, seqlen, nheads, headdim_qk) | ||
| 556 | + assert dqk.shape == (batch, nheads, seqlen) | ||
| 557 | + if d_ok_state is not None: | ||
| 558 | + num_sequences = Cu_Seqlens.shape[0] - 1 if Cu_Seqlens is not None else batch | ||
| 559 | + assert d_ok_state.shape == (num_sequences, nheads, headdim_qk) | ||
| 560 | + assert nheads % nheads_qk == 0, "nheads must be multiple of nheads_qk for GQA support" | ||
| 561 | + | ||
| 562 | + # Ensure contiguity | ||
| 563 | + q = q.contiguous() if not q.is_contiguous() else q | ||
| 564 | + k = k.contiguous() if not k.is_contiguous() else k | ||
| 565 | + scale = scale.contiguous() if not scale.is_contiguous() else scale | ||
| 566 | + gamma = gamma.contiguous() if not gamma.is_contiguous() else gamma | ||
| 567 | + dqk = dqk.contiguous() if not dqk.is_contiguous() else dqk | ||
| 568 | + angles = angles.contiguous() if not angles.is_contiguous() else angles | ||
| 569 | + dq_in = dq_in.contiguous() if not dq_in.is_contiguous() else dq_in | ||
| 570 | + dk_in = dk_in.contiguous() if not dk_in.is_contiguous() else dk_in | ||
| 571 | + q_bias = q_bias.contiguous() if q_bias.stride(-1) != 1 else q_bias | ||
| 572 | + k_bias = k_bias.contiguous() if k_bias.stride(-1) != 1 else k_bias | ||
| 573 | + if d_ok_state is not None and not d_ok_state.is_contiguous(): | ||
| 574 | + d_ok_state = d_ok_state.contiguous() | ||
| 575 | + | ||
| 576 | + HEADDIM_QK = triton.next_power_of_2(headdim_qk) | ||
| 577 | + BLOCK_HEADDIM_QK = min(HEADDIM_QK, 64) | ||
| 578 | + | ||
| 579 | + dq = torch.empty((batch, seqlen, nheads_qk, headdim_qk), dtype=dq_in.dtype, device=q.device) | ||
| 580 | + dk = torch.empty((batch, seqlen, nheads_qk, headdim_qk), dtype=dk_in.dtype, device=k.device) | ||
| 581 | + dangles = torch.empty((batch, seqlen, nheads, headdim_angles), dtype=angles.dtype, device=angles.device) | ||
| 582 | + dscale = torch.empty((batch, nheads, HEADDIM_QK // BLOCK_HEADDIM_QK, seqlen), dtype=scale.dtype, device=scale.device) | ||
| 583 | + dgamma = torch.empty((batch, nheads, HEADDIM_QK // BLOCK_HEADDIM_QK, seqlen), dtype=gamma.dtype, device=gamma.device) | ||
| 584 | + dq_bias_partial = torch.empty((batch, nchunks, nheads, headdim_qk), dtype=torch.float32, device=q.device) | ||
| 585 | + dk_bias_partial = torch.empty((batch, nchunks, nheads, headdim_qk), dtype=torch.float32, device=k.device) | ||
| 586 | + | ||
| 587 | + grid = (nchunks, batch) | ||
| 588 | + | ||
| 589 | + mamba3_siso_bwd_kernel_rotary_bias_angles[grid]( | ||
| 590 | + q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk, | ||
| 591 | + dq, dk, dangles, dscale, dgamma, dq_bias_partial, dk_bias_partial, | ||
| 592 | + q.stride(0), q.stride(1), q.stride(2), q.stride(3), | ||
| 593 | + k.stride(0), k.stride(1), k.stride(2), k.stride(3), | ||
| 594 | + scale.stride(0), scale.stride(1), scale.stride(2), | ||
| 595 | + gamma.stride(0), gamma.stride(1), gamma.stride(2), | ||
| 596 | + q_bias.stride(0), q_bias.stride(1), | ||
| 597 | + k_bias.stride(0), k_bias.stride(1), | ||
| 598 | + angles.stride(0), angles.stride(1), angles.stride(2), angles.stride(3), | ||
| 599 | + dq_in.stride(0), dq_in.stride(1), dq_in.stride(2), dq_in.stride(3), | ||
| 600 | + dk_in.stride(0), dk_in.stride(1), dk_in.stride(2), dk_in.stride(3), | ||
| 601 | + dqk.stride(0), dqk.stride(1), dqk.stride(2), | ||
| 602 | + dq.stride(0), dq.stride(1), dq.stride(2), dq.stride(3), | ||
| 603 | + dk.stride(0), dk.stride(1), dk.stride(2), dk.stride(3), | ||
| 604 | + dangles.stride(0), dangles.stride(1), dangles.stride(2), dangles.stride(3), | ||
| 605 | + dscale.stride(0), dscale.stride(1), dscale.stride(2), dscale.stride(3), | ||
| 606 | + dgamma.stride(0), dgamma.stride(1), dgamma.stride(2), dgamma.stride(3), | ||
| 607 | + dq_bias_partial.stride(0), dq_bias_partial.stride(1), | ||
| 608 | + dq_bias_partial.stride(2), dq_bias_partial.stride(3), | ||
| 609 | + dk_bias_partial.stride(0), dk_bias_partial.stride(1), | ||
| 610 | + dk_bias_partial.stride(2), dk_bias_partial.stride(3), | ||
| 611 | + seqlen, nheads_qk, nheads, headdim_qk, headdim_angles, | ||
| 612 | + CHUNK_SIZE=chunk_size, | ||
| 613 | + HEADDIM_QK=HEADDIM_QK, | ||
| 614 | + BLOCK_HEADDIM_QK=BLOCK_HEADDIM_QK, | ||
| 615 | + GQA_RATIO=GQA_RATIO, | ||
| 616 | + ) | ||
| 617 | + | ||
| 618 | + dscale = torch.sum(dscale, dim=2) | ||
| 619 | + dgamma = torch.sum(dgamma, dim=2) | ||
| 620 | + dq_bias = dq_bias_partial.sum(dim=(0, 1)) | ||
| 621 | + dk_bias = dk_bias_partial.sum(dim=(0, 1)) | ||
| 622 | + | ||
| 623 | + if d_ok_state is not None: | ||
| 624 | + apply_dk_state_post( | ||
| 625 | + d_ok_state, angles, k, k_bias, dk, dk_bias, dangles, Cu_Seqlens | ||
| 626 | + ) | ||
| 627 | + return dq, dk, dq_bias, dk_bias, dangles, dscale, dgamma | ||
| 628 | + | ||
| 629 | + | ||
| 630 | +def apply_dk_state_post( | ||
| 631 | + d_ok_state: torch.Tensor, | ||
| 632 | + angles: torch.Tensor, | ||
| 633 | + k: torch.Tensor, | ||
| 634 | + k_bias: torch.Tensor, | ||
| 635 | + dk: torch.Tensor, | ||
| 636 | + dk_bias: torch.Tensor, | ||
| 637 | + dangles: torch.Tensor, | ||
| 638 | + Cu_Seqlens: Optional[torch.Tensor] = None, | ||
| 639 | +): | ||
| 640 | + batch, seqlen, nheads, headdim_angles = angles.shape | ||
| 641 | + _, _, headdim_qk = d_ok_state.shape | ||
| 642 | + nheads_qk = k.shape[2] | ||
| 643 | + GQA_RATIO = nheads // nheads_qk | ||
| 644 | + | ||
| 645 | + is_varlen = Cu_Seqlens is not None | ||
| 646 | + if is_varlen: | ||
| 647 | + num_sequences = Cu_Seqlens.shape[0] - 1 | ||
| 648 | + assert batch == 1 | ||
| 649 | + else: | ||
| 650 | + num_sequences = batch | ||
| 651 | + | ||
| 652 | + d_ok_state = d_ok_state.contiguous() if not d_ok_state.is_contiguous() else d_ok_state | ||
| 653 | + angles = angles.contiguous() if not angles.is_contiguous() else angles | ||
| 654 | + k = k.contiguous() if not k.is_contiguous() else k | ||
| 655 | + k_bias = k_bias.contiguous() if not k_bias.is_contiguous() else k_bias | ||
| 656 | + | ||
| 657 | + HEADDIM_QK = triton.next_power_of_2(headdim_qk) | ||
| 658 | + | ||
| 659 | + grid = (nheads, num_sequences) | ||
| 660 | + | ||
| 661 | + mamba3_siso_bwd_kernel_dk_state_post[grid]( | ||
| 662 | + d_ok_state, angles, k, k_bias, Cu_Seqlens, | ||
| 663 | + dk, dk_bias, dangles, | ||
| 664 | + d_ok_state.stride(0), d_ok_state.stride(1), d_ok_state.stride(2), | ||
| 665 | + angles.stride(0), angles.stride(1), angles.stride(2), angles.stride(3), | ||
| 666 | + k.stride(0), k.stride(1), k.stride(2), k.stride(3), | ||
| 667 | + k_bias.stride(0), k_bias.stride(1), | ||
| 668 | + Cu_Seqlens.stride(0) if is_varlen else 0, | ||
| 669 | + dk.stride(0), dk.stride(1), dk.stride(2), dk.stride(3), | ||
| 670 | + dk_bias.stride(0), dk_bias.stride(1), | ||
| 671 | + dangles.stride(0), dangles.stride(1), dangles.stride(2), dangles.stride(3), | ||
| 672 | + seqlen, headdim_qk, headdim_angles, | ||
| 673 | + HEADDIM_QK=HEADDIM_QK, | ||
| 674 | + GQA_RATIO=GQA_RATIO, | ||
| 675 | + IS_VARLEN=is_varlen, | ||
| 676 | + num_warps=2, | ||
| 677 | + num_stages=3, | ||
| 678 | + ) | ||
| 679 | + | ||
| 680 | + | ||
| 681 | +def compute_ddt_dtrap_dinput_states( | ||
| 682 | + dscale: torch.Tensor, | ||
| 683 | + dgamma: torch.Tensor, | ||
| 684 | + dt: torch.Tensor, | ||
| 685 | + trap: torch.Tensor, | ||
| 686 | + d_issm_state: Optional[torch.Tensor] = None, | ||
| 687 | + input_k_state: Optional[torch.Tensor] = None, | ||
| 688 | + input_v_state: Optional[torch.Tensor] = None, | ||
| 689 | + Cu_Seqlens: Optional[torch.Tensor] = None, | ||
| 690 | +) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor], Optional[torch.Tensor], Optional[torch.Tensor]]: | ||
| 691 | + """Compute dDT, dTrap from dScale/dGamma, and optionally input state gradients.""" | ||
| 692 | + batch, nheads, seqlen = dscale.shape | ||
| 693 | + has_input_state = d_issm_state is not None | ||
| 694 | + is_varlen = Cu_Seqlens is not None | ||
| 695 | + | ||
| 696 | + if is_varlen: | ||
| 697 | + num_sequences = Cu_Seqlens.shape[0] - 1 | ||
| 698 | + assert batch == 1 | ||
| 699 | + else: | ||
| 700 | + num_sequences = batch | ||
| 701 | + | ||
| 702 | + assert dgamma.shape == (batch, nheads, seqlen) | ||
| 703 | + assert dt.shape == (batch, nheads, seqlen) | ||
| 704 | + assert trap.shape == (batch, nheads, seqlen) | ||
| 705 | + | ||
| 706 | + if has_input_state: | ||
| 707 | + assert input_k_state is not None and input_v_state is not None | ||
| 708 | + headdim_v, headdim_qk = d_issm_state.shape[2], d_issm_state.shape[3] | ||
| 709 | + assert d_issm_state.shape == (num_sequences, nheads, headdim_v, headdim_qk) | ||
| 710 | + assert input_k_state.shape == (num_sequences, nheads, headdim_qk) | ||
| 711 | + assert input_v_state.shape == (num_sequences, nheads, headdim_v) | ||
| 712 | + else: | ||
| 713 | + headdim_v, headdim_qk = 64, 128 | ||
| 714 | + | ||
| 715 | + dscale = dscale.contiguous() if not dscale.is_contiguous() else dscale | ||
| 716 | + dgamma = dgamma.contiguous() if not dgamma.is_contiguous() else dgamma | ||
| 717 | + dt = dt.contiguous() if not dt.is_contiguous() else dt | ||
| 718 | + trap = trap.contiguous() if not trap.is_contiguous() else trap | ||
| 719 | + | ||
| 720 | + if has_input_state: | ||
| 721 | + d_issm_state = d_issm_state.contiguous() if not d_issm_state.is_contiguous() else d_issm_state | ||
| 722 | + input_k_state = input_k_state.contiguous() if not input_k_state.is_contiguous() else input_k_state | ||
| 723 | + input_v_state = input_v_state.contiguous() if not input_v_state.is_contiguous() else input_v_state | ||
| 724 | + | ||
| 725 | + dDT = torch.empty_like(dt, dtype=torch.float32) | ||
| 726 | + dTrap = torch.empty_like(trap, dtype=torch.float32) | ||
| 727 | + | ||
| 728 | + if has_input_state: | ||
| 729 | + d_Input_SSM_State = torch.empty_like(d_issm_state) | ||
| 730 | + d_Input_K_State = torch.empty((num_sequences, nheads, headdim_qk), dtype=torch.float32, device=dt.device) | ||
| 731 | + d_Input_V_State = torch.empty((num_sequences, nheads, headdim_v), dtype=torch.float32, device=dt.device) | ||
| 732 | + else: | ||
| 733 | + d_Input_SSM_State = None | ||
| 734 | + d_Input_K_State = None | ||
| 735 | + d_Input_V_State = None | ||
| 736 | + | ||
| 737 | + HEADDIM_V = triton.next_power_of_2(headdim_v) if has_input_state else 64 | ||
| 738 | + HEADDIM_QK = triton.next_power_of_2(headdim_qk) if has_input_state else 128 | ||
| 739 | + | ||
| 740 | + if is_varlen: | ||
| 741 | + grid = (nheads, num_sequences) | ||
| 742 | + else: | ||
| 743 | + grid = (nheads, batch) | ||
| 744 | + | ||
| 745 | + mamba3_siso_bwd_kernel_ddt_dtrap_dinput_states[grid]( | ||
| 746 | + dscale, dgamma, dt, trap, | ||
| 747 | + d_issm_state if has_input_state else dscale, | ||
| 748 | + input_k_state if has_input_state else dscale, | ||
| 749 | + input_v_state if has_input_state else dscale, | ||
| 750 | + Cu_Seqlens, | ||
| 751 | + dDT, dTrap, | ||
| 752 | + d_Input_SSM_State if has_input_state else dDT, | ||
| 753 | + d_Input_K_State if has_input_state else dDT, | ||
| 754 | + d_Input_V_State if has_input_state else dDT, | ||
| 755 | + dscale.stride(0), dscale.stride(1), dscale.stride(2), | ||
| 756 | + dgamma.stride(0), dgamma.stride(1), dgamma.stride(2), | ||
| 757 | + dt.stride(0), dt.stride(1), dt.stride(2), | ||
| 758 | + trap.stride(0), trap.stride(1), trap.stride(2), | ||
| 759 | + d_issm_state.stride(0) if has_input_state else 0, | ||
| 760 | + d_issm_state.stride(1) if has_input_state else 0, | ||
| 761 | + d_issm_state.stride(2) if has_input_state else 0, | ||
| 762 | + d_issm_state.stride(3) if has_input_state else 0, | ||
| 763 | + input_k_state.stride(0) if has_input_state else 0, | ||
| 764 | + input_k_state.stride(1) if has_input_state else 0, | ||
| 765 | + input_k_state.stride(2) if has_input_state else 0, | ||
| 766 | + input_v_state.stride(0) if has_input_state else 0, | ||
| 767 | + input_v_state.stride(1) if has_input_state else 0, | ||
| 768 | + input_v_state.stride(2) if has_input_state else 0, | ||
| 769 | + Cu_Seqlens.stride(0) if Cu_Seqlens is not None else 0, | ||
| 770 | + dDT.stride(0), dDT.stride(1), dDT.stride(2), | ||
| 771 | + dTrap.stride(0), dTrap.stride(1), dTrap.stride(2), | ||
| 772 | + d_Input_SSM_State.stride(0) if has_input_state else 0, | ||
| 773 | + d_Input_SSM_State.stride(1) if has_input_state else 0, | ||
| 774 | + d_Input_SSM_State.stride(2) if has_input_state else 0, | ||
| 775 | + d_Input_SSM_State.stride(3) if has_input_state else 0, | ||
| 776 | + d_Input_K_State.stride(0) if has_input_state else 0, | ||
| 777 | + d_Input_K_State.stride(1) if has_input_state else 0, | ||
| 778 | + d_Input_K_State.stride(2) if has_input_state else 0, | ||
| 779 | + d_Input_V_State.stride(0) if has_input_state else 0, | ||
| 780 | + d_Input_V_State.stride(1) if has_input_state else 0, | ||
| 781 | + d_Input_V_State.stride(2) if has_input_state else 0, | ||
| 782 | + seqlen, headdim_v, headdim_qk, | ||
| 783 | + HEADDIM_V=HEADDIM_V, | ||
| 784 | + HEADDIM_QK=HEADDIM_QK, | ||
| 785 | + HAS_INPUT_STATE=has_input_state, | ||
| 786 | + IS_VARLEN=is_varlen, | ||
| 787 | + ) | ||
| 788 | + | ||
| 789 | + return dDT, dTrap, d_Input_SSM_State, d_Input_K_State, d_Input_V_State | ||
| @@ -0,0 +1,39 @@ | |||
| 1 | +""" | ||
| 2 | +Mamba-3 Util Functions. | ||
| 3 | + | ||
| 4 | +Copyright (c) 2025, Dao AI Lab, Goombalab | ||
| 5 | +""" | ||
| 6 | + | ||
| 7 | +import triton | ||
| 8 | +import triton.language as tl | ||
| 9 | + | ||
| 10 | + | ||
| 11 | +def cos_approx(x): | ||
| 12 | + """ | ||
| 13 | + Polynomial cosine approximation for the small rotary-angle range. | ||
| 14 | + """ | ||
| 15 | + x2 = x * x | ||
| 16 | + return 1.0 - 0.5 * x2 + 0.041666666666666664 * x2 * x2 - 0.001388888888888889 * x2 * x2 * x2 | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +def sin_approx(x): | ||
| 21 | + """ | ||
| 22 | + Polynomial sine approximation for the small rotary-angle range. | ||
| 23 | + """ | ||
| 24 | + x2 = x * x | ||
| 25 | + return x * (1.0 - 0.16666666666666666 * x2 + 0.008333333333333333 * x2 * x2 - 0.0001984126984126984 * x2 * x2 * x2) | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def sigmoid_approx(x): | ||
| 29 | + """ | ||
| 30 | + Sigmoid helper kept as a JIT-callable wrapper for schedule readability. | ||
| 31 | + """ | ||
| 32 | + return tl.sigmoid(x) | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +def silu(x): | ||
| 36 | + """ | ||
| 37 | + SiLU (Swish) activation function: x * sigmoid(x). | ||
| 38 | + """ | ||
| 39 | + return x*tl.sigmoid(x) | ||
| @@ -0,0 +1 @@ | |||
| 1 | +# arch35 mamba3 package | ||
| @@ -0,0 +1,9 @@ | |||
| 1 | +""" | ||
| 2 | +Mamba-3 Backward Pass Triton Kernels for Ascend NPU (arch35 stub). | ||
| 3 | + | ||
| 4 | +Copyright (c) 2026, Huawei Technologies Co., Ltd. All rights reserved. | ||
| 5 | +Copyright (c) 2025, Dao AI Lab, Goombalab | ||
| 6 | +""" | ||
| 7 | + | ||
| 8 | +# arch35 path is not yet implemented. The API layer will raise NotImplementedError | ||
| 9 | +# when is_arch35() returns True, so this stub is a placeholder for future work. | ||
| @@ -0,0 +1,117 @@ | |||
| 1 | +import itertools | ||
| 2 | +import random | ||
| 3 | + | ||
| 4 | +from atk.case_generator.generator.base_generator import CaseGenerator | ||
| 5 | +from atk.case_generator.generator.generate_types import GENERATOR_REGISTRY | ||
| 6 | +from atk.configs.case_config import CaseConfig | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | +class Mamba3SisoBwdDqkthetaGenerator(CaseGenerator): | ||
| 11 | + CASES = list( | ||
| 12 | + itertools.product( | ||
| 13 | + [1, 2], # batch | ||
| 14 | + [32, 64, 96, 128], # seqlen | ||
| 15 | + [1, 2], # q/k heads | ||
| 16 | + [1, 2, 4], # full heads | ||
| 17 | + [64, 128], # q/k dim | ||
| 18 | + [32, 64], # chunk size | ||
| 19 | + ) | ||
| 20 | + ) | ||
| 21 | + | ||
| 22 | + _case_pool = None | ||
| 23 | + _case_index = 0 | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + def _build_pool(cls): | ||
| 27 | + return [case for case in cls.CASES if case[3] % case[2] == 0] | ||
| 28 | + | ||
| 29 | + def after_case_config(self, case_config: CaseConfig) -> CaseConfig: | ||
| 30 | + if Mamba3SisoBwdDqkthetaGenerator._case_pool is None: | ||
| 31 | + Mamba3SisoBwdDqkthetaGenerator._case_pool = self._build_pool() | ||
| 32 | + | ||
| 33 | + if Mamba3SisoBwdDqkthetaGenerator._case_index < len(Mamba3SisoBwdDqkthetaGenerator._case_pool): | ||
| 34 | + batch, seqlen, nheads_qk, nheads, headdim_qk, chunk_size = ( | ||
| 35 | + Mamba3SisoBwdDqkthetaGenerator._case_pool[Mamba3SisoBwdDqkthetaGenerator._case_index] | ||
| 36 | + ) | ||
| 37 | + else: | ||
| 38 | + batch = random.choice([1, 2]) | ||
| 39 | + seqlen = random.choice([32, 64, 96, 128, 160]) | ||
| 40 | + nheads_qk = random.choice([1, 2, 4]) | ||
| 41 | + nheads = random.choice([h for h in [1, 2, 4, 8] if h % nheads_qk == 0]) | ||
| 42 | + headdim_qk = random.choice([64, 128]) | ||
🟡 Medium Priority changed line: affected behavior: 当 failure mode: 与 suggested fix: 将第 42 行改为 建议:将第 42 行改为 ![]() ![]() 不准确? | |||
| 43 | + chunk_size = random.choice([32, 64]) | ||
| 44 | + | ||
| 45 | + q_input = case_config.inputs[0] | ||
| 46 | + dtype = q_input.dtype | ||
| 47 | + headdim_angles = headdim_qk // 2 | ||
| 48 | + | ||
| 49 | + shapes = [ | ||
| 50 | + [batch, seqlen, nheads_qk, headdim_qk], | ||
| 51 | + [batch, seqlen, nheads_qk, headdim_qk], | ||
| 52 | + [batch, nheads, seqlen], | ||
| 53 | + [batch, nheads, seqlen], | ||
| 54 | + [nheads, headdim_qk], | ||
| 55 | + [nheads, headdim_qk], | ||
| 56 | + [batch, seqlen, nheads, headdim_angles], | ||
| 57 | + [batch, seqlen, nheads, headdim_qk], | ||
| 58 | + [batch, seqlen, nheads, headdim_qk], | ||
| 59 | + [batch, nheads, seqlen], | ||
| 60 | + ] | ||
| 61 | + dtypes = [dtype, dtype, "fp32", "fp32", dtype, dtype, "fp32", dtype, dtype, "fp32"] | ||
| 62 | + | ||
| 63 | + for input_cfg, shape, input_dtype in zip(case_config.inputs[:10], shapes, dtypes): | ||
| 64 | + if input_cfg.type == "tensor": | ||
| 65 | + input_cfg.shape = shape | ||
| 66 | + input_cfg.dtype = input_dtype | ||
| 67 | + | ||
| 68 | + case_config.inputs[10].value = chunk_size | ||
| 69 | + Mamba3SisoBwdDqkthetaGenerator._case_index += 1 | ||
| 70 | + return case_config | ||
| 71 | + | ||
| 72 | + | ||
| 73 | + | ||
| 74 | +class Mamba3SisoBwdDdtGenerator(CaseGenerator): | ||
| 75 | + CASES = list( | ||
| 76 | + itertools.product( | ||
| 77 | + [1, 2], # batch | ||
| 78 | + [1, 2, 4], # nheads | ||
| 79 | + [32, 64, 96, 128], # seqlen | ||
| 80 | + ) | ||
| 81 | + ) | ||
| 82 | + | ||
| 83 | + _case_pool = None | ||
| 84 | + _case_index = 0 | ||
| 85 | + | ||
| 86 | + | ||
| 87 | + def _build_pool(cls): | ||
| 88 | + return cls.CASES | ||
| 89 | + | ||
| 90 | + def after_case_config(self, case_config: CaseConfig) -> CaseConfig: | ||
| 91 | + if Mamba3SisoBwdDdtGenerator._case_pool is None: | ||
| 92 | + Mamba3SisoBwdDdtGenerator._case_pool = self._build_pool() | ||
| 93 | + | ||
| 94 | + if Mamba3SisoBwdDdtGenerator._case_index < len(Mamba3SisoBwdDdtGenerator._case_pool): | ||
| 95 | + batch, nheads, seqlen = ( | ||
| 96 | + Mamba3SisoBwdDdtGenerator._case_pool[Mamba3SisoBwdDdtGenerator._case_index] | ||
| 97 | + ) | ||
| 98 | + else: | ||
| 99 | + batch = random.choice([1, 2]) | ||
| 100 | + nheads = random.choice([1, 2, 4, 8]) | ||
| 101 | + seqlen = random.choice([32, 64, 96, 128, 160]) | ||
| 102 | + | ||
| 103 | + shapes = [ | ||
| 104 | + [batch, nheads, seqlen], | ||
| 105 | + [batch, nheads, seqlen], | ||
| 106 | + [batch, nheads, seqlen], | ||
| 107 | + [batch, nheads, seqlen], | ||
| 108 | + ] | ||
| 109 | + dtypes = ["fp32", "fp32", "fp32", "fp32"] | ||
| 110 | + | ||
| 111 | + for input_cfg, shape, input_dtype in zip(case_config.inputs[:4], shapes, dtypes): | ||
| 112 | + if input_cfg.type == "tensor": | ||
| 113 | + input_cfg.shape = shape | ||
| 114 | + input_cfg.dtype = input_dtype | ||
| 115 | + | ||
| 116 | + Mamba3SisoBwdDdtGenerator._case_index += 1 | ||
| 117 | + return case_config | ||
| @@ -0,0 +1,163 @@ | |||
| 1 | +api: pytorch | ||
| 2 | +version: v1.0 | ||
| 3 | +name: torch_mamba3_siso_bwd_dqktheta.TorchMamba3SisoBwdDqkthetaApi | ||
| 4 | +api_type: torch_mamba3_siso_bwd_dqktheta | ||
| 5 | +triton_name: triton_mamba3_siso_bwd_dqktheta.TritonMamba3SisoBwdDqkthetaApi | ||
| 6 | +triton_api_type: triton_mamba3_siso_bwd_dqktheta | ||
| 7 | +dtype_numbers: 10 | ||
| 8 | +generate: generate_mamba3_siso_bwd_dqktheta | ||
| 9 | +backward: false | ||
| 10 | +standard: | ||
| 11 | + acc: single_bm | ||
| 12 | + perf: not_key | ||
| 13 | +inputs: | ||
| 14 | + - name: q | ||
| 15 | + type: tensor | ||
| 16 | + required: true | ||
| 17 | + dtypes: | ||
| 18 | + values: [ fp16, bf16 ] | ||
| 19 | + ranges: | ||
| 20 | + valid: | ||
| 21 | + values: [ [-0.2, 0.2] ] | ||
| 22 | + invalid: | ||
| 23 | + values: [ [-0.2, 0.2] ] | ||
| 24 | + shapes: | ||
| 25 | + dim_numbers: | ||
| 26 | + values: [ 4 ] | ||
| 27 | + max_length: 4294967295 | ||
| 28 | + - name: k | ||
| 29 | + type: tensor | ||
| 30 | + required: true | ||
| 31 | + dtypes: | ||
| 32 | + values: [ fp16, bf16 ] | ||
| 33 | + ranges: | ||
| 34 | + valid: | ||
| 35 | + values: [ [-0.2, 0.2] ] | ||
| 36 | + invalid: | ||
| 37 | + values: [ [-0.2, 0.2] ] | ||
| 38 | + shapes: | ||
| 39 | + dim_numbers: | ||
| 40 | + values: [ 4 ] | ||
| 41 | + max_length: 4294967295 | ||
| 42 | + - name: scale | ||
| 43 | + type: tensor | ||
| 44 | + required: true | ||
| 45 | + dtypes: | ||
| 46 | + values: [ fp32 ] | ||
| 47 | + ranges: | ||
| 48 | + valid: | ||
| 49 | + values: [ [0.0, 1.0] ] | ||
| 50 | + invalid: | ||
| 51 | + values: [ [0.0, 1.0] ] | ||
| 52 | + shapes: | ||
| 53 | + dim_numbers: | ||
| 54 | + values: [ 3 ] | ||
| 55 | + max_length: 4294967295 | ||
| 56 | + - name: gamma | ||
| 57 | + type: tensor | ||
| 58 | + required: true | ||
| 59 | + dtypes: | ||
| 60 | + values: [ fp32 ] | ||
| 61 | + ranges: | ||
| 62 | + valid: | ||
| 63 | + values: [ [0.0, 0.3] ] | ||
| 64 | + invalid: | ||
| 65 | + values: [ [0.0, 0.3] ] | ||
| 66 | + shapes: | ||
| 67 | + dim_numbers: | ||
| 68 | + values: [ 3 ] | ||
| 69 | + max_length: 4294967295 | ||
| 70 | + - name: q_bias | ||
| 71 | + type: tensor | ||
| 72 | + required: true | ||
| 73 | + dtypes: | ||
| 74 | + values: [ fp16, bf16 ] | ||
| 75 | + ranges: | ||
| 76 | + valid: | ||
| 77 | + values: [ [-0.05, 0.05] ] | ||
| 78 | + invalid: | ||
| 79 | + values: [ [-0.05, 0.05] ] | ||
| 80 | + shapes: | ||
| 81 | + dim_numbers: | ||
| 82 | + values: [ 2 ] | ||
| 83 | + max_length: 4294967295 | ||
| 84 | + - name: k_bias | ||
| 85 | + type: tensor | ||
| 86 | + required: true | ||
| 87 | + dtypes: | ||
| 88 | + values: [ fp16, bf16 ] | ||
| 89 | + ranges: | ||
| 90 | + valid: | ||
| 91 | + values: [ [-0.05, 0.05] ] | ||
| 92 | + invalid: | ||
| 93 | + values: [ [-0.05, 0.05] ] | ||
| 94 | + shapes: | ||
| 95 | + dim_numbers: | ||
| 96 | + values: [ 2 ] | ||
| 97 | + max_length: 4294967295 | ||
| 98 | + - name: angles | ||
| 99 | + type: tensor | ||
| 100 | + required: true | ||
| 101 | + dtypes: | ||
| 102 | + values: [ fp32 ] | ||
| 103 | + ranges: | ||
| 104 | + valid: | ||
| 105 | + values: [ [0.0, 0.25] ] | ||
| 106 | + invalid: | ||
| 107 | + values: [ [0.0, 0.25] ] | ||
| 108 | + shapes: | ||
| 109 | + dim_numbers: | ||
| 110 | + values: [ 4 ] | ||
| 111 | + max_length: 4294967295 | ||
| 112 | + - name: dq_in | ||
| 113 | + type: tensor | ||
| 114 | + required: true | ||
| 115 | + dtypes: | ||
| 116 | + values: [ fp16, bf16 ] | ||
| 117 | + ranges: | ||
| 118 | + valid: | ||
| 119 | + values: [ [-0.2, 0.2] ] | ||
| 120 | + invalid: | ||
| 121 | + values: [ [-0.2, 0.2] ] | ||
| 122 | + shapes: | ||
| 123 | + dim_numbers: | ||
| 124 | + values: [ 4 ] | ||
| 125 | + max_length: 4294967295 | ||
| 126 | + - name: dk_in | ||
| 127 | + type: tensor | ||
| 128 | + required: true | ||
| 129 | + dtypes: | ||
| 130 | + values: [ fp16, bf16 ] | ||
| 131 | + ranges: | ||
| 132 | + valid: | ||
| 133 | + values: [ [-0.2, 0.2] ] | ||
| 134 | + invalid: | ||
| 135 | + values: [ [-0.2, 0.2] ] | ||
| 136 | + shapes: | ||
| 137 | + dim_numbers: | ||
| 138 | + values: [ 4 ] | ||
| 139 | + max_length: 4294967295 | ||
| 140 | + - name: dqk | ||
| 141 | + type: tensor | ||
| 142 | + required: true | ||
| 143 | + dtypes: | ||
| 144 | + values: [ fp32 ] | ||
| 145 | + ranges: | ||
| 146 | + valid: | ||
| 147 | + values: [ [0.0, 0.2] ] | ||
| 148 | + invalid: | ||
| 149 | + values: [ [0.0, 0.2] ] | ||
| 150 | + shapes: | ||
| 151 | + dim_numbers: | ||
| 152 | + values: [ 3 ] | ||
| 153 | + max_length: 4294967295 | ||
| 154 | + - name: chunk_size | ||
| 155 | + type: attr | ||
| 156 | + required: false | ||
| 157 | + dtypes: | ||
| 158 | + values: [ int ] | ||
| 159 | + ranges: | ||
| 160 | + valid: | ||
| 161 | + values: [ 32, 64 ] | ||
| 162 | + invalid: | ||
| 163 | + values: [ 32, 64 ] | ||
🟡 Medium Priority 变更内容: 建议:为 ![]() ![]() 不准确? | |||
| @@ -0,0 +1,187 @@ | |||
| 1 | +# Copyright (c) 2026, Huawei Technologies Co., Ltd. All rights reserved. | ||
| 2 | +# Copyright (c) 2025, Dao AI Lab, Goombalab | ||
| 3 | + | ||
| 4 | +import torch | ||
| 5 | + | ||
| 6 | + | ||
| 7 | +def _apply_rotary(x: torch.Tensor, angles: torch.Tensor) -> torch.Tensor: | ||
| 8 | + """Apply rotary embedding to x using angles.""" | ||
| 9 | + out = x.to(torch.float32).clone() | ||
| 10 | + rotary_dim = min(angles.shape[-1], out.shape[-1] // 2) | ||
| 11 | + if rotary_dim <= 0: | ||
| 12 | + return out | ||
| 13 | + cos = torch.cos(angles[..., :rotary_dim].to(torch.float32)) | ||
| 14 | + sin = torch.sin(angles[..., :rotary_dim].to(torch.float32)) | ||
| 15 | + even = out[..., 0:2 * rotary_dim:2].clone() | ||
| 16 | + odd = out[..., 1:2 * rotary_dim:2].clone() | ||
| 17 | + out[..., 0:2 * rotary_dim:2] = even * cos - odd * sin | ||
| 18 | + out[..., 1:2 * rotary_dim:2] = even * sin + odd * cos | ||
| 19 | + return out | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +def _inv_apply_rotary(x: torch.Tensor, angles: torch.Tensor) -> torch.Tensor: | ||
| 23 | + """Apply inverse rotary embedding to x using angles.""" | ||
| 24 | + out = x.to(torch.float32).clone() | ||
| 25 | + rotary_dim = min(angles.shape[-1], out.shape[-1] // 2) | ||
| 26 | + if rotary_dim <= 0: | ||
| 27 | + return out | ||
| 28 | + cos = torch.cos(angles[..., :rotary_dim].to(torch.float32)) | ||
| 29 | + sin = torch.sin(angles[..., :rotary_dim].to(torch.float32)) | ||
| 30 | + even = out[..., 0:2 * rotary_dim:2].clone() | ||
| 31 | + odd = out[..., 1:2 * rotary_dim:2].clone() | ||
| 32 | + out[..., 0:2 * rotary_dim:2] = even * cos + odd * sin | ||
| 33 | + out[..., 1:2 * rotary_dim:2] = -even * sin + odd * cos | ||
| 34 | + return out | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def ref_compute_dqktheta( | ||
| 38 | + q: torch.Tensor, | ||
| 39 | + k: torch.Tensor, | ||
| 40 | + scale: torch.Tensor, | ||
| 41 | + gamma: torch.Tensor, | ||
| 42 | + q_bias: torch.Tensor, | ||
| 43 | + k_bias: torch.Tensor, | ||
| 44 | + angles: torch.Tensor, | ||
| 45 | + dq_in: torch.Tensor, | ||
| 46 | + dk_in: torch.Tensor, | ||
| 47 | + dqk: torch.Tensor, | ||
| 48 | + chunk_size: int = 64, | ||
| 49 | +) -> tuple: | ||
| 50 | + """Reference implementation of compute_dqktheta.""" | ||
| 51 | + batch, seqlen, nheads_qk, headdim_qk = q.shape | ||
| 52 | + nheads = scale.shape[1] | ||
| 53 | + GQA_RATIO = nheads // nheads_qk | ||
| 54 | + work_dtype = torch.float32 | ||
| 55 | + | ||
| 56 | + dq = torch.zeros((batch, seqlen, nheads_qk, headdim_qk), device=q.device, dtype=work_dtype) | ||
| 57 | + dk = torch.zeros((batch, seqlen, nheads_qk, headdim_qk), device=k.device, dtype=work_dtype) | ||
| 58 | + dangles = torch.zeros((batch, seqlen, nheads, angles.shape[-1]), device=angles.device, dtype=work_dtype) | ||
| 59 | + dscale = torch.zeros((batch, nheads, seqlen), device=scale.device, dtype=work_dtype) | ||
| 60 | + dgamma = torch.zeros((batch, nheads, seqlen), device=gamma.device, dtype=work_dtype) | ||
| 61 | + dq_bias = torch.zeros((nheads, headdim_qk), device=q.device, dtype=work_dtype) | ||
| 62 | + dk_bias = torch.zeros((nheads, headdim_qk), device=k.device, dtype=work_dtype) | ||
| 63 | + | ||
| 64 | + for b_idx in range(batch): | ||
| 65 | + for qk_head_idx in range(nheads_qk): | ||
| 66 | + for gqa_idx in range(GQA_RATIO): | ||
| 67 | + nhead_idx = qk_head_idx * GQA_RATIO + gqa_idx | ||
| 68 | + | ||
| 69 | + q_pre = q[b_idx, :, qk_head_idx, :].to(work_dtype) + q_bias[nhead_idx].to(work_dtype) | ||
| 70 | + k_pre = k[b_idx, :, qk_head_idx, :].to(work_dtype) + k_bias[nhead_idx].to(work_dtype) | ||
| 71 | + dq_in_h = dq_in[b_idx, :, nhead_idx, :].to(work_dtype) | ||
| 72 | + dk_in_h = dk_in[b_idx, :, nhead_idx, :].to(work_dtype) | ||
| 73 | + dqk_h = dqk[b_idx, nhead_idx, :].to(work_dtype) | ||
| 74 | + scale_h = scale[b_idx, nhead_idx, :].to(work_dtype) | ||
| 75 | + gamma_h = gamma[b_idx, nhead_idx, :].to(work_dtype) | ||
| 76 | + angles_h = angles[b_idx, :, nhead_idx, :].to(work_dtype) | ||
| 77 | + | ||
| 78 | + # dGamma = dQK * (Q_wbias . K_wbias) | ||
| 79 | + qk_dot = (q_pre * k_pre).sum(dim=-1) | ||
| 80 | + dgamma[b_idx, nhead_idx, :] = dqk_h * qk_dot | ||
| 81 | + | ||
| 82 | + # cos/sin | ||
| 83 | + cos_ang = torch.cos(angles_h) | ||
| 84 | + sin_ang = torch.sin(angles_h) | ||
| 85 | + | ||
| 86 | + # dScale = sum(dK_in * K_rot) | ||
| 87 | + k_rot = _apply_rotary(k_pre, angles_h) | ||
| 88 | + dscale[b_idx, nhead_idx, :] = (dk_in_h * k_rot).sum(dim=-1) | ||
| 89 | + | ||
| 90 | + # Inverse rotary | ||
| 91 | + dk_in_scaled = dk_in_h * scale_h.unsqueeze(-1) | ||
| 92 | + dq_unrot = _inv_apply_rotary(dq_in_h, angles_h) | ||
| 93 | + dk_unrot = _inv_apply_rotary(dk_in_scaled, angles_h) | ||
| 94 | + | ||
| 95 | + # Add dQK path | ||
| 96 | + dqk_scaled = (dqk_h * gamma_h).unsqueeze(-1) | ||
| 97 | + dq_pre = dq_unrot + dqk_scaled * k_pre | ||
| 98 | + dk_pre = dk_unrot + dqk_scaled * q_pre | ||
| 99 | + | ||
| 100 | + # Accumulate GQA | ||
| 101 | + dq[b_idx, :, qk_head_idx, :] += dq_pre | ||
| 102 | + dk[b_idx, :, qk_head_idx, :] += dk_pre | ||
| 103 | + dq_bias[nhead_idx, :] += dq_pre.sum(dim=0) | ||
| 104 | + dk_bias[nhead_idx, :] += dk_pre.sum(dim=0) | ||
| 105 | + | ||
| 106 | + # dAngles | ||
| 107 | + rotary_dim = min(angles_h.shape[-1], headdim_qk // 2) | ||
| 108 | + if rotary_dim > 0: | ||
| 109 | + even_q = q_pre[:, 0:2 * rotary_dim:2] | ||
| 110 | + odd_q = q_pre[:, 1:2 * rotary_dim:2] | ||
| 111 | + even_k = k_pre[:, 0:2 * rotary_dim:2] | ||
| 112 | + odd_k = k_pre[:, 1:2 * rotary_dim:2] | ||
| 113 | + even_dq = dq_in_h[:, 0:2 * rotary_dim:2] | ||
| 114 | + odd_dq = dq_in_h[:, 1:2 * rotary_dim:2] | ||
| 115 | + even_dk = dk_in_scaled[:, 0:2 * rotary_dim:2] | ||
| 116 | + odd_dk = dk_in_scaled[:, 1:2 * rotary_dim:2] | ||
| 117 | + | ||
| 118 | + dtheta_q = even_dq * (-even_q * sin_ang - odd_q * cos_ang) + \ | ||
| 119 | + odd_dq * (even_q * cos_ang - odd_q * sin_ang) | ||
| 120 | + dtheta_k = even_dk * (-even_k * sin_ang - odd_k * cos_ang) + \ | ||
| 121 | + odd_dk * (even_k * cos_ang - odd_k * sin_ang) | ||
| 122 | + dangles[b_idx, :, nhead_idx, :rotary_dim] = dtheta_q + dtheta_k | ||
| 123 | + | ||
| 124 | + return dq, dk, dq_bias, dk_bias, dangles, dscale, dgamma | ||
| 125 | + | ||
| 126 | + | ||
| 127 | +def ref_compute_ddt_dtrap_dinput_states( | ||
| 128 | + dscale: torch.Tensor, | ||
| 129 | + dgamma: torch.Tensor, | ||
| 130 | + dt: torch.Tensor, | ||
| 131 | + trap: torch.Tensor, | ||
| 132 | +) -> tuple: | ||
| 133 | + """Reference implementation of compute_ddt_dtrap_dinput_states.""" | ||
| 134 | + batch, nheads, seqlen = dscale.shape | ||
| 135 | + work_dtype = torch.float32 | ||
| 136 | + | ||
| 137 | + dDT = torch.zeros_like(dt, dtype=work_dtype) | ||
| 138 | + dTrap = torch.zeros_like(trap, dtype=work_dtype) | ||
| 139 | + | ||
| 140 | + for b_idx in range(batch): | ||
| 141 | + for h_idx in range(nheads): | ||
| 142 | + dscale_h = dscale[b_idx, h_idx].to(work_dtype) | ||
| 143 | + dgamma_h = dgamma[b_idx, h_idx].to(work_dtype) | ||
| 144 | + dt_h = dt[b_idx, h_idx].to(work_dtype) | ||
| 145 | + trap_h = torch.sigmoid(trap[b_idx, h_idx].to(work_dtype)) | ||
| 146 | + | ||
| 147 | + for t in range(seqlen): | ||
| 148 | + dscale_prev = dscale_h[t - 1] if t > 0 else torch.tensor(0.0, device=dt.device, dtype=work_dtype) | ||
| 149 | + ddt_t = (dgamma_h[t] + dscale_h[t]) * trap_h[t] + dscale_prev * (1.0 - trap_h[t]) | ||
| 150 | + dtrap_t = (dgamma_h[t] + dscale_h[t]) * dt_h[t] - dscale_prev * dt_h[t] | ||
| 151 | + dtrap_presig_t = dtrap_t * trap_h[t] * (1.0 - trap_h[t]) | ||
| 152 | + | ||
| 153 | + dDT[b_idx, h_idx, t] = ddt_t | ||
| 154 | + dTrap[b_idx, h_idx, t] = dtrap_presig_t | ||
| 155 | + | ||
| 156 | + return dDT, dTrap | ||
| 157 | + | ||
| 158 | + | ||
| 159 | +def make_inputs_dqktheta(batch, seqlen, nheads_qk, nheads, headdim_qk, dtype, device): | ||
| 160 | + """Create test inputs for compute_dqktheta.""" | ||
| 161 | + torch.manual_seed(2026 + batch + seqlen + nheads + headdim_qk) | ||
| 162 | + headdim_angles = headdim_qk // 2 | ||
| 163 | + | ||
| 164 | + q = (torch.rand(batch, seqlen, nheads_qk, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.4 | ||
| 165 | + k = (torch.rand(batch, seqlen, nheads_qk, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.4 | ||
| 166 | + scale = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 0.5 + 0.5 | ||
| 167 | + gamma = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 0.3 | ||
| 168 | + q_bias = (torch.rand(nheads, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.1 | ||
| 169 | + k_bias = (torch.rand(nheads, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.1 | ||
| 170 | + angles = torch.rand(batch, seqlen, nheads, headdim_angles, device=device, dtype=torch.float32) * 0.25 | ||
| 171 | + dq_in = (torch.rand(batch, seqlen, nheads, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.4 | ||
| 172 | + dk_in = (torch.rand(batch, seqlen, nheads, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.4 | ||
| 173 | + dqk = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 0.2 | ||
| 174 | + | ||
| 175 | + return q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk | ||
| 176 | + | ||
| 177 | + | ||
| 178 | +def make_inputs_ddt(batch, nheads, seqlen, dtype, device): | ||
| 179 | + """Create test inputs for compute_ddt_dtrap_dinput_states.""" | ||
| 180 | + torch.manual_seed(2027 + batch + seqlen + nheads) | ||
| 181 | + | ||
| 182 | + dscale = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 0.2 | ||
| 183 | + dgamma = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 0.2 | ||
| 184 | + dt = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 0.1 | ||
| 185 | + trap = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 2.0 - 1.0 | ||
| 186 | + | ||
| 187 | + return dscale, dgamma, dt, trap | ||
| @@ -0,0 +1,401 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +# Copyright (c) 2026, Huawei Technologies Co., Ltd. All rights reserved. | ||
| 3 | + | ||
| 4 | +from __future__ import annotations | ||
| 5 | + | ||
| 6 | +import argparse | ||
| 7 | +import ast | ||
| 8 | +import json | ||
| 9 | +import math | ||
| 10 | +import os | ||
| 11 | +import statistics | ||
| 12 | +from pathlib import Path | ||
| 13 | +from typing import Any | ||
| 14 | + | ||
| 15 | +import torch | ||
| 16 | + | ||
| 17 | +from mindspeed_ops.api.triton.mamba3_siso_bwd import compute_dqktheta, compute_ddt_dtrap_dinput_states | ||
| 18 | +from tests.atk_tests.triton.mamba3_siso_bwd.reference_impl import ( | ||
| 19 | + make_inputs_dqktheta, | ||
| 20 | + make_inputs_ddt, | ||
| 21 | + ref_compute_dqktheta, | ||
| 22 | + ref_compute_ddt_dtrap_dinput_states, | ||
| 23 | +) | ||
| 24 | +from tests.utils import get_mare, get_mere, get_rmse | ||
| 25 | + | ||
| 26 | + | ||
| 27 | +REPO_ROOT = Path(__file__).resolve().parents[4] | ||
| 28 | + | ||
| 29 | +FILE_INVENTORY = [ | ||
| 30 | + { | ||
| 31 | + "path": "mindspeed_ops/api/triton/mamba3_siso_bwd.py", | ||
| 32 | + "kind": "production api", | ||
| 33 | + "purpose": "公开 Python API;做 arch32 guard、参数转发和结果返回。", | ||
| 34 | + "review_check": "不能实现 reference 计算,不能调用 torch/torch_npu/vendor whole-op fallback。", | ||
| 35 | + }, | ||
| 36 | + { | ||
| 37 | + "path": "mindspeed_ops/arch32/triton/mamba3/mamba3_siso_bwd_impl.py", | ||
| 38 | + "kind": "production kernel", | ||
| 39 | + "purpose": "Triton-Ascend kernel 和 host dispatch;包含 rotary_bias_angles、dk_state_post、ddt_dtrap_dinput_states 三个 kernel。", | ||
| 40 | + "review_check": "生产计算必须进入本文件内 Triton kernel;dispatch 只能按运行时 shape/dtype/metadata。", | ||
| 41 | + }, | ||
| 42 | + { | ||
| 43 | + "path": "mindspeed_ops/arch32/triton/mamba3/mamba3_utils.py", | ||
| 44 | + "kind": "production helper", | ||
| 45 | + "purpose": "Triton JIT helper:rotary 小角度 sin/cos 多项式、sigmoid、silu。", | ||
| 46 | + "review_check": "仅提供 JIT helper,不读取外部 reference、case、golden 或上游包。", | ||
| 47 | + }, | ||
| 48 | + { | ||
| 49 | + "path": "tests/unit_tests/triton/test_mamba3_siso_bwd.py", | ||
| 50 | + "kind": "unit test", | ||
| 51 | + "purpose": "pytest 精度单测,覆盖 compute_dqktheta 和 compute_ddt_dtrap_dinput_states 两条生产路径。", | ||
| 52 | + "review_check": "测试侧可调用 torch reference;生产入口不能反向依赖测试代码。", | ||
| 53 | + }, | ||
| 54 | + { | ||
| 55 | + "path": "tests/atk_tests/triton/mamba3_siso_bwd/generate_mamba3_siso_bwd.py", | ||
| 56 | + "kind": "ATK generator", | ||
| 57 | + "purpose": "ATK 用例生成器;从提交内参数空间生成 shape/dtype/attr。", | ||
| 58 | + "review_check": "不读取外部 task cases、answer key 或私有 workload 文件。", | ||
| 59 | + }, | ||
| 60 | + { | ||
| 61 | + "path": "tests/atk_tests/triton/mamba3_siso_bwd/mamba3_siso_bwd.yaml", | ||
| 62 | + "kind": "ATK config", | ||
| 63 | + "purpose": "ATK 算子配置,声明输入、dtype、取值范围、baseline API 与 Triton API。", | ||
| 64 | + "review_check": "配置中的有效范围要与本交付支持边界一致。", | ||
| 65 | + }, | ||
| 66 | + { | ||
| 67 | + "path": "tests/atk_tests/triton/mamba3_siso_bwd/triton_mamba3_siso_bwd.py", | ||
| 68 | + "kind": "ATK wrapper", | ||
| 69 | + "purpose": "ATK baseline/candidate wrapper;baseline 仅用于测试侧对比。", | ||
| 70 | + "review_check": "candidate wrapper 必须调用 `mindspeed_ops.api.triton.mamba3_siso_bwd`。", | ||
| 71 | + }, | ||
| 72 | + { | ||
| 73 | + "path": "tests/atk_tests/triton/mamba3_siso_bwd/reference_impl.py", | ||
| 74 | + "kind": "test reference", | ||
| 75 | + "purpose": "提交内 torch reference,用于 UT/ATK/自证脚本精度校验。", | ||
| 76 | + "review_check": "只能被测试和自证脚本引用,生产路径不能 import 它。", | ||
| 77 | + }, | ||
| 78 | + { | ||
| 79 | + "path": "tests/atk_tests/triton/mamba3_siso_bwd/self_check_mamba3_siso_bwd.py", | ||
| 80 | + "kind": "self-check script", | ||
| 81 | + "purpose": "一键生成 Markdown/JSON 自证报告,包含文件范围、静态 no-fallback、live 精度。", | ||
| 82 | + "review_check": "只依赖 PR 内文件和当前 MindSpeed-Ops 运行环境;不读取外部评测目录。", | ||
| 83 | + }, | ||
| 84 | + { | ||
| 85 | + "path": "docs/triton/mamba3_siso_bwd.md", | ||
| 86 | + "kind": "documentation", | ||
| 87 | + "purpose": "中文实践说明和审查说明,记录支持边界、验证命令和证据口径。", | ||
| 88 | + "review_check": "文档声明必须能由本脚本或仓内测试复核。", | ||
| 89 | + }, | ||
| 90 | +] | ||
| 91 | + | ||
| 92 | +PRODUCTION_FILES = [item["path"] for item in FILE_INVENTORY if item["kind"].startswith("production")] | ||
| 93 | + | ||
| 94 | +REVIEW_CASES_DQKTHETA = [ | ||
| 95 | + {"case_id": 1, "batch": 1, "seqlen": 32, "nheads_qk": 1, "nheads": 1, "headdim_qk": 64, "dtype": "float16", "chunk_size": 64}, | ||
| 96 | + {"case_id": 2, "batch": 1, "seqlen": 64, "nheads_qk": 1, "nheads": 2, "headdim_qk": 64, "dtype": "float16", "chunk_size": 64}, | ||
| 97 | + {"case_id": 3, "batch": 2, "seqlen": 64, "nheads_qk": 2, "nheads": 4, "headdim_qk": 64, "dtype": "float16", "chunk_size": 32}, | ||
| 98 | + {"case_id": 5, "batch": 1, "seqlen": 96, "nheads_qk": 1, "nheads": 2, "headdim_qk": 64, "dtype": "bfloat16", "chunk_size": 64}, | ||
| 99 | + {"case_id": 6, "batch": 2, "seqlen": 96, "nheads_qk": 1, "nheads": 2, "headdim_qk": 64, "dtype": "bfloat16", "chunk_size": 64}, | ||
| 100 | + {"case_id": 7, "batch": 1, "seqlen": 128, "nheads_qk": 2, "nheads": 4, "headdim_qk": 64, "dtype": "float16", "chunk_size": 64}, | ||
| 101 | +] | ||
| 102 | + | ||
| 103 | +REVIEW_CASES_DDT = [ | ||
| 104 | + {"case_id": 101, "batch": 1, "nheads": 1, "seqlen": 32, "dtype": "float16"}, | ||
| 105 | + {"case_id": 102, "batch": 1, "nheads": 2, "seqlen": 64, "dtype": "float16"}, | ||
| 106 | + {"case_id": 103, "batch": 2, "nheads": 2, "seqlen": 64, "dtype": "float16"}, | ||
| 107 | + {"case_id": 104, "batch": 1, "nheads": 4, "seqlen": 96, "dtype": "bfloat16"}, | ||
| 108 | + {"case_id": 105, "batch": 2, "nheads": 4, "seqlen": 128, "dtype": "float16"}, | ||
| 109 | +] | ||
| 110 | + | ||
| 111 | +QUICK_CASE_IDS = {1, 101} | ||
| 112 | +ABS_TOL = 1e-3 | ||
| 113 | +REL_RMSE_TOL = 1e-2 | ||
| 114 | + | ||
| 115 | + | ||
| 116 | +def _round(value: float, digits: int = 6) -> float: | ||
| 117 | + return round(float(value), digits) | ||
| 118 | + | ||
| 119 | + | ||
| 120 | +def _max_abs(actual: torch.Tensor, expected: torch.Tensor) -> float: | ||
| 121 | + return (actual.detach().cpu().float() - expected.detach().cpu().float()).abs().max().item() | ||
| 122 | + | ||
| 123 | + | ||
| 124 | +def _relative_rmse(actual: torch.Tensor, expected: torch.Tensor) -> float: | ||
| 125 | + diff = actual.detach().cpu().float().flatten() - expected.detach().cpu().float().flatten() | ||
| 126 | + base = expected.detach().cpu().float().flatten() | ||
| 127 | + return torch.sqrt(torch.mean(diff * diff)).item() / (torch.sqrt(torch.mean(base * base)).item() + 1e-8) | ||
| 128 | + | ||
| 129 | + | ||
| 130 | +def _dtype(name: str) -> torch.dtype: | ||
| 131 | + return {"float16": torch.float16, "bfloat16": torch.bfloat16}[name] | ||
| 132 | + | ||
| 133 | + | ||
| 134 | +def _has_npu() -> bool: | ||
| 135 | + return hasattr(torch, "npu") and torch.npu.is_available() | ||
| 136 | + | ||
| 137 | + | ||
| 138 | +def _sync() -> None: | ||
| 139 | + if _has_npu(): | ||
| 140 | + torch.npu.synchronize() | ||
| 141 | + | ||
| 142 | + | ||
| 143 | +def _scan_imports(path: Path) -> list[str]: | ||
| 144 | + tree = ast.parse(path.read_text(encoding="utf-8")) | ||
| 145 | + imports: list[str] = [] | ||
| 146 | + for node in ast.walk(tree): | ||
| 147 | + if isinstance(node, ast.Import): | ||
| 148 | + imports.extend(alias.name for alias in node.names) | ||
| 149 | + elif isinstance(node, ast.ImportFrom) and node.module: | ||
| 150 | + imports.append(node.module) | ||
| 151 | + return imports | ||
| 152 | + | ||
| 153 | + | ||
| 154 | +def static_no_fallback_check() -> dict[str, Any]: | ||
| 155 | + missing_files = [item["path"] for item in FILE_INVENTORY if not (REPO_ROOT / item["path"]).is_file()] | ||
| 156 | + forbidden_import_fragments = [ | ||
| 157 | + "state_spaces", "state-spaces", "mamba_ssm", "golden", "reference", | ||
| 158 | + "tests.unit_tests", "tests.atk_tests", | ||
| 159 | + ] | ||
| 160 | + forbidden_text_fragments = [ | ||
| 161 | + "torch.ops", "torch_npu.ops", "torch_npu.contrib", "torch_npu.npu_", | ||
| 162 | + "mamba_ssm", "task golden files", "refer/upstreams", "state-spaces-mamba", | ||
| 163 | + "--external-evidence-root", | ||
| 164 | + ] | ||
| 165 | + allowed_runtime_fragments = { | ||
| 166 | + "torch": "tensor/runtime API for input metadata and device checks", | ||
| 167 | + "torch_npu": "runtime availability probe only; no torch_npu whole-op call is allowed", | ||
| 168 | + "triton": "Triton-Ascend JIT and launch API", | ||
| 169 | + "triton.language": "Triton language primitives", | ||
| 170 | + } | ||
| 171 | + file_results = [] | ||
| 172 | + violations = [f"missing file: {item}" for item in missing_files] | ||
| 173 | + for rel in PRODUCTION_FILES: | ||
| 174 | + path = REPO_ROOT / rel | ||
| 175 | + if not path.is_file(): | ||
| 176 | + continue | ||
| 177 | + text = path.read_text(encoding="utf-8") | ||
| 178 | + imports = _scan_imports(path) | ||
| 179 | + file_violations = [] | ||
| 180 | + for item in imports: | ||
| 181 | + if any(fragment in item for fragment in forbidden_import_fragments): | ||
| 182 | + file_violations.append(f"forbidden import: {item}") | ||
| 183 | + for fragment in forbidden_text_fragments: | ||
| 184 | + if fragment in text: | ||
| 185 | + file_violations.append(f"forbidden text: {fragment}") | ||
| 186 | + file_results.append({"path": rel, "imports": imports, "violations": file_violations}) | ||
| 187 | + violations.extend(f"{rel}: {v}" for v in file_violations) | ||
| 188 | + | ||
| 189 | + return { | ||
| 190 | + "passed": not violations, | ||
| 191 | + "file_inventory": FILE_INVENTORY, | ||
| 192 | + "missing_files": missing_files, | ||
| 193 | + "production_files": PRODUCTION_FILES, | ||
| 194 | + "allowed_runtime_fragments": allowed_runtime_fragments, | ||
| 195 | + "file_results": file_results, | ||
| 196 | + "violations": violations, | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + | ||
| 200 | +def _run_dqktheta_case(case: dict[str, Any], device: torch.device): | ||
| 201 | + inputs = make_inputs_dqktheta( | ||
| 202 | + case["batch"], case["seqlen"], case["nheads_qk"], case["nheads"], | ||
| 203 | + case["headdim_qk"], _dtype(case["dtype"]), device, | ||
| 204 | + ) | ||
| 205 | + actual = compute_dqktheta(*inputs, chunk_size=case["chunk_size"]) | ||
| 206 | + expected = ref_compute_dqktheta(*inputs, chunk_size=case["chunk_size"]) | ||
| 207 | + _sync() | ||
| 208 | + | ||
| 209 | + name = f"dqktheta_c{case['case_id']}" | ||
| 210 | + results = [] | ||
| 211 | + for i, (tensor_name, act, exp) in enumerate(zip( | ||
| 212 | + ["dq", "dk", "dangles", "dscale", "dgamma"], | ||
| 213 | + actual[:5], expected[:5], | ||
| 214 | + )): | ||
| 215 | + max_abs = _max_abs(act, exp) | ||
| 216 | + relative_rmse = _relative_rmse(act, exp) | ||
| 217 | + mare = get_mare(act, exp) | ||
| 218 | + mere = get_mere(act, exp) | ||
| 219 | + rmse = get_rmse(act, exp) | ||
| 220 | + accuracy_passed = max_abs <= ABS_TOL or relative_rmse <= REL_RMSE_TOL | ||
| 221 | + results.append({ | ||
| 222 | + "tensor": tensor_name, "max_abs": _round(max_abs, 10), | ||
| 223 | + "relative_rmse": _round(relative_rmse, 10), | ||
| 224 | + "mare": _round(mare, 10), "mere": _round(mere, 10), | ||
| 225 | + "rmse": _round(rmse, 10), "accuracy_passed": accuracy_passed, | ||
| 226 | + }) | ||
| 227 | + | ||
| 228 | + all_passed = all(r["accuracy_passed"] for r in results) | ||
| 229 | + return { | ||
| 230 | + "case_id": case["case_id"], "dtype": case["dtype"], | ||
| 231 | + "shape": f"B{case['batch']}_S{case['seqlen']}_Hq{case['nheads_qk']}_H{case['nheads']}_K{case['headdim_qk']}_CS{case['chunk_size']}", | ||
| 232 | + "results": results, "passed": all_passed, | ||
| 233 | + } | ||
| 234 | + | ||
| 235 | + | ||
| 236 | +def _run_ddt_case(case: dict[str, Any], device: torch.device): | ||
| 237 | + inputs = make_inputs_ddt( | ||
| 238 | + case["batch"], case["nheads"], case["seqlen"], _dtype(case["dtype"]), device, | ||
| 239 | + ) | ||
| 240 | + actual_ddt, actual_dtrap, *_ = compute_ddt_dtrap_dinput_states(*inputs) | ||
| 241 | + expected_ddt, expected_dtrap = ref_compute_ddt_dtrap_dinput_states(*inputs) | ||
| 242 | + _sync() | ||
| 243 | + | ||
| 244 | + results = [] | ||
| 245 | + for tensor_name, act, exp in [("dDT", actual_ddt, expected_ddt), ("dTrap", actual_dtrap, expected_dtrap)]: | ||
| 246 | + max_abs = _max_abs(act, exp) | ||
| 247 | + relative_rmse = _relative_rmse(act, exp) | ||
| 248 | + mare = get_mare(act, exp) | ||
| 249 | + mere = get_mere(act, exp) | ||
| 250 | + rmse = get_rmse(act, exp) | ||
| 251 | + accuracy_passed = max_abs <= ABS_TOL or relative_rmse <= REL_RMSE_TOL | ||
| 252 | + results.append({ | ||
| 253 | + "tensor": tensor_name, "max_abs": _round(max_abs, 10), | ||
| 254 | + "relative_rmse": _round(relative_rmse, 10), | ||
| 255 | + "mare": _round(mare, 10), "mere": _round(mare, 10), | ||
| 256 | + "rmse": _round(rmse, 10), "accuracy_passed": accuracy_passed, | ||
| 257 | + }) | ||
| 258 | + | ||
| 259 | + all_passed = all(r["accuracy_passed"] for r in results) | ||
| 260 | + return { | ||
| 261 | + "case_id": case["case_id"], "dtype": case["dtype"], | ||
| 262 | + "shape": f"B{case['batch']}_H{case['nheads']}_S{case['seqlen']}", | ||
| 263 | + "results": results, "passed": all_passed, | ||
| 264 | + } | ||
| 265 | + | ||
| 266 | + | ||
| 267 | +def live_case_check(cases_dqktheta: list, cases_ddt: list) -> dict[str, Any]: | ||
| 268 | + if not _has_npu(): | ||
| 269 | + return {"passed": False, "skipped": True, "reason": "NPU is not available", "cases": [], "metrics": None} | ||
| 270 | + | ||
| 271 | + device = torch.device("npu") | ||
| 272 | + all_rows = [] | ||
| 273 | + | ||
| 274 | + for case in cases_dqktheta: | ||
| 275 | + row = _run_dqktheta_case(case, device) | ||
| 276 | + all_rows.append(row) | ||
| 277 | + | ||
| 278 | + for case in cases_ddt: | ||
| 279 | + row = _run_ddt_case(case, device) | ||
| 280 | + all_rows.append(row) | ||
| 281 | + | ||
| 282 | + passed = all(row["passed"] for row in all_rows) | ||
| 283 | + return {"passed": passed, "skipped": False, "cases": all_rows, "metrics": None} | ||
| 284 | + | ||
| 285 | + | ||
| 286 | +def build_report(quick: bool) -> dict[str, Any]: | ||
| 287 | + static = static_no_fallback_check() | ||
| 288 | + dqktheta_cases = [case for case in REVIEW_CASES_DQKTHETA if not quick or case["case_id"] in QUICK_CASE_IDS] | ||
| 289 | + ddt_cases = [case for case in REVIEW_CASES_DDT if not quick or case["case_id"] in QUICK_CASE_IDS] | ||
| 290 | + live = live_case_check(dqktheta_cases, ddt_cases) | ||
| 291 | + passed = static["passed"] and live["passed"] | ||
| 292 | + return { | ||
| 293 | + "passed": passed, | ||
| 294 | + "mode": "quick" if quick else "full", | ||
| 295 | + "accuracy_rule": { | ||
| 296 | + "abs_tol": ABS_TOL, | ||
| 297 | + "relative_rmse_tol": REL_RMSE_TOL, | ||
| 298 | + "pass_rule": "max_abs <= abs_tol OR relative_rmse <= relative_rmse_tol", | ||
| 299 | + }, | ||
| 300 | + "static_no_fallback": static, | ||
| 301 | + "live_cases": live, | ||
| 302 | + "known_boundary": { | ||
| 303 | + "arch": "arch32 production path; arch35 fails loudly", | ||
| 304 | + "operator_scope": "dense non-varlen backward path (dqktheta, ddt_dtrap)", | ||
| 305 | + "unsupported_scope": ["varlen", "state-return", "full backward combined"], | ||
| 306 | + "known_limitation": "headdim_qk=128 triggers NPU UB overflow (loop unrolling doubles buffer usage); current PR covers headdim_qk=64 only", | ||
| 307 | + }, | ||
| 308 | + } | ||
| 309 | + | ||
| 310 | + | ||
| 311 | +def render_markdown(report: dict[str, Any]) -> str: | ||
| 312 | + lines = [ | ||
| 313 | + "# mamba3_siso_bwd 自证报告", | ||
| 314 | + "", | ||
| 315 | + "## 0. 运行与结论", | ||
| 316 | + "", | ||
| 317 | + f"- overall: {'PASS' if report['passed'] else 'FAIL'}", | ||
| 318 | + f"- mode: `{report['mode']}`", | ||
| 319 | + f"- static_no_fallback: {'PASS' if report['static_no_fallback']['passed'] else 'FAIL'}", | ||
| 320 | + f"- live_cases: {'PASS' if report['live_cases']['passed'] else 'FAIL'}", | ||
| 321 | + "- repo_root: `<MindSpeed-Ops repository root>`", | ||
| 322 | + "- external_workspace_required: `no`", | ||
| 323 | + "", | ||
| 324 | + "## 1. 提交范围", | ||
| 325 | + "", | ||
| 326 | + "| 文件 | 类型 | 用途 | 审查项 | 存在 |", | ||
| 327 | + "| --- | --- | --- | --- | --- |", | ||
| 328 | + ] | ||
| 329 | + for item in report["static_no_fallback"]["file_inventory"]: | ||
| 330 | + path = item["path"] | ||
| 331 | + exists = (REPO_ROOT / path).is_file() | ||
| 332 | + lines.append( | ||
| 333 | + f"| `{path}` | {item['kind']} | {item['purpose']} | {item['review_check']} | " | ||
| 334 | + f"{'yes' if exists else 'NO'} |" | ||
| 335 | + ) | ||
| 336 | + | ||
| 337 | + lines.extend(["", "## 2. 无 fallback 静态检查", ""]) | ||
| 338 | + lines.extend(["允许的生产依赖边界:", "", "| import/text | 用途 |", "| --- | --- |"]) | ||
| 339 | + for name, reason in report["static_no_fallback"]["allowed_runtime_fragments"].items(): | ||
| 340 | + lines.append(f"| `{name}` | {reason} |") | ||
| 341 | + lines.extend(["", "生产文件 import 扫描:", ""]) | ||
| 342 | + for item in report["static_no_fallback"]["file_results"]: | ||
| 343 | + imports = ", ".join(f"`{name}`" for name in item["imports"]) or "(none)" | ||
| 344 | + lines.append(f"- `{item['path']}` imports: {imports}") | ||
| 345 | + lines.append("") | ||
| 346 | + if report["static_no_fallback"]["violations"]: | ||
| 347 | + for item in report["static_no_fallback"]["violations"]: | ||
| 348 | + lines.append(f"- FAIL: {item}") | ||
| 349 | + else: | ||
| 350 | + lines.append("- PASS: production files do not import or reference torch_npu high-level ops, golden/reference code, upstream runtime packages, peer code, or other backend fallback.") | ||
| 351 | + | ||
| 352 | + lines.extend(["", "## 3. live 精度", ""]) | ||
| 353 | + live = report["live_cases"] | ||
| 354 | + if live.get("skipped"): | ||
| 355 | + lines.append(f"- SKIPPED: {live['reason']}") | ||
| 356 | + else: | ||
| 357 | + lines.append("| case | dtype | shape | tensor | max_abs | rel_rmse | MARE | MERE | RMSE | result |") | ||
| 358 | + lines.append("| ---: | --- | --- | --- | ---: | ---: | ---: | ---: | ---: | --- |") | ||
| 359 | + for item in live["cases"]: | ||
| 360 | + for r in item["results"]: | ||
| 361 | + lines.append( | ||
| 362 | + f"| {item['case_id']} | {item['dtype']} | `{item['shape']}` | {r['tensor']} " | ||
| 363 | + f"| {r['max_abs']:.10f} | {r['relative_rmse']:.10f} " | ||
| 364 | + f"| {r['mare']:.10f} | {r['mere']:.10f} | {r['rmse']:.10f} " | ||
| 365 | + f"| {'PASS' if r['accuracy_passed'] else 'FAIL'} |" | ||
| 366 | + ) | ||
| 367 | + | ||
| 368 | + boundary = report["known_boundary"] | ||
| 369 | + lines.extend([ | ||
| 370 | + "", | ||
| 371 | + "## 4. 已知边界", | ||
| 372 | + "", | ||
| 373 | + f"- arch: {boundary['arch']}", | ||
| 374 | + f"- operator_scope: {boundary['operator_scope']}", | ||
| 375 | + "- unsupported_scope: " + ", ".join(boundary["unsupported_scope"]), | ||
| 376 | + f"- known_limitation: {boundary.get('known_limitation', '(none)')}", | ||
| 377 | + ]) | ||
| 378 | + return "\n".join(lines) + "\n" | ||
| 379 | + | ||
| 380 | + | ||
| 381 | +def parse_args() -> argparse.Namespace: | ||
| 382 | + parser = argparse.ArgumentParser(description="Generate self-contained mamba3_siso_bwd review report.") | ||
| 383 | + parser.add_argument("--quick", action="store_true", help="Run only two representative cases.") | ||
| 384 | + parser.add_argument("--output", type=Path, default=None, help="Optional markdown output path.") | ||
| 385 | + parser.add_argument("--json", action="store_true", help="Print JSON instead of markdown.") | ||
| 386 | + return parser.parse_args() | ||
| 387 | + | ||
| 388 | + | ||
| 389 | +def main() -> int: | ||
| 390 | + args = parse_args() | ||
| 391 | + report = build_report(quick=args.quick) | ||
| 392 | + text = json.dumps(report, indent=2, ensure_ascii=False) if args.json else render_markdown(report) | ||
| 393 | + if args.output: | ||
| 394 | + args.output.write_text(text, encoding="utf-8") | ||
| 395 | + else: | ||
| 396 | + print(text, end="") | ||
| 397 | + return 0 if report["passed"] else 1 | ||
| 398 | + | ||
| 399 | + | ||
| 400 | +if __name__ == "__main__": | ||
| 401 | + raise SystemExit(main()) | ||
| @@ -0,0 +1,75 @@ | |||
| 1 | +import torch | ||
| 2 | + | ||
| 3 | +from atk.configs.dataset_config import InputDataset | ||
| 4 | +from atk.tasks.api_execute import register | ||
| 5 | +from atk.tasks.api_execute.base_api import BaseApi | ||
| 6 | +from atk.tasks.api_execute.triton_base_api import TritonBaseApi | ||
| 7 | +from mindspeed_ops.api.triton.mamba3_siso_bwd import compute_dqktheta, compute_ddt_dtrap_dinput_states | ||
| 8 | +from tests.atk_tests.triton.mamba3_siso_bwd.reference_impl import ( | ||
| 9 | + ref_compute_dqktheta, | ||
| 10 | + ref_compute_ddt_dtrap_dinput_states, | ||
| 11 | +) | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +def _get_dqktheta_inputs(input_data: InputDataset): | ||
| 15 | + kwargs = input_data.kwargs | ||
| 16 | + q = kwargs.get("q") | ||
| 17 | + k = kwargs.get("k") | ||
| 18 | + scale = kwargs.get("scale") | ||
| 19 | + gamma = kwargs.get("gamma") | ||
| 20 | + q_bias = kwargs.get("q_bias") | ||
| 21 | + k_bias = kwargs.get("k_bias") | ||
| 22 | + angles = kwargs.get("angles") | ||
| 23 | + dq_in = kwargs.get("dq_in") | ||
| 24 | + dk_in = kwargs.get("dk_in") | ||
| 25 | + dqk = kwargs.get("dqk") | ||
| 26 | + chunk_size = kwargs.get("chunk_size", 64) | ||
| 27 | + return q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk, chunk_size | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +def _get_ddt_inputs(input_data: InputDataset): | ||
| 31 | + kwargs = input_data.kwargs | ||
| 32 | + dscale = kwargs.get("dscale") | ||
| 33 | + dgamma = kwargs.get("dgamma") | ||
| 34 | + dt = kwargs.get("dt") | ||
| 35 | + trap = kwargs.get("trap") | ||
| 36 | + return dscale, dgamma, dt, trap | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + | ||
| 40 | +class TorchMamba3SisoBwdDqkthetaApi(BaseApi): | ||
| 41 | + def __call__(self, input_data: InputDataset, with_output: bool = False): | ||
| 42 | + q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk, chunk_size = _get_dqktheta_inputs(input_data) | ||
| 43 | + return ref_compute_dqktheta( | ||
| 44 | + q=q, k=k, scale=scale, gamma=gamma, q_bias=q_bias, k_bias=k_bias, | ||
| 45 | + angles=angles, dq_in=dq_in, dk_in=dk_in, dqk=dqk, chunk_size=chunk_size, | ||
| 46 | + ) | ||
| 47 | + | ||
| 48 | + | ||
| 49 | + | ||
| 50 | +class TritonMamba3SisoBwdDqkthetaApi(TritonBaseApi): | ||
| 51 | + def __call__(self, input_data: InputDataset, with_output: bool = False): | ||
| 52 | + q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk, chunk_size = _get_dqktheta_inputs(input_data) | ||
| 53 | + return compute_dqktheta( | ||
| 54 | + q=q, k=k, scale=scale, gamma=gamma, q_bias=q_bias, k_bias=k_bias, | ||
| 55 | + angles=angles, dq_in=dq_in, dk_in=dk_in, dqk=dqk, chunk_size=chunk_size, | ||
| 56 | + ) | ||
| 57 | + | ||
| 58 | + | ||
| 59 | + | ||
| 60 | +class TorchMamba3SisoBwdDdtApi(BaseApi): | ||
| 61 | + def __call__(self, input_data: InputDataset, with_output: bool = False): | ||
| 62 | + dscale, dgamma, dt, trap = _get_ddt_inputs(input_data) | ||
| 63 | + return ref_compute_ddt_dtrap_dinput_states( | ||
| 64 | + dscale=dscale, dgamma=dgamma, dt=dt, trap=trap, | ||
| 65 | + ) | ||
| 66 | + | ||
| 67 | + | ||
| 68 | + | ||
| 69 | +class TritonMamba3SisoBwdDdtApi(TritonBaseApi): | ||
| 70 | + def __call__(self, input_data: InputDataset, with_output: bool = False): | ||
| 71 | + dscale, dgamma, dt, trap = _get_ddt_inputs(input_data) | ||
| 72 | + ddt, dtrap, *_ = compute_ddt_dtrap_dinput_states( | ||
| 73 | + dscale=dscale, dgamma=dgamma, dt=dt, trap=trap, | ||
| 74 | + ) | ||
| 75 | + return ddt, dtrap | ||
| @@ -0,0 +1,262 @@ | |||||||||||||||
| 1 | +# Copyright (c) 2026, Huawei Technologies Co., Ltd. All rights reserved. | ||||||||||||||
| 2 | +# Copyright (c) 2025, Dao AI Lab, Goombalab | ||||||||||||||
| 3 | + | ||||||||||||||
| 4 | +import pytest | ||||||||||||||
| 5 | +import torch | ||||||||||||||
| 6 | + | ||||||||||||||
| 7 | +from mindspeed_ops.api.triton.mamba3_siso_bwd import compute_dqktheta, compute_ddt_dtrap_dinput_states | ||||||||||||||
| 8 | +from tests.utils import assert_close | ||||||||||||||
| 9 | + | ||||||||||||||
| 10 | + | ||||||||||||||
| 11 | +# ============================================================================= | ||||||||||||||
| 12 | +# Reference implementations | ||||||||||||||
| 13 | +# ============================================================================= | ||||||||||||||
| 14 | + | ||||||||||||||
| 15 | +def _apply_rotary(x: torch.Tensor, angles: torch.Tensor) -> torch.Tensor: | ||||||||||||||
| 16 | + """Apply rotary embedding to x using angles.""" | ||||||||||||||
| 17 | + out = x.to(torch.float32).clone() | ||||||||||||||
| 18 | + rotary_dim = min(angles.shape[-1], out.shape[-1] // 2) | ||||||||||||||
| 19 | + if rotary_dim <= 0: | ||||||||||||||
| 20 | + return out | ||||||||||||||
| 21 | + cos = torch.cos(angles[..., :rotary_dim].to(torch.float32)) | ||||||||||||||
| 22 | + sin = torch.sin(angles[..., :rotary_dim].to(torch.float32)) | ||||||||||||||
| 23 | + even = out[..., 0:2 * rotary_dim:2].clone() | ||||||||||||||
| 24 | + odd = out[..., 1:2 * rotary_dim:2].clone() | ||||||||||||||
| 25 | + out[..., 0:2 * rotary_dim:2] = even * cos - odd * sin | ||||||||||||||
| 26 | + out[..., 1:2 * rotary_dim:2] = even * sin + odd * cos | ||||||||||||||
| 27 | + return out | ||||||||||||||
| 28 | + | ||||||||||||||
| 29 | + | ||||||||||||||
| 30 | +def _inv_apply_rotary(x: torch.Tensor, angles: torch.Tensor) -> torch.Tensor: | ||||||||||||||
| 31 | + """Apply inverse rotary embedding to x using angles.""" | ||||||||||||||
| 32 | + out = x.to(torch.float32).clone() | ||||||||||||||
| 33 | + rotary_dim = min(angles.shape[-1], out.shape[-1] // 2) | ||||||||||||||
| 34 | + if rotary_dim <= 0: | ||||||||||||||
| 35 | + return out | ||||||||||||||
| 36 | + cos = torch.cos(angles[..., :rotary_dim].to(torch.float32)) | ||||||||||||||
| 37 | + sin = torch.sin(angles[..., :rotary_dim].to(torch.float32)) | ||||||||||||||
| 38 | + even = out[..., 0:2 * rotary_dim:2].clone() | ||||||||||||||
| 39 | + odd = out[..., 1:2 * rotary_dim:2].clone() | ||||||||||||||
| 40 | + out[..., 0:2 * rotary_dim:2] = even * cos + odd * sin | ||||||||||||||
| 41 | + out[..., 1:2 * rotary_dim:2] = -even * sin + odd * cos | ||||||||||||||
| 42 | + return out | ||||||||||||||
| 43 | + | ||||||||||||||
| 44 | + | ||||||||||||||
| 45 | +def ref_compute_dqktheta( | ||||||||||||||
| 46 | + q: torch.Tensor, | ||||||||||||||
| 47 | + k: torch.Tensor, | ||||||||||||||
| 48 | + scale: torch.Tensor, | ||||||||||||||
| 49 | + gamma: torch.Tensor, | ||||||||||||||
| 50 | + q_bias: torch.Tensor, | ||||||||||||||
| 51 | + k_bias: torch.Tensor, | ||||||||||||||
| 52 | + angles: torch.Tensor, | ||||||||||||||
| 53 | + dq_in: torch.Tensor, | ||||||||||||||
| 54 | + dk_in: torch.Tensor, | ||||||||||||||
| 55 | + dqk: torch.Tensor, | ||||||||||||||
| 56 | + chunk_size: int = 64, | ||||||||||||||
| 57 | +) -> tuple: | ||||||||||||||
| 58 | + """Reference implementation of compute_dqktheta.""" | ||||||||||||||
| 59 | + batch, seqlen, nheads_qk, headdim_qk = q.shape | ||||||||||||||
| 60 | + nheads = scale.shape[1] | ||||||||||||||
| 61 | + GQA_RATIO = nheads // nheads_qk | ||||||||||||||
| 62 | + work_dtype = torch.float32 | ||||||||||||||
| 63 | + | ||||||||||||||
| 64 | + dq = torch.zeros((batch, seqlen, nheads_qk, headdim_qk), device=q.device, dtype=work_dtype) | ||||||||||||||
| 65 | + dk = torch.zeros((batch, seqlen, nheads_qk, headdim_qk), device=k.device, dtype=work_dtype) | ||||||||||||||
| 66 | + dangles = torch.zeros((batch, seqlen, nheads, angles.shape[-1]), device=angles.device, dtype=work_dtype) | ||||||||||||||
| 67 | + dscale = torch.zeros((batch, nheads, seqlen), device=scale.device, dtype=work_dtype) | ||||||||||||||
| 68 | + dgamma = torch.zeros((batch, nheads, seqlen), device=gamma.device, dtype=work_dtype) | ||||||||||||||
| 69 | + dq_bias = torch.zeros((nheads, headdim_qk), device=q.device, dtype=work_dtype) | ||||||||||||||
| 70 | + dk_bias = torch.zeros((nheads, headdim_qk), device=k.device, dtype=work_dtype) | ||||||||||||||
| 71 | + | ||||||||||||||
| 72 | + for b_idx in range(batch): | ||||||||||||||
| 73 | + for qk_head_idx in range(nheads_qk): | ||||||||||||||
| 74 | + for gqa_idx in range(GQA_RATIO): | ||||||||||||||
| 75 | + nhead_idx = qk_head_idx * GQA_RATIO + gqa_idx | ||||||||||||||
| 76 | + | ||||||||||||||
| 77 | + q_pre = q[b_idx, :, qk_head_idx, :].to(work_dtype) + q_bias[nhead_idx].to(work_dtype) | ||||||||||||||
| 78 | + k_pre = k[b_idx, :, qk_head_idx, :].to(work_dtype) + k_bias[nhead_idx].to(work_dtype) | ||||||||||||||
| 79 | + dq_in_h = dq_in[b_idx, :, nhead_idx, :].to(work_dtype) | ||||||||||||||
| 80 | + dk_in_h = dk_in[b_idx, :, nhead_idx, :].to(work_dtype) | ||||||||||||||
| 81 | + dqk_h = dqk[b_idx, nhead_idx, :].to(work_dtype) | ||||||||||||||
| 82 | + scale_h = scale[b_idx, nhead_idx, :].to(work_dtype) | ||||||||||||||
| 83 | + gamma_h = gamma[b_idx, nhead_idx, :].to(work_dtype) | ||||||||||||||
| 84 | + angles_h = angles[b_idx, :, nhead_idx, :].to(work_dtype) | ||||||||||||||
| 85 | + | ||||||||||||||
| 86 | + # dGamma = dQK * (Q_wbias . K_wbias) | ||||||||||||||
| 87 | + qk_dot = (q_pre * k_pre).sum(dim=-1) | ||||||||||||||
| 88 | + dgamma[b_idx, nhead_idx, :] = dqk_h * qk_dot | ||||||||||||||
| 89 | + | ||||||||||||||
| 90 | + # cos/sin | ||||||||||||||
| 91 | + cos_ang = torch.cos(angles_h) | ||||||||||||||
| 92 | + sin_ang = torch.sin(angles_h) | ||||||||||||||
| 93 | + | ||||||||||||||
| 94 | + # dScale = sum(dK_in * K_rot) | ||||||||||||||
| 95 | + k_rot = _apply_rotary(k_pre, angles_h) | ||||||||||||||
| 96 | + dscale[b_idx, nhead_idx, :] = (dk_in_h * k_rot).sum(dim=-1) | ||||||||||||||
| 97 | + | ||||||||||||||
| 98 | + # Inverse rotary | ||||||||||||||
| 99 | + dk_in_scaled = dk_in_h * scale_h.unsqueeze(-1) | ||||||||||||||
| 100 | + dq_unrot = _inv_apply_rotary(dq_in_h, angles_h) | ||||||||||||||
| 101 | + dk_unrot = _inv_apply_rotary(dk_in_scaled, angles_h) | ||||||||||||||
| 102 | + | ||||||||||||||
| 103 | + # Add dQK path | ||||||||||||||
| 104 | + dqk_scaled = (dqk_h * gamma_h).unsqueeze(-1) | ||||||||||||||
| 105 | + dq_pre = dq_unrot + dqk_scaled * k_pre | ||||||||||||||
| 106 | + dk_pre = dk_unrot + dqk_scaled * q_pre | ||||||||||||||
| 107 | + | ||||||||||||||
| 108 | + # Accumulate GQA | ||||||||||||||
| 109 | + dq[b_idx, :, qk_head_idx, :] += dq_pre | ||||||||||||||
| 110 | + dk[b_idx, :, qk_head_idx, :] += dk_pre | ||||||||||||||
| 111 | + dq_bias[nhead_idx, :] += dq_pre.sum(dim=0) | ||||||||||||||
| 112 | + dk_bias[nhead_idx, :] += dk_pre.sum(dim=0) | ||||||||||||||
| 113 | + | ||||||||||||||
| 114 | + # dAngles | ||||||||||||||
| 115 | + rotary_dim = min(angles_h.shape[-1], headdim_qk // 2) | ||||||||||||||
| 116 | + if rotary_dim > 0: | ||||||||||||||
| 117 | + even_q = q_pre[:, 0:2 * rotary_dim:2] | ||||||||||||||
| 118 | + odd_q = q_pre[:, 1:2 * rotary_dim:2] | ||||||||||||||
| 119 | + even_k = k_pre[:, 0:2 * rotary_dim:2] | ||||||||||||||
| 120 | + odd_k = k_pre[:, 1:2 * rotary_dim:2] | ||||||||||||||
| 121 | + even_dq = dq_in_h[:, 0:2 * rotary_dim:2] | ||||||||||||||
| 122 | + odd_dq = dq_in_h[:, 1:2 * rotary_dim:2] | ||||||||||||||
| 123 | + even_dk = dk_in_scaled[:, 0:2 * rotary_dim:2] | ||||||||||||||
| 124 | + odd_dk = dk_in_scaled[:, 1:2 * rotary_dim:2] | ||||||||||||||
| 125 | + | ||||||||||||||
| 126 | + dtheta_q = even_dq * (-even_q * sin_ang - odd_q * cos_ang) + \ | ||||||||||||||
| 127 | + odd_dq * (even_q * cos_ang - odd_q * sin_ang) | ||||||||||||||
| 128 | + dtheta_k = even_dk * (-even_k * sin_ang - odd_k * cos_ang) + \ | ||||||||||||||
| 129 | + odd_dk * (even_k * cos_ang - odd_k * sin_ang) | ||||||||||||||
| 130 | + dangles[b_idx, :, nhead_idx, :rotary_dim] = dtheta_q + dtheta_k | ||||||||||||||
| 131 | + | ||||||||||||||
| 132 | + return dq, dk, dq_bias, dk_bias, dangles, dscale, dgamma | ||||||||||||||
| 133 | + | ||||||||||||||
| 134 | + | ||||||||||||||
| 135 | +def ref_compute_ddt_dtrap_dinput_states( | ||||||||||||||
| 136 | + dscale: torch.Tensor, | ||||||||||||||
| 137 | + dgamma: torch.Tensor, | ||||||||||||||
| 138 | + dt: torch.Tensor, | ||||||||||||||
| 139 | + trap: torch.Tensor, | ||||||||||||||
| 140 | +) -> tuple: | ||||||||||||||
| 141 | + """Reference implementation of compute_ddt_dtrap_dinput_states.""" | ||||||||||||||
| 142 | + batch, nheads, seqlen = dscale.shape | ||||||||||||||
| 143 | + work_dtype = torch.float32 | ||||||||||||||
| 144 | + | ||||||||||||||
| 145 | + dDT = torch.zeros_like(dt, dtype=work_dtype) | ||||||||||||||
| 146 | + dTrap = torch.zeros_like(trap, dtype=work_dtype) | ||||||||||||||
| 147 | + | ||||||||||||||
| 148 | + for b_idx in range(batch): | ||||||||||||||
| 149 | + for h_idx in range(nheads): | ||||||||||||||
| 150 | + dscale_h = dscale[b_idx, h_idx].to(work_dtype) | ||||||||||||||
| 151 | + dgamma_h = dgamma[b_idx, h_idx].to(work_dtype) | ||||||||||||||
| 152 | + dt_h = dt[b_idx, h_idx].to(work_dtype) | ||||||||||||||
| 153 | + trap_h = torch.sigmoid(trap[b_idx, h_idx].to(work_dtype)) | ||||||||||||||
| 154 | + | ||||||||||||||
| 155 | + for t in range(seqlen): | ||||||||||||||
| 156 | + dscale_prev = dscale_h[t - 1] if t > 0 else torch.tensor(0.0, device=dt.device, dtype=work_dtype) | ||||||||||||||
| 157 | + ddt_t = (dgamma_h[t] + dscale_h[t]) * trap_h[t] + dscale_prev * (1.0 - trap_h[t]) | ||||||||||||||
| 158 | + dtrap_t = (dgamma_h[t] + dscale_h[t]) * dt_h[t] - dscale_prev * dt_h[t] | ||||||||||||||
| 159 | + dtrap_presig_t = dtrap_t * trap_h[t] * (1.0 - trap_h[t]) | ||||||||||||||
| 160 | + | ||||||||||||||
| 161 | + dDT[b_idx, h_idx, t] = ddt_t | ||||||||||||||
| 162 | + dTrap[b_idx, h_idx, t] = dtrap_presig_t | ||||||||||||||
| 163 | + | ||||||||||||||
| 164 | + return dDT, dTrap | ||||||||||||||
| 165 | + | ||||||||||||||
| 166 | + | ||||||||||||||
| 167 | +def make_inputs(batch, seqlen, nheads_qk, nheads, headdim_qk, dtype, device): | ||||||||||||||
| 168 | + """Create test inputs for the backward operators.""" | ||||||||||||||
| 169 | + torch.manual_seed(2026 + batch + seqlen + nheads + headdim_qk) | ||||||||||||||
| 170 | + headdim_angles = headdim_qk // 2 | ||||||||||||||
| 171 | + | ||||||||||||||
| 172 | + q = (torch.rand(batch, seqlen, nheads_qk, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.4 | ||||||||||||||
| 173 | + k = (torch.rand(batch, seqlen, nheads_qk, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.4 | ||||||||||||||
| 174 | + scale = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 0.5 + 0.5 | ||||||||||||||
| 175 | + gamma = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 0.3 | ||||||||||||||
| 176 | + q_bias = (torch.rand(nheads, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.1 | ||||||||||||||
| 177 | + k_bias = (torch.rand(nheads, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.1 | ||||||||||||||
| 178 | + angles = torch.rand(batch, seqlen, nheads, headdim_angles, device=device, dtype=torch.float32) * 0.25 | ||||||||||||||
| 179 | + dq_in = (torch.rand(batch, seqlen, nheads, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.4 | ||||||||||||||
| 180 | + dk_in = (torch.rand(batch, seqlen, nheads, headdim_qk, device=device, dtype=dtype) - 0.5) * 0.4 | ||||||||||||||
| 181 | + dqk = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 0.2 | ||||||||||||||
| 182 | + dt = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 0.1 | ||||||||||||||
| 183 | + trap = torch.rand(batch, nheads, seqlen, device=device, dtype=torch.float32) * 2.0 - 1.0 | ||||||||||||||
| 184 | + | ||||||||||||||
| 185 | + return q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk, dt, trap | ||||||||||||||
| 186 | + | ||||||||||||||
| 187 | + | ||||||||||||||
| 188 | +# ============================================================================= | ||||||||||||||
| 189 | +# Test cases | ||||||||||||||
| 190 | +# ============================================================================= | ||||||||||||||
| 191 | + | ||||||||||||||
| 192 | +TEST_CASES_DQKTHETA = [ | ||||||||||||||
| 193 | + pytest.param(1, 32, 1, 1, 64, torch.float16, 64, id="fp16_small"), | ||||||||||||||
| 194 | + pytest.param(1, 64, 1, 2, 64, torch.float16, 64, id="fp16_gqa"), | ||||||||||||||
| 195 | + pytest.param(1, 96, 1, 2, 64, torch.bfloat16, 64, id="bf16_multi_chunk"), | ||||||||||||||
| 196 | + pytest.param(2, 64, 2, 4, 64, torch.float16, 32, id="fp16_batch2"), | ||||||||||||||
| 197 | +] | ||||||||||||||
| 198 | + | ||||||||||||||
| 199 | +TEST_CASES_DDT = [ | ||||||||||||||
| 200 | + pytest.param(1, 32, 1, 1, 64, torch.float16, 64, id="ddt_small"), | ||||||||||||||
| 201 | + pytest.param(1, 64, 1, 2, 64, torch.float16, 64, id="ddt_gqa"), | ||||||||||||||
| 202 | + pytest.param(2, 96, 1, 2, 64, torch.bfloat16, 64, id="ddt_bf16_batch2"), | ||||||||||||||
| 203 | +] | ||||||||||||||
| 204 | + | ||||||||||||||
| 205 | + | ||||||||||||||
| 206 | + | ||||||||||||||
| 207 | + "batch,seqlen,nheads_qk,nheads,headdim_qk,dtype,chunk_size", | ||||||||||||||
| 208 | + TEST_CASES_DQKTHETA, | ||||||||||||||
| 209 | +) | ||||||||||||||
| 210 | +def test_compute_dqktheta(batch, seqlen, nheads_qk, nheads, headdim_qk, dtype, chunk_size): | ||||||||||||||
| 211 | + if not hasattr(torch, "npu") or not torch.npu.is_available(): | ||||||||||||||
| 212 | + pytest.skip("NPU is not available") | ||||||||||||||
| 213 | + device = torch.device("npu") | ||||||||||||||
| 214 | + q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk, dt, trap = make_inputs( | ||||||||||||||
| 215 | + batch, seqlen, nheads_qk, nheads, headdim_qk, dtype, device | ||||||||||||||
| 216 | + ) | ||||||||||||||
| 217 | + | ||||||||||||||
| 218 | + actual = compute_dqktheta( | ||||||||||||||
| 219 | + q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk, | ||||||||||||||
| 220 | + chunk_size=chunk_size, | ||||||||||||||
| 221 | + ) | ||||||||||||||
| 222 | + | ||||||||||||||
| 223 | + expected = ref_compute_dqktheta( | ||||||||||||||
| 224 | + q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk, chunk_size=chunk_size, | ||||||||||||||
| 225 | + ) | ||||||||||||||
| 226 | + | ||||||||||||||
| 227 | + for i, (name, act, exp) in enumerate(zip( | ||||||||||||||
| 228 | + ["dq", "dk", "dangles", "dscale", "dgamma"], | ||||||||||||||
| 229 | + actual[:5], | ||||||||||||||
| 230 | + expected[:5], | ||||||||||||||
| 231 | + )): | ||||||||||||||
| 232 | + assert_close(f"compute_dqktheta_{name}", exp, act, 1e-3, err_atol=1e-3) | ||||||||||||||
🟡 Medium Priority changed line: affected behavior: failure mode: 若 kernel 内 dq_bias/dk_bias 计算有误(如 stride 错误、GQA 分组索引偏移),用户在生产环境中得到的 bias 梯度将是错误的,但所有测试仍会 PASS。 suggested fix: 在 建议:在 test_compute_dqktheta 和 self_check 的 _run_dqktheta_case 中补齐 dq_bias 和 dk_bias 的精度校验 ![]() ![]() 不准确? 🟡 Medium Priority changed line:
affected behavior: dscale 和 dgamma 是 kernel 的关键输出,它们直接作为 failure mode: dscale/dgamma 的错误会传播到 ddt/dtrap 计算,但测试无法发现。自证报告中的 "dscale"/"dgamma" 行实际展示的是 dk_bias/dangles 精度,造成虚假信心。 suggested fix: 修正为 建议:修正名称列表与实际返回值对齐,补齐 dscale 和 dgamma 的精度校验 改动建议
![]() ![]() 不准确? | |||||||||||||||
| 233 | + | ||||||||||||||
| 234 | + | ||||||||||||||
| 235 | + | ||||||||||||||
| 236 | + "batch,seqlen,nheads_qk,nheads,headdim_qk,dtype,chunk_size", | ||||||||||||||
| 237 | + TEST_CASES_DDT, | ||||||||||||||
| 238 | +) | ||||||||||||||
| 239 | +def test_compute_ddt_dtrap(batch, seqlen, nheads_qk, nheads, headdim_qk, dtype, chunk_size): | ||||||||||||||
| 240 | + if not hasattr(torch, "npu") or not torch.npu.is_available(): | ||||||||||||||
| 241 | + pytest.skip("NPU is not available") | ||||||||||||||
| 242 | + device = torch.device("npu") | ||||||||||||||
| 243 | + q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk, dt, trap = make_inputs( | ||||||||||||||
| 244 | + batch, seqlen, nheads_qk, nheads, headdim_qk, dtype, device | ||||||||||||||
| 245 | + ) | ||||||||||||||
| 246 | + | ||||||||||||||
| 247 | + # First compute dqktheta to get dscale and dgamma | ||||||||||||||
| 248 | + dq, dk, dq_bias_out, dk_bias_out, dangles, dscale, dgamma = compute_dqktheta( | ||||||||||||||
| 249 | + q, k, scale, gamma, q_bias, k_bias, angles, dq_in, dk_in, dqk, | ||||||||||||||
| 250 | + chunk_size=chunk_size, | ||||||||||||||
| 251 | + ) | ||||||||||||||
| 252 | + | ||||||||||||||
| 253 | + actual_ddt, actual_dtrap, *_ = compute_ddt_dtrap_dinput_states( | ||||||||||||||
| 254 | + dscale, dgamma, dt, trap, | ||||||||||||||
| 255 | + ) | ||||||||||||||
| 256 | + | ||||||||||||||
| 257 | + expected_ddt, expected_dtrap = ref_compute_ddt_dtrap_dinput_states( | ||||||||||||||
| 258 | + dscale, dgamma, dt, trap, | ||||||||||||||
| 259 | + ) | ||||||||||||||
| 260 | + | ||||||||||||||
| 261 | + assert_close("compute_ddt_dtrap_ddt", expected_ddt, actual_ddt, 1e-2, err_atol=1e-2) | ||||||||||||||
| 262 | + assert_close("compute_ddt_dtrap_dtrap", expected_dtrap, actual_dtrap, 1e-2, err_atol=1e-2) | ||||||||||||||


🟡 Medium Priority
changed line:
generate_mamba3_siso_bwd.py第 17 行[64, 128]以及第 27 行_build_pool仅过滤nheads % nheads_qk == 0,未过滤headdim_qk == 128。affected behavior:
Mamba3SisoBwdDqkthetaGenerator的CASES和_build_pool会生成headdim_qk=128的用例。YAML 中dtype_numbers=10配合 2 种 dtype 共产生 20 个用例,按生成顺序前 20 个中有约一半是headdim_qk=128。failure mode: 文档(
docs/triton/mamba3_siso_bwd.md第 207 行及self_check_mamba3_siso_bwd.py第 306 行)明确声明headdim_qk=128在 NPU 上因 UB 溢出暂不支持,当前 PR 仅覆盖headdim_qk=64。ATK 框架执行这些用例时,kernel 会因 NPU UB 溢出而崩溃或产生错误结果,导致 ATK 测试大量失败。trigger condition: 当 ATK 框架按
mamba3_siso_bwd.yaml配置运行generate_mamba3_siso_bwd_dqktheta生成器时必然触发。suggested fix: 在
_build_pool中增加case[4] == 64过滤条件,同步在after_case_config的 else 分支中将random.choice([64, 128])改为64。建议:在
_build_pool的列表推导中增加case[4] == 64过滤条件,并在 else 分支中将headdim_qk固定为 64。具体修改:第 27 行改为return [case for case in cls.CASES if case[3] % case[2] == 0 and case[4] == 64];第 42 行改为headdim_qk = 64。