Files
matmul-analysis/Matmul/3_源码对比/3.1_mat_mul_v3源码解析.html

409 lines
41 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>mat_mul_v3 算子源码分析 —— 分支决策与 tiling/swizzle 实现</title>
<style>
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; max-width: 980px; margin: 40px auto; padding: 0 24px; line-height: 1.75; color: #1f2328; background:#fff; }
h1 { font-size: 28px; border-bottom: 2px solid #d0d7de; padding-bottom: 12px; }
h2 { font-size: 22px; border-bottom: 1px solid #d0d7de; padding-bottom: 8px; margin-top: 36px; }
h3 { font-size: 18px; margin-top: 28px; }
code { background:#f6f8fa; padding: 2px 6px; border-radius: 4px; font-family: "SF Mono", Consolas, monospace; font-size: 0.9em; color:#c7254e; }
pre { background:#f6f8fa; padding: 16px; border-radius: 8px; overflow-x:auto; }
pre code { background:none; padding:0; color:#24292e; }
table { border-collapse: collapse; width: 100%; margin: 16px 0; font-size: 14px; }
th, td { border: 1px solid #d0d7de; padding: 8px 12px; text-align: left; vertical-align: top; }
th { background:#f6f8fa; font-weight: 600; }
blockquote { border-left: 4px solid #d0d7de; margin: 16px 0; padding: 4px 16px; color:#57606a; background:#f6f8fa; }
hr { border:none; border-top:1px solid #d0d7de; margin: 24px 0; }
li { margin: 4px 0; }
strong { color:#0a3069; }
</style>
</head><body><h1>mat_mul_v3 算子源码分析 —— 分支决策与 tiling/swizzle 实现</h1>
</blockquote>
<hr/>
<h2>0. 源码结构总览</h2>
<p>mat_mul_v3 采用 <strong>Host TilingCPU+ Device KernelNPU</strong> 两层结构,中间以 <strong>tiling key位域编码+ tiling data参数结构体</strong> 传递决策:</p>
<pre><code>┌─────────────────────────── HostCPU 侧,算子启动前执行一次)───────────────────────────┐
│ MatMulV3Tiling::DoTiling() │
│ └─ MMTilingRegistry::DoTilingImpl() 按 priority 依次尝试各 tiling 类(策略模式) │
│ ├─ MatMulV3KEqZeroTiling (priority 0) │
│ ├─ MatMulV3ToMulTiling (priority 1) │
│ ├─ MatMulV3ToVectorTiling (priority 2) │
│ ├─ MatMulV3BasicStreamKTiling (priority 3) │
│ ├─ MatMulV3BasicAswtTiling (priority 4) │
│ └─ MatMulV3AswTiling (priority 999, 兜底) │
│ 每个类IsCapable() 判条件 → DoOpTiling() 算参数 → 编码 tiling key + tiling data │
└─────────────────────────────────────────────────────────────────────────────────────────┘
│ tiling key分支选择结果+ tiling data参数
┌─────────────────────────── DeviceNPU 侧,每个核独立执行)────────────────────────────┐
│ mat_mul_v3() 入口if constexpr 按 (ApiLevel, FullLoad, Model, L0C2Out) 四元组分发 │
│ ├─ MatMulInputKEqZeroClearOutput K==0 清零 │
│ ├─ MatMulToMulActKernel / MatMulToVectorActKernel 退化向量乘 │
│ ├─ MatMulStreamKKernel / MatMulStreamKSplitKKernel StreamK 切 K │
│ ├─ MatMulBasicKernel / MatMulBasicSplitKKernel ASWT 基础模板 │
│ ├─ MatMulAL1FullLoadKernel / MatMulBL1FullLoadKernel A/B 全载 │
│ └─ MatMulFixpipeOptiTensorKernel Fixpipe 随路搬出优化 │
└─────────────────────────────────────────────────────────────────────────────────────────┘</code></pre>
<p><strong>关键枚举</strong><code>mat_mul_v3_tiling_key_public.h</code></p>
<table><thead><tr><th>维度</th><th>枚举值</th><th>含义</th></tr></thead><tbody>
<tr><td>MatMulV3Model</td><td>BASIC=0 / STREAM_K=1 / K_EQUAL_ZERO=2 / TO_MUL=3 / TO_MULTI_MUL=4 / SLICE=5 / BASIC_SPLIT_K=6 / SK_SPLIT_K=7</td><td>主计算模型</td></tr>
<tr><td>MatMulV3FullLoad</td><td>NONE=0 / A_FULL_LOAD=1 / B_FULL_LOAD=2 / AB_FULL_LOAD=3</td><td>L1 全载模式</td></tr>
<tr><td>MatMulV3L0C2Out</td><td>ON_THE_FLY=0 / ND_FIXPIPE_1_1=1 / ND_FIXPIPE_1_2=2</td><td>L0C 输出方式</td></tr>
<tr><td>MatMulV3ApiLevel</td><td>HIGH=0 / BASIC=1 / TENSOR=2</td><td>编程接口层级kernel 实际用 BASIC/TENSOR</td></tr>
</tbody></table>
<p><strong>贯穿全文的符号约定(维度标注)</strong></p>
<table><thead><tr><th>符号</th><th>维度</th><th>含义</th></tr></thead><tbody>
<tr><td><code>M, N, K</code></td><td>元素个数</td><td>矩阵 <code>C[M,N] = A[M,K] · B[K,N]</code> 的三维</td></tr>
<tr><td><code>dtypeA, dtypeB</code></td><td>字节/元素</td><td>A、B 的 dtype 字节数FP16/BF16=2FP32=4</td></tr>
<tr><td><code>baseM, baseN, baseK</code></td><td>元素个数</td><td>L0 分块尺寸L0A/L0B 一次装的数据块),对齐 16</td></tr>
<tr><td><code>singleCoreM/N/K</code></td><td>元素个数</td><td>单核负责的 M/N/K 范围(= base 或更大)</td></tr>
<tr><td><code>mL1, nL1, kL1</code></td><td>元素个数</td><td>L1 中的分块尺寸(= singleCore 或 base见各分支</td></tr>
<tr><td><code>stepKa, stepKb</code></td><td>无量纲(个数)</td><td>K 方向 L1 分片步数K 在 L1 中切几段)</td></tr>
<tr><td><code>depthA1, depthB1</code></td><td>无量纲(个数)</td><td>L1 流水深度A/B 在 L1 中缓存的 tile 层数)</td></tr>
<tr><td><code>mCnt, nCnt</code></td><td>无量纲(个数)</td><td>M/N 方向 base 块个数 <code>mCnt=⌈M/baseM⌉</code></td></tr>
<tr><td><code>aicNum, aivNum</code></td><td></td><td>AIC 核数=32、AIV 核数=64</td></tr>
</tbody></table>
<p><strong>关键硬件规格(原理性推导的输入,来自 950PR 架构解读)</strong></p>
<table><thead><tr><th>规格</th><th>数值</th><th>推导用途</th></tr></thead><tbody>
<tr><td>Cube 每拍 MAC 数</td><td>16×16×16 = 4096 MAC = 8192 FLOP</td><td>单 AIC BF16 算力 = 1.65GHz × 8192 ≈ <strong>13.5 TFLOPS</strong></td></tr>
<tr><td>L0A / L0B / L0C / L1 / UB</td><td>64KB / 64KB / 256KB / 512KB / 512KB</td><td>分块尺寸上限(<code>baseM·baseN·4B ≤ L0C</code> → baseM=baseN=256 上限)</td></tr>
<tr><td>MTE1 / MTE3</td><td>256 BPC ≈ 422.4 GB/s单 AIC</td><td>核内 L1→L0、L0C→GM 搬移耗时</td></tr>
<tr><td>GM / L2 带宽</td><td>1.6 TB/s / 5.2 TB/s</td><td>MTE2 综合带宽(重复读走 L2 才喂饱 Cube</td></tr>
<tr><td>单 AIC 分摊 HBM</td><td>1.6TB/s ÷ 32 ≈ 50 GB/s</td><td>算术强度要求 = 13.5T/50G ≈ <strong>270 FLOP/Byte</strong>(带宽极紧张)</td></tr>
</tbody></table>
<hr/>
<h2>1. 分支决策总表DAV_3510 = 950PR</h2>
<p>策略优先级定义在 <code>matmul_v3_tiling_strategy.h</code></p>
<pre><code>{ NpuArch::DAV_3510, {K_EQUAL_ZERO, TO_MUL, TO_MULTI_MUL, BASIC_STREAM_K, BASIC_ASWT} }</code></pre>
<p><code>MMTilingRegistry::DoTilingImpl</code> 按 priority 顺序逐个 <code>IsCapable() + DoTiling()</code><strong>第一个成功者胜出</strong>(每个 tiling 类的 <code>DoTiling</code> 返回失败则尝试下一优先级)。</p>
<table><thead><tr><th>Priority</th><th>分支</th><th>Model 枚举</th><th>触发条件IsCapable</th><th>Device Kernel</th></tr></thead><tbody>
<tr><td>0</td><td>K_EQUAL_ZERO</td><td>K_EQUAL_ZERO(2)</td><td>无 bias 且 <code>K==0</code></td><td>MatMulInputKEqZeroClearOutput</td></tr>
<tr><td>1</td><td>TO_MUL</td><td>TO_MUL(3)</td><td>高精度FP32 ∧ 非slice ∧ (M==1 N==1) ∧ A/B 均 FP32</td><td>MatMulToMulActKernel</td></tr>
<tr><td>2</td><td>TO_MULTI_MUL</td><td>TO_MULTI_MUL(4)</td><td>高精度FP32 ∧ 非slice ∧ <code>!ATrans∧BTrans</code> ∧ 无bias ∧ FP32</td><td>MatMulToVectorActKernel</td></tr>
<tr><td>3</td><td>BASIC_STREAM_K</td><td>STREAM_K(1)/SK_SPLIT_K(7)</td><td>deterministic≤1 ∧ A为ND ∧ 非slice ∧ aivNum==2·aicNum ∧ (SKDPSK)</td><td>MatMulStreamKKernel 等</td></tr>
<tr><td>4</td><td>BASIC_ASWT</td><td>BASIC(0)/SLICE(5)/BASIC_SPLIT_K(6)</td><td>无条件(主分支)</td><td>MatMulBasicKernel 等(含全载/fixpipe 子分支)</td></tr>
<tr><td>999</td><td>BASE(ASW)</td><td>BASIC(0)</td><td>无条件(最终兜底,老 Cmct 接口)</td><td>MatMulActKernel</td></tr>
</tbody></table>
</blockquote>
<hr/>
<h2>2. 各分支详细分析</h2>
</blockquote>
<hr/>
<h3>2.1 K_EQUAL_ZEROpriority 0</h3>
<p><strong>触发条件</strong><code>!hasBias &amp;&amp; K == 0</code></p>
<p><strong>原理</strong>K=0 时 <code>C[i,j] = 0</code>,输出恒为全零矩阵。若走 CubeMMAD 对空 K 做无效计算;而 64 个 AIV 是天然的并行批量写单元,直接对 <code>C</code><code>M×N</code> 个元素清零即可。</p>
<p><strong>实现</strong><code>matmul_v3_k_equal_zero_tiling.cpp</code> + <code>mat_mul_input_k_eq_zero_clear_output.h</code></p>
<pre><code>totalDataAmount = M · N // 需清零的元素总数(元素个数)
usedCoreNum = aivNum // 64 个 AIV 并行</code></pre>
<p>Device 侧 <code>MatMulInputKEqZeroClearOutput(biasGM, cGM, tilingData)</code>:每个 AIV 负责一段连续的 <code>C</code> 地址,一次搬移清零。数据流极简:</p>
<pre><code>AIV ──(写0)──► GM(C) // 无 GM 读、无 Cube 参与</code></pre>
<p><strong>评价</strong>:正确且高效(不浪费 Cube、不产生 GM 读流量)。适用面窄(仅 K==0 且无 bias</p>
<hr/>
<h3>2.2 TO_MULpriority 1—— M==1 或 N==1 的 FP32 退化向量乘</h3>
<p><strong>触发条件</strong><code>isForceGrpAccForFp32</code>op_impl_mode_enum==0x4 高精度 FP32∧ 非 slice ∧ (M==1 N==1) ∧ dtypeA==dtypeB==FP32。</p>
<p><strong>原理(结合硬件)</strong>Cube 的 MMAD 指令最小分形是 <strong>16×16×16</strong>。当 <code>M==1</code>MMAD 的 M 维 16 个 lane 只能用 1 个 → <strong>Cube 算力利用率仅 1/16 ≈ 6%</strong>。此时 <code>[1,K]·[K,N]</code> 本质是<strong>一个 K 维向量与 N 个 K 维向量的点积</strong>,用 AIV 的向量乘加(<code>Mul+ReduceSum</code>)天然适合,且 64 个 AIV 的 FP32 算力 27 TFLOPS 足够覆盖。</p>
<p><strong>实现</strong><code>matmul_v3_to_mul_tiling.cpp</code> + <code>mat_mul_to_mul_cmct.h</code>数据流为「GM→UB→AIV 向量乘→UB→GM」全程不走 Cube</p>
<pre><code>GM(A[M=1,K]) ──MTE2──► UB ──AIV向量乘加──► UB ──MTE3──► GM(C[N])
GM(B[K,N]) ──MTE2──► UB</code></pre>
<p>tiling 参数由 <strong>UB 容量</strong><code>ubSize/4</code> 个 float反推</p>
<pre><code>tmpMN = ⌈(M·N) / aivNum⌉ // 每核摊到的 MN 元素数(元素个数)
baseMN = min(Align(tmpMN, 对齐), BASE_MN) // MN 方向分块(元素个数)
baseK = (ubCount - baseMN - biasCount)/5/baseMN // K 方向分块,受 UB 容量约束(元素个数)
loopK = ⌈K / baseK⌉ // K 循环次数(无量纲)
usedCoreNum = min(⌈M·N/baseMN⌉, aivNum) // 实际核数</code></pre>
<p><code>dataCopyMode</code> 区分「K 内轴连续((K,N)/(K,M) 布局」还是「MN 内轴连续」,决定 baseK/baseMN 的取舍顺序。</p>
<p><strong>评价</strong>:对退化场景绕开 Cube 走 AIV 正确。局限:① 仅覆盖高精度 FP32<strong>FP16/BF16 的 M==1/N==1 无退化分支、仍走 Cube</strong>§6 改进点);② TO_MULTI_MUL 不支持 bias。</p>
<hr/>
<h3>2.3 TO_MULTI_MULpriority 2—— 特定转置组合的 FP32 退化向量乘</h3>
<p><strong>触发条件</strong>高精度FP32 ∧ 非slice ∧ <code>!ATrans ∧ BTrans</code> ∧ 无bias ∧ FP32。</p>
<p><strong>原理</strong>:与 TO_MUL 同(退化向量乘),但锁定 <code>!ATrans∧BTrans</code>A 行主序K 连续、B 列主序K 连续),<strong>内积方向 K 两侧都连续</strong>,向量乘访存顺序、无需 gather。</p>
<p><strong>实现</strong><code>matmul_v3_to_multi_mul_tiling.cpp</code> + <code>mat_mul_to_multi_mul_cmct.h</code>同样「GM→UB→AIV 向量乘→UB→GM」。核数分配用 <code>CalcBasicBlock()</code> 启发式均衡:</p>
<pre><code>mCore = ⌈M/baseM⌉, nCore = ⌈N/baseN⌉
if (mCore·nCore &lt; aivNum) CalcBasicBlock() // 块数不足分满核时重调 baseM/baseN
while (baseN ≥ 2·baseM ∧ nCore &lt; aivNum/2): // N 侧块太多 → nCore·=2, baseN=⌈N/nCore⌉
while (baseM ≥ 2·baseN ∧ mCore &lt; aivNum/2): // M 侧块太多 → mCore·=2, baseM=⌈M/mCore⌉
usedCoreNum = min(mCore·nCore, aivNum)</code></pre>
<p><strong>评价</strong>:与 TO_MUL 同族但更窄(限转置、无 bias。共同原则<strong>矩阵乘退化到 M==1 或 N==1 时,绕开 Cube 走 AIV</strong>,规避 Cube 16 对齐分形浪费。</p>
<hr/>
<h3>2.4 BASIC_STREAM_Kpriority 3—— 切 K 满核</h3>
<h4>2.4.0 解决的问题(硬件原理)</h4>
<p><strong>核心矛盾</strong>baseM=baseN=256 时,若 M、N 较小(如 M=N=256<code>mCnt·nCnt = 1×1 = 1</code> 个 MN 块,但 <code>aicNum=32</code><strong>只有 1 个核工作、31 核空闲</strong>,算力利用率 1/32。950PR「算力/带宽比极高」架构下,大 K 瘦高形状(如 <code>[256, 8192]×[8192, 256]</code>)是典型 caseMN 方向分不满核,但 K 很大、总计算量足够喂饱 32 核。</p>
<p><strong>解法</strong>:把 K 轴切成 <code>kCnt</code> 份分给多个核,每个核算 <code>M×N×singleCoreK</code><strong>部分和</strong>,最后跨核累加。有效并行度变为 <code>mCnt·nCnt·kCnt</code></p>
<pre><code>普通 ASWTM=N=256, K=8192kCnt=1:
┌────────────────────────────────────────────┐
│ 核0: [256×256] × [256×8192] (算完整 K) │ ← 只有 1 核31 核空闲
└────────────────────────────────────────────┘
StreamK切 KkCnt=32:
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 核0: K[0:256] │ │ 核1: K[256:512]│ ... │ 核31: K[...] │ ← 32 核各算一段 K
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
└──────────────────┴──────────────────────────┘
▼ 核间累加workspaceAIV 顺序累加)
C[M,N] = Σ_k 部分和</code></pre>
<h4>2.4.1 分支结构(含 3 个子模式)</h4>
<p><strong>IsCapable 前置条件</strong><code>MatMulV3BasicStreamKTiling::IsCapable</code></p>
<pre><code>deterministicLevel ≤ 1 // 切 K 核间累加顺序不确定,强一致性场景禁用整分支
∧ aFormat == ND // 左矩阵必须 ND
∧ 非 slice // 不支持非连续视图
∧ aivNum == 2·aicNum // 必须 AIC:AIV = 1:2
∧ (CheckStreamKSKTiling() CheckStreamKDPSKTiling())</code></pre>
<p><strong>子模式 ①SK纯切 K</strong> —— <code>CheckStreamKSKTilingDav3510</code></p>
<pre><code>align(K, 256) &gt;= max(8192, aicNum·256B) / dtypeA // K 足够大
mCnt · nCnt &lt;= aicNum / 2 // MN 块数不超过核数一半</code></pre>
<p>语义M/N 小到「MN 块数 ≤ 核数一半」K 大 → 全 K 切分,每核算一段 K。</p>
<p>FP32 且非 hf32 时,<code>alignValue</code> 从 256 退化为 32<code>BLOCK_BYTE_SIZE</code>)。</p>
<p><strong>子模式 ②DPSK数据并行 StreamK</strong> —— <code>CheckStreamKDPSKTilingDav3510</code></p>
<pre><code>M % 256 == 0 且 N % 256 == 0 // MN 严格 256 对齐
K &gt;= max(8192, aicNum·128B) / dtypeA // K 足够大
totalMNCnt &gt;= aicNum // MN 块数能分满核(主轮)
totalMNCnt % aicNum != 0 // 但有尾轮余数
(totalMNCnt % aicNum) &lt;= aicNum/2 // 余数不超过核数一半</code></pre>
<p>语义:<strong>主轮 MN 块数 ≥ 核数DP 满核、K 不分片),尾轮剩余 MN 块 &lt; 核数</strong> → 尾轮把 K 切开分给空闲核,且提前执行让 AIV 累加与 AIC 下一轮计算重叠(数据并行)。</p>
<p><strong>子模式 ③SK_SPLIT_K</strong>SK/DPSK 基础上,若 <code>FP32 ∧ singleCoreK ≥ FP32_SPLIT_K_THRESHOLD</code>,则单核内再切 K<code>splitSingleK</code>,保 FP32 累加精度model 由 <code>STREAM_K</code> 升级为 <code>SK_SPLIT_K</code></p>
<h4>2.4.2 tiling 参数推导(<code>DoOpTiling</code></h4>
<pre><code>baseM, baseN // 由 ResetBase + 后续逻辑给出§3.2),通常 256
mCnt = ⌈M/baseM⌉, nCnt = ⌈N/baseN⌉
totalMNCnt = mCnt · nCnt
if (totalMNCnt &lt;= aicNum/2): // 纯 SK 模式
调整 mCnt/nCnt 到 [aicNum/3, aicNum/2] 区间 // 避免切得过碎
baseM = Align(⌈M/mCnt⌉, 16); baseN = Align(⌈N/nCnt⌉, 16)
kCnt = ⌊aicNum / totalMNCnt⌋ // 每个 MN 块分配多少核切 K
singleCoreK = ⌈K / kCnt⌉ // 单核 K 分片(元素个数)
else: // DPSK 模式(尾轮切 K
kCnt = aicNum / (totalMNCnt % aicNum)
singleCoreK = ⌈K / kCnt⌉
kCnt = ⌈K / singleCoreK⌉ // 回算实际切分数
// baseK受 L0A 容量约束§3.3
kValueMax = ⌊L0A/DB/dtypeA / max(baseM, baseN)⌋ (按 128B/dtype 或 16 对齐)
baseK = min(singleCoreK, kValueMax)
CalL1Tiling(...) // 复用 §3.4 计算 stepKa/stepKb
workspace = aicNum·256·256·4B + 20·MB // 核间累加工作区(字节)</code></pre>
<p><strong>L0C2Out</strong>N 大且 N 不对齐(<code>N&gt;64 ∧ N%16≠0 ∧ M&gt;2 ∧ M·N≥256</code>)→ <code>ND_FIXPIPE_1_2</code>,否则 <code>ON_THE_FLY</code>(同 §2.5.4 的 fixpipe 逻辑)。</p>
<h4>2.4.3 Device 数据流(<code>block_scheduler_streamk.h</code> + StreamK kernel</h4>
<p>StreamK 的 L1 只装一个 base 块(<code>mL1=baseM, nL1=baseN</code>),核间通过 workspace 累加:</p>
<pre><code>DP 主轮(每核一个 MN 块,算完整 K:
GM(A块) ─MTE2─► L1 ─MTE1─► L0A ─┐
├─MMAD─► L0C ─Fixpipe─► GM(C块) // 直接写结果
GM(B块) ─MTE2─► L1 ─MTE1─► L0B ─┘
SK 尾轮(每核一个 MN 块的一段 K:
GM(A块) ─► L1 ─► L0A ─┐
├─MMAD─► L0C ─Fixpipe─► workspace(部分和) // 写部分和
GM(B块) ─► L1 ─► L0B ─┘
然后 AIV 从 workspace 顺序累加Atomic/确定性累加)─► GM(C块)</code></pre>
<p><code>CheckIsSkScene(tileIdx)</code> 判定当前核索引属于 DP 轮还是 SK 轮:<code>ceil((tileIdx+1)/usedCoreNum) == ceil(tileNum/usedCoreNum)</code>(最后一轮是 SK</p>
<h4>2.4.4 评价</h4>
<ul>
<li><strong>优点</strong>:① 解决&quot;MN 块数不足分满核、但 K 大&quot;的场景,大 K 瘦高形状算力利用率可从 1/32 提升到接近满核;② DPSK 让尾轮 SK 与 AIC 计算重叠,减少核空闲。</li>
<li><strong>缺点</strong>:① workspace 核间累加带来<strong>额外 GM 写+读流量</strong>(部分和写出 + AIV 读回950PR 带宽紧张下成本不低;② 累加顺序不确定 → <code>deterministicLevel&gt;1</code> 整分支禁用;③ 仅 A 为 ND④ DPSK 要求 M/N 严格 256 对齐;⑤ FP32 切 K 需额外 <code>splitSingleK</code> 保精度,复杂度高。</li>
</ul>
<hr/>
<h3>2.5 BASIC_ASWTpriority 4—— 主分支(含 4 个子分支)</h3>
<p><strong>IsCapable</strong>:无条件返回 true除命中 StreamK 场景外的绝大多数 case 都落在这里)。</p>
<p><code>DoOpTiling</code> 先调用父类 <code>MatMulV3AswTiling::DoOpTiling()</code> 完成基础 tiling<code>ResetBase→GetRebalanceBlock→CalcTailBasicBlock→CalL1Tiling</code>,即 §3 的完整流程),得到 baseM/baseN/baseK 后,再按顺序做<strong>子分支决策</strong></p>
<pre><code>DoOpTiling():
isSlice_ = IsSelfNonContiguous() // 子分支④:非连续 3D slice
l0C2Out_ = GetL0C2Out() // 子分支③fixpipe 优化判定
if (!isSlice_ &amp;&amp; CheckAL1FullLoad()) → DoAL1FullLoad() // 子分支①A 全载
elif (!isSlice_ &amp;&amp; CheckBL1FullLoad()) → DoBL1FullLoad() // 子分支②B 全载
elif (l0C2Out_ == ON_THE_FLY) → 普通场景L1 剩余容量均分 stepK
else → fixpipe 优化场景
CheckFp32SplitK() // FP32 大 K → BASIC_SPLIT_K
CheckApiLevelAndModel() // BASIC / TENSOR api</code></pre>
<p>ASWT 的<strong>标准数据流</strong>(所有子分支共享的骨架,<code>mat_mul_asw_kernel.h</code><code>mm_.Iterate()</code></p>
<pre><code>GM(A块) ─MTE2─► L1(A) ─MTE1─► L0A ─┐
├─ MMAD(16×16×16) ─► L0C ─Fixpipe─► GM(C块)
GM(B块) ─MTE2─► L1(B) ─MTE1─► L0B ─┘
MTE2 重复读可命中 L25.2TB/sL1 用 Double Buffer 掩盖搬移延迟)</code></pre>
<hr/>
<h4>2.5.1 子分支①A 全载 L1A_FULL_LOAD</h4>
<p><strong>触发条件</strong><code>CheckAL1FullLoad</code></p>
<pre><code>l0C2Out == ON_THE_FLY // 不与 fixpipe 叠加
∧ cubeBoundParam &gt; cubeBoundEdge // 非 CubeBoundMTE2 是瓶颈,重复读代价高)
∧ nCnt &gt; aicNum // N 方向块数多于核数 → 同一 A 块被跨 N 的核重复读
∧ !(K &lt;= 128 &amp;&amp; mCnt != 1) // 排除 Fixp Bound 多轮
∧ (M·K·dtypeA + bias) &lt;= 3/4·L1 // 整个 A + bias 能装进 3/4 L1384KB</code></pre>
<p><strong>原理(结合硬件)</strong>:当 <code>nCnt &gt; aicNum</code>,同一列(固定 m的 A 块会被 <code>nCnt</code> 个核重复读。若 A 的 <code>M×K</code> 整个能装进 L1则把它<strong>常驻 L1</strong>,只流式搬 B → <strong>A 的重复读从 GM/L2 级别降为 0</strong>,只剩 B 的单次读 + A 的单次读。对 950PR 的 1.6TB/s 带宽是直接省流。</p>
<pre><code>A 全载数据流A 常驻 L1B 按 baseN 流式):
GM(A[M×K]) ─MTE2─► L1(A, 常驻, 不再搬出)
│每次只搬 B 块:
GM(B[K×baseN]) ─MTE2─► L1(B) ─MTE1─► L0B ─┐
L1(A) ─MTE1─► L0A ────────────────────────┼─MMAD─► L0C ─Fixpipe─► GM(C)</code></pre>
<p><strong>tiling</strong><code>DoAL1FullLoad</code></p>
<pre><code>singleCoreM = M // M 不再分核A 常驻M 方向一块)
singleCoreN = baseN // N 方向按 baseN 分
aL1Size = M·K·dtypeA // A 全载字节数
remainL1 = L1 - (aL1Size + bias) // L1 剩余容量
maxBaseN = min(remainL1/(baseK·dtypeB·DB), L0C/(baseM·4B·DB)) // 受 L1 剩余 + L0C 双缓冲约束
baseN = min(baseN, maxBaseN, ⌈N/aicNum⌉对齐16) // 三者取小
stepKa = ⌈K/baseK⌉, stepM = ⌈M/baseM⌉ // K、M 方向 L1 分片
stepKb = 由 B 的 L1 搬移量 + 256B 对齐确定 // B 侧 K 分片
l1BufferNum = (4·B搬移量 + aL1Size + bias &gt; L1) ? 2 : 4 // 能否 4-buffer
dbL0C = (baseM·baseN·4B·2 &lt;= L0C) ? 2 : 1 // L0C 双缓冲
usedCoreNum = min(nCore·batchNum, aicNum)</code></pre>
<hr/>
<h4>2.5.2 子分支②B 全载 L1B_FULL_LOAD</h4>
<p>与 A 全载完全对称:<code>!CubeBound ∧ mCnt &gt; aicNum ∧ (K·N·dtypeB + bias) &lt;= 3/4·L1</code> 时,整个 B 常驻 L1A 按 baseM 流式。</p>
<pre><code>B 全载数据流B 常驻 L1A 按 baseM 流式):
GM(B[K×N]) ─MTE2─► L1(B, 常驻)
GM(A[baseM×K]) ─MTE2─► L1(A) ─MTE1─► L0A ─┐
L1(B) ─MTE1─► L0B ────────────────────────┼─MMAD─► L0C ─Fixpipe─► GM(C)</code></pre>
<p>tiling 对称:<code>singleCoreN=N</code><code>singleCoreM=baseM</code><code>baseM = min(baseM, 剩余容量上限, L0C上限, ⌈M/aicNum⌉)</code></p>
<hr/>
<h4>2.5.3 子分支③Fixpipe 随路搬出优化ND_FIXPIPE_1_1 / 1_2</h4>
<p><strong>触发条件</strong><code>GetL0C2OutDav3510</code></p>
<pre><code>isValidMKN = (K &lt;= 256) ∧ (M &gt;= 256) // 计算量小、M 大
isMultiRound = (mCnt·nCnt &gt; aicNum) // 多轮(搬出流水与计算重叠才有意义)
isUnalignedN = (N·cDtypeSize % 128 != 0) ∧ (N·cDtypeSize &gt; 256) // N 不对齐 128B
fixpipeBound = isValidMKN ∧ isMultiRound ∧ isUnalignedN
→ 满足且 aivNum == 2·aicNum
FP16/BF16 → ND_FIXPIPE_1_1 FP32 → ND_FIXPIPE_1_2</code></pre>
<p><strong>原理(结合硬件)</strong>:小 K 大 M 场景MMAD 计算时间短,<code>T_FIXPIPE = M·N·dtypeC / BW_fixp</code> 可能超过 <code>T_MMAD</code>,成为 <strong>Fixpipe Bound</strong>。950PR 的 Fixpipe 随路硬化L0C→GM 直接量化/排布转换)但<strong>对 N 不对齐(非 128B 对齐)的搬出效率低</strong><code>ND_FIXPIPE_1_1/1_2</code> 用 AIV 辅助做搬出对齐1V1 或 1V2 的 AIC:AIV 配比),配合 <code>SetMMLayoutTransform(true)</code>fixp 用 N 搬出)+ UnitFlag512B 粒度同步),让 Fixpipe 与 Cube 并行、搬出对齐。</p>
<pre><code>普通ON_THE_FLY: MMAD ──────► 等算完 ──Fixpipe──► GM (串行)
Fixpipe 优化: MMAD(512B) ─► 立即随路 Fixpipe ─► GM (并行,粒度 512B)</code></pre>
<hr/>
<h4>2.5.4 子分支④:非连续 SliceSLICE model</h4>
<p><strong>触发条件</strong><code>IsSelfNonContiguous(context)</code> —— self 是 view 且 storageShape 为 1D、self 3D / mat2 2DcreateView with TensorV2 场景)。此时 model=SLICE<code>MatMulBasicKernel</code><code>sliceM/srcNdStride</code> 做非连续搬移NDDMA 多维重排),<code>GetBlockShape</code> 对 sliceM 对齐。</p>
<hr/>
<h4>2.5.5 评价</h4>
<ul>
<li><strong>优点</strong>:① 通用性强,覆盖绝大多数 shape② SWAT 提升 L2 命中§4③ A/B 全载在&quot;一侧重复读多、另一侧能装进 L1&quot;时显著减少 GM→L1 流量;④ fixpipe 优化在特定小 K 大 M 场景让搬出与计算并行;⑤ 尾轮负载均衡§3.5)减少尾轮算力浪费。</li>
<li><strong>缺点</strong>:① baseM/baseN 是<strong>启发式搜索</strong>§3.2 的 <code>cubeBoundParam/balanceRate</code> 权衡),非严格最优;② A/B 全载只支持&quot;整个矩阵常驻 L1&quot;一种粒度A/B 稍大于 3/4 L1 时直接放弃存在优化断档§6③ fixpipe 条件苛刻K≤256 ∧ M≥256 ∧ N 不对齐),覆盖面窄;④ GM 带宽未区分读写§6</li>
</ul>
<hr/>
<h3>2.6 BASEpriority 999—— 老 Cmct 接口兜底</h3>
<p><strong>实现</strong><code>matmul_v3_asw_tiling.cpp</code> + <code>mat_mul_asw_kernel.h</code> / <code>mat_mul_asw_block.h</code></p>
<ul>
<li><code>DoOpTiling</code>: <code>ResetBase → GetRebalanceBlock → CalcTailBasicBlock → CalL1Tiling</code>(与 BASIC_ASWT 基础 tiling 完全相同的引擎§3</li>
<li>Device 走 <code>MatMulActKernel</code>(老 Cmct 接口),核内 <code>mm_.Iterate()</code> 执行标准 <code>GM→L1→L0A/L0B→Cube→L0C→Fixpipe</code> 流水;<code>SetMMLayoutTransform(true)</code> 让 Fixpipe 用 N 搬出实现 Cube 与 Fixpipe 并行;</li>
<li>块索引(<code>MatmulAswBlock::UpdateBasicIndex</code>)同样实现 SWAT 窗口 + 蛇形扫描 + 尾轮重切§4</li>
</ul>
<p><strong>与 BASIC_ASWT 的区别</strong>:两者共用同一套 tiling 引擎和 swizzle区别只在 <strong>Device 接口代际</strong>——BASE 走老 Cmct 接口(无全载/fixpipe/StreamK 的 kernel 模板BASIC_ASWT 走新 Blaze 接口(支持全载/fixpipe/SplitK。实际运行时 BASIC_ASWTpriority 4几乎总是先命中BASE 仅在注册表异常时兜底。</p>
<p><strong>评价</strong>:保证正确性;性能上限低于主分支(缺全载/fixpipe/SplitK 等新优化)。</p>
<hr/>
<h2>3. 核心 tiling 算法ASW 系分支共用的参数计算引擎)</h2>
</blockquote>
<h3>3.1 ResetBase初始值</h3>
<pre><code>// ResetBaseDav3510950PR
usedCoreNum = aicNum = 32
baseM = 256; baseN = 256 // 950PR 的 base 块(上代为 128
baseK = 128B / dtypeA // FP16/BF16=64 元素FP32=32 元素
iterateOrder = ITER_COL_FIRST // 列优先遍历
singleCoreK = K; singleCoreM/N = baseM/N</code></pre>
<p><strong>为什么 baseM=baseN=256</strong>L0C=256KBFP32 累加 <code>256×256×4B = 256KB</code> 恰好填满 L0C。baseM=baseN=256 是&quot;L0C 能容纳的最大方块&quot;,也是重复读最少的配置(<code>重复读 ∝ 1/baseM + 1/baseN</code>,见 §3.2 的 <code>cubeBoundParam</code>。950PR 的 L0C 比上代128KB大一倍所以 baseM 从 128 升到 256。</p>
<p><strong>为什么 baseK = 128B/dtype</strong>GM→L1 高效搬移要求单次 dValue ≥ 128B连续列数据量。K 方向对齐 128B 保证 MTE2 搬移效率。</p>
<h3>3.2 GetRebalanceBlockbaseM/baseN 最优搜索,核心)</h3>
<p>分两步:</p>
<p><strong>① Roofline 判 CubeBound</strong><code>cubeBoundEdge</code> 计算):</p>
<pre><code>hbmBW = freq · 32核 · 31B/拍 / 1024 ≈ 1.6 TB/s
l2BW = freq · 32核 · 100B/拍 / 1024 ≈ 5.2 TB/s
singleCoreComputePower = freq · 8 ≈ 13.2 TFLOPS单核 BF16
computePower = singleCoreComputePower · aicNum
cmr = (M+N) / (M·N) // 临界算术强度相关量1/元素)
l2CacheUsage = max(batch·(M+N)·K·dtypeA / L2, 1) // L2 占用倍率(无量纲)
cubeBoundEdge = (l2BW/computePower)
+ l2CacheUsage·(1 - l2BW/hbmBW)·cmr
- (1 + l2BW/hbmBW)/K
cubeBoundParam = 1/baseM + 1/baseN // 重复读因子1/元素)
// Cube Bound 条件cubeBoundParam &lt;= cubeBoundEdge</code></pre>
<p>物理含义:<code>cubeBoundParam</code> 是当前 tiling 的 MTE2 重复搬移强度,<code>cubeBoundEdge</code>&quot;恰好 Cube Bound&quot;的临界强度。<code>cubeBoundParam ≤ cubeBoundEdge</code> ⇔ MTE2 不成为瓶颈。这与官方 <code>matmul_performance.md</code> 的 MTE2 Bound 条件 <code>BW_mte2 ≥ (1/baseN + 1/baseM)·dtype·16·16·16·核数·频率</code> 等价。</p>
<p><strong>② 搜索最优 (baseM, baseN)</strong>:在 <code>maxBaseM × maxBaseN</code> 解空间双重循环,每个候选算:</p>
<pre><code>curCubeBoundParam = 1/curBaseM + 1/curBaseN
curBalanceRate = GetBalanceRateWithTail(...) // 尾轮负载均衡率(无量纲 0~1
目标:优先满足 CubeBound 且 balanceRate 更高;
否则综合 (curCubeBoundParam/curBalanceRate) 评选(值小者优)</code></pre>
<p><code>maxBaseM/maxBaseN</code><code>GetMaxBaseWithLimit</code> 计算,受 L0A/L0C/L1/bias table/K 对齐多重约束。搜索步长 <code>baseMAlignUnit/baseNAlignUnit</code> 与转置、fixp bound 相关16 / 32 / 64 / 128B 对齐)。</p>
<h3>3.3 GetBaseKbaseK 约束)</h3>
<pre><code>maxBaseK = L0A_SIZE/DB_SIZE/dtypeA / max(baseM, baseN) // 受 L0A 容量约束(元素个数)
// K 能全载进 L0A 则取 K 对齐值;否则按 256B 对齐;再退候选 {128,64,32,16}</code></pre>
<h3>3.4 CalL1TilingK 方向 L1 分片)</h3>
<pre><code>isKInner = !ATrans || BTrans // K 是否内轴
maxStepK = min(⌈K/baseK⌉, 8) // K 方向 L1 分片数上限 8
遍历 stepK1..maxStepK:
满足 (aL1+bL1)·DB &lt;= L1 且 K 256B 对齐 且 单次搬移量约束
stepKa = stepKb = resKL1 / baseK // K 方向 L1 分片步数(无量纲)
depthA1 = stepKa·DB; depthB1 = stepKb·DB // L1 流水深度tile 层数)
singleCoreM = baseM; singleCoreN = baseN</code></pre>
<h3>3.5 CalcTailBasicBlock尾轮重切</h3>
<pre><code>tailCnt = (mCnt·nCnt &gt; aicNum) ? (mCnt·nCnt % aicNum) : 0
// 尾轮把 base 块在 M/N 方向重切 mTailCnt×nTailCnt 份,
// 使尾轮也尽量填满核,且保持 128B 搬移对齐
while ((mTailCnt+1)·nTailCnt·tailCnt &lt;= aicNum ∧ 搬移对齐) mTailCnt++</code></pre>
<hr/>
<h2>4. Swizzle 编排SWAT 窗口 + 蛇形扫描)</h2>
</blockquote>
<h3>4.1 ASWT 的 SWAT 蛇形滑动(<code>UpdateMNTileIdx</code></h3>
<pre><code>mainWindow = min(4, mCnt) // 固定窗口 4 个 base 块WINDOW_LEN=4
mainRow = mCnt / mainWindow - 1
tailWindow = mCnt - mainRow·mainWindow
rowIdx = tileIdx / nCnt / mainWindow
if (rowIdx &lt; mainRow): // 主窗口区
mTileIdx = rowIdx·mainWindow + tileIdx % mainWindow // 窗口内 M 小步滑动
nTileIdx = (tileIdx / mainWindow) % nCnt // N 方向连续滑动
else: // 尾窗口区M 剩余不足一窗)
mTileIdx = mainRow·mainWindow + tailIndex % tailWindow
nTileIdx = (tailIndex / tailWindow) % nCnt
if (rowIdx % 2 != 0): // 蛇形:奇数行 N 反向扫描
nTileIdx = nCnt - 1 - nTileIdx</code></pre>
<p><strong>物理含义(结合硬件)</strong>:把 M 轴按窗口4 个 base 块)分组,窗口内沿 N 连续滑动、M 小步滑动,使<strong>相邻核访问的数据在 GM 地址上空间邻近</strong> → 最大化 L2 命中(重复读走 5.2TB/s 的 L2 而非 1.6TB/s 的 GM。奇数行 N 反向(蛇形)让相邻两轮的首尾块空间相邻,进一步复用 L2。示意图</p>
<pre><code>M方向 base 块(窗口=4
┌──────────────────────────────────────────┐
│ 窗口0: 核0→N0, 核1→N1, 核2→N2, 核3→N3 (N 连续) │
│ 窗口1: 核4→N3, 核5→N2, 核6→N1, 核7→N0 (蛇形反向) │
│ ... │
└──────────────────────────────────────────┘
(每行窗口内 N 方向连续滑动 → 相邻核读相邻 A 块 → L2 命中)</code></pre>
<h3>4.2 尾轮重切(<code>GetBlockShape</code></h3>
<p>最后一轮把单个 base 块在 M/N 方向再切 <code>mTailCnt × nTailCnt</code> 份分给更多核,<code>blockIdx % tailCnt</code> 决定每核拿哪个子块,消除尾轮&quot;核数远大于剩余块数&quot;的算力浪费(配合 §3.5)。</p>
<h3>4.3 StreamK 的 DP/SK 编排(<code>block_scheduler_streamk.h</code></h3>
<p>在 SWAT 蛇形基础上叠加 <strong>DP 主轮 + SK 尾轮</strong> 判定:</p>
<pre><code>tileNum = (mCnt·nCnt - tailMNTileNum) + tailMNTileNum·skKTileNum·batch
└──────── DP 部分(每核一个MN块) ┘ └───── SK 尾轮(每MN块切K) ─────┘
CheckIsSkScene(tileIdx) = (⌈(tileIdx+1)/usedCoreNum⌉ == ⌈tileNum/usedCoreNum⌉)
// DP 轮kTileNum=1K 不分片SK 轮kTileNum=skKTileNumK 分片)</code></pre>
<p>DP 主轮每核一个 MN 块算完整 K数据并行SK 尾轮把剩余 MN 块切 K 分给空闲核提前执行AIV 累加与 AIC 下一轮重叠)。</p>
<hr/>
<h2>5. 各分支优缺点对比</h2>
<table><thead><tr><th>分支</th><th>适用场景</th><th>优点</th><th>缺点 / 局限</th></tr></thead><tbody>
<tr><td>K_EQUAL_ZERO</td><td>K==0 且无 bias</td><td>极简AIV 清零不浪费 Cube</td><td>仅空矩阵乘</td></tr>
<tr><td>TO_MUL</td><td>高精度 FP32 且 M==1/N==1</td><td>避开 Cube 分形浪费AIV 向量乘</td><td>仅 FP32 高精度</td></tr>
<tr><td>TO_MULTI_MUL</td><td>高精度 FP32 且 !ATrans∧BTrans</td><td>同上 + 内积方向连续</td><td>更窄(限转置、无 bias</td></tr>
<tr><td>BASIC_STREAM_K</td><td>M/N 小、K 大≥8192</td><td>切 K 满核DPSK 尾轮与计算重叠</td><td>workspace 额外带宽;累加顺序不确定;仅 A 为 NDDPSK 要求 256 对齐</td></tr>
<tr><td>BASIC_ASWT</td><td>通用主分支</td><td>SWAT 提 L2 命中A/B 全载减重复读fixpipe 并行搬出;尾轮均衡</td><td>baseM/N 启发式非最优;全载/fixpipe 场景窄GM 带宽不分读写</td></tr>
<tr><td>BASE(ASW)</td><td>最终兜底</td><td>保证正确性</td><td>老接口,无全载/fixpipe/StreamK 优化</td></tr>
</tbody></table>
<hr/>
<h2>6. 初步观察到的可改进点(衔接任务 3.2</h2>
<p>在通读源码过程中浮现的改进线索,留待后续用性能模型严格论证:</p>
<ol>
<li><strong>baseM/baseN 搜索是启发式的</strong><code>GetRebalanceBlock</code><code>cubeBoundParam/balanceRate</code> 复合指标剪枝,而非直接代入 <code>T_total = max(T_MMAD, T_MTE2, T_MTE1, T_FIXPIPE)</code> 精确评估。理论上可用性能模型对候选解精确打分。</li>
<li><strong>SWAT 窗口固定为 4</strong><code>WINDOW_LEN=4</code>):未根据 L2 容量、shape、核数自适应调窗。窗口大小直接影响 L2 命中率与重复读量的权衡。</li>
<li><strong>A/B 全载只有&quot;整个矩阵常驻&quot;一种粒度</strong>:没有&quot;部分驻留&quot;(多个 base 块驻留 L1 的中间态A/B 稍大于 3/4 L1 时直接放弃,存在优化断档。</li>
<li><strong>fixpipe 优化覆盖窄</strong>:仅 <code>K≤256 ∧ M≥256 ∧ N 不对齐</code> 触发,其它 Fixpipe Bound 场景未覆盖。</li>
<li><strong>GM 带宽未区分读写</strong><code>GetHbmBW</code> 统一按 <code>32核·31B/拍</code> 换算,未区分读/写共享 1.6TB/s 的竞争,可能高估有效带宽。</li>
<li><strong>M==1/N==1 的 FP16/BF16 无退化分支</strong>TO_MUL/TO_MULTI_MUL 仅覆盖 FP32 高精度FP16/BF16 的退化场景仍走 Cube1/16 利用率)。</li>
</ol>
<hr/>
<h2>附录:关键源码文件索引</h2>
<table><thead><tr><th></th><th>文件</th><th>内容</th></tr></thead><tbody>
<tr><td>Host</td><td><code>op_host/op_tiling/arch35/matmul_v3_tiling_strategy.h</code></td><td>分支优先级定义</td></tr>
<tr><td>Host</td><td><code>op_host/op_tiling/arch35/matmul_tiling_registry.h</code></td><td>策略注册与 DoTilingImpl 调度</td></tr>
<tr><td>Host</td><td><code>op_host/op_tiling/arch35/matmul_v3_tiling_advanced.cpp</code></td><td>主入口 + 各 Phase</td></tr>
<tr><td>Host</td><td><code>op_host/op_tiling/arch35/matmul_v3_tiling_helper.cpp</code></td><td>ResetBase/GetRebalanceBlock/CalL1Tiling/GetL0C2Out</td></tr>
<tr><td>Host</td><td><code>op_host/op_tiling/arch35/matmul_v3_common_advanced.h</code></td><td>常量与数据结构定义</td></tr>
<tr><td>Host</td><td><code>op_host/op_tiling/arch35/matmul_v3_basic_streamk_tiling.cpp</code></td><td>StreamK/DPSK 条件与 tiling</td></tr>
<tr><td>Host</td><td><code>op_host/op_tiling/arch35/matmul_v3_basic_aswt_tiling.cpp</code></td><td>ASWT 子分支(全载/fixpipe</td></tr>
<tr><td>Host</td><td><code>op_host/op_tiling/arch35/matmul_v3_{k_equal_zero,to_mul,to_multi_mul,asw}_tiling.cpp</code></td><td>其余分支</td></tr>
<tr><td>Device</td><td><code>op_kernel/arch35/mat_mul_v3.cpp</code></td><td>kernel 入口 if constexpr 分发</td></tr>
<tr><td>Device</td><td><code>op_kernel/arch35/mat_mul_v3_tiling_key_public.h</code></td><td>枚举定义</td></tr>
<tr><td>Device</td><td><code>op_kernel/arch35/mat_mul_tiling_data.h</code></td><td>tiling data 结构</td></tr>
<tr><td>Device</td><td><code>op_kernel/arch35/block_scheduler_aswt.h</code></td><td>ASWT swizzleSWAT+蛇形+尾轮)</td></tr>
<tr><td>Device</td><td><code>op_kernel/arch35/block_scheduler_streamk.h</code></td><td>StreamK swizzleDP/SK</td></tr>
<tr><td>Device</td><td><code>op_kernel/arch35/mat_mul_asw_block.h</code> / <code>mat_mul_asw_kernel.h</code></td><td>老接口 ASW 块调度与主循环</td></tr>
<tr><td>Device</td><td><code>op_kernel/arch35/mat_mul_{al1,bl1}_full_load.h</code></td><td>A/B 全载 kernel 模板</td></tr>
<tr><td>Device</td><td><code>op_kernel/arch35/mat_mul_streamk.h</code> / <code>mat_mul_fixpipe.h</code> / <code>mat_mul_basic_split_k.h</code></td><td>StreamK/Fixpipe/SplitK 模板</td></tr>
</tbody></table></body></html>