perf(kernel): optimize Blackwell grouped GEMM primitives#19988
perf(kernel): optimize Blackwell grouped GEMM primitives#19988spectrometerHBH wants to merge 21 commits into
Conversation
Drop .agents/skills/tir-bench and related AGENTS.md/.gitignore entries.
* feat(lower-tirx): add vector PTX ld/st and remove copy_xxb Extend T.ptx.ld/st with dst/vec forms, retire copy_xxb intrinsics, and route reg/gmem_smem tile copy dispatch through inline PTX ld/st with unit tests. * test(lower-tirx): fix PTX ld/st tests to use behavior not source reads Add GPU reg↔shared roundtrip checks in test_ptx_ld_st_ops, drop test_reg_nested_copy_op source-path hacks, and assert PTX ld/st via compiled CUDA in test_reg. * test(tirx): document cta_sync stand-in after copy_bytes removal Namespace printer test still needs a T.cuda device_intrin after copy_xxb was deleted. * test(tirx): drop stale copy_bytes comment in namespace test * test(tirx): drop redundant warpgroup PTX ld/st compile grep test test_reg_roundtrip already covers the same warpgroup reg<->shared kernel with GPU execution and numerical checks. * test(tirx): consolidate PTX ld/st width tests into test_ptx_ld_st_ops Delete legacy test_cuda_copy.py and cover 128/64/32/16/8-byte shared copies with one parametrized GPU test. Fix u8 ld return type to uint32 per PTX codegen constraints. * test(tirx): dedupe PTX ld/st width kernels with case table Drive shared-copy GPU tests from _SHARED_COPY_CASES and three dtype templates instead of five copy-pasted num_bytes branches. * test(tirx): use one shared-copy kernel with uint32 smem Single _shared_scratch_copy_kernel entry: uint32 smem for 128/64/32b, small 16b/8b branches only where PTX scratch/buffer types require it. * test(tirx): single shared-copy kernel via closure dtype vars TVMScript accepts smem_dtype/tmp_dtype from the builder closure; only in-body branching on buffer declarations is unsupported.
#7) Extend RewriteSimplifier with unsigned-only identities (x%x, x%1, x/x, x/1) and floormod(x*c1, c2) -> 0 when c1 is divisible by c2. Signed rules that assume no wraparound are intentionally not applied to unsigned dtypes.
Add references/TIRX_BENCH_IMPLS support, in-bench rounds aggregation, and store CUDA event and proton timings in microseconds. Point AGENTS.md at tirx-kernels tir_bench and simplify tir-test import setup.
Add an optional tma_dtype="tf32" to copy_async(tma) so an fp32 gmem buffer is loaded via a CU_TENSOR_MAP_DATA_TYPE_TFLOAT32 descriptor. The TMA hardware then round-to-nearest truncates fp32 -> tf32 ON LOAD, matching a tf32 MMA's operand precision (and a torch allow_tf32 / DeepGEMM reference). Loading as FLOAT32 and letting the tf32 MMA RZ-truncate the top 19 bits at read diverges by up to one tf32 ULP (~5e-4 on a GEMM output, _calc_diff ~1.5e-7 > a 1e-8 gate). - runtime.cuTensorMapEncodeTiled (cuda_device_api.cc): optional trailing int force_cu_dtype (>= 0 overrides the dtype-derived CUtensorMapDataType; 11 == TFLOAT32). Backward-compatible -- older callers omit the arg. - tma.py: _TMA_DTYPE_TO_CU maps "tf32"/"tfloat32" -> 11; reads op_call.config["tma_dtype"], validates a float32 buffer, folds it into the tensormap cache key, and appends force_cu_dtype to the encode call ONLY when set so the default path stays byte-identical (existing codegen tests unchanged). tir-test (tests/python/tirx): 2040 passed, 0 failed.
Extend tcgen05 dense MMA dispatch with semantic A/B dtypes (fp8, tf32 via is_AB_tf32) and add end-to-end gemm_async tests for fp8 and tf32 TMA loads.
* fix(infra): reg-copy R2S/S2R for tcgen05 split-laneid atom layouts Preserve r_perm through align_layouts_raw so split-laneid register layouts pair correctly with swizzled SMEM. Add layout and compile tests. * refactor(infra): shorten align_layouts_raw docstring * test(infra): document tcgen05 D epilogue reg-copy regression case Name the production copy (shapes, layouts, slice), assert layout pairing 3 vs 1 groups, and check generated outer loop f<16 with st.shared.v2.u32. * test(infra): add GPU roundtrip for tcgen05 D epilogue reg copy Exercise R→S deposit and S→R reload on Layout-F reg vs 128B swizzled SMEM; assert bit-exact recovery of the host-filled logical tile. * refactor(op-dispatch): remove slice@llvm layout workarounds FA4's global pre-canon in TileLayoutNode::Slice makes llvm-only slicing unnecessary; slice and canonicalize under sctx.target in reg, ldstmatrix, and elementwise dispatch.
Wire maximum into BINARY_OPS with scalar FMNMX lowering (no f32x2 vec). Add GPU reg roundtrip test for Tx.maximum on local operands.
* fix(lower-tirx): slice elementwise reg layouts under sctx.target (B00011) Run layout slice and canonicalize under sctx.target in _align_layouts_no_post_canon and _check_layout_operands_agree so split-laneid tcgen05 atom layouts do not hit conflicting thread scopes during dispatch. * test(op-dispatch): add B00011 tcgen05 cast regression tests Cover warpgroup cast on split-laneid tcgen05 atoms and drop redundant hasattr guards before TileLayout.canonicalize(). * test(op-dispatch): add GPU regression for tcgen05 warpgroup cast Roundtrip scatter/cast/gather on the split-laneid .16x256b atom layout mirrors the tf32 A-cast path and checks bf16→fp32 values on device. * test(op-dispatch): drop redundant B00011 predicate unit test Keep compile and GPU roundtrip regressions for tcgen05 warpgroup cast only. * refactor(op-dispatch): drop unrelated elementwise changes from B00011 PR Revert noop _common.py slice split and unrelated hasattr cleanup; keep only _check_layout_operands_agree slice+canon under sctx.target. * refactor(op-dispatch): drop hasattr guard before layout canonicalize Operands are always TileLayouts in the reg elementwise predicate path.
…matrix (#18) Step 2 canonicalized the register/shared layout *before* slicing, to fuse a split-laneid tcgen05 atom (separate laneid + wid_in_wg axes) into a single tid_in_wg axis on the full layout — otherwise slicing left an ill-formed sub-layout that GetScope rejected, silently dropping ldmatrix for a scalar reg path. `TileLayout.Slice` now globally pre-canonicalizes internally before grouping, so the Python pre-slice canonicalize is redundant: `slice().canonicalize()` produces the same layout. Drop it. Add a compile-only regression test: a warpgroup `.16x256b` atom loaded from a 128B-swizzled SMEM tile must emit `ldmatrix.x4` (the tf32-prenorm cast-warp A load).
…r cp (#19) The smem->tmem cp dispatch encoded a fresh SMEM matrix descriptor per buffer (cache key included hash(s_buf)) and set LDO=16 (a placeholder cp ignores for data). Instead encode ONE descriptor template at SMEM base 0 — so the cache key drops the per-buffer hash and identical (ldo, sdo, swizzle) templates are shared — and patch its 14-bit address field per cp via `cvta(addr) >> 4 & 0x3FFF` (`_desc_set_addr`, mirroring the hand-rolled `replace_smem_desc_addr`). LDO is set to 0 since cp ignores it for data and a non-zero LDO only bloats the address-patch codegen. Add a compile-only regression test: a 4-tile copy emits one `encode_matrix_descriptor` reused across four `tcgen05.cp.32x128b.warpx4` issues, each with the per-cp address-field patch.
* feat(op-dispatch): support uint32 shape extents in TMA copy A deepgemm TMA source buffer wants a uint32 runtime shape (e.g. shape_m) with no int32 cast. Two grouping proofs blocked it: - gmem: `_canonicalize_gmem` fused contiguous dims into one prod-extent iter (n*64), so regrouping by the buffer shape needed `(n*64) % n == 0` — unprovable for unsigned under wraparound. Drop the canonicalize: a plain gmem buffer already has one iter per dim, so grouping only needs the overflow-free `dim % dim` proof. The grouped result is identical for every signed case (group re-splits to the buffer dims), so this is a pure proof simplification (full golden suite unchanged) that also unlocks uint32 shapes. - smem: an unsigned slice base leaks its dtype into the copy extent; `_regroup_smem_by_extgt1_shape` now views unsigned extents as signed for the structural proof (value-preserving; emitted base stays unsigned). * refactor(op-dispatch): canonicalize TMA gmem groups after grouping * fix(layout): fold unsigned floormod constants
#20) A shared->shared warp_xor_swizzle permute indexed both operands through ``buf[...]``, so the swizzled layout lowered to a per-element IMAD flatten on the hot SF-transpose path. For a 4/8-byte dtype with both operands in shared memory, compute one base ptr via ``ptr_to(stride_offset)`` and add a compile-time ``off * dtype_bytes`` per register slot, then issue ``T.ptx.ld/st(..., space="shared")`` directly. The permute only shuffles bits, so move them through an unsigned container of the matching width: ``ld.bN`` rejects a float return dtype, so this also lets a float32/float64 shared tile use the direct path (the predicate already admitted 4/8-byte floats — previously they hit a codegen error). Add a compile-only test asserting the direct ld.shared/st.shared path fires for both uint32 (SF case) and float32.
…mm_async (#15) Rework the tcgen05 gemm_async SMEM matrix-descriptor handling: - Build the descriptor per MMA from the buffer base address, selected by a new ``smem_desc`` config: * ``hoist`` (default): allocate + encode one warp-uniform descriptor per operand and add the per-MMA 16B offset (``smem_desc_add_16B_offset``). * ``recompute``: build the full descriptor inline per MMA (``_uniform_desc``) with no allocated/encoded cell — one fewer live register on the fa4 hot path at the cost of a few ALU ops. The descriptor base is always the buffer origin (stage 0); the per-MMA operand offset is applied on top, so both modes are address-correct. - ``_atom_off``: a leading atom dim of extent 1 contributes no LBO/SBO, so its (meaningless) stride no longer leaks into the descriptor offset. - Inline the A/B operands and SF addresses into the MMA call via ``T.meta_var`` to avoid per-iteration LMEM temporaries, and fold ``needs_sf_id`` / descriptor-rotation handling so the block-scaled and dense paths share one ``main_impl``. Add a compile-only test asserting the hoist vs recompute descriptor fingerprints (and that both emit the MMA).
…24) Split bench() into bench_tk (ThunderKittens group-input protocol, preserved) and a new Triton-standard bench() taking pure-launch closures. Three cold-cache timers (event/proton/cudagraph_proton); proton is the global default via timer=None. Proton sessions finalize in a finally so an error can't poison the profiler.
Drop the --impls / TIRX_BENCH_IMPLS filter primitives (OURS_IMPLS, bench_impls_mode, bench_only_ours, filter_impls). bench() and bench_tk() now always build and time every reference impl passed via `references`; the "ours"-mode skip guard and the now-dead empty-funcs early return are removed. A reference builder that raises is still recorded as BASELINE_ERROR instead of failing the workload.
…cate to LambdaExpr (#26) * refactor(infra): remove dead async_structs Pipeline and CopyPipeline The Pipeline/CopyPipeline nodes in tirx/async_structs have no remaining users. Drop the header and implementation. * refactor(infra): merge tirx_op/tirx_stmt into tile_primitive.h, rename Predicate to LambdaExpr Consolidate the tile-primitive headers: merge tirx_stmt.h (TilePrimitiveCall) and tirx_op.h (Op declarations + DispatchContext) into a single tile_primitive.h, and move the reified-lambda object (formerly Predicate) into the same header under the name LambdaExpr (type key tirx.LambdaExpr). LambdaExpr keeps the existing semantics: vars carry SEqHashDefRecursive so they act as lambda binders for structural eq/hash, and Apply substitutes them with concrete indices (now via plain Substitute, since the select predicate is int32-only and dtype legalization is a no-op). The Python wrapper, FFI registration, builder, TRN codegen, printer dispatch, and tests all follow the rename. Update all #include sites to the new header. * refactor(infra): consolidate Python tile-primitive IR nodes into tile_primitive.py Mirror the C++ `tile_primitive.h` on the Python side: move `LambdaExpr`, `DispatchContext`, and `TilePrimitiveCall` (plus the `normalize_const_arg` helper) from `lambda_expr.py`, `operator/tile_primitive/dispatch_context.py`, and `stmt.py` into a single top-level `tirx/tile_primitive.py`. Update all direct importers and the `tirx` package re-exports. The three classes remain available at `tvm.tirx.*`; `tvm.tirx.stmt.TilePrimitiveCall` is gone (use `tvm.tirx.tile_primitive` or `tvm.tirx.TilePrimitiveCall`). * docs(infra): fix stale tirx_kernels.tir_bench refs to bench_suite The bench module was renamed to `tirx_kernels.bench_suite` but `AGENTS.md` and the `tir-test` skill still referenced the old `tirx_kernels.tir_bench` path, so `python -m tirx_kernels.tir_bench --check-imports` failed with ModuleNotFoundError.
…ow/sub/rearrange) (#28) * feat(layout): support Buffer.permute on swizzle-composed layouts Buffer.permute only handled plain TileLayouts; on a ComposeLayout it crashed (no group method). The swizzle permutes the flat offset, so it commutes with a permutation of the logical dims: unwrap the compose, permute the inner tile layout's dim groups, and re-compose with the same swizzle. This enables deriving re-factored views of swizzled buffers by reshape-permute-reshape instead of restating the full stride/swizzle layout by hand (e.g. the flashmla KV-gather warp view). Unit test pins the composed-layout permute and its script roundtrip. * fix(tvmscript): make decl_buffer alias printing roundtrippable and deterministic Three fixes to the DeclBuffer sugar printer: - Remove the slice sugar (child printed as parent[slices]): that print reparses as a BufferRegion, not a Buffer, so any later Buffer use of the alias (stores, views) breaks on roundtrip. Such aliases now print as plain T.decl_buffer, which reparses exactly. - Prefer a parent whose layout matches the child structurally (two-pass candidate scan), so reshapes of an already-laid-out view print as a bare parent.view(shape) instead of restating the layout. - Order parent candidates by (name, structural hash) instead of the unordered obj2info iteration, so the printed parent choice is stable across print/parse roundtrips. * feat(layout): buffer dim-surgery views and einops-style rearrange Tile-primitive operands are tensor + region, but deriving a tensor view previously meant a raw T.decl_buffer restating the full stride/swizzle layout, and regions carried a*k:(a+1)*k index arithmetic. Add a dimension-surgery algebra on Buffer -- every operation returns a derived view sharing the data, carries the physical placement (layout iters + swizzle) automatically, and never moves data: - unflatten(dim, sizes) / flatten(start_dim, end_dim): torch-aligned dim split/merge as pure reshapes (one -1 factor inferred; flatten never copies -- multi-iter axes make the merges legal views). - select(dim, index) / narrow(dim, start, length): torch-aligned; the index/start (dynamic PrimExpr allowed) folds into elem_offset through the dim's layout iters, decomposed mixed-radix for multi-iter dims; narrow on multi-iter dims requires inner-block alignment and rejects loudly otherwise. - Buffer.sub[...]: numpy basic-indexing view constructor (int drops the dim, a:b narrows, a::s strides via unflatten + select); a separate accessor because plain buf[...] keeps its BufferLoad/BufferRegion semantics. - Buffer.rearrange(pattern, **sizes): einops-style bijective rearrangement compiled onto unflatten/permute/flatten, with pattern- referencing error messages. E.g. a warp's 16 interleaved rows of a swizzled chunk-major tile: k_nope.unflatten(1, (4, 4, 4)).select(2, warp).flatten(1, 2) or as a family view: k_nope.rearrange("b (s w r) c -> b w (s r) c", w=4, r=4) Tests cover each method on plain and swizzle-composed layouts, dynamic indices, mixed-radix offsets, sub/rearrange equivalence to the explicit chains, misalignment and pattern error rejection, and script roundtrips. * fix(layout): validate static bounds in the dim-surgery views Review findings on the new view APIs: - unflatten/rearrange accepted non-bijective factorizations silently (prod(sizes) != extent, or a -1 factor inferred from a non-divisible extent), dropping or over-running elements. Validate whenever the extent and factors are statically known; rearrange also reports the offending composite with the pattern text. - select/narrow/sub performed no static bounds checks, producing negative or out-of-range elem_offsets and even negative shapes. Check statically known values: select requires 0 <= index < extent, narrow requires start >= 0, length >= 1, start + length <= extent, and sub's stepped slice requires start in [0, step). Dynamic (PrimExpr) indices remain the caller's responsibility, matching region semantics; the docstrings say so explicitly. Regression tests cover every rejected form. * fix(layout): reject non-positive unflatten factors Negative factors whose product happens to equal the extent slipped past the bijectivity check (e.g. unflatten(0, (-2, -5)) on extent 10, and the equivalent rearrange sizes). Require every statically known size to be positive, with the single -1 infer slot as the only exception, before inference and the product check. Regression tests cover both the unflatten and rearrange forms. * fix(layout): gate swizzled-view offsets and validate rearrange singleton sizes select/narrow/sub folded their pre-swizzle linear offset straight into elem_offset and rewrapped the swizzle unchanged. The swizzle XOR only commutes with offsets that are multiples of its period 2^(per_element + atom_len + swizzle_len), so any sub-period offset produced a view addressing the wrong bytes under layout application. _rebuild_view now proves the offset is period-aligned (statically or via the analyzer) and raises otherwise, pointing at region slicing or an explicit layout as the escape hatch. rearrange silently ignored an explicit size on a singleton axis; a statically known mismatch with the dim extent now raises. * fix(tvmscript): accept PrimExpr offsets in the S/R layout-spec sugar _LayoutSpec.__add__/__radd__ gated on _OnAxis | _OffsetExpr | int and returned NotImplemented for a plain PrimExpr, so a printed layout like T.S[(2, 8):(128, 1)] + w * 8 fell back to PrimExpr.__radd__ and crashed the reparse. _OffsetExprLike and _to_offset_expr already treat a bare PrimExpr as an Axis.m term; accept it in the isinstance gates too. Regression-covered by the dynamic-offset roundtrip in the swizzle view test added next. * feat(layout): keep non-commuting view offsets inside the swizzled layout The swizzle-commutation gate on select/narrow/sub rejected every folded offset that was not a swizzle-period multiple. Those views are representable exactly: a TileLayout offset sits inside the ComposeLayout, so Apply computes swizzle(offset + rest) - the true address - and the printer/parser and BufferLoad lowering (Canonicalize()->Apply) already honor it. _rebuild_view now folds the offset into elem_offset only when it provably commutes with the swizzle (period multiple, statically or via the analyzer) and otherwise adds it to the derived tile layout's m-axis offset. Views on swizzled layouts are total again; no raising path remains. Verified address-equivalent against the parent layout per coordinate, including sub-period static offsets, dynamic offsets, and fixed-point windows below 2^(per_element + atom_len).
There was a problem hiding this comment.
Code Review
This pull request refactors TVM's TIRx dialect, moving TilePrimitiveCall and DispatchContext to tvm.tirx.tile_primitive, replacing Predicate with LambdaExpr, and introducing robust dimension-surgery views (unflatten, flatten, select, narrow, sub, rearrange) to Buffer. It also extends PTX load/store intrinsics, optimizes tcgen05 block-scaled MMA and copy operations, and updates the benchmark utilities. The reviewer feedback highlights several critical issues: a scoping violation in tcgen05_cp.py from an over-simplified cache key, a missing cache_policy argument in ptx_ld_acquire, a missing cache_policy in the generated C signatures for relaxed and acquire loads, a potential AttributeError in scalar_dtype for raw Python scalars, fragile line-number parsing in monitor_gpu.sh, and import alias inconsistencies in binary.py.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| def _get_or_create_desc(sctx, s_buf, ldo, sdo, swizzle): | ||
| cache_key = f"smem_tmem_desc:{hash(s_buf)}:{int(ldo)}:{int(sdo)}:{int(swizzle)}" | ||
| # Cache descriptor template at SMEM 0; patch addr per cp. | ||
| cache_key = f"smem_tmem_desc:{int(ldo)}:{int(sdo)}:{int(swizzle)}" |
There was a problem hiding this comment.
Removing hash(s_buf) from the cache key can lead to compilation errors due to scoping violations when multiple distinct shared memory buffers are used in the same kernel. Since desc_buf is allocated via add_post_buffer_def_stmt(s_buf, wrap), its scope is bound to s_buf's definition block. If another buffer s_buf2 hits the cache and reuses desc_buf, it will attempt to access desc_buf outside of its valid scope, resulting in an undefined variable error during TVM compilation. Including s_buf.name in the cache key (similar to gemm_async/tcgen05.py) resolves this safely.
| cache_key = f"smem_tmem_desc:{int(ldo)}:{int(sdo)}:{int(swizzle)}" | |
| cache_key = f"smem_tmem_desc:{s_buf.name}:{int(ldo)}:{int(sdo)}:{int(swizzle)}" |
| return call_intrin( | ||
| return_type, "tirx.ptx.ld_acquire", addr, return_type, ptx_type, scope, space | ||
| return_type, | ||
| "tirx.ptx.ld_acquire", | ||
| addr, | ||
| return_type, | ||
| scope, | ||
| space, | ||
| vec, | ||
| ptx_type, | ||
| int(has_cache_policy), | ||
| 0, | ||
| l1_evict, | ||
| l2_evict, | ||
| prefetch_size, | ||
| ) |
There was a problem hiding this comment.
In ptx_ld_acquire (when to_dst is False), cache_policy is completely missing from the arguments passed to call_intrin. This is a major discrepancy compared to ptx_ld_relaxed and ptx_ld which always pass cache_policy as the second argument. This omission will cause compilation or runtime errors because the generated C++ function expects cache_policy when has_cache is true, but it is not passed.
| return call_intrin( | |
| return_type, "tirx.ptx.ld_acquire", addr, return_type, ptx_type, scope, space | |
| return_type, | |
| "tirx.ptx.ld_acquire", | |
| addr, | |
| return_type, | |
| scope, | |
| space, | |
| vec, | |
| ptx_type, | |
| int(has_cache_policy), | |
| 0, | |
| l1_evict, | |
| l2_evict, | |
| prefetch_size, | |
| ) | |
| return call_intrin( | |
| return_type, | |
| "tirx.ptx.ld_acquire", | |
| addr, | |
| cache_policy, | |
| return_type, | |
| scope, | |
| space, | |
| vec, | |
| ptx_type, | |
| int(has_cache_policy), | |
| 0, | |
| l1_evict, | |
| l2_evict, | |
| prefetch_size, | |
| ) |
| sig = ( | ||
| "(void* address, unsigned long long cache_policy)" if form == "weak" else "(void* address)" | ||
| ) |
There was a problem hiding this comment.
In _ptx_ld_form_parts, the C signature sig only includes cache_policy if form == "weak". However, relaxed and acquire forms also support and use cache_policy when has_cache is true. If has_cache is true for relaxed or acquire, the generated C function will fail to compile because cache_policy is referenced in the asm block but is not present in the function signature. Changing the condition to if form in ("weak", "relaxed", "acquire") resolves this.
| sig = ( | |
| "(void* address, unsigned long long cache_policy)" if form == "weak" else "(void* address)" | |
| ) | |
| sig = ( | |
| "(void* address, unsigned long long cache_policy)" | |
| if form in ("weak", "relaxed", "acquire") | |
| else "(void* address)" | |
| ) |
| monitor_gpu.sh --gpu 3 --interval 2 # poll every 2 seconds | ||
| monitor_gpu.sh --log /tmp/gpu.log # also tee to a log file | ||
| EOF | ||
| sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//' |
There was a problem hiding this comment.
Hardcoding line numbers like '2,12p' in sed is extremely fragile because any change to the top of the script (such as adding comments, changing imports, or adding license headers) will break the help output. Keeping the heredoc cat <<'EOF' is much more robust and standard.
| sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//' | |
| cat <<'EOF' | |
| Watch a single GPU for foreign processes (anyone other than the current | |
| user) appearing during a long-running test. Intended companion to | |
| '/tir-test': leave this running in a side terminal while pytest runs, and | |
| it will alert if someone else lands on the same GPU. | |
| Usage: | |
| monitor_gpu.sh # uses $CUDA_VISIBLE_DEVICES, defaults to 0 | |
| monitor_gpu.sh --gpu 3 # watch GPU 3 | |
| monitor_gpu.sh --gpu 3 --interval 2 # poll every 2 seconds | |
| monitor_gpu.sh --log /tmp/gpu.log # also tee to a log file | |
| EOF |
| def scalar_dtype(scalar) -> str: | ||
| dtype = getattr(scalar, "dtype", None) | ||
| if dtype is not None: | ||
| return str(dtype) | ||
| ty = getattr(scalar, "ty", None) | ||
| if ty is None and hasattr(scalar, "expr_ty"): | ||
| ty = scalar.expr_ty() | ||
| dtype = getattr(ty, "dtype", None) | ||
| if dtype is None: | ||
| raise AttributeError(f"{type(scalar).__name__} has no dtype-bearing PrimType") | ||
| return str(dtype) |
There was a problem hiding this comment.
In scalar_dtype, if scalar is a raw Python float or int, the function will raise an AttributeError because Python built-in types do not have dtype or ty attributes. Adding a fallback check for isinstance(scalar, (float, int)) makes the function much more robust and prevents unexpected crashes when raw Python scalars are passed.
| def scalar_dtype(scalar) -> str: | |
| dtype = getattr(scalar, "dtype", None) | |
| if dtype is not None: | |
| return str(dtype) | |
| ty = getattr(scalar, "ty", None) | |
| if ty is None and hasattr(scalar, "expr_ty"): | |
| ty = scalar.expr_ty() | |
| dtype = getattr(ty, "dtype", None) | |
| if dtype is None: | |
| raise AttributeError(f"{type(scalar).__name__} has no dtype-bearing PrimType") | |
| return str(dtype) | |
| def scalar_dtype(scalar) -> str: | |
| if isinstance(scalar, float): | |
| return "float32" | |
| if isinstance(scalar, int): | |
| return "int32" | |
| dtype = getattr(scalar, "dtype", None) | |
| if dtype is not None: | |
| return str(dtype) | |
| ty = getattr(scalar, "ty", None) | |
| if ty is None and hasattr(scalar, "expr_ty"): | |
| ty = scalar.expr_ty() | |
| dtype = getattr(ty, "dtype", None) | |
| if dtype is None: | |
| raise AttributeError(f"{type(scalar).__name__} has no dtype-bearing PrimType") | |
| return str(dtype) |
| import operator | ||
| from typing import Any | ||
|
|
||
| from tvm.script import tirx as Tx |
|
|
||
|
|
||
| def _compute_maximum(src_vals, extras, dt): | ||
| return Tx.max(src_vals[0], src_vals[1]) |
Summary
tcgen05MMA and commit operations.Motivation
The downstream grouped FP8 GEMM needs predicated warp-specialized MMA issue, explicit TMA cache policy, and lower-overhead persistent scheduling. Without these changes, the generated SASS performs redundant descriptor and scheduler work on the critical producer paths.
Validation
pre-commit runpython -m pytest tests/python/tirx/operator/tile_primitive/cuda/copy_async/test_tma.py -k test_copy_tma_codegen— 54 passedpython -m pytest tests/python/tirx/operator/tile_primitive/cuda/gemm_async/test_gemm_async.py -k test_gemm_smem_desc_hoist_vs_recompute— 2 passedpython -m pytest tests/python/tirx/codegen/test_codegen_blackwell.py -k test_tcgen05_block_scaled_mma_predicate_codegen— 1 passed