[BugFix] Skip DecoupleTypeCast on Evaluate roots to keep cp.async operands in their address spaces - #2869
Conversation
…rands in their address spaces
|
👋 Hi! Thank you for contributing to the TileLang project. Please remember to run We appreciate you taking this step! Our team will review your contribution, and we look forward to your awesome work! 🚀 |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough
Changescp.async type-cast handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@testing/python/transform/test_tilelang_transform_decouple_type_cast.py`:
- Around line 654-657: Update the assertions in the test around the source
inspection to collect lines containing “cp_async_gs” and assert that at least
one such line is present, rather than accepting the broader “cp_async”
substring. Then verify each collected cp_async_gs line does not contain
“_local_cast”.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 81057ebe-3eb3-4f0c-be0c-b9cb3fc42b87
📒 Files selected for processing (2)
testing/python/transform/test_tilelang_transform_decouple_type_cast.pytilelang/transform/decouple_type_cast.py
The codegen regression test filtered kernel source with , so a source with no global->shared calls left the operand assertion with nothing to inspect and the test passed vacuously. The preceding did not catch that: cp_async_commit/cp_async_wait come from the pipeline's sync skeleton, a separate emit site, and match that substring on their own. Collect the cp_async_gs lines, assert the collection is non-empty, then check each one for _local_cast operands.
Summary
Fixes #2497.
DecoupleTypeCastwas rewriting the address operands oftl.ptx_cp_asyncintolocal staging buffers, so CUDA codegen emitted
cp_async_gs_conditional<N>(local, local)instead of the requiredglobal -> sharedform. The resulting kernel fails at launch withCUDA_ERROR_INVALID_ADDRESS_SPACE(reported on H800 / sm_90).The fix is a single guard: the pass now leaves
Evaluateroots alone.Decoupling works by splitting the value edge of a
BufferStoreto insert astaging buffer; an
Evaluatediscards its result and stores nothing, so thatedge does not exist and the transform has nothing to act on.
Root Cause
The pass detects "mixed-precision compute" by looking for
Castnodes in thevectorized loop body (
_has_cast). Two gates then decide the rewrite, and thebug needs both.
Gate 1 — misclassified trigger. With int64 index arithmetic,
T.copylowersto a
tl.ptx_cp_async(dst, src, num_elems, predicate)whose guard predicatecarries explicit
T.Cast("int64", ...)nodes:The casts inside
arg0/arg1sit inBufferLoad.indicesand are correctlyskipped by the existing empty
visit_buffer_load_. The ones in the predicate(
arg3) are not wrapped in anyBufferLoad, so_CastFinderreportsfound=Trueand the pass concludes a mixed-precision decouple is needed. Withint32 indices no explicit cast node is produced and the pass is a no-op — which
is why the bug is int64-specific.
Gate 2 — address operand treated as data.
MemoryAccessCollector.visit_call_recurses into all args of non-
if_then_elsecalls, so it reachestl.access_ptr(BufferLoad(Input, ...), ...)and collects thatBufferLoadas adata access. Per
src/op/builtin.h, theBufferLoadargument oftl.access_ptris an address carrier (kept in that shape so it can lower totvm_access_ptr), not a value to read.AccessReplacerthen rewrites it toBufferLoad(Input_local_cast_1, [vec]), pointing the operand at registers.The result: the real address arithmetic is hoisted into two synchronous copy
loops, and the surviving
cp.asyncis both redundant and illegal.Note the predicate is left byte-for-byte identical: the casts that triggered the
whole rewrite are never acted on, which is itself evidence the trigger is
spurious.
Changes
tilelang/transform/decouple_type_cast.py(+11) — early return when the loopbody root, after inlining flat
Binds and stripping oneIfThenElselayer, isan
Evaluate. Placed next to the existing_contains_seq_stmtguard andbefore
MemoryAccessCollectoris constructed, so both gates are closed atonce. Reuses the
extract_if_conditioncall that the function already needs.Opaque intrinsic statements such as
tl.ptx_cp_asyncland here, and theiroperands are addresses with address-space constraints (dst shared, src global)
that staging buffers would violate.
The guard matches
Evaluatespecifically rather than "anything that is not aBufferStore". The narrow form rests on a language-level invariant — anEvaluatecannot store a value, so the edge decoupling needs cannot exist —instead of on current test coverage, and it leaves every other statement shape
on its existing path.
testing/python/transform/test_tilelang_transform_decouple_type_cast.py(+87) —two regression tests, one per layer.
Tested
TL_LOWER_TRACE)CHANGED->NO-OP, matching the int32 controlcp_asynccalls, 0 malformed (was 2/2);dst=&shared_buf[...],src=&Input[...]; 92 -> 67 linesdecoupled_castblock, no_local_castbuffertesting/python/transform/diff -rq, exit 0)format.shRegression tests are two-layered.
test_no_transform_evaluate_root_opaque_intrinsicasserts at the transform layer that the pass is a no-op on an
Evaluate(ptx_cp_async(...))root viastructural_equal— fast and precise.test_codegen_pipelined_int64_index_cp_async_operandscompiles the reproducershape (int64 indices +
T.Pipelined(num_stages=2)+disable_tma=True) andasserts no
cp_asyncoperand contains_local_cast. The second layer pins theuser-visible symptom, so the test still catches the bug if a future refactor
changes how correctness is enforced, or if malformed codegen reappears via a
different path.
Negative check. Neutering the guard (
if False:) makes both new tests failwith exactly the issue's shape,
cp_async_gs_conditional<4>((&(shared_buf_local_cast[...])), (&(Input_local_cast_1[...])), ...),confirming they fail for the intended reason.
On "no behavior change". The example codegen diff is the load-bearing
evidence here, not the unit tests: fp8 per-token cast (the pass's main use case),
mixed-precision elementwise, pipelined gemm with
num_stages=3, and a pipelinedfp8 shared store all generate identical CUDA before and after. Across the 19
existing tests in the target file, every transforming site has a
BufferStoreroot after condition stripping (7 direct, 1 behind an
if); anEvaluaterootnever appears, so nothing previously transformed is now skipped.
DecoupleTypeCastruns in the cuda, cpu, rocm, metal and webgpu pipelines, sothe guard applies to all of them; the codegen-diff evidence covers cuda.
Reproducer
Needs int64 index arithmetic,
num_stages >= 2,disable_tma=True, and aconsumer of the shared buffer. Gemm, varlen batching, dynamic shapes and
data-dependent trip counts are all irrelevant.
Note for reviewers
Two adjacent items were deliberately left out of this PR.
MemoryAccessCollectortreating thetl.access_ptrBufferLoadas a dataaccess (gate 2) is still there, but with the guard in place that code path is
unreachable in the current lowering pipeline: the collector only runs on
BufferStoreroots, andtl.access_ptrappears only after lowering tointrinsics, where the root is
Evaluate. Adding a second filter there would beuntestable defensive code.
The collector skips
if_then_elseconditions but_CastFinderdoes not — areal asymmetry, with no known input where it causes a wrong decision. Changing
the detection predicate to fix an unobservable difference seemed worse than
leaving it documented here.
Overview
This PR fixes issue
#2497by preventingDecoupleTypeCastfrom rewriting address operands oftl.ptx_cp_asyncoperations. The bug occurs when auto-cp.asynclowering with int64 index arithmetic generatescp_async_gs_conditional(local, local)instead of the required global-to-shared form, causingCUDA_ERROR_INVALID_ADDRESS_SPACEat kernel launch on H800/sm_90 GPUs.Root Cause
Two gates trigger the bug:
Gate 1: With int64 index arithmetic,
T.copylowers totl.ptx_cp_asyncwith explicitT.Cast("int64", ...)nodes in the guard predicate. The_CastFinderdetects these as mixed-precision compute, triggering theDecoupleTypeCastpass.Gate 2:
MemoryAccessCollector.visit_call_recurses into all non-if_then_elsecall arguments, treating thetl.access_ptr(BufferLoad(...), ...)address carrier as a data access. TheAccessReplacerthen rewrites it to use local staging buffers.Fix
The fix adds an early return when the loop body root (after inlining flat
Binds and stripping oneIfThenElselayer) is anEvaluate. SinceEvaluatediscards its result and stores nothing, the value edge decoupling requires cannot exist. This preserves address-space constraints for opaque intrinsic statements liketl.ptx_cp_async.Changes to
tilelang/transform/decouple_type_cast.pyAdded guard logic (+11 lines) that checks if the normalized root statement is an
Evaluate. When true, the pass returns the loop unchanged. This prevents rewriting of address operands in opaque intrinsic calls that have strict address-space constraints (destination must be shared memory, source must be global memory).Changes to
testing/python/transform/test_tilelang_transform_decouple_type_cast.pyAdded two regression tests (+87 lines):
test_no_transform_evaluate_root_opaque_intrinsic(): Verifies that vectorized loops withEvaluateroot statements containing opaque intrinsics remain unchanged after transformation.test_codegen_pipelined_int64_index_cp_async_operands(): Validates the end-to-end behavior for pipelined global-to-shared copies, confirming that generatedcp_async_gslines use correct global and shared memory addresses instead of local register casts.The test refinement in the second commit improves test robustness by:
cp_async_gslines specifically (global-to-shared async copies)_local_castoperandsThis ensures the test validates the intended user-visible symptom rather than passing vacuously when no global-to-shared calls exist.
Validation
Evaluate(ptx_cp_async(...))loop rootsCUDA_ERROR_INVALID_ADDRESS_SPACEon TileLang 0.1.9, including fully divisible KV-loop configurations withnum_stages=2and automatic async-copy lowering enabled