Skip to content

perf(kernel): optimize Blackwell grouped GEMM primitives#19988

Draft
spectrometerHBH wants to merge 21 commits into
apache:mainfrom
spectrometerHBH:group_fp8
Draft

perf(kernel): optimize Blackwell grouped GEMM primitives#19988
spectrometerHBH wants to merge 21 commits into
apache:mainfrom
spectrometerHBH:group_fp8

Conversation

@spectrometerHBH

Copy link
Copy Markdown
Contributor

Summary

  • Add predicate support for SM100 block-scaled tcgen05 MMA and commit operations.
  • Preserve and reuse hoisted shared-memory descriptors in grouped GEMM pipelines.
  • Add configurable TMA L2 promotion and include it in tensor-map caching and encoding.
  • Prune compile-time-only tile-scheduler branches and keep pipeline state warp-uniform.

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 run
  • python -m pytest tests/python/tirx/operator/tile_primitive/cuda/copy_async/test_tma.py -k test_copy_tma_codegen — 54 passed
  • python -m pytest tests/python/tirx/operator/tile_primitive/cuda/gemm_async/test_gemm_async.py -k test_gemm_smem_desc_hoist_vs_recompute — 2 passed
  • python -m pytest tests/python/tirx/codegen/test_codegen_blackwell.py -k test_tcgen05_block_scaled_mma_predicate_codegen — 1 passed

spectrometerHBH and others added 21 commits June 27, 2026 10:11
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.
)

Factor ptxas flags into _ptxas_option_flags() and forward
TVM_CUDA_PTXAS_REG_LEVEL / TVM_CUDA_PTXAS_EXTRA_OPTS for both nvcc
(comma-joined) and nvrtc (one --ptxas-options entry per token).
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).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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)}"

Comment on lines 3558 to 3572
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,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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,
)

Comment on lines +392 to 394
sig = (
"(void* address, unsigned long long cache_policy)" if form == "weak" else "(void* address)"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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\}//'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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

Comment on lines +86 to +96
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To maintain consistency with the rest of the codebase where T is used for tvm.script.tirx and Tx is used for tvm.script.tirx.tile, please import tirx as T.

Suggested change
from tvm.script import tirx as Tx
from tvm.script import tirx as T



def _compute_maximum(src_vals, extras, dt):
return Tx.max(src_vals[0], src_vals[1])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use T.max instead of Tx.max to align with the updated import alias T for tvm.script.tirx.

Suggested change
return Tx.max(src_vals[0], src_vals[1])
return T.max(src_vals[0], src_vals[1])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant