已合并
适配 BatchMatmul 与 Matmul 样例至 cannbotdsl 0.7 #127
ykxxlzhong创建于 24 天前
适配 BatchMatmul 与 Matmul 样例至 cannbotdsl 0.7 #127
已合并
ykxxlzhong创建于 24 天前
共 4 个文件变更+178-122
@@ -19,7 +19,7 @@ $$
19| bias | 可选加性偏置,经 BT(Bias Table)折入 init MMAD(只加一次);`[N]` 共享(广播到 M 与 batch)或 `[*c_batch, N]` 逐 batch(需与输出 batch 精确匹配,BatchMatMulV3 契约);dtype 为 fp32 或输入 dtype |19| bias | 可选加性偏置,经 BT(Bias Table)折入 init MMAD(只加一次);`[N]` 共享(广播到 M 与 batch)或 `[*c_batch, N]` 逐 batch(需与输出 batch 精确匹配,BatchMatMulV3 契约);dtype 为 fp32 或输入 dtype |
20| 输入 | a/b 需 contiguous 或 canonical 转置视图(其余非连续布局拒绝) |20| 输入 | a/b 需 contiguous 或 canonical 转置视图(其余非连续布局拒绝) |
21| 多核并行 | 展平 batch×M×N tile 空间线性 stride 调度(grid = min(tile 总数, 平台 cube 核数,dav-3510 为 28),batch 数任意) |21| 多核并行 | 展平 batch×M×N tile 空间线性 stride 调度(grid = min(tile 总数, 平台 cube 核数,dav-3510 为 28),batch 数任意) |
22-| 尾块处理 | `tile_view` 裁剪 + ND2NZ 引擎零填充,M/N/K 任意值 |22+| 尾块处理 | `tile_slice` 裁剪 + ND2NZ 引擎零填充,M/N/K 任意值 |
23| L2 hint | 逐 copy `l2_cache_ctl` 策略:A/B 默认旁路、按复用分析(跨 tile 重读 / broadcast)使能,含 nd2nz 方向 + 128 元素对齐守卫;C 按容量使能;bias 恒使能 |23| L2 hint | 逐 copy `l2_cache_ctl` 策略:A/B 默认旁路、按复用分析(跨 tile 重读 / broadcast)使能,含 nd2nz 方向 + 128 元素对齐守卫;C 按容量使能;bias 恒使能 |
24| 支持架构 | NPU ARCH 3510(Ascend 950PR / Ascend 950DT) |24| 支持架构 | NPU ARCH 3510(Ascend 950PR / Ascend 950DT) |
25 25 
@@ -124,4 +124,4 @@ pytest test/matmul/batch_matmul/test_batch_matmul.py -v
124 124 
125- 大方形 fp16/fp32 与主线差距 1.1~1.25×(残余为主线策略矩阵:尾块分裂 / StreamK / ND_FIXPIPE AIV 协同回写);深批量小矩阵反超主线(0.82×)125- 大方形 fp16/fp32 与主线差距 1.1~1.25×(残余为主线策略矩阵:尾块分裂 / StreamK / ND_FIXPIPE AIV 协同回写);深批量小矩阵反超主线(0.82×)
126- HF32 模式比任何 fp32 路径快 3~7×(torch 亦支持 `torch.npu.matmul.allow_hf32=True`,模式对齐后主线仍略优)126- HF32 模式比任何 fp32 路径快 3~7×(torch 亦支持 `torch.npu.matmul.allow_hf32=True`,模式对齐后主线仍略优)
127-- JIT 便捷入口每次调用含 ~100ms 框架重 trace 开销;低延迟场景请用 `run.compile()` AOT 路径(~156us/调用,同步语义)127+- JIT 便捷入口每次调用含 ~100ms 框架重 trace 开销;低延迟场景请用 `cannbotdsl.compile(op.run, *specs)` AOT 路径(`TensorSpec` 描述入参,返回可重复调用的编译产物)
@@ -21,18 +21,17 @@ import dataclasses
21import os21import os
22 22 
23import torch23import torch
24-from cannbotdsl import (24+from cannbotdsl import dtypes, get_mem_size, get_platform_info
25- MemLoc, Tensor, dtypes, get_mem_size, get_platform_info,
26- make_copy_engine, mem_copy, tile_slice as tile_view,
27-)
28-from cannbotdsl.ops.arch import get_block_idx, get_block_num
29from cannbotdsl.channel import Channel25from cannbotdsl.channel import Channel
30from cannbotdsl.lang.constexpr import const_expr26from cannbotdsl.lang.constexpr import const_expr
27+from cannbotdsl.lang.host import host
31from cannbotdsl.lang.jit import jit28from cannbotdsl.lang.jit import jit
32from cannbotdsl.lang.kernel import kernel29from cannbotdsl.lang.kernel import kernel
30+from cannbotdsl.ops.arch import get_block_idx, get_block_num
33from cannbotdsl.ops.cube import enable_hf32, set_fp32_mode31from cannbotdsl.ops.cube import enable_hf32, set_fp32_mode
34from cannbotdsl.ops.matmul import matmul as dsl_matmul32from cannbotdsl.ops.matmul import matmul as dsl_matmul
35-from cannbotdsl.tensor import ceil_div33+from cannbotdsl.ops.memcpy import make_copy_engine, mem_copy
34+from cannbotdsl.tensor import MemLoc, Tensor, ceil_div, tile_slice
36 35 
37 36 
38# ============================================================================37# ============================================================================
@@ -114,10 +113,12 @@ def _decode_batch_terms(src_batch, c_batch):
114 ps = (1,) * (n - len(src_batch)) + tuple(int(d) for d in src_batch)113 ps = (1,) * (n - len(src_batch)) + tuple(int(d) for d in src_batch)
115 114 
116 divs = [1] * n115 divs = [1] * n
116+ muls = [1] * (n + 1)
117 acc = 1117 acc = 1
118 for d in range(n - 1, -1, -1):118 for d in range(n - 1, -1, -1):
119 divs[d] = acc119 divs[d] = acc
120 acc *= c_batch[d]120 acc *= c_batch[d]
121+ muls[d] = muls[d + 1] * ps[d]
121 122 
122 full = ps == tuple(c_batch)123 full = ps == tuple(c_batch)
123 terms = []124 terms = []
@@ -126,7 +127,7 @@ def _decode_batch_terms(src_batch, c_batch):
126 continue # broadcast dim: source coordinate is always 0127 continue # broadcast dim: source coordinate is always 0
127 if c_batch[d] == 1:128 if c_batch[d] == 1:
128 continue # extent-1 output dim: coordinate is always 0129 continue # extent-1 output dim: coordinate is always 0
129- terms.append((divs[d], c_batch[d], _prod(ps[d + 1:])))130+ terms.append((divs[d], c_batch[d], muls[d + 1]))
130 return full, terms131 return full, terms
131 132 
132 133 
@@ -180,9 +181,7 @@ def _l2_capacity_bytes() -> int:
180 f"BMM_L2_SIZE_BYTES must be an integer byte count; got {raw!r}"181 f"BMM_L2_SIZE_BYTES must be an integer byte count; got {raw!r}"
181 ) from exc182 ) from exc
182 if value <= 0:183 if value <= 0:
183- raise ValueError(184+ raise ValueError(f"BMM_L2_SIZE_BYTES must be positive; got {value}")
184- f"BMM_L2_SIZE_BYTES must be positive; got {value}"
185- )
186 return value185 return value
187 186 
188 187 
@@ -191,18 +190,14 @@ def _l2_capacity_bytes() -> int:
191 190 
192def _validate_inputs(a, b, hf32):191def _validate_inputs(a, b, hf32):
193 if a.dtype != b.dtype:192 if a.dtype != b.dtype:
194- raise TypeError(193+ raise TypeError(f"a and b must have the same dtype, got {a.dtype} vs {b.dtype}")
195- f"a and b must have the same dtype, got {a.dtype} vs {b.dtype}"
196- )
197 if a.dtype not in _TORCH_DTYPE_TO_DSL:194 if a.dtype not in _TORCH_DTYPE_TO_DSL:
198 raise TypeError(195 raise TypeError(
199 f"only torch.float16, torch.bfloat16 and torch.float32 are "196 f"only torch.float16, torch.bfloat16 and torch.float32 are "
200 f"supported; got {a.dtype}"197 f"supported; got {a.dtype}"
201 )198 )
202 if hf32 and a.dtype != torch.float32:199 if hf32 and a.dtype != torch.float32:
203- raise ValueError(200+ raise ValueError(f"hf32=True is only valid for float32 inputs; got {a.dtype}")
204- f"hf32=True is only valid for float32 inputs; got {a.dtype}"
205- )
206 if a.device != b.device:201 if a.device != b.device:
207 raise ValueError(202 raise ValueError(
208 f"a and b must be on the same device, got {a.device} vs {b.device}"203 f"a and b must be on the same device, got {a.device} vs {b.device}"
@@ -211,8 +206,7 @@ def _validate_inputs(a, b, hf32):
211 raise ValueError(f"inputs must be NPU tensors; got device {a.device}")206 raise ValueError(f"inputs must be NPU tensors; got device {a.device}")
212 if not (2 <= a.dim() <= _MAX_RANK and 2 <= b.dim() <= _MAX_RANK):207 if not (2 <= a.dim() <= _MAX_RANK and 2 <= b.dim() <= _MAX_RANK):
213 raise ValueError(208 raise ValueError(
214- f"a and b must have rank 2..{_MAX_RANK}, "209+ f"a and b must have rank 2..{_MAX_RANK}, got rank {a.dim()} and {b.dim()}"
215- f"got rank {a.dim()} and {b.dim()}"
216 )210 )
217 211 
218 212 
@@ -256,24 +250,19 @@ def _normalize_bias(bias, a, c_batch, n):
256 if bias is None:250 if bias is None:
257 return None, False, False251 return None, False, False
258 if bias.device != a.device:252 if bias.device != a.device:
259- raise ValueError(253+ raise ValueError(f"bias must be on the same device as a; got {bias.device}")
260- f"bias must be on the same device as a; got {bias.device}"
261- )
262 if bias.dtype not in (a.dtype, torch.float32):254 if bias.dtype not in (a.dtype, torch.float32):
263 raise TypeError(255 raise TypeError(
264 f"bias dtype must be the input dtype or float32; got {bias.dtype}"256 f"bias dtype must be the input dtype or float32; got {bias.dtype}"
265 )257 )
266 if bias.dim() == 1:258 if bias.dim() == 1:
267 if bias.shape[0] != n:259 if bias.shape[0] != n:
268- raise ValueError(260+ raise ValueError(f"bias length must equal N ({n}); got {bias.shape[0]}")
269- f"bias length must equal N ({n}); got {bias.shape[0]}"
270- )
271 return bias, True, False261 return bias, True, False
272 if bias.dim() == len(c_batch) + 1:262 if bias.dim() == len(c_batch) + 1:
273 if tuple(bias.shape[:-1]) != c_batch or bias.shape[-1] != n:263 if tuple(bias.shape[:-1]) != c_batch or bias.shape[-1] != n:
274 raise ValueError(264 raise ValueError(
275- f"per-batch bias shape must be {(*c_batch, n)}; "265+ f"per-batch bias shape must be {(*c_batch, n)}; got {tuple(bias.shape)}"
276- f"got {tuple(bias.shape)}"
277 )266 )
278 return bias, False, True267 return bias, False, True
279 raise ValueError(268 raise ValueError(
@@ -311,8 +300,11 @@ def _degenerate_result(ctx: _KernelCtx):
311 300 
312 def zeros_out():301 def zeros_out():
313 z = torch.zeros(302 z = torch.zeros(
314- *ctx.c_batch, ctx.m, ctx.n,303+ *ctx.c_batch,
315- dtype=ctx.a_kern.dtype, device=ctx.a_kern.device,304+ ctx.m,
305+ ctx.n,
306+ dtype=ctx.a_kern.dtype,
307+ device=ctx.a_kern.device,
316 )308 )
317 return z[0] if ctx.squeeze_out else z309 return z[0] if ctx.squeeze_out else z
318 310 
@@ -351,8 +343,10 @@ def _pad_n_tail(ctx: _KernelCtx):
351 b_kern = torch.nn.functional.pad(b_kern, (0, n_eff - n))343 b_kern = torch.nn.functional.pad(b_kern, (0, n_eff - n))
352 if bias is not None:344 if bias is not None:
353 bias_pad = torch.zeros(345 bias_pad = torch.zeros(
354- *(() if ctx.bias_shared else ctx.c_batch), n_eff,346+ *(() if ctx.bias_shared else ctx.c_batch),
355- dtype=bias.dtype, device=bias.device,347+ n_eff,
348+ dtype=bias.dtype,
349+ device=bias.device,
356 )350 )
357 bias_pad[..., :n] = bias351 bias_pad[..., :n] = bias
358 bias = bias_pad352 bias = bias_pad
@@ -362,7 +356,7 @@ def _pad_n_tail(ctx: _KernelCtx):
362def _reshape_output(c, c_n, ctx: _KernelCtx):356def _reshape_output(c, c_n, ctx: _KernelCtx):
363 """Slice the N pad back off (if any) and restore the batch dims."""357 """Slice the N pad back off (if any) and restore the batch dims."""
364 if c_n != ctx.n:358 if c_n != ctx.n:
365- c = c[..., :ctx.n]359+ c = c.narrow(-1, 0, ctx.n)
366 if ctx.squeeze_out:360 if ctx.squeeze_out:
367 return c[0]361 return c[0]
368 return c.reshape(*ctx.c_batch, ctx.m, ctx.n)362 return c.reshape(*ctx.c_batch, ctx.m, ctx.n)
@@ -376,8 +370,12 @@ def _run_kernel(ctx: _KernelCtx):
376 has_bias = ctx.bias is not None370 has_bias = ctx.bias is not None
377 b_kern, bias, n_eff = _pad_n_tail(ctx)371 b_kern, bias, n_eff = _pad_n_tail(ctx)
378 tiling = BatchMatmulTiling(372 tiling = BatchMatmulTiling(
379- ctx.m, n_eff, ctx.k, dtype,373+ ctx.m,
380- kern_trans_b=ctx.kern_trans_b, has_bias=has_bias,374+ n_eff,
375+ ctx.k,
376+ dtype,
377+ kern_trans_b=ctx.kern_trans_b,
378+ has_bias=has_bias,
381 )379 )
382 # Zero-copy batch flatten (contiguous views, .view never copies).380 # Zero-copy batch flatten (contiguous views, .view never copies).
383 a3 = ctx.a_kern.view(_prod(ctx.a_kern.shape[:-2]), *ctx.a_kern.shape[-2:])381 a3 = ctx.a_kern.view(_prod(ctx.a_kern.shape[:-2]), *ctx.a_kern.shape[-2:])
@@ -477,7 +475,9 @@ class BatchMatmulTiling:
477 # A/B share the dtype; bound base_k by the smaller of L0A/L0B475 # A/B share the dtype; bound base_k by the smaller of L0A/L0B
478 # (double-buffered), both queried from the platform.476 # (double-buffered), both queried from the platform.
479 max_k = (477 max_k = (
480- min(self.L0A_SIZE, self.L0B_SIZE) // self.DB_SIZE // dtype_size478+ min(self.L0A_SIZE, self.L0B_SIZE)
479+ // self.DB_SIZE
480+ // dtype_size
481 // max(self.base_m, self.base_n)481 // max(self.base_m, self.base_n)
482 )482 )
483 k_align = _ceil_align(self.k, self.BASIC_BLOCK_16)483 k_align = _ceil_align(self.k, self.BASIC_BLOCK_16)
@@ -491,8 +491,7 @@ class BatchMatmulTiling:
491 491 
492 max_step = min(_ceil_div(self.k, self.base_k), self.MAX_STEP_K)492 max_step = min(_ceil_div(self.k, self.base_k), self.MAX_STEP_K)
493 bias_l1 = (493 bias_l1 = (
494- self.base_n * self.DATA_SIZE_FP32 * self.DB_SIZE494+ self.base_n * self.DATA_SIZE_FP32 * self.DB_SIZE if self.has_bias else 0
495- if self.has_bias else 0
496 )495 )
497 step_k = 1496 step_k = 1
498 for s in range(1, max_step + 1):497 for s in range(1, max_step + 1):
@@ -569,9 +568,8 @@ class BatchMatmulKernel:
569 # L2 cache-hint policy: A/B bypass only on proven reuse and only on568 # L2 cache-hint policy: A/B bypass only on proven reuse and only on
570 # the linear nd2nz path with 128-aligned K/k_l1; C cached iff the569 # the linear nd2nz path with 128-aligned K/k_l1; C cached iff the
571 # whole batched output fits L2; bias always cached.570 # whole batched output fits L2; bias always cached.
572- aligned = (571+ aligned = (tiling.k % _L2_BYPASS_ALIGN_ELEMS == 0) and (
573- (tiling.k % _L2_BYPASS_ALIGN_ELEMS == 0)572+ tiling.k_l1 % _L2_BYPASS_ALIGN_ELEMS == 0
574- and (tiling.k_l1 % _L2_BYPASS_ALIGN_ELEMS == 0)
575 )573 )
576 reuse_a = tiling.n_tiles > 1 or not ctx.a_full # N-tile re-reads / bcast574 reuse_a = tiling.n_tiles > 1 or not ctx.a_full # N-tile re-reads / bcast
577 reuse_b = tiling.m_tiles > 1 or not ctx.b_full # M-tile re-reads / bcast575 reuse_b = tiling.m_tiles > 1 or not ctx.b_full # M-tile re-reads / bcast
@@ -580,8 +578,9 @@ class BatchMatmulKernel:
580 self.l2_ctl_a = 1 if (reuse_a or not nd_path_a or not aligned) else 0578 self.l2_ctl_a = 1 if (reuse_a or not nd_path_a or not aligned) else 0
581 self.l2_ctl_b = 1 if (reuse_b or not nd_path_b or not aligned) else 0579 self.l2_ctl_b = 1 if (reuse_b or not nd_path_b or not aligned) else 0
582 self.l2_ctl_c = (580 self.l2_ctl_c = (
583- 1 if self.bs * tiling.m * tiling.n * tiling.dtype_size581+ 1
584- <= _l2_capacity_bytes() else 0582+ if self.bs * tiling.m * tiling.n * tiling.dtype_size <= _l2_capacity_bytes()
583+ else 0
585 )584 )
586 585 
587 # MMAD unit flag (batch_mat_mul_v3 Normal config): 2 while586 # MMAD unit flag (batch_mat_mul_v3 Normal config): 2 while
@@ -611,8 +610,7 @@ class BatchMatmulKernel:
611 return tuple(padded)610 return tuple(padded)
612 611 
613 @kernel612 @kernel
614- def bmm_kernel(self, gm_a: Tensor, gm_b: Tensor, gm_c: Tensor,613+ def bmm_kernel(self, gm_a: Tensor, gm_b: Tensor, gm_c: Tensor, gm_bias: Tensor):
615- gm_bias: Tensor):
616 t = self.t614 t = self.t
617 615 
618 # Compile-time fp32 compute-mode selection (persistent cube state).616 # Compile-time fp32 compute-mode selection (persistent cube state).
@@ -632,12 +630,8 @@ class BatchMatmulKernel:
632 """Declare the L1/L0/bias channels and the copy engines."""630 """Declare the L1/L0/bias channels and the copy engines."""
633 t = self.t631 t = self.t
634 632 
635- l1_b_shape = (633+ l1_b_shape = (t.base_n, t.k_l1) if t.kern_trans_b else (t.k_l1, t.base_n)
636- (t.base_n, t.k_l1) if t.kern_trans_b else (t.k_l1, t.base_n)634+ l1_a_shape = (t.k_l1, t.base_m) if self.kern_trans_a else (t.base_m, t.k_l1)
637- )
638- l1_a_shape = (
639- (t.k_l1, t.base_m) if self.kern_trans_a else (t.base_m, t.k_l1)
640- )
641 self.l1_a = Channel(635 self.l1_a = Channel(
642 MemLoc.L1,636 MemLoc.L1,
643 shape=l1_a_shape,637 shape=l1_a_shape,
@@ -664,12 +658,18 @@ class BatchMatmulKernel:
664 # bias-only so the channel never eats into exact-fit no-bias L1.658 # bias-only so the channel never eats into exact-fit no-bias L1.
665 if const_expr(self.has_bias):659 if const_expr(self.has_bias):
666 self.l1_bias = Channel(660 self.l1_bias = Channel(
667- MemLoc.L1, shape=(t.base_n,), dtype=self.bias_dtype,661+ MemLoc.L1,
668- depth=2, data_format="nd",662+ shape=(t.base_n,),
663+ dtype=self.bias_dtype,
664+ depth=2,
665+ data_format="nd",
669 )666 )
670 self.l1_bt = Channel(667 self.l1_bt = Channel(
671- MemLoc.BIAS, shape=(t.base_n,), dtype=dtypes.float32,668+ MemLoc.BIAS,
672- depth=2, data_format="nd",669+ shape=(t.base_n,),
670+ dtype=dtypes.float32,
671+ depth=2,
672+ data_format="nd",
673 )673 )
674 674 
675 self.eng_a = make_copy_engine(675 self.eng_a = make_copy_engine(
@@ -681,17 +681,15 @@ class BatchMatmulKernel:
681 self.eng_bias = make_copy_engine(format_transform="identity")681 self.eng_bias = make_copy_engine(format_transform="identity")
682 682 
683 @jit683 @jit
684- def _compute_output_tile(self, gm_a, gm_b, gm_c, gm_bias, tile_idx):684+ def _decode_tile_batch_src(self, tile_idx):
685- """Bias load + K reduction + FIXPIPE for one output tile; tail tiles685+ """Tile-index decode: (b_idx, mn_idx) plus the broadcast-resolved flat
686- are clipped by tile_view and zero-padded by the nd2nz engines.686+ source indices (see _decode_batch_terms; full ⇒ source index equals
687- """687+ the output batch index). mn_idx packs (m_idx, n_idx) into one scalar
688+ for the 5-value coding rules (codecheck G.FNM.03/05). Shared [N]
689+ bias lives in slot 0; full-batch bias by output batch."""
688 b_idx = tile_idx // self.mn_tiles690 b_idx = tile_idx // self.mn_tiles
689- rem = tile_idx % self.mn_tiles691+ mn_idx = tile_idx % self.mn_tiles
690- m_idx = rem // self.n_tiles
691- n_idx = rem % self.n_tiles
692 692 
693- # Broadcast-resolved flat batch indices (see _decode_batch_terms);
694- # full ⇒ source index equals the output batch index.
695 a_src = b_idx693 a_src = b_idx
696 if const_expr(not self.a_full):694 if const_expr(not self.a_full):
697 a_src = (695 a_src = (
@@ -708,10 +706,20 @@ class BatchMatmulKernel:
708 + ((b_idx // self.b2[0]) % self.b2[1]) * self.b2[2]706 + ((b_idx // self.b2[0]) % self.b2[1]) * self.b2[2]
709 + ((b_idx // self.b3[0]) % self.b3[1]) * self.b3[2]707 + ((b_idx // self.b3[0]) % self.b3[1]) * self.b3[2]
710 )708 )
711- # Shared [N] bias lives in slot 0; full-batch bias by output batch.
712 bias_src = 0709 bias_src = 0
713 if const_expr(self.bias_full):710 if const_expr(self.bias_full):
714 bias_src = b_idx711 bias_src = b_idx
712+ return b_idx, mn_idx, a_src, b_src, bias_src
713+ 
714+ @jit
715+ def _compute_output_tile(self, gm_a, gm_b, gm_c, gm_bias, tile_idx):
716+ """Bias load + K reduction + FIXPIPE for one output tile; tail tiles
717+ are clipped by tile_slice and zero-padded by the nd2nz engines.
718+ """
719+ decoded = self._decode_tile_batch_src(tile_idx)
720+ b_idx, mn_idx, a_src, b_src, bias_src = decoded
721+ m_idx = mn_idx // self.n_tiles
722+ n_idx = mn_idx % self.n_tiles
715 723 
716 base_m, base_n = self.t.base_m, self.t.base_n724 base_m, base_n = self.t.base_m, self.t.base_n
717 a2d = gm_a[a_src, None, None]725 a2d = gm_a[a_src, None, None]
@@ -726,8 +734,9 @@ class BatchMatmulKernel:
726 if const_expr(self.has_bias):734 if const_expr(self.has_bias):
727 mem_copy(735 mem_copy(
728 self.l1_bias.produce(),736 self.l1_bias.produce(),
729- tile_view(gm_bias[bias_src, None], (base_n,), (n_idx,)),737+ tile_slice(gm_bias[bias_src, None], (base_n,), (n_idx,)),
730- engine=self.eng_bias, l2_cache_ctl=1,738+ engine=self.eng_bias,
739+ l2_cache_ctl=1,
731 )740 )
732 mem_copy(self.l1_bt.produce(), self.l1_bias.consume())741 mem_copy(self.l1_bt.produce(), self.l1_bias.consume())
733 bias = self.l1_bt.consume()742 bias = self.l1_bt.consume()
@@ -737,45 +746,68 @@ class BatchMatmulKernel:
737 # mn_idx packs (m_idx, n_idx) into one scalar purely to keep746 # mn_idx packs (m_idx, n_idx) into one scalar purely to keep
738 # _k_reduction within the repo's 5-formal-parameter coding rule747 # _k_reduction within the repo's 5-formal-parameter coding rule
739 # (codecheck G.FNM.03); it is a rule workaround, not business design.748 # (codecheck G.FNM.03); it is a rule workaround, not business design.
740- self._k_reduction(a2d, b2d, c_w, bias, m_idx * self.n_tiles + n_idx)749+ self._k_reduction(a2d, b2d, c_w, bias, mn_idx)
741 750 
742- gm_c_tile = tile_view(c2d, (base_m, base_n), (m_idx, n_idx))751+ gm_c_tile = tile_slice(c2d, (base_m, base_n), (m_idx, n_idx))
743 if const_expr(self.use_unit_flag):752 if const_expr(self.use_unit_flag):
744 # Unit-flag path: the FIXPIPE reads the produce alias; a753 # Unit-flag path: the FIXPIPE reads the produce alias; a
745 # consume() read would desync against the auto-triggered FIXPIPE.754 # consume() read would desync against the auto-triggered FIXPIPE.
746- mem_copy(gm_c_tile, c_w, engine=self.eng_c, unit_flag=3,755+ mem_copy(
747- l2_cache_ctl=self.l2_ctl_c)756+ gm_c_tile,
757+ c_w,
758+ engine=self.eng_c,
759+ unit_flag=3,
760+ l2_cache_ctl=self.l2_ctl_c,
761+ )
748 else:762 else:
749 mem_copy(gm_c_tile, self.l0c.consume(), l2_cache_ctl=self.l2_ctl_c)763 mem_copy(gm_c_tile, self.l0c.consume(), l2_cache_ctl=self.l2_ctl_c)
750 764 
765+ @jit
766+ def _load_ab_l1(self, a2d, b2d, k_l1_idx, mn_idx):
767+ """GM -> L1 copies of one k_l1 segment for both operands (mn_idx packs
768+ (m_idx, n_idx) for the 5-arg coding rule)."""
769+ m_idx = mn_idx // self.n_tiles
770+ n_idx = mn_idx % self.n_tiles
771+ t = self.t
772+ base_m, base_n = t.base_m, t.base_n
773+ k_l1 = t.k_l1
774+ 
775+ if const_expr(self.kern_trans_a):
776+ gm_a_tile = tile_slice(a2d, (k_l1, base_m), (k_l1_idx, m_idx))
777+ else:
778+ gm_a_tile = tile_slice(a2d, (base_m, k_l1), (m_idx, k_l1_idx))
779+ mem_copy(
780+ self.l1_a.produce(),
781+ gm_a_tile,
782+ engine=self.eng_a,
783+ l2_cache_ctl=self.l2_ctl_a,
784+ )
785+ 
786+ if const_expr(t.kern_trans_b):
787+ gm_b_tile = tile_slice(b2d, (base_n, k_l1), (n_idx, k_l1_idx))
788+ else:
789+ gm_b_tile = tile_slice(b2d, (k_l1, base_n), (k_l1_idx, n_idx))
790+ mem_copy(
791+ self.l1_b.produce(),
792+ gm_b_tile,
793+ engine=self.eng_b,
794+ l2_cache_ctl=self.l2_ctl_b,
795+ )
796+ 
751 @jit797 @jit
752 def _k_reduction(self, a2d, b2d, c_w, bias, mn_idx):798 def _k_reduction(self, a2d, b2d, c_w, bias, mn_idx):
753 """GM → L1 → L0 K reduction and MMAD accumulation for one tile799 """GM → L1 → L0 K reduction and MMAD accumulation for one tile
754 (mn_idx packs (m_idx, n_idx) for the 5-arg coding rule)."""800 (mn_idx packs (m_idx, n_idx) for the 5-arg coding rule)."""
755- m_idx = mn_idx // self.n_tiles
756- n_idx = mn_idx % self.n_tiles
757 t = self.t801 t = self.t
758 base_m, base_n = t.base_m, t.base_n802 base_m, base_n = t.base_m, t.base_n
759 base_k, k_l1, k_total = t.base_k, t.k_l1, t.k803 base_k, k_l1, k_total = t.base_k, t.k_l1, t.k
760 last_l1_idx = self.k_l1_tiles - 1804 last_l1_idx = self.k_l1_tiles - 1
761 805 
762 for k_l1_idx in range(self.k_l1_tiles):806 for k_l1_idx in range(self.k_l1_tiles):
763- if const_expr(self.kern_trans_a):807+ self._load_ab_l1(a2d, b2d, k_l1_idx, mn_idx)
764- gm_a_tile = tile_view(a2d, (k_l1, base_m), (k_l1_idx, m_idx))
765- else:
766- gm_a_tile = tile_view(a2d, (base_m, k_l1), (m_idx, k_l1_idx))
767 # One consume per k_l1 segment: the inner loop slices the selected808 # One consume per k_l1 segment: the inner loop slices the selected
768 # alias (a consume per slice would rotate the cursor off the slot).809 # alias (a consume per slice would rotate the cursor off the slot).
769- mem_copy(self.l1_a.produce(), gm_a_tile, engine=self.eng_a,
770- l2_cache_ctl=self.l2_ctl_a)
771 a_l1 = self.l1_a.consume()810 a_l1 = self.l1_a.consume()
772- 
773- if const_expr(t.kern_trans_b):
774- gm_b_tile = tile_view(b2d, (base_n, k_l1), (n_idx, k_l1_idx))
775- else:
776- gm_b_tile = tile_view(b2d, (k_l1, base_n), (k_l1_idx, n_idx))
777- mem_copy(self.l1_b.produce(), gm_b_tile, engine=self.eng_b,
778- l2_cache_ctl=self.l2_ctl_b)
779 b_l1 = self.l1_b.consume()811 b_l1 = self.l1_b.consume()
780 812 
781 # The final k_l1 segment carries only its valid base_k blocks.813 # The final k_l1 segment carries only its valid base_k blocks.
@@ -783,31 +815,38 @@ class BatchMatmulKernel:
783 k_l0_per_l1 = ceil_div(min(k_l1, k_remaining), base_k)815 k_l0_per_l1 = ceil_div(min(k_l1, k_remaining), base_k)
784 for k_l0_idx in range(k_l0_per_l1):816 for k_l0_idx in range(k_l0_per_l1):
785 if const_expr(self.kern_trans_a):817 if const_expr(self.kern_trans_a):
786- l1_a_slice = tile_view(a_l1, (base_k, base_m), (k_l0_idx, 0))818+ l1_a_slice = tile_slice(a_l1, (base_k, base_m), (k_l0_idx, 0))
787 else:819 else:
788- l1_a_slice = tile_view(a_l1, (base_m, base_k), (0, k_l0_idx))820+ l1_a_slice = tile_slice(a_l1, (base_m, base_k), (0, k_l0_idx))
789 a_l0 = self.l0a.produce()821 a_l0 = self.l0a.produce()
790 mem_copy(a_l0, l1_a_slice)822 mem_copy(a_l0, l1_a_slice)
791 823 
792 if const_expr(t.kern_trans_b):824 if const_expr(t.kern_trans_b):
793- l1_b_slice = tile_view(b_l1, (base_n, base_k), (0, k_l0_idx))825+ l1_b_slice = tile_slice(b_l1, (base_n, base_k), (0, k_l0_idx))
794 b_l0 = self.l0b.produce()826 b_l0 = self.l0b.produce()
795 mem_copy(b_l0, l1_b_slice, transpose=False)827 mem_copy(b_l0, l1_b_slice, transpose=False)
796 else:828 else:
797- l1_b_slice = tile_view(b_l1, (base_k, base_n), (k_l0_idx, 0))829+ l1_b_slice = tile_slice(b_l1, (base_k, base_n), (k_l0_idx, 0))
798 b_l0 = self.l0b.produce()830 b_l0 = self.l0b.produce()
799 mem_copy(b_l0, l1_b_slice, transpose=True)831 mem_copy(b_l0, l1_b_slice, transpose=True)
800 832 
801- is_first_k_block = (k_l1_idx == 0 and k_l0_idx == 0)833+ is_first_k_block = k_l1_idx == 0 and k_l0_idx == 0
802 unit_flag = 0834 unit_flag = 0
803 if const_expr(self.use_unit_flag):835 if const_expr(self.use_unit_flag):
804- is_last_k_block = (k_l1_idx == last_l1_idx836+ is_last_k_block = (
805- and k_l0_idx == k_l0_per_l1 - 1)837+ k_l1_idx == last_l1_idx and k_l0_idx == k_l0_per_l1 - 1
838+ )
806 unit_flag = 3 if is_last_k_block else 2839 unit_flag = 3 if is_last_k_block else 2
807- dsl_matmul(c_w, a_l0, b_l0, init=is_first_k_block,840+ dsl_matmul(
808- bias=bias, unit_flag=unit_flag)841+ c_w,
842+ a_l0,
843+ b_l0,
844+ init=is_first_k_block,
845+ bias=bias,
846+ unit_flag=unit_flag,
847+ )
809 848 
810- @jit849+ @host
811 def run(self, gm_a: Tensor, gm_b: Tensor, gm_c: Tensor, gm_bias: Tensor):850 def run(self, gm_a: Tensor, gm_b: Tensor, gm_c: Tensor, gm_bias: Tensor):
812 self.bmm_kernel[self.used_core_num](gm_a, gm_b, gm_c, gm_bias)851 self.bmm_kernel[self.used_core_num](gm_a, gm_b, gm_c, gm_bias)
813 852 
@@ -855,11 +894,23 @@ def batch_matmul(
855 bias, bias_shared, bias_full = _normalize_bias(bias, a, c_batch, n)894 bias, bias_shared, bias_full = _normalize_bias(bias, a, c_batch, n)
856 895 
857 ctx = _KernelCtx(896 ctx = _KernelCtx(
858- a_kern=a_kern, b_kern=b_kern, bias=bias, c_batch=c_batch,897+ a_kern=a_kern,
859- m=m, n=n, k=k, hf32=hf32,898+ b_kern=b_kern,
860- kern_trans_a=a_transposed, kern_trans_b=not b_transposed,899+ bias=bias,
861- a_full=a_full, a_terms=a_terms, b_full=b_full, b_terms=b_terms,900+ c_batch=c_batch,
862- bias_shared=bias_shared, bias_full=bias_full, squeeze_out=squeeze_out,901+ m=m,
902+ n=n,
903+ k=k,
904+ hf32=hf32,
905+ kern_trans_a=a_transposed,
906+ kern_trans_b=not b_transposed,
907+ a_full=a_full,
908+ a_terms=a_terms,
909+ b_full=b_full,
910+ b_terms=b_terms,
911+ bias_shared=bias_shared,
912+ bias_full=bias_full,
913+ squeeze_out=squeeze_out,
863 )914 )
864 out = _degenerate_result(ctx)915 out = _degenerate_result(ctx)
865 if out is not None:916 if out is not None:
@@ -25,10 +25,10 @@ from cannbotdsl.ops.arch import get_block_idx, get_block_num
25from cannbotdsl.channel import Channel25from cannbotdsl.channel import Channel
26 26 
27 27 
28-from cannbotdsl.lang.jit import jit28+from cannbotdsl.lang.host import host
29from cannbotdsl.lang.kernel import kernel29from cannbotdsl.lang.kernel import kernel
30from cannbotdsl.ops.matmul import matmul as dsl_matmul30from cannbotdsl.ops.matmul import matmul as dsl_matmul
31-from cannbotdsl.tensor import tile_view31+from cannbotdsl.tensor import tile_slice
32from cannbotdsl import MemLoc, Tensor32from cannbotdsl import MemLoc, Tensor
33from cannbotdsl.ops.memcpy import make_copy_engine, mem_copy33from cannbotdsl.ops.memcpy import make_copy_engine, mem_copy
34 34 
@@ -669,12 +669,8 @@ class MatmulKernel:
669 MemLoc.L0C, shape=(t.base_m, t.base_n), dtype=dtypes.float32, depth=t.l0c_db669 MemLoc.L0C, shape=(t.base_m, t.base_n), dtype=dtypes.float32, depth=t.l0c_db
670 )670 )
671 671 
672- nd2nz_engine_a = make_copy_engine(672+ nd2nz_engine_a = make_copy_engine(format_transform="nd2nz")
673- format_transform="nd2nz", dtype=t.a_dtype, pad_value=0.0673+ nd2nz_engine_b = make_copy_engine(format_transform="nd2nz")
674- )
675- nd2nz_engine_b = make_copy_engine(
676- format_transform="nd2nz", dtype=t.b_dtype, pad_value=0.0
677- )
678 674 
679 n_tiles = self.n_tiles675 n_tiles = self.n_tiles
680 main_window = self.main_window676 main_window = self.main_window
@@ -702,36 +698,46 @@ class MatmulKernel:
702 698 
703 n_idx = (n_tiles - 1 - n_idx) if (row_idx % 2 != 0) else n_idx699 n_idx = (n_tiles - 1 - n_idx) if (row_idx % 2 != 0) else n_idx
704 700 
701+ l0c_acc = l0c.produce()
705 for k_l1_idx in range(k_l1_tiles):702 for k_l1_idx in range(k_l1_tiles):
706- gm_a_tile = tile_view(gm_a, (t.base_m, t.k_l1), (m_idx, k_l1_idx))703+ gm_a_tile = tile_slice(gm_a, (t.base_m, t.k_l1), (m_idx, k_l1_idx))
707- gm_b_tile = tile_view(gm_b, (t.base_n, t.k_l1), (n_idx, k_l1_idx))704+ gm_b_tile = tile_slice(gm_b, (t.base_n, t.k_l1), (n_idx, k_l1_idx))
708 mem_copy(705 mem_copy(
709- l1_a,706+ l1_a.produce(),
710 gm_a_tile,707 gm_a_tile,
711 engine=nd2nz_engine_a,708 engine=nd2nz_engine_a,
712 l2_cache_ctl=self.l2_cache_ctl_a,709 l2_cache_ctl=self.l2_cache_ctl_a,
713 )710 )
711+ l1_a_tensor = l1_a.consume()
714 mem_copy(712 mem_copy(
715- l1_b,713+ l1_b.produce(),
716 gm_b_tile,714 gm_b_tile,
717 engine=nd2nz_engine_b,715 engine=nd2nz_engine_b,
718 l2_cache_ctl=self.l2_cache_ctl_b,716 l2_cache_ctl=self.l2_cache_ctl_b,
719 )717 )
718+ l1_b_tensor = l1_b.consume()
720 719 
721 for k_l0_idx in range(k_l0_per_l1):720 for k_l0_idx in range(k_l0_per_l1):
722- l1_a_slice = tile_view(l1_a, (t.base_m, t.base_k), (0, k_l0_idx))721+ l1_a_slice = tile_slice(
723- l1_b_slice = tile_view(l1_b, (t.base_n, t.base_k), (0, k_l0_idx))722+ l1_a_tensor, (t.base_m, t.base_k), (0, k_l0_idx)
724- mem_copy(l0a, l1_a_slice)723+ )
725- mem_copy(l0b, l1_b_slice)724+ l1_b_slice = tile_slice(
725+ l1_b_tensor, (t.base_n, t.base_k), (0, k_l0_idx)
726+ )
727+ mem_copy(l0a.produce(), l1_a_slice)
728+ l0a_tensor = l0a.consume()
729+ mem_copy(l0b.produce(), l1_b_slice)
730+ l0b_tensor = l0b.consume()
726 global_k = k_l1_idx * k_l0_per_l1 + k_l0_idx731 global_k = k_l1_idx * k_l0_per_l1 + k_l0_idx
727- dsl_matmul(l0c, l0a, l0b, init=(global_k == 0))732+ dsl_matmul(l0c_acc, l0a_tensor, l0b_tensor, init=(global_k == 0))
733+ l0c_tensor = l0c_acc
728 734 
729- gm_c_tile = tile_view(gm_c, (t.base_m, t.base_n), (m_idx, n_idx))735+ gm_c_tile = tile_slice(gm_c, (t.base_m, t.base_n), (m_idx, n_idx))
730- mem_copy(gm_c_tile, l0c)736+ mem_copy(gm_c_tile, l0c_tensor)
731 737 
732 # JIT wrapper used by the Python interface. The bracket syntax launches the738 # JIT wrapper used by the Python interface. The bracket syntax launches the
733 # kernel with the number of AIC cores selected by host-side tiling.739 # kernel with the number of AIC cores selected by host-side tiling.
734- @jit740+ @host
735 def run(self, gm_a: Tensor, gm_b: Tensor, gm_c: Tensor):741 def run(self, gm_a: Tensor, gm_b: Tensor, gm_c: Tensor):
736 self.matmul_kernel[self.t.used_core_num](gm_a, gm_b, gm_c)742 self.matmul_kernel[self.t.used_core_num](gm_a, gm_b, gm_c)
737 743 
@@ -64,7 +64,6 @@ _TEST_SHAPES = [
64 pytest.param(1, 32, 1, id="M1-K32-N1-minimal"),64 pytest.param(1, 32, 1, id="M1-K32-N1-minimal"),
65 pytest.param(256, 256, 256, id="M256-K256-N256-multiTile"),65 pytest.param(256, 256, 256, id="M256-K256-N256-multiTile"),
66 pytest.param(256, 736, 96, id="M256-K736-N96-mixedTail3"),66 pytest.param(256, 736, 96, id="M256-K736-N96-mixedTail3"),
67- pytest.param(2048, 2176, 2048, id="M2048-K2176-N2048-b1tail"),
68 pytest.param(2048, 4608, 2048, id="M2048-K4608-N2048-kw128s36"),67 pytest.param(2048, 4608, 2048, id="M2048-K4608-N2048-kw128s36"),
69]68]
70 69