mirror of
https://github.com/tile-ai/tilelang.git
synced 2026-10-02 06:34:36 +08:00
+17








e5a02f9aab
* [Ascend] Refresh comparison snapshot at upstream #3174 Comparison-only snapshot of Ascend at 925bfbb9aff0fb57a4ed9fecc3591b8dcac21c8f. Use upstream #3174 (62bba8d20d) as the logical synchronization point. Preserve the complete tree of the previous comparison head d0d2f40be354cc0a4d6ed037b34278c8746fa031, including submodule references. No source changes or additional integration resolutions are introduced. This snapshot is for upstream comparison only. * [Ascend] Keep the CUDA default facade and reach Ascend through its own dialect Upstream #3186 removes the shared `Kernel`, so `tilelang.language` can no longer adapt to the host at runtime (the removed `check_ascend_availability()` probe was the only thing making one import work for both). The fork has to pick a dialect for the default facade, and it now keeps upstream's choice: `tilelang.language` is the CUDA dialect, and Ascend is reached explicitly, like every other backend. That is the option with no upstream divergence: the 136 fork-owned Ascend files (examples/ascend, testing/ascend, tilelang/ascend) take the explicit import, while upstream's files stay untouched. Previously the fork had converted six upstream files to `tilelang.cuda.language` to work around the Ascend facade; those are reverted to upstream's `import tilelang.language as T`, and the materialize-launch test added by #3186 passes unmodified (19 tests). Ascend coverage for the launch refactor moves to a fork-owned file, `testing/ascend/transform/test_ascend_materialize_kernel_launch.py`, which pins the decoupled lowering (grid -> thread_extent, placeholders dropped), the `cthread` launch dimension, and the backward-compatible default of `lower_grid_binding`. Effect on testing/python/ (H100, CUDA 13.1): 155 failed / 3347 passed before, 17 failed / 3557 passed after. The 138 removed failures were all "module 'tilelang.language' has no attribute ..." and "Kernel() got an unexpected keyword argument 'threads'" from the dialect swap. 6 of the remaining 17 fail identically on upstream main (missing nvidia-cutlass-dsl); the other 11 are the pre-existing shared-code deltas from the reducer and thread-storage-sync work, unrelated to the launch refactor. * [Ascend] Point the remaining explicit dialect imports at the Ascend package Follow-up to the facade change: files that pulled individual dialect symbols with `from tilelang.language import <name>` still went through the CUDA facade. - `from tilelang.language import simd as S` (6 files) -> the Ascend `simd` module, which is an Ascend extension. - `from tilelang.language import kernel as K` (test_simtvf_thread_binding) -> the Ascend kernel module. It has to be this one: the SimtVF-aware get_thread_binding/get_thread_extent accessors now live there, so importing the shared module returned the launch placeholder `tx` instead of the SimtVF var `simtvf_tx`. - test_simtvf_frame_type asserted SimtVFFrame is importable from `tilelang.language`; it is an Ascend frame, so it now imports it from the Ascend dialect. Verified the three SimtVF shapes these cover still lower through the new launch materialization (warp-reduce style, vf var scope, and the simtvf_tx/ty/tz naming contract); none of them trips the new "body references thread index" diagnostic, so it is not a false positive on legitimate T.SimtVF code. * [Ascend] Restore CANN 9.2 codegen compatibility lost by the snapshot base The snapshot's Ascend tree is asc@925bfbb9, which predates the two commits that made generated kernels compile on CANN 9.2 / bisheng: - 1037a5fc "use the C API exclusively" (#399), which also moved the block index off the CUDA builtin ("reserve block_idx", "modify block_idx due to cann") - bbcf1e47 "keep assert failure handling out of line" (#438) CANN 9.2 rejects the `blockIdx.x` builtin outside a SIMT context, and says so in c_api/utils/sys_var.h: "use block_idx for pure Vector, pure Cube, and Mix(1, 1). For Mix(1, 2), please use block_idx on the Cube core and block_idx * asc_get_sub_block_num() + asc_get_sub_block_id() on Vector cores." Every mixed kernel is a Mix(1, 2) launch, so the AIC body could not compile: "can only use thread or block infos in simt function or simt callee". Three places emitted or consumed `blockIdx.x` where CANN now requires the C API `block_idx`: - src/ascend/codegen/codegen_ascend.cc: map the blockIdx.x thread var to `block_idx`, reserve that name so the name supply cannot hand it to another var, keep skipping the block index when capturing SIMT_VF helper arguments, and include `block_idx` in the int32 cast-back list. - src/tl_templates/ascend/debug.h: the debug/assert helpers are emitted into both a `__aicore__` and a `__simt_callee__` namespace. The former is not a SIMT function, so `TL_DEFINE_ASCEND_DEBUG` now takes the block index as a parameter -- `block_idx` for the aicore namespace, `blockIdx.x` for the simt one -- instead of hardcoding `blockIdx.x` in the macro body. - tilelang/profiler/bench.py: get_msprof_cache_flush_kernel is Ascend-only (target="ascend", T.SimtVF, l2_cache_ctrl="WTS_FV") and was reaching those through the default language facade. Since the facade is the CUDA dialect again, it imports tilelang.ascend.language instead. This was the one remaining shared file the facade change had missed. testing/ascend/ on 8x Ascend950DT (CANN 9.2.0): 812 passed / 255 failed before, 857 passed / 210 failed after, against 804/250 for the pre-refactor baseline. The 45 bisheng device-compilation failures are gone; the only failures this branch still adds over the baseline are the 5 in test_tilelang_ascend_l1_l0_alignment_guards.py, where CUDA's GemmMMA.infer_layout now raises before the Ascend L1->L0 alignment guard gets to run. examples/ascend/example_gemm.py: all 7 sections compile and PASS, with max_diff matching the reference asc tree (fp8 2.14e-04 / 2.44e-04 run to run). * feat(ascend): Use the C API exclusively to eliminate compilation overhead caused by mixed API usage. (#399) * Use capi to eliminate the loss of compilation time caused by mixed api. * reserve block_idx * modify block_idx due to cann * refactor(ascend): finish C API lowering and simplify SIMD wrappers * fix(ascend): return benchmark latency for blockscaled GEMM * refactor(ascend): trim C API migration scope Restore the device assert tests, Bisheng architecture tests, and CUDA debug helper to the merge base. Remove the unused Python GM/UB DMA wrappers and update the remaining pad-value documentation. * test(ascend): expect C API locks in counter-channel spill * refactor(ascend): share SIMD call emission and precision selection Use one printer for SIMD expression operands and trailing control enums, and share SFU wrapper selection between zeroing and merging calls. Preserve argument validation, print order, and the existing float32 precision fallbacks. * fix(ci): inherit runner Torch stack in Ascend workflows * fix(ci): bootstrap pre-commit without pipx * Revert "fix(ci): bootstrap pre-commit without pipx" This reverts commit 04c742d292eec79863b8400e915c81e3483b20f7. * fix(ci): install pipx before lint * fix(ci): install pipx with configured Python * fix(ci): avoid uninstalling shared pipx dependencies --------- Co-authored-by: xuruifan <xuruifan@deepseek.com> Co-authored-by: silentCoder-dev <silentcoder@foxmail.com> Co-authored-by: Denver Jin <denverjin@deepseek.com> * Update ci.yml (#437) * [Ascend] Fix auto-scheduler memory detector ODR violation (#435) Co-authored-by: silentCoder-dev <silentcoder@foxmail.com> * fix(ascend): analyze multi-buffer eligibility on scheduled IR (#422) * fix(ascend): analyze multi-buffer eligibility on scheduled IR * fix(ascend): align guarded broadcast fill analysis * fix(ascend): rewrite multi-buffer scheduling guards * fix(ascend): clarify multi-buffer scheduling constraints * fix(ascend): keep assert failure handling out of line (#438) Bisheng can fail to select a stacksave instruction when an inlined device_assert_with_msg is followed by an Ascend sync wait. The failure reproduces with a five-character message on CANN 9.2.0 targeting dav-3510. Move printf and assert(false) into a noinline failure helper while retaining the conditional check in the inline wrapper. Preserve the diagnostic text, BlockIdx, and failure action for both AICore and SIMT callers. Previously completed local validation: the standalone reproducer fails before the change and compiles afterward with Bisheng at -O2. Representative MoE and lossless quantization generated kernels also compile with the fixed header, and pre-commit passes for the header. No runtime or performance measurements were performed. * fix(ascend): correct latency and initiation interval estimates (#436) * fix(ascend): correct latency and initiation interval estimates * fix(ascend): preserve HF32 mode across guarded tasks * fix(ascend): preserve physical row widths for dynamic MTE copies * feat(ascend): add assume conflict hints (#339) * feat(ascend): add conflict hints * fix(ascend): preserve dependency distances * docs(ascend): fix conflict hint operand comment * docs(ascend): align conflict hint entry types * test(ascend): run UB fills in vector functions * test(ascend): update assume conflict codegen assertions --------- Co-authored-by: mengyuhao <mengyuhao@deepseek.com> * [Ascend] Use native merging for validated SIMD overloads (#440) * [Ascend] Restore native SIMD vadds merging * fix(ascend): use native merging for validated SIMD overloads Extend the native vadds merging path to the verified overloads of 30 more SIMD operation families. Forcing zeroing plus select through C API wrappers can introduce predicate spills in masked updates, as seen in SwiGLU. Gate native dispatch by operation and element type, and map vector vdupv to the corresponding CCE vdup overload. Keep scalar BF16 vdup on software merging because CANN 9.2 merges through an uninitialized temporary; select that fallback from the destination vector type. Preserve precision-specific SFU algorithms and the existing dispatch for unvalidated overloads. Add codegen and NPU regression coverage for native calls, dtype fallbacks, precision wrappers, broadcast/reduction layouts, and preservation of inactive destination lanes. Document the merging behavior and exceptions. Validation: native build with CUDA disabled, scoped format.sh, and diff checks passed. On Ascend 950DT with CANN 9.2, 80 codegen/legalization/precision checks and 44 NPU operation/type cases passed; the latter cover 132 empty, full, and sparse mask checks. Broader per-operation performance is not inferred from the earlier SwiGLU vadds measurements. --------- Co-authored-by: silentCoder-dev <silentcoder@foxmail.com> * [Refactor] Add backend namespaces for auto_schedule (#441) * feat(ascend): support effective K on padded L0 tiles (#439) * feat(ascend): support compact L0 GEMM regions Guard transposed L1->L0 copies at the region level: the transposed load issues whole 16-row groups on the L0 M/N axis, so a compact source region whose MN extent is not 16-aligned emitted a misaligned MTE instruction and trapped the device at runtime. Also require the L0 destination K region to cover the whole-group rounding of the source K region, which otherwise writes past a compact L0 allocation. Make the tl.tileop.gemm M/N/K metadata follow static operation region extents for Ascend L0 GEMMs, so the latency/cost model sees the real MAD geometry instead of the padded buffer shapes and stops overestimating region-restricted compressions. * [Ascend][GEMM] Use effective K with padded L0 storage * fix(ascend): validate transpose alignment for nonempty copies * fix(ascend): keep effective L0 GEMM regions in backend * test(ascend): trim padded L0 GEMM regressions * revert(ascend): restore blockscaled GEMM behavior * [Ascend] Point the cherry-picked Ascend tests at the Ascend dialect The asc commits just picked wrote four new test files against the Ascend-only facade (import tilelang.language as T, then T.simd / T.Kernel). This branch keeps upstream's default facade (CUDA), so they have to name the dialect explicitly, like the rest of the fork-owned Ascend files. Fixes 114 failures: test_tilelang_ascend_simdvf_merging (101, "module 'tilelang.language' has no attribute 'simd'"), test_ascend_assume_conflict (9), test_tilelang_ascend_l0_gemm_effective_k (2), and two in test_ascend_annotate_multi_buffer_eligible that #422 rewrote back onto the facade. testing/ascend/ on 8x Ascend950DT (CANN 9.2.0): 921 passed / 304 failed before this commit, 1035 passed / 190 failed after. Against the pre-refactor baseline (804/250) the branch now has 231 more passing tests and no failure that the baseline does not also have. * [Ascend] Require a usable CUDA device before auto-detecting the CUDA target Target auto-detection returns the first registered backend that reports itself available, and _detect_cuda_target() only asked check_cuda_availability(), which just looks for a CUDA toolkit/nvcc path. A host that merely has the CUDA toolkit installed was therefore auto-detected as CUDA even with no CUDA device at all -- which is exactly this NPU box, where /usr/local/cuda exists next to the NPU stack. Every Ascend example and test that omitted target= then lowered through tilelang/cuda/pipeline.py and died, e.g.: AttributeError: module 'tilelang.cuda._ffi_api' has no attribute 'AnnotateDeviceBoundTmaCopies' (examples/ascend/flash_attention/test_gqa.py) The asc branch happened to be immune because its `tilelang.language` facade imported tilelang.ascend.target early, registering the Ascend detector ahead of CUDA's. That is import-order luck: the facade here is the CUDA dialect, so CUDA registers first and wins. Fix the detector instead of the order -- the same predicate _detect_torch_cuda_arch() already uses -- so auto-detection no longer depends on which dialect a facade happened to pull in. An explicit target="cuda" still resolves everywhere. testing/ascend/ on 8x Ascend950DT (CANN 9.2.0): 1035 passed / 190 failed before, 1225 passed / 0 failed after. examples/ascend/flash_attention/test_gqa.py and test_mha.py both compile and pass. * [Ascend] Resolve nested thread scopes in the shared thread accessors T.rng_init() raised "The thread extent is not known at trace time" for Ascend (examples/ascend/test_simtvf_rng.py, both the ascend and pto targets), even though the call sits inside a `with T.SimtVF(threads=...)` block. tilelang/cuda/language/random.py binds the backend-neutral surface: import tilelang.language.common as T ... ex = T.get_thread_extent() so it reaches tilelang.language.kernel.get_thread_extent by name. The earlier refactor moved the SimtVF-aware resolution out of those shared accessors and into tilelang/ascend/language/kernel.py; a dialect-local override only shadows the dialect namespace and cannot intercept T.get_thread_extent() there. The call therefore fell through to the launch frame, and Ascend's T.Kernel declares no threads= (the NPU launch has no threadIdx at kernel scope), so it raised. Move the mechanism back to where the accessors live. The stack itself is backend-neutral -- "the innermost nested thread scope" -- and only the frame that pushes onto it is Ascend-owned (T.SimtVF via SimtVFContext). The Ascend dialect module now re-exports the accessors instead of redefining them, so tilelang.ascend.language.kernel stays a complete view of the thread API. This is the same class of miss as tilelang/profiler/bench.py earlier: fork-owned behaviour that has to be reachable from backend-neutral callers cannot live in a dialect namespace alone. Verified on 8x Ascend950DT (CANN 9.2.0): testing/ascend/ 1225 passed, 0 failed examples/ascend/ 186 passed, 0 failed plus the SimtVF thread-binding contract tests (20 passed) and examples/ascend/test_simtvf_rng.py (2 passed). * [Ascend] Remove the PTO and VMI content PTO was a second Ascend codegen backend (a "pto" key on the ascend target selecting PTODSL source plus the ptoas toolchain) and VMI was its virtual vector intrinsic surface (T.vmi.*). Both are dropped from this branch, leaving the AscendC path as the only Ascend codegen. 111 files changed, +294/-12810, 21 files deleted. Deleted outright: - src/ascend/codegen/{codegen_pto.cc,codegen_pto.h,rt_mod_pto.cc} - tilelang/contrib/ptodsl/ (the PTO DSL: gemm, rng, simt, dcache_bypass) - tilelang/ascend/language/vmi.py - 11 PTO-only tests/examples, plus example_rmsnorm_persistent_simtvf.py and its test, which existed to exercise PTO's persistent-fragment lowering Core removals: - target.py: _make_pto_target, target_is_pto, normalize_pto_target and the "pto" normalizer registration. target_is_plain_ascend is gone too -- with PTO removed it was identical to target_is_ascend, so its two call sites use that directly. - backend.py / codegen.py / execution_backend.py: the PTO BackendModule and its DeviceCodegen and cython-only ExecutionBackendSpec. - wrapper.py: the whole PTO host-wrapper family (_PTOKernelDescriptor, _PTOKernelCallSite, _PTOHostKernelCall, _PTOHostCallCollector, TLPTOSourceWrapper, ~560 lines) and the TLWrapper dispatch arm. - libgen.py: the PTODSL->ptoas compile path, update_pto_kernels and the PTO temp dir helpers (~230 lines). - utils.py/tvm_ffi.py: is_pto_target and its branches; the tvm_ffi param-shape path and the two "plain ascend" predicates collapse to is_ascend_target. - engine/param.py: the PTO-only packed storage ABI. storage_packing_factor and _is_pto_target go away (it always returned 1 without PTO); storage_shape stays as the named hook but is now the logical shape unchanged. - src/op/builtin.{h,cc}: the whole tl.vmi.* op family -- the three registration macros and 52 op registrations -- plus codegen's IsVmiOp rejection path and the tl.vmi.* clauses in ascend_pipe.h's pipe classification. - src/transform/make_packed_api.cc: the is_pto key test. - CI: the PTOAS wheel install and both `-m pto` test steps. Tests and examples (66 files): every pto parametrization dropped while keeping its ascend/asc variant, PTO-only helpers and imports removed, and 10 examples' `--target` choices reduced to ["ascend"]. Verified on 8x Ascend950DT (CANN 9.2.0): testing/ascend/ 1010 passed, 0 failed (was 1225; ~215 PTO-variant tests gone) examples/ascend/ 96 passed, 0 failed (was 186; the rest were pto variants) collection: 1118 tests, 0 errors and the built library no longer contains any "tl.vmi." or "target.build.tilelang_pto" string. * [Backend] Move AllReduce backend policy out of the shared reduce lowerers src/backend/common/op/reduce.h is backend-neutral, but it had grown two TargetIsAscend branches: - CheckAllReduceWidth skipped its power-of-two requirement on Ascend - NeedsWorkspace restated AscendAllReduce's constexpr dispatch to decide whether the lowering needs a shared-memory workspace Both are properties of the backend's all-reduce algorithm, not of the IR, so they belong to the backend `Impl` -- the same place the neighbouring policy already lives (Impl::SupportsFp16Bf16NanReduce, Impl::GetPreferredVectorizedSize, Impl::MakeBatchAllReduce). The shared lowerers now ask the Impl and no longer know that Ascend exists. - CheckAllReduceWidth(reducing_threads, scale, op_name, requires_power_of_two_width = true): keeps the three universal checks (positive threads, positive scale, scale divides threads) and gates the XOR-butterfly power-of-two check on the flag. The Target parameter is gone. - NeedsWorkspace is deleted from common; the two shared call sites use Impl::AllReduceNeedsWorkspace(...). Each backend Impl answers both: - cuda/rocm Reduce and FinalizeReducer: power-of-two required, workspace only when reducing_threads > 32. - ascend Reduce forwards to src/ascend/op/ascend_allreduce_policy.h, which states the two answers once for the Ascend backend. The standalone Ascend finalize lowerer (which is not a CRTP Impl) uses that header directly. The workspace predicate keeps its original semantics; it now lives next to tl::AscendAllReduce, whose dispatch it mirrors, instead of being restated in shared code. Verified on 8x Ascend950DT (CANN 9.2.0): testing/ascend/ 1010 passed, 0 failed, including the non-power-of-two reduce widths (6,5) and (7,3) that motivated the opt-out. src/backend/common/ is now free of TargetIsAscend. * [Backend] Keep the AllReduce XOR-butterfly rule in the algorithm, not in the backend Follow-up to9a928180, which answered the shared lowerer's questions with one per-assertion backend predicate (Impl::AllReduceWidthRequiresPowerOfTwo). That named a check rather than a capability, so every further check would have added another such hook. Reshape it: - CheckAllReduceWidth(reducing_threads, scale, op_name) keeps only the three checks that hold for every all-reduce (positive threads, positive scale, scale divides threads). Its signature and body are now identical to upstream's. - The power-of-two requirement moves to CheckXorButterflyWidth(reducing_threads, scale), named after the algorithm that actually imposes it. It is called from the backends that emit an XOR-butterfly intrinsic -- cuda/rocm in MakeBatchAllReduce and MakeScalarAllReduce -- and never from shared code. Ascend does not call it: AscendAllReduce falls back to a shared-memory tree reduction (ub_reduce) for arbitrary widths. - Impl::AllReduceWidthRequiresPowerOfTwo is gone from all five Impls, and the Make{Batch,Scalar}AllReduce signatures are untouched. The remaining backend hook is Impl::AllReduceNeedsWorkspace, which asks a real capability question ("does your lowering need a scratch buffer") rather than restating an assertion. The rule's diagnostic loses its "tl.reduce:" / "tl.finalize_reducer:" prefix because the emit helpers do not know which tile op drove them. Threading that label through would have been the only alternative, and it would have put a caller-supplied string into the intrinsic-naming helpers; the message names the algorithm instead, which is the actionable part. src/backend/common/ is free of TargetIsAscend, and this is now true of the AllReduce path without any target-shaped hook. Verified on 8x Ascend950DT (CANN 9.2.0): testing/ascend/ 1010 passed, 0 failed; examples/ascend/ 96 passed, 0 failed; the non-power-of-two reduce widths (6,5) and (7,3) still lower. * [Layout] Rename MapRegion to MapRegionBounds * [Ascend] Inline AllReduce workspace policy * [Ascend] Drop the vestigial CPU codegen hooks src/cpu/codegen/codegen_c.{h,cc} carried two hunks that the snapshot inherited from the pre-rebase Ascend commit f64773d3 ("feat(ascend): Add __ubuf__ shared memory support for SimtVF"). Neither has a consumer today: - Init() took a prelude_include parameter whose default was exactly the string it replaced ("#include <tl_templates/cpp/common.h>\n"), so every 5-argument call produced byte-identical output. No caller anywhere passed a sixth argument -- not at this HEAD, and not at f64773d3 either. The parameter was dead on arrival. - PrintType, VisitExpr_(BroadcastNode) and VisitStmt_(AllocBufferNode) were relaxed from final to override so that CodeGenTileLangAscend could subclass CodeGenTileLangC. CodeGenTileLangAscend now derives from tvm::codegen::CodeGenC directly and has its own Init(bool) that writes the Ascend preludes (codegen_ascend.cc:327), so nothing in the tree derives from CodeGenTileLangC at all. The one caller of CodeGenTileLangC::Init is src/cpu/codegen/rt_mod_c.cc, which uses the default. (codegen_c_host.cc calls CodeGenCHost::Init, a different class.) src/backend/common/codegen/ and src/cuda/codegen/ are otherwise unchanged from upstream; this was the only codegen file the branch touched outside src/ascend/. Reverting both files to upstream compiles cleanly (2 TUs rebuilt, exit 0) and keeps testing/python/cpu/ at 88 passed, 0 failed. * [Ascend] Restore upstream VerifyParallelLoop Restore src/transform/verify_parallel_loop.cc byte-for-byte from upstream-main at85fd8fc2d3. Leave all other Ascend changes untouched. * [Ascend] Drop the re-added common target_utils include src/cuda/op/copy.cc and src/metal/op/copy.cc each carried one line the snapshot should not have: #include "backend/common/target_utils.h" Upstream PR #2361 (2913ad3f, "Cleanup Metal Codegen, split AsyncCopy lowering and common target utils") deleted exactly that line from both files and replaced it with the backend-specific cuda/target_utils.h and metal/target_utils.h. The old asc history replayed a pre-#2361 diff on top of the split, so the deleted line and its replacement ended up side by side. Neither file uses anything from it. backend/common/target_utils.h declares only TargetHasAsyncCopy, which appears zero times in cuda/op/copy.cc. Every helper the file calls -- TargetIsCuda, TargetIsCuTeDSL, TargetIsHopper, TargetIsSm100, TargetHasBulkCopy, TargetHasStmatrix, TargetHasTmem -- is declared in cuda/target_utils.h, included on the next line. metal/op/copy.cc is the same story with TargetIsMetal and TargetMetalGetWarpSize, both from metal/target_utils.h. Checked that this is not a header-chain repair: none of the changed headers in copy.cc's include chain (op/copy.h, op/utils.h, transform/common/loop_fusion_utils.h) adds or removes an include, and the six */target_utils.h headers declare no colliding names. With the line gone both files are byte-identical to upstream-main, so the argument is identity with content upstream CI compiles rather than a test result. Local rebuild recompiled only src/metal/op/copy.cc (exit 0). src/cuda/op/copy.cc is not compiled on the Ascend box (USE_CUDA=OFF). * [Backend] Route AllReduce width checks through the backend Impl4d40c190moved the XOR-butterfly power-of-two rule out of CheckAllReduceWidth and into the backends' Make{Batch,Scalar}AllReduce emit helpers. That put the rule after ResolveAllReduceThreadRange, which reports a different problem first: for a reducing width that is both a non-power-of-two and warp-misaligned (48 with warp_size 32), the "partial scalar AllReduce requires a warp-aligned thread range" ICHECK fires and the power-of-two diagnostic never runs. Three CUDA tests regressed on the message, and 96-thread cases still passed because 96 % 32 == 0, so the ordering hole only shows up at the intersection of the two rules. The emit helpers cannot be reordered instead: MakeScalarAllReduce takes the thread offset and extent that ResolveAllReduceThreadRange produces. The check has to move earlier, which means the shared lowerer must know which width preconditions its backend imposes. So the shared call sites now ask the backend: Impl::CheckAllReduceWidth(reducing_threads, scale, op_name, target); replacing the three direct calls to the free function of the same name, at the same points in the lowering (both tl.reduce paths and the tl.finalize_reducer path). The rule text still lives in exactly one place, backend/common/op/reduce.h -- CheckAllReduceWidth for the checks every all-reduce lowering owes, and CheckXorButterflyWidth for the extra requirement of the XOR-butterfly shuffle all-reduce. Each Impl states which apply to it: - cuda/rocm call both, since their all-reduce has no arbitrary-width fallback; - ascend calls only the universal one. AscendAllReduce does have XOR-butterfly (asc_shfl_xor) and hardware-reduce paths, but run() gates both of them on is_pow2(threads), so they can only ever see a power-of-two logical width. Every other width goes to the ub_reduce fallback, which folds threads/scale slots along a scale-stride loop and needs no power of two. The rule is therefore vacuous on Ascend, not absent from it. src/ascend/op/finalize_reducer.cc implements LowerFinalizeReducer directly rather than through FinalizeReducerLowerer, so it keeps calling the universal rule and now says why. No target branching appears in shared code: it only forwards to Impl, which is the same shape as the existing Impl::AllReduceNeedsWorkspace hook. The eight CheckXorButterflyWidth calls scattered through the four emit helpers are gone, so Make{Batch,Scalar}AllReduce name intrinsics and nothing else. A future width constraint needs no new hook: it becomes one more line in each backend's CheckAllReduceWidth. Verified on 8x Ascend950DT (CANN 9.2.0): testing/ascend/ 1010 passed, 12 skipped, 0 failed. On 8x H100 (CUDA 13.1), the three regressed tests plus the four that share their parametrization: 7 passed. Full testing/python/ is 23 failed, 3551 passed -- all 15 failures of the pre-change baseline are still there, and the 8 extras are the known flaky group (sm75 x4, tma_dsmem x2, func_attrs x2), so nothing new regressed. src/rocm/** is compiled by no build available here. Both ROCm TUs were checked with c++ -fsyntax-only using the compile flags of a sibling TU (exit 0), after confirming with a negative control that the check catches a deliberate error. * [Ascend] Register the Ascend intrinsic Ops from the Ascend backend src/op/builtin.{h,cc} carried 111 Ascend intrinsic Ops -- 82 tl.simd.* and 29 tl.ascend_* -- plus the tl.enable_auto_schedule pass option, with a comment explaining that "the SIMD ops stay here since the Ascend backend keeps its builtins in the common file". Upstream #2855 had already given CUDA, Metal and ROCm their own op/builtin.{h,cc}; Ascend was the one backend still squatting on the neutral header, which is why a backend-neutral file listed asc_shfl_xor masks and Cube MAD instructions. Move them to src/ascend/op/builtin.{h,cc}, mirroring cuda/op/builtin.{h,cc}: declarations and the pass-config key in the header, TIR_DEFINE_TL_BUILTIN registrations in the .cc. The T.simd.* ops need a macro of their own because they register as "tl.simd.<name>" while their accessors are simd_<name>; that macro is now local to ascend/op/builtin.cc instead of sitting in the neutral one. The 14 Ascend files that name these ops now include "ascend/op/builtin.h" instead of "op/builtin.h". src/ascend/CMakeLists.txt needs no change: the src/ascend/op/*.cc glob picks the new file up. src/op/builtin.cc is now byte-identical to upstream, and src/op/builtin.h differs only by the rng_* and device_assert* redeclarations. Those stay: a neutral pass (transform/loop_vectorize) and the Ascend codegen both name those ops, while #2855 registers them under the CUDA dialect, so the neutral header is the only place both can see. Two other neutral edits the branch had made are dropped with the move -- a deleted blank line before kLocalVarInit, and a "// Pointer access metadata op" comment on access_ptr. Verified: tl.simd.*, tl.ascend_*, tl.rng_init, tl.device_assert, tl.access_ptr and tl.any_sync all resolve through Op::Get at runtime. On 8x Ascend950DT (CANN 9.2.0) testing/ascend/ is 1010 passed, 12 skipped, 0 failed. On 8x H100 (CUDA 13.1) the USE_CUDA=ON build is clean and testing/python/ is 23 failed, 3551 passed -- the same 23 as before this change, with the 15-failure stable baseline a strict subset and the other 8 the known flaky group (sm75 x4, tma_dsmem x2, func_attrs x2). * [Ascend] Drop the PTO-era codegen_py.cc append from the Ascend CMake src/ascend/CMakeLists.txt added src/cuda/codegen/codegen_py.cc to the Ascend source list unconditionally, with a comment explaining that PTO reuses CodeGenTileLangPY. PTO is gone from this branch, and nothing else needs that append: - codegen_py.h is included only by codegen_cutedsl.h, so the sole consumer of CodeGenTileLangPY is CodeGenTileLangCuTeDSL; - codegen_cutedsl.cc and rt_mod_cutedsl.cc both live in src/cuda/CMakeLists.txt's TILE_LANG_CUDA_ACTIVE_SRCS, which lists codegen_py.cc as well. So with USE_CUDA=ON the append was a duplicate entry for a file the CUDA list already owns, and with USE_CUDA=OFF nothing in the build referenced the class at all. codegen_py.cc registers no op, global, or pass of its own. Verified: a USE_CUDA=OFF rebuild no longer produces codegen_py.cc.o and still links; testing/ascend/ is 1010 passed, 12 skipped, 0 failed. On the USE_CUDA=ON box the reconfigure leaves the CUDA source set unchanged (ninja reports no work to do) and codegen_py.cc.o is still produced by the CUDA list, so CuTeDSL keeps its base class. * [Ascend] Drop the PTO reference from the Ascend skill Section 8 (SIMD MicroAPI) pointed at the PTO Micro-Instruction Spec for lane widths, rounding modes, saturation behavior and mask predicates. The PTO backend is gone from this branch, so the skill should not send a reader to it. The section's own introduction already names the right source: the T.simd.* ops map directly to Ascend CCE MicroAPI instructions. This was the last PTO or VMI mention under .agents/. * [Ascend] Lazy-load libascendcl via an ascendcl stub and gate the backend behind USE_ASCEND Formalize the ad-hoc dlopen loader in ascend_module.cc into a CUDA/ROCm-style stub library (libstub_ascendcl.so): the runtime module now calls the ACL entrypoints directly and the stub resolves them lazily, preferring the copy already loaded by torch_npu (RTLD_DEFAULT/RTLD_NEXT), then LD_LIBRARY_PATH, then well-known CANN install roots. aclGetRecentErrMsg degrades to nullptr so error reporting never throws; a missing required symbol is reported by name. Add USE_ASCEND to TILELANG_BACKENDS with env-var handling and CANN auto-detection (probed before the pip-provided CUDA toolkit). Only target_utils.cc and layout/ascend_layouts.cc stay unconditional: common transforms call TargetIsAscend, and tilelang.layout registers tl.AscendFractalLayout at import time. TILELANG_USE_ASCEND_STUBS=OFF links the real libascendcl instead. Verified on NPU hardware (testing/ascend, 1010 passed) and on a CANN-less GB200 host where a single USE_CUDA+USE_ROCM+USE_ASCEND wheel imports cleanly and runs CUDA kernels. * [Ascend] Move the copy and gemm hints into the Ascend dialect66c003c3(#3203) made each backend declare the op knobs it actually honors instead of advertising another backend's vocabulary on the common surface. The Ascend backend was still doing the latter: T.copy carried transpose, l2_cache_ctrl, unit_flag_ctrl, sub_blockid, scale, pad_value and data_select in tilelang/language/copy_op.py, and T.gemm carried unit_flag_ctrl in tilelang/language/gemm_op.py, with the Ascend-only bodies inline in the common implementations -- including a call from common code into tilelang.ascend.language.dma for the pad-register write. Both now follow the pattern the CUDA and ROCm dialects use: - tilelang/language/copy_op.py keeps only the target-neutral core (src, dst, coalesced_width, annotations, loop_layout) and its signature is identical to upstream's again. - tilelang/ascend/language/copy_op.py shadows copy() with the seven Ascend keywords plus _L2_CACHE_CTRL_MAP / _normalize_l2_cache_ctrl, packs them into the annotations dict, and delegates to the common implementation. The scale case still builds the three-region call itself, since the common copy only passes two regions. dual_copy in the same module now resolves to the Ascend copy and keeps working unchanged. - tilelang/ascend/language/gemm_op.py shadows gemm() with unit_flag_ctrl and packs it the same way. - tilelang/ascend/language/__init__.py exports both, so tilelang.ascend.language.copy/gemm resolve to the Ascend dialect while tilelang.language.copy/gemm stay on the CUDA dialect as #3203 intends. Test placement follows the code: l2_cache_ctrl is an Ascend knob, so test_copy_l2_cache_ctrl_string_annotation_matches_keyword moves out of testing/python/language/test_tilelang_language_copy.py (which runs the default CUDA facade) into testing/ascend/language/test_tilelang_ascend_copy.py, as #3203 did for the ROCm k_pack regression test. The Ascend dialect test gains assertions that the seven hints are present on the Ascend signature and absent from the common one. Verified on 8x Ascend950DT (CANN 9.2.0): testing/ascend/ 1012 passed, 12 skipped, 0 failed; examples/ascend/ 96 passed, 0 failed. On 8x H100 (CUDA 13.1) testing/python/ is back to its 23-failure baseline with the moved test gone from the generic suite. * [Testing] Add a requires_ascend feature mark testing/python/backend/test_tilelang_backend_auto_schedule.py exercises Ascend's boolean scheduling flag, but it lives in the suite the CUDA-only CI job runs. Since9b1b8799gated the Ascend sources behind USE_ASCEND, a build without it does not register tl.enable_auto_schedule, so those four tests fail with "Invalid config option" instead of skipping. tilelang.testing re-exported TVM's requires_cuda/requires_rocm/requires_metal/ requires_llvm but defined none of its own. TVM's Feature constructor publishes itself into Feature._all_features, so a backend can register its own mark on the same registry; requires_ascend does that, which also makes `pytest -m ascend` work. Two fields are deliberately left unset, unlike requires_cuda: - target_kind_enabled gates on TVM_TEST_TARGETS, and its DEFAULT_TEST_TARGETS fallback has no ascend entry, so setting it would skip every Ascend test unless the environment named ascend. - target_kind_hardware would call tvm.device("ascend").exist, but ascend is a TileLang target kind and not a registered TVM runtime: tvm.runtime.enabled("ascend") raises "Unknown optional runtime ascend". Device availability is checked through the existing tilelang.ascend.target.check_ascend_availability() (torch_npu) instead. That split matches the two meanings in play: compile_time_check is "this build has USE_ASCEND", run_time_check is "this machine has an NPU". The scheduling tests only need the first, so they take the compile-only marks and keep running on a host with the backend built but no device attached. Verified: with USE_ASCEND=ON (8x Ascend950DT) 3 passed, 2 skipped -- the skips are the CUDA-gated cases -- and `pytest -m ascend` collects all five. With the CUDA-only build on 8x H100 all five report "Compile-time support for Ascend not present". * [Testing] Gate the Ascend suite on a build that has the backend testing/ascend/ holds 83 files and had no backend gating at all. On a build without USE_ASCEND, running it produced a wall of failures -- 4 failed out of 5 in testing/ascend/language/test_tilelang_ascend_copy.py -- instead of saying that the backend is not in the library. The gate has to act at collection time, not through a per-item mark. Four modules do backend work at import scope and raise while pytest imports them, before any mark could apply: simtvf/test_simtvf_fragment_narrow_checker.py Target kind "ascend" is not defined test_tilelang_ascend_multibuffer_alignment.py No module named 'torch_npu' layout/test_tilelang_ascend_norm_fn.py Operator tl.ascend_set_hf32_mode is not registered transform/test_ascend_legalize_simd_merging.py Operator tl.simd.pset is not registered So testing/ascend/conftest.py ignores its directory when tilelang.testing.ascend_backend_compiled() is false, which keeps those imports from happening. That predicate becomes public for the conftest to use; the requires_ascend feature keeps it as its compile-time check. testing/conftest.py already errors out when nothing was collected, to catch misconfigured runs. An entirely gated Ascend suite is a legitimate outcome, so it now reports one line instead: "Skipped the Ascend test suite: the Ascend backend is not built into this library." The gate is only the compile-time half of requires_ascend. Device availability stays a per-test concern, so source-only lowering tests keep running on a host with the backend built but no NPU attached. Verified: with USE_ASCEND=OFF (8x H100, CUDA-only build) `pytest testing/ascend/` reports the skip line and exits 0, with no collection errors. With USE_ASCEND=ON (8x Ascend950DT) the suite is unchanged at 1012 passed, 12 skipped, 0 failed. * [Ascend] Register tl.conflict_hint from the Ascend backend src/op/schedule_hint.cc registered tl.conflict_hint, the marker op for user-declared auto-schedule conflict facts. Nothing about it is neutral: - it is emitted only by tilelang/ascend/language/schedule_hint.py, through assume_conflict / assume_no_conflict; - it is consumed only by src/ascend/transform/normalize_conflict_hints.cc, which rewrites the marker into Fors' "conflict_hint" annotation and the tilelang_root "tl.root_conflict_hints" attribute before AutoSchedule reads them. AutoSchedule is the Ascend scheduler, so the op belongs with the rest of the Ascend registrations. It moves into src/ascend/op/builtin.{h,cc} and src/op/schedule_hint.cc is deleted. Using TIR_DEFINE_TL_BUILTIN here is exact, not approximate: it registers under "tl.conflict_hint" and sets TScriptPrinterName to "conflict_hint", which is precisely what the original hand-written TVM_REGISTER_OP did. num_inputs stays -1 and the effect kind stays kPure, so RemoveNoOp still drops an unconsumed marker when auto-schedule is off. Two neighbours in src/op/ were audited and deliberately left alone: - simt_vf.{h,cc} and simd_vf.{h,cc} define SimtVFOp and SimdVFOp, which neutral passes must dispatch on (src/transform/lower_tile_op.cc, storage_rewrite.cc, layout_inference.cc, lower_device_kernel_launch.cc, loop_fusion_utils.h). Moving them would make those neutral files depend on an Ascend header. - the sf / sf_range fields the branch added to CopyNode in src/op/copy.h are shared-struct members read by the Ascend L1->L0 lowering, the same shape as GemmNode's sfa/sfb, and are not op registrations. Verified: tl.conflict_hint resolves with num_inputs == -1; testing/ascend/ is 1012 passed, 12 skipped, 0 failed on 8x Ascend950DT (CANN 9.2.0). * [Ascend] Move the Ascend scheduler attributes into the Ascend tree src/transform/common/attr.h carried six constants the branch had added. Four of them name IR that only exists between MaterializeScheduleUnits and LowerScheduledTIR, which are both Ascend passes: kAscendPerCoreTask, kAscendTask, kAscendStage read only under src/ascend/ kScheduleUnit read only under src/ascend/ (materialize_schedule_units, auto_schedule/*) They move to a new src/ascend/transform/attr.h, next to the other Ascend transform headers (ascend_pipe.h, buffer_version.h, core_mask.h, estimate_latency.h). The constants keep the tvm::tl::attr namespace and their names, so the nine consumers only needed the include. Two more constants in that hunk, tilelang_simt_vf_captures and tilelang_simd_vf_captures, are deleted outright: grepping both the symbols and their string values leaves only their own declarations, so nothing has read them since the kernel-launch frame stopped using per-backend marker annotations. kBufferVersion stays in the neutral header. Two of its three readers are Ascend (normalize_buffer_version.cc, rewrite_buffer_version_layout.cc) but the third is src/transform/lower_tile_op.cc, which is backend-neutral, so moving it would mean a neutral pass including an Ascend header. src/transform/common/attr.h is now free of Ascend-only names. Verified: the build is clean and testing/ascend/ is 1012 passed, 12 skipped, 0 failed on 8x Ascend950DT (CANN 9.2.0). * [Ascend] Give Ascend its own on-chip utils and leading-dim access ptr src/op/utils.cc had gained a leading_dims_offset_only parameter on MakeAccessPtrFromRegion. Only Ascend used it -- the three call sites are all in src/ascend/op/copy.cc -- so the neutral function goes back to upstream verbatim and the behaviour moves to src/ascend/op/utils.h as MakeAscendLeadingDimAccessPtr. The helper exists because the Ascend L1->L0 loads pass the M/K start positions as their own intrinsic arguments (ascend_load_cbuf_to_ca(dst, src, mStartPosition, kStartPosition, ...) and the asc_copy_l12l0a_mx scale companion). The pointer must therefore carry only the leading dims -- the pipeline version, say -- and counting the inner two dims into its offset would place the position twice. It is built on the neutral MakeAccessPtrFromRegion rather than duplicating the stride arithmetic: the innermost two dims are re-based to zero before delegating, which leaves their extents (and so the reported extent) alone and drops them from the offset. ffi::Array is copy-on-write, so the caller's region is untouched. Regions with fewer than two dims are left as they are, because a 1-D region has no inner 2-D position to separate and the neutral path uses its min as the offset. The five on-chip scope predicates the branch had also put in the neutral header -- IsL1Buffer, IsL0ABuffer, IsL0BBuffer, IsL0CBuffer, IsAscendOnChipBuffer -- move to the same new header. Their twelve consumers are all under src/ascend/; the scopes they name (shared.l1, shared.l0a/b/c and the .dyn variants) exist only on Ascend. They compose the neutral IsSharedBuffer, so the include direction stays Ascend -> neutral. src/op/utils.{h,cc} are now byte-identical to upstream. Verified: the build is clean and testing/ascend/ is 1012 passed, 12 skipped, 0 failed on 8x Ascend950DT (CANN 9.2.0). * [Ascend] Drop an unreachable VF-block guard from InjectSoftwarePipeline src/transform/inject_pipeline.cc's VisitStmt_(const SBlockNode *) early-returned for blocks named SIMT_VF, VECTOR or CUBE, skipping the pass's buffer bookkeeping for them. The guard cannot fire. Those block names are produced only by the Ascend SimtVF / Cube / Vector frames (src/ir.cc, and lower_scheduled_tir.cc for the scheduled path), while InjectSoftwarePipeline is invoked by the cuda, rocm, metal and webgpu pipelines and by nothing else -- tilelang/ascend/pipeline.py never calls it. So the pass never sees those names, and on the targets that do run it they have no producer. That also explains why removing it changes nothing: testing/ascend/ is 1012 passed, 12 skipped, 0 failed and examples/ascend/ is 96 passed, 0 failed both with and without it. It was not merely untested, it was unreachable, and it made a backend-neutral pass recognise Ascend block names. src/transform/inject_pipeline.cc is now byte-identical to upstream. The same SIMT_VF name check exists in four other neutral passes (common/loop_fusion_utils.h, layout_inference.cc, lower_tile_op.cc, thread_storage_sync.cc). Those passes *are* in the Ascend pipeline, so they are not automatically dead and are left alone here. * [Ascend] Restore the CPU pipeline's MaterializeKernelLaunch call00a1cee0taught MaterializeKernelLaunch to separate the program-index space from the thread space, adding lower_grid_binding as a new leading parameter that defaults to None (follow lower_thread_binding), and the Ascend pipeline passes it explicitly: lower_grid_binding=True with lower_thread_binding=False, because Ascend has a real 1-D core grid but no threads at kernel scope. The same commit also expanded the CPU pipeline's call to spell out lower_grid_binding=False and default_threads=None. Neither is needed: with lower_thread_binding=False the parameter already derives False, and default_threads is documented as ignored when lower_thread_binding is False. The expanded call therefore restates the defaults and nothing else. The CUDA, ROCm, Metal and WebGPU pipelines were left on the old call and keep working off the same default, which is the point of the None fallback. CPU can do the same, so it goes back to upstream's one-liner and tilelang/cpu/pipeline.py is byte-identical to upstream again. Verified: testing/python/cpu/ is 88 passed, 0 failed. * [Ascend] Own allow_autoschedule in the Ascend pipeline tilelang/backend/pass_pipeline/pipeline_utils.py had gained allow_autoschedule(), a sibling of the backend-neutral helpers that live there (allow_vectorize, allow_global_thread_synchronization, should_*). It reads TL_ENABLE_AUTO_SCHEDULE, which exists only when the Ascend backend is compiled in, and it has exactly one consumer: tilelang/ascend/pipeline.py, which uses it to decide whether to run the AutoSchedule chain. It moves into tilelang/ascend/pipeline.py, which is where the pass order it gates already lives (per the backend layout rule that a backend keeps its own pipeline). The name and behaviour are unchanged, so the call site is untouched; only the import moves, in that module and in the Ascend-gated test_tilelang_backend_auto_schedule. tilelang/backend/pass_pipeline/pipeline_utils.py is now byte-identical to upstream. Verified: allow_autoschedule resolves from tilelang.ascend.pipeline; testing/python/backend/test_tilelang_backend_auto_schedule.py is 3 passed, 2 skipped (the skips are its CUDA cases); testing/ascend/ is 1012 passed, 12 skipped, 0 failed on 8x Ascend950DT (CANN 9.2.0). * [Ascend] Move the NPU frontend frames out of the shared src/ir.cc src/ir.cc carried four Ascend-only TIR frames plus the Ascend mixed launch interleaved with the shared #3186 launch machinery. Only the shared frames remain there now: kept in src/ir.cc tl.Parallel, tl.Pipelined, tl.Persistent, tl.KernelLaunch, tl.WarpSpecialize, tl.SideEffect moved to src/ascend/ir.cc tl.SimtVFFrame, tl.SimdVFFrame, tl.CubeFrame, tl.VectorFrame, tl.MixedKernelLaunch, and the buffer-stealing helpers their Enter/ExitWithScope use src/ir.cc loses 576 lines and gains 3: the new include, plus the two .def chains that now terminate where the Ascend registrations were removed. A dialect launch variant still has to build a tl.KernelLaunchFrame -- tilelang.ascend.language.kernel binds its launch surface to that object type -- so KernelLaunchFrameNode, KernelLaunchFrame and the two frame factories they are assembled from (MakeThreadBindingFrame, MakeLaunchThreadFrame) move verbatim into a new neutral src/ir.h, static -> inline. That is the only structural change outside the move. src/ascend/ir.cc goes into TILE_LANG_ASCEND_ALWAYS_SRCS rather than the Ascend-only source list. tilelang/__init__.py imports tilelang.ascend unconditionally and tilelang.ascend.language.frame binds tl.CubeFrame and friends with @register_object at import time; a missing type key raises "Cannot find object type index", so keeping the translation unit Ascend-only would break "import tilelang" on every USE_ASCEND=OFF build. This mirrors why ascend_layouts.cc is already in that list. The frames carry no CANN dependency. Verified: clang-format v23.1.0 clean on all three C++ files; git diff --check clean; format.sh clang-format hook passes. Verified: USE_ASCEND=ON build clean. testing/ascend/ 1012 passed, 12 skipped; examples/ascend/ 96 passed; testing/python/cpu/ 88 passed. Verified: USE_ASCEND=OFF configure compiles exactly ir.cc, target_utils.cc and ascend_layouts.cc from src/ascend/, with one object file each; both ir.cc translation units also compile standalone under that configuration. Verified: on a CUDA host (USE_ASCEND=OFF) the library links, "import tilelang" succeeds with all 9 FFI entrypoints and the 4 Python frame classes present, and the full testing/python/ suite reports 23 failed / 3554 passed / 481 skipped, matching the pre-change baseline failure for failure. * [Ascend] Declare NPU codegen capabilities as target kind attributes Three shared passes branched on TargetIsAscend to reach NPU-specific decisions. Each of those is a property of the target's codegen rather than of the target's name, so the Ascend target kind now declares them and the passes query the declaration: supports_kernel_status_return (default false) AscendC kernel entries return void, so the host cannot read an int32 status code from the device the way CPU-codegen targets do. supports_vector_predicate (default true) AscendC emits lane-wise vector predicates, so a vectorized Select does not need a uniform condition. max_vector_bits (default 64) Widest vector load/store the vectorizer may issue. split_host_device.cc now reads supports_kernel_status_return before falling back to its device-type heuristic, which keeps CPU / ext_dev / Hexagon on the status-code path. loop_vectorize.cc reads the other two: the Select-condition uniformity rule and MaxVectorLoadBits. The attributes are declared only on the ascend kind, so every other target sees them absent and keeps its previous behaviour unchanged. The three MaxVectorLoadBits call sites all receive a target that cannot be undefined: loop_vectorize.cc uses Target::Current(false), which checks rather than returning a null Target, and the layout cost model is reached from LayoutInference, which ICHECKs the target attribute. Verified: Target("ascend") resolves the three to false/true/64, while cuda, llvm, ext_dev, hexagon and rocm report all three absent, so the fallbacks are untouched. Verified: USE_ASCEND=ON build clean; clang-format v23.1.0 clean on all three files. Cold-cache runs: testing/ascend/ 1012 passed 12 skipped, examples/ascend/ 96 passed, testing/python/cpu/ 88 passed, testing/python/transform/test_tilelang_transform_loop_vectorize.py 16 passed. Verified: on a CUDA host (USE_ASCEND=OFF) the build is clean and the full testing/python/ suite reports 23 failed / 3554 passed / 481 skipped, matching the pre-change baseline failure for failure. * [Ascend] Declare the grid-only launch model as a target attribute Replace the TargetIsAscend branches in LowerDeviceKernelLaunch with a `launch_grid_only` attribute on the ascend target kind: launch-param collection and the thread_extent metadata keep only the blockIdx.* grid axes when the attribute is set, since threadIdx domains are SimtVF region-local lanes baked into codegen rather than runtime launch dimensions. Also drop the forced call_packed guard and its launch_params ICHECK exemption: MakePackedAPI runs before this pass and rewrites the host function's target to the host target, so an ascend callee can never compare same-device-type with its caller here (verified empirically: 531/531 launch sites in the full suite take the packed path already). * [Ascend] Restore MakePackedAPI's strict undefined-variable check The local/local.fragment exemption papered over device-internal handles leaking into host functions during early SIMD-VF bring-up. The leak no longer exists in the current pipeline (the full Ascend suite passes with the strict upstream check), and a genuinely leaked variable should fail here with an actionable message instead of crashing later inside host codegen. * [Ascend] Replace shared-pass VF dispatch with registries and move the VF ops under src/ascend LowerTileOp and LayoutInference no longer hard-code Ascend behavior; backends contribute through three registries: - LoweredParallelLoopHook (transform/common/lower_hooks.h): CUDA registers PTX cp.async injection from its own translation unit, removing the TargetIsCuda branch and the cuda includes from the shared pass. - AttrNodeRemapHook: Ascend registers the `tl.buffer_version` key remap from src/ascend/transform/buffer_version.cc. The map stays Var-keyed deliberately: distinct versioned storages may share one name (see test_auto_schedule_distinguishes_same_name_buffer_version_annotations), so names cannot serve as stable keys. - RegionOpImpl (op/region_op.h): block-name keyed {make, enter_scope}. LayoutInference wraps region blocks via `make` for its inference worklist; both passes push the region execution scope via `enter_scope`, replacing two duplicated copies of the SimtVF thread-context machinery. The SimtVFOp/SimdVFOp lowering branches in LowerTileOp were elaborate no-ops (their Lower() is a passthrough) and are removed outright. simt_vf/simd_vf move to src/ascend/op/ and register their RegionOpImpl there. They stay in TILE_LANG_ASCEND_ALWAYS_SRCS, matching the always-available VF frontend frames, so every build configuration behaves exactly as before. * [Ascend] Isolate ThreadSync and restore upstream pass Keep the existing Ascend behavior in a private implementation with isolated symbols and a separate pass registration. Route the Ascend pipeline through the private pass. Restore the common ThreadSync implementation and regression tests byte-for-byte to upstream-main at66c003c3. Validation: both implementations compiled and linked in an audit DSO; 64 IR tests passed and 20 original-versus-private IR comparisons matched. Formatting and lint checks passed. * [Ascend] Rebase constr_visitor onto the upstream text, keeping the two load-bearing deltas The fork variant of constr_visitor.h diverged from upstream by 284 lines, of which only two hunks are functionally required (upstream #3220): - Constr::Populate simplifies a predicate before EnterConstraint so a bound boolean guard expands into the condition it represents (five flag-allocation tests fail without it). - ConstrSet::Populate installs binds before predicates so expansion works on Merge/RenameFrom-scrambled sequences (four multibuffer counter tests fail without it), at the documented cost of wide bind-time bounds. Everything else (Guard visibility, Merge comparison details, comments, style) adopts the upstream text. TaskAwareConstrVisitor no longer names the now-private Guard type; it follows the base class's own SeqStmt idiom instead. * [Transform] Restore bind-before-use order when merging constraint sets (#3221) ConstrSet::Merge concatenates two lexically-collected sets, so an entry of the first set can read a bind var the second set defines. Replaying that order digests the predicate before its definition exists -- EnterConstraint evaluates eagerly, exactly like Bind -- and the guard fact is silently lost, which downstream reports as unprovable guard disjointness on merged sets (#3220). Two changes: - Merge now normalizes the concatenated list: entries keep their order, except that a bind is hoisted (with the binds its definition reads, recursively) directly before the first entry reading its var. Within a single lexically-collected set nothing reads a later bind, so the program-order tightening guarantee of #2805 is untouched. - Constr::Populate simplifies a predicate against the installed binds before entering it, so a guard held in a bound boolean var expands into the condition it represents. tl.analysis.ConstrSetsProve exposes merge+populate+CanProve to Python so the merged-set scenarios stay testable without staging a pass pipeline. (cherry picked from commit825bfd7ead) * [Transform] Preserve MaterializeKernelLaunch positional compatibility * [Ascend] Isolate LayoutInference/LowerTileOp and restore the upstream passes Following the ThreadSync isolation precedent: Ascend owns full forks of the two lowering orchestrators, and the shared passes go back to the upstream text byte-for-byte. - src/ascend/transform/{layout_inference,lower_tile_op}.cc (namespace tvm::tl::ascend, registered as tl.transform.AscendLayoutInference / AscendLowerTileOp) embed the Ascend behavior directly: VF region scopes (helpers in vf_regions.h), the Var-keyed tl.buffer_version remap, int32 layout-index coercion, and symbolic thread-extent tolerance. The CUDA cp.async post-processing is dropped from the Ascend fork. VF regions no longer enter the inference worklist: the entries were placeholders (InferLayout returned nothing), so the hollow SimtVFOp/SimdVFOp wrapper classes are deleted outright. - src/transform/{lower_tile_op.cc,layout_inference/layout_inference.cc} and src/cuda/transform/ptx_async_copy_injector.cc are restored to tileai/main. The registry indirection they consumed (lower_hooks, region_op, the buffer-version remap hook) is removed. The forks start at upstream #3221 content; upstream changes to the shared passes now need manual porting, and the shared/forked pair doubles as a cross-check of lowering behavior. * [Op] Promote the rng and device_assert builtins to common rng_init/rng_rand/rng_rand_float and device_assert/device_assert_with_msg are consumed by the CUDA and Ascend codegens and by the shared vectorizer, so their home is src/op/builtin.{h,cc}. The shared header already carried duplicate declarations as a layering shim for the Ascend side; move the definitions over and drop the CUDA-side declarations and definitions. Registration names are unchanged; this is a pure relocation. * [Frontend] Split the launch frame factory declarations from their definitions MakeThreadBindingFrame and MakeLaunchThreadFrame move from inline definitions in ir.h to definitions in ir.cc; the header keeps the declarations and the materialization contract comments. Pure layout change: both frontend TUs (src/ir.cc, src/ascend/ir.cc) keep linking against the single shared implementation. * [Transform] Census dialect copy refinements in ReducerPlanAndMaterialize The per-buffer use census matched copies by op identity, so a copy spelled with a dialect op would be invisible to reducer planning. Recognize any op whose builder yields a strict CopyNode refinement; spellings that build a plain CopyNode (async_copy, tma_copy) keep their historical exclusion, so existing targets census identically. * [Ascend] Type the Ascend copy as a tl.tileop.ascend_copy dialect op The Ascend frontend now emits tl.tileop.ascend_copy, whose TLOpBuilder constructs AscendCopyNode : CopyNode — the MX scale-factor companion region plus decode-once typed views of the Ascend copy hint annotations (nd2nz, dual_dst_ctl, transpose, data_select, unit_flag_ctl, sub_blockid, l2_cache_ctrl, pad_value). Lowering and layout inference move onto the node's virtual methods; the CopyImpl registry entry stays as a bridge that upgrades plain tl.tileop.copy calls (e.g. copies synthesized by ReducerPlanAndMaterialize) onto the same implementation, and Ascend matchers accept both spellings via IsAscendCopyCall. This restores shared src/op/copy.cc to upstream byte-for-byte and shrinks the src/op/copy.h delta to un-finalizing CopyNode so dialects can extend it. * [Transform] Restore common StorageRewrite to upstream Sync src/transform/storage_rewrite.cc exactly with tile-ai/tilelang main at030556de97. Ascend uses its private MergeUBAllocations pass and no longer invokes StorageRewrite in its default pipeline. Validation: rebuilt the restored pass in isolation against the matching TVM runtime; all 10 mixed-dtype/control cases match upstream, and all 13 lexical allocation and inplace-toggle tests pass. * [Ascend] Sink the VF loop-fusion and validator variants into the dialect ParallelLoopFuserSkipSimdVF and the validator's SIMD_VF skip carried Ascend VF vocabulary in shared headers while serving exactly one caller, AscendLayoutInference. Both move next to it as subclasses. src/transform/layout_inference/ is byte-identical to upstream again; loop_fusion_utils.h keeps only a target-neutral extension seam (a virtual PreserveParallelLoopNest hook, required because the ForNode visitor is final). The SIMD_VF skip itself stays load-bearing: SIMD_VF T.Parallel loops are lowered by AscendSimdVFLowerParallel and never carry loop-layout annotations, so the post-inference validator must not descend into those regions. * [Ascend] Own the dialect's print and device_assert surfaces tilelang/cuda/language/print.py decided the shared-buffer print gating by probing the host for an NPU at import time (torch.npu.is_available), and tilelang/cuda/debug.py extended its assert gate the same way — the compile target never entered the decision. On an NPU host that meant CUDA shared prints lost their single-thread gating; on a CUDA-only host an Ascend kernel's print would call get_thread_bindings inside a threads-less kernel. The Ascend dialect now owns both policies: ascend/language/print.py prints without CUDA-style thread gating (a kernel body runs once per AI core and UB is core-local), reusing the target-neutral debug_print_* macro emitters; ascend/debug.py gates device_assert on the NPU toolkit the way the CUDA dialect gates on nvcc. Both CUDA modules and cuda/transform/__init__.py return to upstream byte-for-byte — the latter's LowerPTXAsyncCopy wrapper had no callers and its FFI pass no longer exists. * [Ascend] Hold the target scope at lower call sites, not inside lower tilelang.lower expects its caller to hold the target scope: the vectorize planner consults Target::Current(false) (fail-loud), the JIT path and tools/compile_only already enter it, and upstream's own tests wrap bare lower calls the same way. The fork's with-target inside lower_to_host_device_ir papered over that contract for every caller; drop it and restore tilelang/engine/lower.py to upstream byte-for-byte. The thirteen Ascend tests whose lowering actually reaches the planner now enter the scope explicitly at their call sites, as does the annotate_unlimit_memory example. Also remove a stray module-scope test invocation in test_simtvf_fragment_narrow_checker.py that ran the test at import time, and conftest's unused pytest import. * [Ascend] Move the Ascend postproc callback into the dialect register_ascend_postproc(_callback) was appended to the shared tilelang/engine/callback.py; its FFI consumer is Ascend-owned (rt_mod_ascend.cc looks up tilelang_callback_ascend_postproc before emitting the final AscendC source). Move the registration API to tilelang/ascend/callback.py and restore engine/callback.py to upstream byte-for-byte. * [Ascend] Sink the SimtVF nested thread scope into the dialect The SimtVF context stack and the SimtVF-first preamble on the four thread accessors lived in shared tilelang/language/kernel.py, justified by backend-neutral callers reaching the accessors by name. Only one such caller remained — rng_init's default sequence-id derivation — so the justification no longer holds: - tilelang/ascend/language/kernel.py now owns the context stack and dialect accessors that consult the innermost SimtVF scope before falling back to the shared launch-frame accessors; the SimtVF frame already pushed through this module. - tilelang/ascend/language/random.py derives rng_init's default seq (lane id + core index * lane count) through the dialect accessors and delegates the emission; rng_rand/rng_rand_float re-export unchanged. - tilelang/language/kernel.py returns to upstream byte-for-byte. * [Language] Shrink the shared frontend's Ascend deltas Three fossil reductions, no behavior change: - gemm_op.py: drop the fork-added sfa/sfb/sf_k_start plumbing from _gemm_impl. The Ascend blockscaled_gemm now appends the scale-factor regions as trailing tl.tileop.gemm args itself, mirroring upstream's tcgen05_gemm_blockscaled wire format (GemmNode's sfaRegion/sfbRegion are upstream-native). - copy_op.py: fold _normalize_copy_regions_with_extents back into _normalize_copy_regions. The extents returns and copy()'s dst_orig had no consumer anywhere (leftovers of an early nd2nz-scatter frontend); the live parts — the element-count relaxation for differently-major'd fractal copies and the helper being importable by the Ascend dialect's scale= path — are kept. - eager/builder.py: drop "local.simd.var" from is_var. The scope never had a producer in any revision; SimdVF variables use plain local.var. * [Testing] Cover bare-name lower callers with the target scope The caller-holds-the-target-scope migration enumerated call sites by grepping tilelang.lower(, which missed the twenty-four Ascend test files importing lower by bare name from tilelang.engine.lower; the twenty-seven of their tests whose lowering reaches the vectorize planner failed with "Target context required". Enter the scope explicitly at those call sites — inside the shared _device_script / _pass_snapshots helpers where the tests funnel through one, merged into the pytest.raises / PassContext with-statements elsewhere. * [Ascend] Own alloc_shared and drop the orphaned major-layout helper tilelang/language/allocate.py carried two Ascend intrusions: - _annotate_ascend_major_layout had no caller anywhere: it served the #149-era alloc_l1(major=...) surface, orphaned when #244 moved L0 layout selection into the consuming gemm. Deleted. - The bool workaround (bool buffers downgrade to the static "shared" scope for the smem-merge pass) was gated on an NPU host probe (#264) — the wrong axis: an NPU host compiling CUDA kernels skipped the hack too. The common alloc_shared is unconditional again, and the Ascend dialect shadows alloc_shared without the workaround (UB takes bool buffers in the requested scope), matching the dialect's Kernel/copy/print ownership pattern. The shared file is byte-identical to upstream again. * [IR] Align PersistentFor traversal with upstream Restore upstream grouping, coordinate decoding, and tail/break handling. Keep the num_stages and annotations extensions required by Ascend pipelining. Use full-last-dimension groups in the two Ascend FP8 examples to preserve their row-major assignment. Add host-only TIR regressions for static and symbolic domains, ordering, tail coverage, and pipeline annotations. * [Ascend] Clear the tileop/utils layers' Ascend residue - gemm_base.py returns to upstream byte-for-byte: the dead block_scaled alias is gone, and GemmMAD now owns the two Ascend decodes — the unit_flag_ctrl annotation and the is_blockscaled widening (an Ascend L0-input block-scaled gemm carries no SF regions on the node, so the base's structural predicate cannot see it). - utils/tensor.py: map_torch_type had no caller — added by the FP8 codegen work, orphaned when the KernelParam refactor took over dtype mapping, absent upstream. - utils/language.py: drop "shared.l1" from is_shared. GemmMAD never uses the is_gemm_* predicates and the only shared-pass consumer (DecoupleTypeCast) cannot reach an L1 cast — L1 is only touched by DMA copies and cast-carrying DMA copies are rejected up front. * [Ascend] Sink the SimdVF reduce path into the dialect The shared reduce macro branched on the Ascend SimdVF builder state through a deferred dialect import (module scope would cycle). The dialect now owns that path: tilelang/ascend/language/reduce_op.py emits shared-to-shared reductions inside SimdVF directly on the UB regions (no fragment exists there to round-trip through) and delegates everything else; the thin reduce_* wrappers are redeclared since the common ones call the common reduce by module-level binding. The remaining shared reduce_op.py delta is the target-neutral region normalization (retrieve_shape/_get_buffer), which lets reduce accept BufferRegion/BufferLoad operands like copy and gemm do — an upstream candidate. * [Misc] Restore .gitignore and .pymarkdown to upstream The pymarkdown front-matter extension protected the YAML-front-matter skill files back when the hook still covered them; they live under .agents/ now, which upstream's own hook config excludes, and a full --all-files run passes with the upstream config. The .gitignore additions were personal-environment entries (CLAUDE.md, .clangd, a local scratch script) and Ascend artifact dirs; the latter belong in .git/info/exclude on the machines that produce them. * [Lint] Apply format.sh fixes across recently touched files clang-format reflows in ascend/op/builtin.h, ascend lower_tile_op.cc (include order) and materialize_kernel_launch.cc (lambda wrap); ruff drops unused imports in jit/adapter/{libgen,wrapper}.py and fixes spacing in testing/__init__.py. No behavior change; build and smoke tests pass. * fix(ascend): honor the element width named by a BRC_* vld dist (#446) T.simd.vld derives the vector type -- and with it the emitted C API and its load granule -- from the source buffer's element type, so a BRC_B16/B32 dist over a narrower buffer silently degraded to the buffer's width. Over a uint8 buffer, BRC_B16 broadcast a single byte instead of the 16-bit element the suffix names, dropping the upper byte of a packed pair. Honour the suffix for BRC_*: widen the result past the buffer element width and size the access footprint as one element of that width. An unknown suffix, or one too narrow to be a whole element of the source buffer, is now rejected instead of loading the wrong granule. Other distributions keep taking their width from the source buffer: UNPK_* legitimately widens, so its suffix describes the loaded data rather than the result and cannot follow the same rule. (cherry picked from commit 177321c0e93205a048c2cc6efa3ac5322b9d7803) * fix(ascend): treat out-of-VF fills as scalar (#448) Two related defects made a T.fill outside a VF block misbehave. 1. Pipe classification. AutoSchedule and InsertSync take a task's hardware pipe from GetAscendFillPipeMask, which reported PIPE_V for every non-L1 fill. A fill only runs on the vector unit inside a VF block, where the enclosing block already contributes PIPE_V through GetAscendBlockPipe; outside one it lowers to element-wise BufferStores issued by the scalar unit. InsertSync therefore saw a PIPE_V producer feeding a PIPE_V consumer and elided the S->V handshake between them, letting the consumer read stale UB contents: a kernel that zero-fills the invalid tail of a partially valid UB tile before reducing it in a T.SimtVF returned garbage for the tail tile. Restore the pre-#426 classification, so a non-L1 fill is PIPE_S and an L1 fill is MTE2. 2. Codegen. Such a fill still emitted a vectorized broadcast store, and the vector constructors it needs (make_float2/make_int2) are simt_callee-only, so bisheng rejects them outside a VF body: a static T.fill over a 64x384 UB tile did not compile at all. Emit one scalar store per lane for a vector broadcast store issued outside a VF body, which is the element-wise scalar work the scalar unit would run anyway. Validation: the reported reproducer now returns [64.0, 64.0, 1.0] instead of [64.0, 64.0, 3.0]; new coverage in testing/ascend/language/test_tilelang_ascend_dynamic_ub.py and testing/ascend/language/test_tilelang_ascend_fill.py (device) and testing/ascend/analysis/test_ascend_task.py (lowered sync events); the full testing/ascend suite passes (1217 passed, 12 skipped). (cherry picked from commit f192961587b6493ff9fcf307b0f7584c2af5770b) * [Examples] Restore deepseek_mhc's example_mhc_post to upstream * [JIT] Drop the identity storage_shape hook KernelParam.storage_shape came in with the packed-FP4 work, when Torch allocation needed a storage shape distinct from the logical one. Torch 2.8's float4_e2m1fn_x2 dtype made the two agree element for element, a cleanup reduced the hook to `return list(self.shape)`, and a dedicated test pinned the identity. A live-called identity with a test asserting nothing happens is speculation, not API; the wrapper call site returns to param.shape with the adapter changes in the next commit. * [Ascend][JIT] Move the Torch NPU integration out of the shared wrapper The DLPack exchange patch (~150 lines) lived inside the shared cython_wrapper.pyx only because that was the one pyx the build compiled; it patches torch.Tensor's global exchange table and has no coupling to the wrapper. It now builds as its own extension, tilelang_ascend_npu_exchange, from tilelang/ascend/torch_npu_exchange.pyx (pure CPython/DLPack, torch_npu imported lazily), and the tilelang/ascend/torch_exchange.py facade points at it — callers are unchanged. The wrapper's three torch.npu.is_available() host probes are gone: device and raw-stream selection are injected by the adapter, which knows the compile target (_device_providers), with defaults preserving the upstream CUDA behavior. An NPU host compiling CUDA kernels no longer allocates outputs on the wrong device. The vestigial __cinit__ target parameter and the storage_shape call site are cleaned up along the way. * [Ascend] Assemble L0 MAD gemm calls in the dialect The shared _gemm_impl carried an is_ascend_l0_gemm branch that substituted allocation-shape M/N/K before the static-tile check (L0 region extents give the effective, possibly symbolic MAD geometry) and an assert allowing higher-order C regions only for L0C. Both sink into the Ascend dialect: _l0_gemm_call assembles the tl.tileop.gemm call itself for L0A/L0B/L0C operands, mirroring the common assembly's positional contract the same way upstream's tcgen05_gemm_blockscaled does; everything else keeps delegating to _gemm_impl. The remaining shared delta is target-neutral (symbolic shape proofs, the clear_accum write-only C access mask). * [Language] Drop copy's no-op empty-annotations normalization annotations=ann and annotations=None produce structurally and textually identical Calls when ann is empty (both normalize to an empty Map), so the conditional was pure noise; restore the upstream spelling. * [Ascend] Fix two tests missed by the dialect and target-scope moves Two test files were missed when the Ascend surfaces moved out of the shared frontend and the target scope moved to lower()'s call sites: - testing/ascend/language/test_tilelang_ascend_simd_vld_brc.py still imported ``tilelang.language as T``, which no longer re-exports the Ascend dialect, so ``T.SimdVF`` raised AttributeError in the three tests that build a SimdVF body. Use ``import tilelang.ascend.language as T`` like the other Ascend tests. - testing/ascend/analysis/test_ascend_task.py called ``lower(main, target="ascend")`` without holding the target scope in two tests, so the vectorize planner's ``Target::Current(false)`` check failed with "Target context required". Wrap those calls in ``with tvm.target.Target("ascend")``, matching the thirteen tests and the annotate_unlimit_memory example already updated indf459687. Both are test-side misses from the isolation work; no library behavior changes. Validation (Ascend NPU host, USE_CUDA=ON + USE_ASCEND=ON build): - pytest -n 32 --maxfail=3 ascend -> 1037 passed, 12 skipped, 0 failed - pytest -n 4 --maxfail=3 ../examples/ascend -> 96 passed * [Profiler] Split timing helpers and add wall-clock benchmarking (#3234) * [Profiler] Separate runtime device backends * [Profiler] Limit backend split to code relocation * [Profiler][Backend] Declare profiling backend compatibility * [Profiler] Keep do_bench in its original module * [Profiler] Split timing helpers and add wall-clock benchmarking * [Profiler] Share CUDA and MPS event and synchronization helpers * [Profiler] Remove native MPS event and CUDA forwarding mock tests (cherry picked from commitdb5584f2d3) * [CI] Include Ascend unit tests in the local CI script * [Profiler] Move msprof timing into its own module * [IR] Guard PersistentFor body instead of breaking * [Runtime] Defer adapter device detection to Torch runtime * [Backend] Align reducer finalization with upstream * [Lint] Fix Ascend reducer namespace formatting * [Cache] Remove Python pipeline cache stamp * [CI] Explicitly enable Ascend in build entry points * [CI][Ascend] Keep performance regression snapshots outside the checkout (#471) * [CI][Ascend] Use the PR base for performance regression (#474) * fix(ascend): preserve cross-iteration WAR dependencies on shared buffers (#466) Condition snapshots outside the constraint Bind stack were not renamed between iterations. Opposite branches in different iterations could therefore be incorrectly treated as mutually exclusive, causing shared buffer WAR dependencies to be omitted. Seed producer and consumer substitutions with variables written in the compared iteration scope before renaming constraints. This prevents MTE2 from overwriting shared UB before an earlier Vector read completes. Fixes the MHC norm backward regression exposed by the dependency ordering change in 7b580f05. Add regression coverage for cross-iteration branch transitions. Co-authored-by: caojian5 <caojian5@huawei.com> * cherry-pick from #3209 to #3238 (#467) * [BugFix][Metal] Respect GEMM buffer region offsets (#3209) (cherry picked from commit030556de97) (cherry picked from commit 52456a97da0c9c4eca63ebe4d30dfb788a89e0a0) * [Runtime] Fix library loading for symlink installs (#3227) (cherry picked from commit4c9cf5c7ea) (cherry picked from commit e4b2117e9614ee549ecc617ec1af5545fb69cc4a) * [CUDA] Fix boolean bitwise negation codegen (#3228) * [CUDA] Fix boolean bitwise negation codegen * [CUDA] Fix vectorized integer bitwise negation Co-authored-by: Карим <kareemm@yandex-team.ru> * [Test] Minimize CUDA bitwise negation regression coverage --------- Co-authored-by: Карим <kareemm@yandex-team.ru> (cherry picked from commit96815790f5) (cherry picked from commit cfe44e0e77361c658e3688a243859fe85e14c0da) * [Example] FP8 sparse MLA forward for DeepSeek V3.2 on Hopper (#3224) (cherry picked from commite350730319) (cherry picked from commit e6f4a344fc36cf992e619c8e550095fa570f2499) * [CPU] Import the CPU dialect in CPU tests (#3236) * [CPU] Import the CPU dialect in CPU tests * [CPU] Move the use_tma rejection test to the dialect op-hints tests With the CPU tests written in the CPU dialect, use_tma is not expressible there any more (it is a CUDA-dialect keyword), so test_tilelang_cpu_atomic.py had to import tilelang.cuda.language just for this one case. The behavior under test - a CUDA-dialect atomic_add(use_tma=True) compiled for target="c" is rejected by the CPU backend - is a cross-dialect property, so it now lives next to the other cross-dialect hint tests and the CPU test file only uses the CPU dialect. The match is also tightened to the backend error text so a Python TypeError cannot satisfy it. --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit80299bf1c5) (cherry picked from commit 9da48685f2c37652ad9129f8266900e8c7b2c97e) * [BugFix][Vectorize] Keep atomic_add scalar for invariant/non-contiguous destinations (#3219) * [Vectorize] Keep atomic_add scalar for invariant/non-contiguous destinations Auto-vectorization widened a scalar atomic_add to AtomicAddx2/x4 based only on dtype and scope, ignoring whether the destination lanes form a contiguous, aligned run. An invariant/broadcast destination (B[i//2], B[(i//2)*2], B[0]) was emitted as a contiguous wide atomic at the base address, silently corrupting neighbouring elements, while an odd base faulted with 'CUDA error: misaligned address'. Add CanVectorizeAtomicTarget: the vectorized loop variable must advance exactly one element per lane in the innermost destination index and the base must be provably aligned to the atomic width. Otherwise fall back to scalar atomics. The check runs on both the original and the var->0 substituted destination, so it covers address_of / tl.access_ptr / tvm_access_ptr uniformly and preserves the trailing memory_order operand. Testing: python -m pytest testing/python/language/test_tilelang_language_atomic.py -q (75 passed, 9 skipped) * [Vectorize] Validate every lane transition in atomic widening CanVectorizeAtomicTarget only compared lanes 0 and 1, so a destination that agrees on the first transition but repeats at larger widths (e.g. B[i % 2] at four lanes) was not rejected by the predicate itself. Check every lane transition up to the vector width instead: exactly one innermost index must advance one element per lane and the others must stay constant across all lanes. Cover B[i % 2] in the existing invariant-destination test by bounding the emitted vector width by the destination's valid run. * [Vectorize] Reuse the vectorizer's Ramp propagation for the atomic target check `CanVectorizeAtomicTarget` re-derived lane contiguity by substituting every lane value into every index and running `CanProveEqual` twice per lane (indices x (lanes-1) x 2 Simplify+prove calls), and needed both the original and the visited destination to recover the base. The `TLVectorizer` already carries this information: visiting an index turns `var_` into `Ramp(0, 1, lanes)`, Add/Sub/Mul keep the Ramp with a scaled stride, and FloorDiv/FloorMod/Select fold into a generic vector. So the destination is widenable iff every leading index visits to a scalar and the innermost one visits to a unit-stride Ramp whose base is provably aligned to the atomic width -- the same Ramp/stride test `IndicesCanVectorize` applies when planning ordinary loads and stores. Replace the free function with `TLVectorizer::AtomicTargetIsContiguous`, which is one visit per index plus a single alignment proof. Behaviour is unchanged: the PR's atomic test file passes (84), 25 probe kernels (split-K 2D fp32/fp16/bf16, region shared->global, runtime offset, `B[i//2]`, `B[i%2]`, `B[i%4]`, `B[2*i]`, `B[0]`, odd base, 2-D constant last index) generate byte-identical `kernel_source` against the previous revision, and hand-written `T.vectorized` atomics keep the same widths and numerics. --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commitc6ece483b1) (cherry picked from commit 8ff7afced3bbc15dadfcde418fb708c0d751d1c6) * [Transform][CUDA] Plan atomic vector widths from destination addresses (#3238) * Plan atomic vector widths from destination addresses * Avoid synthetic buffers in atomic vectorization analysis * Assert atomic vectorization IR invariants (cherry picked from commite688a439a8) (cherry picked from commit 07a76f8829dc555a461310eb6492ee8a853bf64b) --------- Co-authored-by: Anders <anders@magnitude.dev> Co-authored-by: Sepcnt <30561671+sepcnt@users.noreply.github.com> Co-authored-by: Карим <kareemm@yandex-team.ru> Co-authored-by: xuebozhang525-alt <xuebozhang525@gmail.com> Co-authored-by: penguin_wwy <940375606@qq.com> Co-authored-by: Alfred <166222074+Dino1844@users.noreply.github.com> * feat(ascend): merge auto-scheduled on-chip buffers (#379) * [Ascend] Separate buffer lifetime proofs from allocation Generate a positive storage alias contract from automatic or manual synchronization ordering and pack compatible on-chip allocations. Keep physical lifetime footprints separate from logical synchronization regions. Use the Ascend dialect and private lowering pass on asc-on-upstream-main, including typed AscendCopy padding and storage identity remapping. * [Ascend][Test] Hold target scope when inspecting buffer aliases Enter the Ascend target context in the alias-inspection helper and pass that target to lower(). This follows the caller-owned target scope contract and lets the ND2NZ and SIMT lifetime regressions run without changing the private compiler passes. * [Ascend][Tests] Consolidate compiler and runtime regression coverage (#476) * [Ascend][Tests] Consolidate scheduling and transform regressions * [Ascend][Tests] Reduce application-derived regression fixtures * [Ascend][Tests] Simplify DMA, layout, SIMD and runtime regressions * [Ascend][Tests] Fix isolated imports and share neutral IR builders * [Ascend][Tests] Check broadcast metadata cleanup and snapshot boundaries * [Ascend][Tests] Document disabled latency snapshot boundary * fix(ascend): avoid double args in debug printf (#465) * fix(ascend): avoid double args in debug printf - Convert double debug values to float before calling Ascend SIMT printf. - Replace %lf with %f in scalar and buffer debug paths. - Preserve the existing frontend double interface and block-index handling. - Keep the fix compatible with CANN versions that reject double printf parameters. * fix(ascend): implement float32 precision handling for double values in printf * fix(ascend): Reject float64 debug printing * fix(ascend): retain AICore float64 debug printing * fix(ascend): reject unsupported AICore float64 printing * refactor(ascend): clarify float64 print diagnostic * refactor(ascend): simplify float64 print rejection --------- Co-authored-by: caojian <caojian5@huawei.com> * fix(ascend): uniquify cloned VF helper names (#488) * fix(ascend): scalarize fp8 vector arithmetic lane by lane (#494) * fix(ascend): scalarize fp8 vector arithmetic lane by lane Elementwise fp8 arithmetic inside a SimtVF region produced wrong values whenever a thread owned more than one element. For `C = A + B` over fp8 e4m3 with n=256 and 128 threads, `-128 + 64` yielded ~+16 instead of -64; only the one-element-per-thread case (n=128) was correct. Root cause: Ascend carries small fp8 vectors as plain integer typedefs (`fp8_e4_2_t` is `uint16_t`, see tl_templates/ascend/ascend_fp8.h), unlike the CUDA/HIP backends where these are structs. `PrintVecBinaryOp` therefore emitted `*(fp8_e4_2_t *)a + *(fp8_e4_2_t *)b`, which C++ resolves as *integer* addition of two packed byte pairs rather than as floating-point arithmetic. The scalar path was unaffected because a lone fp8 goes through `tl::float_e4m3_t`, whose implicit `operator float()` makes `+` a float add. f16 and bf16 were already listed in `PrintVecBinaryOp`'s scalarization condition for exactly this reason (packed 16-bit carriers with no matching small-vector operation); fp8 was simply missing from the list. Fix: - Add e4m3/e4m3fn/e5m2 vectors to the scalarization set. e8m0fnu is excluded: it is a scale-factor encoding with no scalar arithmetic type, and `GetAscendFP8ScalarValueType` rejects it. - Teach `PrintVecElemLoad`/`PrintVecElemStore` to address one byte per fp8 lane and round-trip through the existing `tl::float_e4m3_t`/`float_e5m2_t` conversions, so the vector path gets the same semantics the scalar path has. These helpers are shared with vector comparisons, Select, unary ops and calls, so fp8 in those contexts is corrected too; comparisons were already safe because `PrintBinaryExpr` dispatches on the bool result dtype. Validation on Ascend950DT (dav-3510): - examples/ascend/test_fp8_vecadd.py is repaired from a 0-collecting harness into a parametrized test over {e4m3, e5m2} x {16, 8, 4, 2, 1} elements per thread. All 5 e4m3 widths failed before this change and all 10 cases pass after it; the generated code now shows per-lane `tl::float_e4m3_t(...)` round-trips instead of a packed integer add. - `pytest examples/ascend`: 106 passed (96 pre-existing + 10 new). - `pytest testing/ascend`: 949 passed. * refactor(ascend): reuse the shared fp8 vector predicate Use IsAscendVectorizableFP8 at the three vector codegen sites instead of maintaining a duplicate FP8 flavor check. Lane load/store already return for scalars, and binary vector emission is reached only for vector values, so the helper's extra lane-count check is redundant. Keep the existing byte-based lane conversion and arithmetic scalarization. Clarify that arithmetic results round to FP8 while copying an FP8 value preserves its encoded bits. This follow-up changes only the C++ codegen file and does not modify tests. Validation: incremental Ascend build and formatting checks passed. All 23 focused FP8, vector-codegen, and vector-select tests passed on Ascend 950. Generated source for 24 cases is byte-for-byte identical before and after the simplification; five dtype lowering probes retain the same outcomes. * feat(ascend): add MMAD traversal direction control (#495) * [Ascend] Align L0C-to-UB fixpipe transfers (#459) * fix(ascend): align L0C-to-UB fixpipe transfers * fix(ascend): keep fixpipe padding bounds analyzable * [Ascend] Add owner-exclusion dependencies to AutoSchedule (#444) * [Ascend] Add owner-exclusion dependencies to AutoSchedule * fix(ascend): harden owner-exclusion scheduling edge cases Preserve owner-external fill metadata for single-version plans, reject malformed Z3 dependency tuples, improve unsupported-distance diagnostics, and share forward/reverse dependency latency calculation. * fix(ascend): honor single-version eligibility opt-out * [CI] Retrigger CI after rebase * [Ascend] Clarify owner diagnostics and validate counter elision * [Ascend] Drop extra storage diagnostics from InsertSync * [Ascend] Warn when single-version opt-out overrides explicit eligibility * [Ascend][AutoSchedule] Preserve iteration order at equal issue times (#512) * [Ascend][AutoSchedule] Preserve iteration order at equal issue times * [Ascend][AutoSchedule] Cover manual-stage issue-time ties * [Docs][Skills] Expand Ascend kernel programming and performance guidance (#442) * docs(skills): add TileLang review and Ascend workflows * docs(skills): separate kernel and backend development guidance * [Docs][Skills] Preserve existing skill names * [Docs][Skills] Shorten Ascend auto-schedule skill name * [Docs][Skills] Keep repository guidance focused on Ascend kernels * [Docs][Skills] Move kernel reproducer guidance to shared skills * [Docs][Skills] Preserve shared skill discovery descriptions * [Docs][Skills] Clarify kernel setup and skill navigation * Refine Ascend skill text for public use * Document T.Stage performance tuning for Ascend kernels * Remove redundant skill catalog * [Skills] Consolidate core Ascend guidance into skill entrypoints * [Skills] Integrate porting guidance into relevant sections * [Skills] Move VF measurement into Ascend performance guidance * [Skills] Keep semantic guidance backend-neutral * [Skills] Merge performance guidance into tilelang-ascend * [Skills] Align Ascend guidance with current APIs and lowering * [Docs][Skills] Document Ascend multibuffer contracts and limits * [Docs][Skills] Define SIMD mask in introductory example --------- Co-authored-by: Denver Jin <denverjin@outlook.com> Co-authored-by: mengyuhao <mengyuhao@deepseek.com> * [Ascend] Disable Bisheng argument data cache preloading by default (#511) * [Ascend] Disable Bisheng argument data cache preloading by default * [Ascend] Preserve paired Bisheng options in Cython compilation * [Ascend] Avoid forwarding explicit Cython compiler flags twice * [JIT] Remove Cython compiler flag handoff comments * fix(ascend): compact sparse sync event ids (#515) Co-authored-by: caojian5 <caojian5@huawei.com> * [Ascend] Initialize NZ transfer parameters for L0C-to-UB copies (#520) * fix(ascend): initialize NZ parameters for L0C-to-UB copies * test(ascend): remove L0C-to-UB NZ parameter regression test * [Ascend] Add optimized GQA FlashAttention backward (#373) * feat(ascend): add optimized GQA FlashAttention backward Add an optional LSE output to the shared FlashAttention forward builder and implement the GQA backward path as a Delta preprocessing launch followed by a frontend-staged AutoSchedule KV-centric mixed kernel. The fused kernel pipelines five GEMMs across AIC and AIV, accumulates dK/dV privately, and reduces BF16 dQ through atomic adds; per-task T.Stage annotations pin the overlapped schedule while AutoSchedule derives the buffer versioning and inserts local and cross-core synchronization. Add focused correctness coverage plus a cold-L2 Torch SDPA benchmark that reports FFTS latency and pipe utilization. Document the fixed 128x128 BF16 constraints, explicit pass configuration, cannsim VF latencies, and the measured full-shape result. Use the tilelang.ascend.language entry point, which is the only dialect registered on asc-on-upstream-main. * test(ascend): track GQA backward performance Register the GQA backward example in the Ascend perf regression script so the fused kernel is measured on every regression run. * perf(ascend): optimize GQA backward FixPipe stores and buffering Improve the KV-centric GQA backward kernel while retaining T.Stage(0/1) constraints and AutoSchedule-managed buffer indices and synchronization. Pair UnitFlag controls on temporary L0C producers and consumers, and write BF16 dQ contributions atomically from FixPipe to GM without the intermediate UB tile or Vector cast. Rebalance Q/dO L1 buffers from three to four versions and P/dS and Vector intermediates from three to two. L1 usage remains 448 KiB while UB usage drops from 243.5 to 162.5 KiB. Preserve the minimum of three query tiles and the existing BF16 gradient tolerances. Extend correctness coverage to short loops and non-power-of-two tile counts, and refresh the implementation and performance documentation. On Ascend950DT with CANN 9.2.0 and TileLang8121f415, five interleaved cold-L2 rounds reduce dQ-zeroing plus fused latency from 6834.581 us to 6418.273 us (402.19 to 428.27 effective TFLOPS). Raw FFTS records confirm 20 measured invocations per profile. Forward and Delta are outside timing. Validation: three GQA backward cases and the existing GQA/MHA forward tests passed using the local TileLang build; 55 additional launches across six shapes/scales passed against FP32 autograd or Torch SDPA. Formatting for all PR files and git diff --check passed. The compiler and runtime are unchanged. * [Ascend][Language] Cherry-pick #3237 and load MX scale factors through L0 SF handles; retire T.copy(scale=) (#475) * [Do not review][Op][Language][CUDA] Add common block-scaled GEMM semantics and backend dispatch (#3237) * [Language][CUDA] Add T.gemm_blockscaled and dispatch block-scaled GEMM by target and accumulator scope Block-scaled GEMM had two explicit entry points (T.tcgen05_gemm_blockscaled for SM100 TCGEN5MMA, T.mma_gemm_blockscaled for SM120 mma.sync) with duplicated bodies and no auto-dispatching tier like T.gemm. Instruction selection also checked the SFA/SFB branch before AllowTcgen5Mma and required SM120 there, so every 1-CTA SM100 block-scaled kernel failed with "requires an SM120 CUDA target" (only use_2cta=True callers survived). - SelectInst now picks the block-scaled instruction from the target and the accumulator scope: SM100 with C in tensor memory lowers to TCGEN05, SM120 with C in a fragment lowers to cuda.mma.blockscaled, anything else is a compile error. There is deliberately no dense fallback. - Add T.gemm_blockscaled as the auto-dispatching entry (CUDA dialect). The explicit variants keep their signatures and share one _gemm_blockscaled_impl; T.tcgen05_gemm_blockscaled now carries is_tcgen05 and uses tl.tileop.tcgen05_gemm so it really pins that path. - sf_layout rides the annotations as a StringImm; unwrap it in the SM120 lowering. - Tests: hardware-free call-protocol checks, an SM100 1-CTA MXFP8 regression test through both entry points, a no-fallback negative test, an SM100 example test, and a gemm_api parametrization of the SM120 NVF4 tests. * [Op] Declare block-scaled GEMM support as a GemmImpl capability The ROCm, Metal and CPU SelectInst implementations never look at the SFA/SFB regions, so a block-scaled GEMM compiled for those targets was lowered as a dense GEMM and silently dropped the scale factors. Add GemmImpl::supports_blockscaled (true only for cuda.Gemm) and reject block-scaled GEMMs in GemmNode::GetGemmInstructionKey before delegating to a backend that does not declare it. Backends no longer need to remember to reject SFA/SFB individually. Add a hardware-free test that a CPU-bound block-scaled GEMM fails at layout inference. * [Op] Give block-scaled GEMM its own tl.tileop.gemm_blockscaled op key Block-scaled GEMM keeps sharing GemmNode with the dense op, but the three frontend entry points now emit a dedicated tl.tileop.gemm_blockscaled op (builder reuses Gemm(args, ann), mirroring tl.tileop.tcgen05_gemm) instead of a 16-argument tl.tileop.gemm / tl.tileop.tcgen05_gemm. The printed IR is self-describing and passes can match block-scaled GEMMs by name rather than by counting call arguments. The explicit TCGEN05 variant is distinguished by the is_tcgen05 annotation, as T.tcgen05_gemm is from T.gemm. Teach the FLOP analyzer the new op name. * [Op] Make block-scaled GEMM a first-class tile op (GemmBlockScaledNode) tl.tileop.gemm_blockscaled now builds GemmBlockScaledNode, a GemmNode subclass that owns the SFA/SFB regions and k_start instead of carrying them as optional trailing slots on the dense node. Passes that only care about "a GEMM" keep matching through GemmNode (IsInstance respects the hierarchy); code that needs the scale factors matches the subclass via AsGemmBlockScaled. The dense op rejects the 16-slot protocol so scale factors can never be silently ignored. - GemmNode: drop the SF fields, un-final the type, make Clone / GetGemmInstructionKey virtual, route Lower/InferLayout through overridable global-function names, share the 13-slot parse. - GemmBlockScaledNode: SF fields + reflection, access regions, Clone, the backend capability check, tl.gemm_blockscaled.{infer_layout,lower}. - producer_consumer_ws / cuda SelectInst read SF fields through the subclass; materialize_ws_schedule keeps the block-scaled op when it marks a tcgen05 atom instead of rewriting it to tl.tileop.tcgen05_gemm. - Python: register tl.GemmBlockScaled as GemmBlockScaled(Gemm) with the matching global functions; backend impls are unchanged. * [CUDA] Drop redundant comment on block-scaled instruction selection * [Op][CUDA] Separate block-scaled GEMM semantics and dispatch Move the block-scaled tile operator into dedicated common files and give CUDA instruction selection its own implementation. Reuse the GEMM backend registry through a typed block-scaled selector instead of the supports_blockscaled flag. Reject TMEM A and incompatible explicit instruction requests in the block-scaled selector. Add CUDA-gated dispatch regression coverage while retaining shared GEMM layout and scheduling behavior. * [Language] Expose block-scaled GEMM in the common dialect * [Test] Remove block-scaled GEMM example tests * [CUDA] Preserve GEMM semantics in warp-specialized schedules * Restore explicit TCGEN05 GEMM ops in warp specialization * Separate Python block-scaled GEMM tile-op registration * [Language] Share dense GEMM slot construction across GEMM frontends _gemm_impl and _gemm_blockscaled_impl each carried their own copy of the operand legalization, region normalization, rank/shape checks, static-dim requirement, mbar validation and 13-slot call construction. Factor that into _gemm_dense_slots(api_name, ...) so the block-scaled frontend only appends the SFA/SFB regions and k_start. Error messages keep their per-entry-point prefix (T.gemm / T.gemm_blockscaled), which examples/autodd relies on. * [CUDA] Make T.gemm_blockscaled synchronous like T.gemm T.gemm_blockscaled is the auto-dispatching tier of the block-scaled GEMM family, so it should follow T.gemm's completion contract: the result is complete when the call returns. On the SM100 TCGEN05 path the lowering now posts completion to `mbar` and inserts the matching mbarrier_wait_parity with the phase LowerTileOp derives from the enclosing loop, exactly as the dense _gemm_ss lowering does; the explicit T.tcgen05_gemm_blockscaled (is_tcgen05) still never waits. No C++ change is needed: InjectPipeline's IsMbarPhaseConsumer and the loop-phase plumbing already match GemmBlockScaledNode through GemmNode. The SM100 correctness kernel now follows each entry point's contract. The synchronous entry waits on a single per-iteration barrier and the MMA warp releases the smem stage itself afterwards, so a missing wait would race the producer's TMA against the in-flight MMA. A hardware-free lowering test pins the wait (with the loop phase threaded through) for T.gemm_blockscaled and its absence for T.tcgen05_gemm_blockscaled. * [Language][CUDA] Move explicit CUDA GEMM variants into the CUDA dialect wgmma_gemm, tcgen05_gemm, tcgen05_gemm_blockscaled, mma_gemm_blockscaled and make_blockscaled_gemm_layout pin a CUDA instruction family and have no target-neutral meaning, but were defined in the common tilelang/language/gemm_op.py and only re-exported from tilelang/cuda/language/intrinsics.py. Define them in tilelang/cuda/language/gemm_op.py next to the CUDA gemm / gemm_blockscaled shadows and import them from there. The common module now holds only gemm, gemm_blockscaled and the shared _impl helpers, and no longer reaches into tilelang.cuda.intrinsics for the TMEM layout helper. Function bodies are relocated verbatim; the dialect export surfaces are unchanged. * [Test] Drop redundant gemm_api parametrization from SM120 NVF4 tests T.mma_gemm_blockscaled and T.gemm_blockscaled build structurally identical tl.tileop.gemm_blockscaled calls, so parametrizing the NVF4 codegen and correctness tests over both entry points compiled the same IR twice on SM120 hardware. The dispatch contract for the unified entry on SM120 is already covered hardware-free by test_blockscaled_instruction_selection. Restore the file to main. * [Op] Require exactly 13 positional slots for tl.tileop.gemm tl.tileop.gemm is registered with variable arity and Gemm::Gemm only bounded the argument count from above, so a call with fewer than 13 slots reached InitFromDenseArgs and indexed past the end of the array. Check for exactly 13 slots up front and report the actual count; the block-scaled hint in the message is kept for the 16-slot case. Cover both the long and the short call in the parse test. * [Op] Lower block-scaled GEMM through the shared tl.gemm entry points GemmBlockScaledNode routed layout inference and lowering through its own tl.gemm_blockscaled.{infer_layout,lower} global functions, selected by virtual InferLayoutGlobalFunc/LowerGlobalFunc hooks on GemmNode. Those functions were copies of tl.gemm.{infer_layout,lower}: both only call infer_layout()/lower() on the Python object, and the object handed over the FFI boundary is already the GemmBlockScaled Python class, so the block-scaled behaviour comes from that class and from the instruction key the C++ selector returns. Drop the hooks and the duplicate global functions; a future GEMM flavour that needs different Python lowering overrides lower() on its class instead. * [CUDA] Split the TCGEN05 block-scaled implementation from the dense class The Python implementation layer still told dense and block-scaled GEMM apart by sniffing scale-factor fields: GemmBase carried SFARegion / SFBRegion / sf_k_start / is_blockscaled via getattr defaults, and GemmTCGEN5 branched on is_blockscaled at five points before diverting to an 81-line _lower_blockscaled. SM120 already had the right shape, with cuda.mma.blockscaled mapped by the registry to its own class. Give TCGEN05 the same shape. The C++ selector returns cuda.tcgen05.blockscaled, which the registry maps to GemmTCGEN5BlockScaled(GemmBlockScaledMixin, GemmTCGEN5). The dense GemmTCGEN5 exposes _warp_partition / _make_mma_emitter / _assign_layouts hooks and a tcgen05_allow_ws flag, and no longer knows about scale factors. GemmBlockScaledMixin (tilelang/tileop/gemm_blockscaled/gemm_blockscaled_base.py) owns the SFA/SFB regions, k_start and the sf_* annotation parsing that the TCGEN05 and SM120 lowerings each duplicated; which scale layouts a backend supports stays with that backend. GemmBase drops its block-scaled properties, matching the dense C++ node. Also drops an unused _FLOAT8_DTYPES constant from gemm_tcgen05.py. The instruction-selection test now also checks that each block-scaled key resolves to a GemmBlockScaledMixin implementation with the expected scale operands and granularities. * [Op] Register block-scaled GEMM backends separately from GemmImpl The block-scaled instruction selector was a slot on the dense GemmImpl struct, so the dense GEMM header forward-declared GemmBlockScaled and src/cuda/op/gemm.cc had to include the block-scaled selector just to fill that slot. That is the same dependency direction the rest of this change removed from GemmNode, GemmBase and GemmTCGEN5. Give block-scaled GEMM its own GemmBlockScaledImpl registry in src/op/gemm_blockscaled.{h,cc}. GemmBlockScaledNode::GetGemmInstructionKey resolves it and, when no backend is registered for the target, still names the dense backend that owns the target in the error. CUDA registers itself from src/cuda/op/gemm_blockscaled.cc with the same target predicate as its dense GemmImpl; the selector becomes internal and the cuda/op/gemm_blockscaled.h header goes away. src/op/gemm.h now has no block-scaled references. Ascend adaptation for asc-on-upstream-main: T.blockscaled_gemm with explicit sfa/sfb operands now emits tl.tileop.gemm_blockscaled (the L0 flavor without operands stays a dense gemm with the "blockscaled" annotation, its scales living in the MX registers). src/ascend/op/gemm_blockscaled.cc registers the block-scaled selector ("ascend.mad.blockscaled"), which the Python registry maps to GemmMADBlockScaled (GemmBlockScaledMixin + GemmMAD); the dense GemmMAD drops its SF-region knowledge like the CUDA classes. insert_oob_padding, ascend_pipe and estimate_latency match both GEMM op spellings. (cherry picked from commitd4787e9bb1) (cherry picked from commit 11daa03f253dff19e97b45015ee46a4c2b048898) * [Ascend] Pin the MX scale-factor slot semantics with a hardware probe Three probes on Ascend 950DT (2026-09-18) establish the contract of the L0A/L0B MX shadow register file that asc_copy_l12l0a_mx/asc_mmad_mx use: - The SF destination is strictly the data tile's L0 address in 16-byte units: offsetting the emitted dst by one fractal (+32 in /16 units) yields wrong products (rel err 1.24 vs 8.8e-9), so the slot association has no addressing slack. - SF slots are STICKY: after overwriting the data at the same L0 address with a plain (scale-less) load, a block-scaled MAD still applies the previously loaded scales (rel err 7.0e-9 against new-data*old-scales, and clearly not unscaled or cleared). Hoisting scale loads out of data reload loops is therefore legal. - The SF load and the data load are order-free on the MTE1 queue: emitting asc_copy_l12l0a_mx before asc_copy_l12l0a is bit-identical. Keep the stickiness probe as a regression test; it also documents the findings for the upcoming split of the scale companion load out of T.copy(scale=...) into standalone SF-view copies. * [Ascend] Add L0 SF views and the standalone MX scale-factor copy alloc_l0a_sf(a_l0)/alloc_l0b_sf(b_l0) return the MX scale-factor view of an L0 data tile: a Buffer aliasing the tile's storage Var with SF dtype/shape, never allocated. The aliasing is the point - the hardware keys a tile's MX scale slots to the data tile's own address (asc_copy_l12l0a_mx dst = data pointer / 16; asc_mmad_mx reads the slots via its A/B data pointers), so the view inherits the tile's multi-buffer versioning, dependence edges and address derivation for free. T.copy(sf_l1, view) emits tl.tileop.ascend_copy with the mx_sf_load annotation (typed AscendCopyNode::is_sf_load) and lowers to the new 8-arg tl.ascend_load_ca_sf/cb_sf intrinsics, emitting exactly the asc_copy_l12l0a_mx/b_mx call the fused T.copy(scale=...) companion form produces; the fused form is unchanged and coexists until its users migrate. The SF destination pointer travels over the shared Var with its offset converted to DATA elements: AscendLowerTileOp's access-ptr rewriting resolves the Var to the allocation-site buffer and remaps the offset through the tile's fractal layout exactly as it does for the data copy's dst pointer. Only the leading (version) dims of the view region feed that pointer; the x/y slot coordinates stay source-driven, as in the fused form. The multi- buffer expansion expresses each alias's leading stride as the allocation pitch in its own elements, so the version byte offsets agree by construction. Aliased partial-footprint views need three pass exemptions, all keyed on the same criterion (total storage bits differ => not a data alias): - layout_inference: alias layout propagation reshapes by the bit ratio, which requires equal footprints; skip partial views at all three propagation sites instead of failing Layout::Reshape. - normalize_fractal_storage: SF views are not canonical Cube data views; their accesses stay in SF coordinates for the scale-load lowering. - auto_schedule storage sizing: size a storage Var by its largest alias, so a tiny SF view seen first can never undersize the L0 allocation. Verified on Ascend 950DT: single-tile and pipelined (NUM_STAGES=2, sliced SF sources) kernels produce bit-identical results to the fused form, and the standalone _mx call carries the same stage offset expression as the data load. testing/ascend regression batch (mx tail fill, blockscaled matrix, alignment guards, estimate latency, multibuffer storage/counter): 247 passed; examples blockscaled/padding suites: 25 passed. * [Ascend] Route L0 block-scaled GEMM through SF views and delete T.copy(scale=) T.blockscaled_gemm now requires sfa/sfb in both flavors: L1 inputs keep passing the L1 scale buffers, and L0 inputs pass the MX scale-factor views of the operand tiles (alloc_l0a_sf/alloc_l0b_sf), loaded by standalone T.copy(sf_l1, view) scale loads. Every Ascend block-scaled GEMM is now a structural tl.tileop.gemm_blockscaled: the "blockscaled" annotation flavor dies, GemmMAD is purely dense (is_blockscaled False; _lower_l0_blockscaled moves to GemmMADBlockScaled, whose infer_layout assigns the SF_K layout to L1-scoped scale operands only), and insert_oob_padding matches block-scaled GEMMs by node type alone. The fused scale-companion form is deleted end to end: the T.copy scale= kwarg and its 3-region wire format, AscendCopyNode::sf/sf_range (+ reflection), the 16-arg ascend_load_cbuf_to_ca/cb protocol and its codegen branch, the op.sf-driven SF layout assignment, and the k_align=64 L1-extent check in copy InferLayout. The pinned "divisible by 64" diagnostic stays covered: GemmMAD's operation-level K%64 assert fires for misaligned GEMMs, and alloc_l0a_sf's default shape derivation rejects a misaligned K at trace time with the same wording. Pass migration: - insert_oob_padding: MxL1KAxisCollector keys MX tail-fill propagation on "L1->L0 data copy whose dst Var feeds a block-scaled GEMM" and skips standalone SF loads (their dst Var IS the data Var, but their source is the differently laid out L1 scale buffer, not MX data). - estimate_latency: SF loads are excluded from the copy cost features, keeping the accounting identical to the fused era when the scale bytes rode the data copy invisibly. - dependency analysis: Var-shared aliases with differing bit footprints are disjoint storage (shared SameAliasFootprint in ascend/op/utils.h, also adopted by normalize_fractal_storage). This removes the false WAW between a data load and its SF load that materialized an asc_sync_pipe(PIPE_MTE1) between the two MTE1 instructions; ordering against the consuming MAD still flows through the view-to-view region pair, since the GEMM reads the same view buffer the SF copy writes. Equal-footprint aliases (T.view/T.reshape reinterprets) keep the conservative conflict. All scale= users migrate to the split form: example_blockscaled_gemm_l0 (pipelined, uint16/uint8 scales), test_blockscaled_gemm, test_gemm_padding_irregular_shapes (transpose + uint8 scales), the blockscaled matrix/alignment/tail-fill tests and the MX slot probes; the dialect-surface test drops the scale kwarg from the expected signature. Verified on Ascend 950DT: testing/ascend 1040 passed / 12 skipped (including the tail-fill geometry goldens and their no-PIPE_MTE1-sync guard); examples blockscaled/padding/padded-copy suites 25 passed. * [Ascend][Language] Rename blockscaled_gemm to gemm_blockscaled The Ascend dialect's block-scaled GEMM entry kept a legacy spelling; now that it emits the first-class tl.tileop.gemm_blockscaled op, name it after the common surface it shadows (upstream #3237's T.gemm_blockscaled), following the dialect-shadowing pattern already used for copy/gemm/unroll. The SFA/SFB parameter names align with the common signature; the Ascend shadow still differs deliberately in that k_start and the sf_*_granularity knobs are implicit (the lowering derives the scale K offset from the SFA region slice, and MX scales cover 32 K elements per factor). The shadow is registered in the dialect-surface test's owned-symbols map; all call sites in tests and examples migrate. No compatibility alias is kept. * [Ascend] Give MX scale-factor handles a dedicated scope alloc_l0a_sf/alloc_l0b_sf now return a buffer in its own storage scope (shared.l0a.sf / shared.l0b.sf) with its own Var, instead of a partial-footprint alias of the data tile. The binding to the tile rides the scale-load op as the mx_sf_of annotation (a Var, attached by the dialect copy from the alloc-time registry); the scope itself classifies the op everywhere else, so the mx_sf_load annotation, AscendCopyNode::is_sf_load and the frontend WeakSet die. The alias-era footprint heuristic is deleted wholesale: SameAliasFootprint and its exemptions in layout inference (three propagation sites), fractal storage normalization, and dependency analysis (the conservative different-buffer rule is restored), plus the storage-sizing representative workaround in AutoSchedule - with separate Vars none of those cases can arise. Scope-keyed classification replaces the op-marker in the tail-fill collector, the latency features and the pipe mask (L1->l0*.sf is MTE1). Version lockstep, the one thing aliasing provided structurally, is now explicit: GetOnChipStorages includes SF handles so the multi-buffer eligibility planner claims an owner loop for them, and AutoSchedule copies the bound tile's selected version count onto the handle after selection (PropagateMxSfHandleVersions), so ResolveCore/InsertSync/Materialize ring handle and tile together. The scale-load lowering resolves the bound tile by Var, computes the stage offset as the handle's leading version index times the tile's (padded) allocation footprint, and emits the destination pointer over the tile's Var exactly like the data copy; handle allocations are dropped in AscendLowerTileOp before storage planning ever sees the scope. During bring-up the unclassified scope fell through to the Vector-pipe default and the scheduler happily hoisted the scale loads past the MAD - the pipe-mask entry is load-bearing. Verified on Ascend 950DT: SF view/slot tests including the pipelined stage-offset identity, the 254-test regression batch (tail-fill geometry goldens, blockscaled matrix, alignment guards, dialect surface, latency goldens, multibuffer suites, cross-iteration snapshots) and the 24 blockscaled/padding example tests all pass; the scale-load scheduling microbenchmark reproduces the alias design's numbers exactly (14.28 / 14.29 / 9.03 us for pinned per-iter / pinned hoisted / default ping-pong). * [Ascend] Derive MX scale-factor bindings from their consuming GEMMs The binding between a scale-factor handle and its data tile was carried by a frontend WeakKeyDictionary (alloc_l0a_sf recorded it, the dialect copy attached it to the scale-load op as mx_sf_of). But the binding is already structural in the IR: the tl.tileop.gemm_blockscaled call that consumes a handle as SFA/SFB names the tile as its A/B operand. Derive it from there instead (CollectMxSfBindings): AutoSchedule's version propagation reads the GEMMs directly, and AscendLowerTileOp resolves the binding onto the parsed copy node before lowering. A handle consumed with two different tiles is now a diagnosed error, and a scale load whose handle feeds no GEMM fails with a message naming the missing consumer. This deletes the registry, the frontend-attached annotation, and the dialect copy's special branch entirely - a scale load is emitted through the ordinary copy path and classified purely by its destination scope, so hand-written or synthesized IR needs no frontend cooperation at all. alloc_l0a_sf's data-tile argument now only drives the default shape derivation and a scope sanity check. Verified on Ascend 950DT: SF view/slot/tail-fill, blockscaled matrix, alignment guards and dialect-surface tests (53 passed) plus the 24 blockscaled/padding example tests. * [Ascend] Polish MX scale-factor handle plumbing after review - Delete the dead "mx_sf_of" annotation decode: nothing emits the annotation; AscendLowerTileOp resolves the binding structurally from the consuming gemm_blockscaled and sets the field directly. - Inject that binding through CopyOnWrite (AscendCopy now declares the COW method) instead of writing through a const_cast. - Align the scale-load empty-copy guard with the DMA path (CanProve short-circuit), explain the single-version-dimension limit in the handle-rank check, and refresh stale fused-copy-era comments. - alloc_l0a_sf/alloc_l0b_sf: refuse the default sf_shape derivation for tiles carrying leading version dims and document that `buf` is advisory - the consuming gemm defines the binding. * [Ascend][Tests] Drop the SF-view codegen regression file The MX scale-factor handle path keeps its numerical coverage through the pipelined block-scaled examples (a wrong stage offset or missing scale load produces wrong products there) and the slot-semantics probe; the per-instruction codegen pins are not worth a dedicated file. * [Ascend] Reject MX scale-factor slot plans that ring out of lockstep From PR475 review: with the scale load in an outer pipelined loop and the data reload in an inner one, both storages ring two versions but the scale load's slot follows the outer loop while the tile and its consuming MAD follow the inner one, so the MAD reads slots whose scales were never loaded - silent wrong results on 950DT (the hardware keys the scale slots to the tile's address). PropagateMxSfHandleVersions equalizes only the version counts; the version index is driven by each storage's own owner loop. Validate in the multi-buffer plan builder, where the owners are known: a handle ringing more than one version must share its data tile's owner loops. The diagnostic names the two workarounds (load the scales in the same pipelined loop as the data tile, or pin the tile to a single version and let the sticky slots serve every reload) and also covers the fully hoisted form, which previously died on the generic "no annotated owner loop" ICHECK. Broadcasting hoisted scale loads into every version slot instead of rejecting them is left as a TODO. * [Ascend] Broadcast hoisted MX scale loads into every version slot Supports the schedule shape the previous commit rejected: a scale load hoisted outside its data tile's pipelined loop (into an outer pipelined loop, or above every loop) while the tile keeps ping-ponging. Plan the handle from its bound tile instead of independently. PropagateMxSfHandleVersions compares owner loops (CollectMultiBufferOwners) and copies the tile's version count only when the scale load rings under the same owners - the lockstep behavior, unchanged. Otherwise the handle stays single-version and the copy lowering broadcasts the load: AscendLowerTileOp injects the tile's slot count (mx_sf_versions) next to the mx_sf_of binding, and LowerMxSfLoad emits one asc_copy_l12l0*_mx per version slot. The scales are invariant across the tile's ring period, slots are sticky, and the plain storage dependence on the unversioned handle orders each broadcast after every consumer of the previous scales, so the inner data ping-pong survives intact and no sync rules change. With unsupported plans no longer constructible, the plan-time rejection (ValidateMxSfHandleOwners) is removed and its regression file now pins the supported behavior instead: broadcast emission for both hoisted forms, the pinned single-slot form, and the review reproducer's numerics on 950DT (rel 5.5e-08; the broadcast pair is guarded by the PIPE_M->PIPE_MTE1 wait on the previous iteration's last MAD). * [Docs][Ascend] Announce Ascend 950 backend support (#519) * [Docs][Ascend] Announce Ascend 950 backend support * Update README.md --------- Co-authored-by: xuruifan <silentcoder@foxmail.com> * [Ascend][Cache] Publish immutable binary cache directories (#524) Port the immutable directory scheme from the CUDA binary cache (tile-ai/tilelang#3177) to AscendBinaryCache, which still published flat mutable files with no integrity verification: a crash could publish a truncated .aibin that load() would then serve forever, and os.replace let later writers overwrite entries that concurrent readers may hold open. Factor the shared behavior into a BinaryCache base (tilelang/cache/binary_cache.py): each key is an immutable directory holding metadata.json (format tag, size, sha256) and the raw binary, staged privately, fsynced, and published with a single rename so the first valid writer wins. Any anomaly on load is a plain cache miss and never deletes shared entries. CUDABinaryCache and AscendBinaryCache now only define their cache roots, format tags, and make_key. * [Docs][Ascend] Clarify kernel programming responsibilities (#527) * [Docs][Ascend] Clarify kernel programming responsibilities * [Docs][Ascend] Clarify programming comparison headings * [Ascend] Bind L0 scale factors at allocation and unify storage planning (#529) * [Ascend] Bind L0 scale factors at allocation and unify storage planning * [Ascend] Preserve SF group owners and prove copy capacity * [Ascend] Reject only proven SF copy overflows * [Ascend] Require positive proof of SF copy capacity * [Ascend] Sync upstream through tile-ai/tilelang#3284 (#533) * [BugFix][Metal] Respect GEMM buffer region offsets (#3209) (cherry picked from commit030556de97) * [Runtime] Fix library loading for symlink installs (#3227) (cherry picked from commit4c9cf5c7ea) * [CUDA] Fix boolean bitwise negation codegen (#3228) * [CUDA] Fix boolean bitwise negation codegen * [CUDA] Fix vectorized integer bitwise negation Co-authored-by: Карим <kareemm@yandex-team.ru> * [Test] Minimize CUDA bitwise negation regression coverage --------- Co-authored-by: Карим <kareemm@yandex-team.ru> (cherry picked from commit96815790f5) * [Example] FP8 sparse MLA forward for DeepSeek V3.2 on Hopper (#3224) (cherry picked from commite350730319) * [CPU] Import the CPU dialect in CPU tests (#3236) * [CPU] Import the CPU dialect in CPU tests * [CPU] Move the use_tma rejection test to the dialect op-hints tests With the CPU tests written in the CPU dialect, use_tma is not expressible there any more (it is a CUDA-dialect keyword), so test_tilelang_cpu_atomic.py had to import tilelang.cuda.language just for this one case. The behavior under test - a CUDA-dialect atomic_add(use_tma=True) compiled for target="c" is rejected by the CPU backend - is a cross-dialect property, so it now lives next to the other cross-dialect hint tests and the CPU test file only uses the CPU dialect. The match is also tightened to the backend error text so a Python TypeError cannot satisfy it. --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit80299bf1c5) * [BugFix][Vectorize] Keep atomic_add scalar for invariant/non-contiguous destinations (#3219) * [Vectorize] Keep atomic_add scalar for invariant/non-contiguous destinations Auto-vectorization widened a scalar atomic_add to AtomicAddx2/x4 based only on dtype and scope, ignoring whether the destination lanes form a contiguous, aligned run. An invariant/broadcast destination (B[i//2], B[(i//2)*2], B[0]) was emitted as a contiguous wide atomic at the base address, silently corrupting neighbouring elements, while an odd base faulted with 'CUDA error: misaligned address'. Add CanVectorizeAtomicTarget: the vectorized loop variable must advance exactly one element per lane in the innermost destination index and the base must be provably aligned to the atomic width. Otherwise fall back to scalar atomics. The check runs on both the original and the var->0 substituted destination, so it covers address_of / tl.access_ptr / tvm_access_ptr uniformly and preserves the trailing memory_order operand. Testing: python -m pytest testing/python/language/test_tilelang_language_atomic.py -q (75 passed, 9 skipped) * [Vectorize] Validate every lane transition in atomic widening CanVectorizeAtomicTarget only compared lanes 0 and 1, so a destination that agrees on the first transition but repeats at larger widths (e.g. B[i % 2] at four lanes) was not rejected by the predicate itself. Check every lane transition up to the vector width instead: exactly one innermost index must advance one element per lane and the others must stay constant across all lanes. Cover B[i % 2] in the existing invariant-destination test by bounding the emitted vector width by the destination's valid run. * [Vectorize] Reuse the vectorizer's Ramp propagation for the atomic target check `CanVectorizeAtomicTarget` re-derived lane contiguity by substituting every lane value into every index and running `CanProveEqual` twice per lane (indices x (lanes-1) x 2 Simplify+prove calls), and needed both the original and the visited destination to recover the base. The `TLVectorizer` already carries this information: visiting an index turns `var_` into `Ramp(0, 1, lanes)`, Add/Sub/Mul keep the Ramp with a scaled stride, and FloorDiv/FloorMod/Select fold into a generic vector. So the destination is widenable iff every leading index visits to a scalar and the innermost one visits to a unit-stride Ramp whose base is provably aligned to the atomic width -- the same Ramp/stride test `IndicesCanVectorize` applies when planning ordinary loads and stores. Replace the free function with `TLVectorizer::AtomicTargetIsContiguous`, which is one visit per index plus a single alignment proof. Behaviour is unchanged: the PR's atomic test file passes (84), 25 probe kernels (split-K 2D fp32/fp16/bf16, region shared->global, runtime offset, `B[i//2]`, `B[i%2]`, `B[i%4]`, `B[2*i]`, `B[0]`, odd base, 2-D constant last index) generate byte-identical `kernel_source` against the previous revision, and hand-written `T.vectorized` atomics keep the same widths and numerics. --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commitc6ece483b1) * [Transform][CUDA] Plan atomic vector widths from destination addresses (#3238) * Plan atomic vector widths from destination addresses * Avoid synthetic buffers in atomic vectorization analysis * Assert atomic vectorization IR invariants (cherry picked from commite688a439a8) * [Do not review][Op][Language][CUDA] Add common block-scaled GEMM semantics and backend dispatch (#3237) * [Language][CUDA] Add T.gemm_blockscaled and dispatch block-scaled GEMM by target and accumulator scope Block-scaled GEMM had two explicit entry points (T.tcgen05_gemm_blockscaled for SM100 TCGEN5MMA, T.mma_gemm_blockscaled for SM120 mma.sync) with duplicated bodies and no auto-dispatching tier like T.gemm. Instruction selection also checked the SFA/SFB branch before AllowTcgen5Mma and required SM120 there, so every 1-CTA SM100 block-scaled kernel failed with "requires an SM120 CUDA target" (only use_2cta=True callers survived). - SelectInst now picks the block-scaled instruction from the target and the accumulator scope: SM100 with C in tensor memory lowers to TCGEN05, SM120 with C in a fragment lowers to cuda.mma.blockscaled, anything else is a compile error. There is deliberately no dense fallback. - Add T.gemm_blockscaled as the auto-dispatching entry (CUDA dialect). The explicit variants keep their signatures and share one _gemm_blockscaled_impl; T.tcgen05_gemm_blockscaled now carries is_tcgen05 and uses tl.tileop.tcgen05_gemm so it really pins that path. - sf_layout rides the annotations as a StringImm; unwrap it in the SM120 lowering. - Tests: hardware-free call-protocol checks, an SM100 1-CTA MXFP8 regression test through both entry points, a no-fallback negative test, an SM100 example test, and a gemm_api parametrization of the SM120 NVF4 tests. * [Op] Declare block-scaled GEMM support as a GemmImpl capability The ROCm, Metal and CPU SelectInst implementations never look at the SFA/SFB regions, so a block-scaled GEMM compiled for those targets was lowered as a dense GEMM and silently dropped the scale factors. Add GemmImpl::supports_blockscaled (true only for cuda.Gemm) and reject block-scaled GEMMs in GemmNode::GetGemmInstructionKey before delegating to a backend that does not declare it. Backends no longer need to remember to reject SFA/SFB individually. Add a hardware-free test that a CPU-bound block-scaled GEMM fails at layout inference. * [Op] Give block-scaled GEMM its own tl.tileop.gemm_blockscaled op key Block-scaled GEMM keeps sharing GemmNode with the dense op, but the three frontend entry points now emit a dedicated tl.tileop.gemm_blockscaled op (builder reuses Gemm(args, ann), mirroring tl.tileop.tcgen05_gemm) instead of a 16-argument tl.tileop.gemm / tl.tileop.tcgen05_gemm. The printed IR is self-describing and passes can match block-scaled GEMMs by name rather than by counting call arguments. The explicit TCGEN05 variant is distinguished by the is_tcgen05 annotation, as T.tcgen05_gemm is from T.gemm. Teach the FLOP analyzer the new op name. * [Op] Make block-scaled GEMM a first-class tile op (GemmBlockScaledNode) tl.tileop.gemm_blockscaled now builds GemmBlockScaledNode, a GemmNode subclass that owns the SFA/SFB regions and k_start instead of carrying them as optional trailing slots on the dense node. Passes that only care about "a GEMM" keep matching through GemmNode (IsInstance respects the hierarchy); code that needs the scale factors matches the subclass via AsGemmBlockScaled. The dense op rejects the 16-slot protocol so scale factors can never be silently ignored. - GemmNode: drop the SF fields, un-final the type, make Clone / GetGemmInstructionKey virtual, route Lower/InferLayout through overridable global-function names, share the 13-slot parse. - GemmBlockScaledNode: SF fields + reflection, access regions, Clone, the backend capability check, tl.gemm_blockscaled.{infer_layout,lower}. - producer_consumer_ws / cuda SelectInst read SF fields through the subclass; materialize_ws_schedule keeps the block-scaled op when it marks a tcgen05 atom instead of rewriting it to tl.tileop.tcgen05_gemm. - Python: register tl.GemmBlockScaled as GemmBlockScaled(Gemm) with the matching global functions; backend impls are unchanged. * [CUDA] Drop redundant comment on block-scaled instruction selection * [Op][CUDA] Separate block-scaled GEMM semantics and dispatch Move the block-scaled tile operator into dedicated common files and give CUDA instruction selection its own implementation. Reuse the GEMM backend registry through a typed block-scaled selector instead of the supports_blockscaled flag. Reject TMEM A and incompatible explicit instruction requests in the block-scaled selector. Add CUDA-gated dispatch regression coverage while retaining shared GEMM layout and scheduling behavior. * [Language] Expose block-scaled GEMM in the common dialect * [Test] Remove block-scaled GEMM example tests * [CUDA] Preserve GEMM semantics in warp-specialized schedules * Restore explicit TCGEN05 GEMM ops in warp specialization * Separate Python block-scaled GEMM tile-op registration * [Language] Share dense GEMM slot construction across GEMM frontends _gemm_impl and _gemm_blockscaled_impl each carried their own copy of the operand legalization, region normalization, rank/shape checks, static-dim requirement, mbar validation and 13-slot call construction. Factor that into _gemm_dense_slots(api_name, ...) so the block-scaled frontend only appends the SFA/SFB regions and k_start. Error messages keep their per-entry-point prefix (T.gemm / T.gemm_blockscaled), which examples/autodd relies on. * [CUDA] Make T.gemm_blockscaled synchronous like T.gemm T.gemm_blockscaled is the auto-dispatching tier of the block-scaled GEMM family, so it should follow T.gemm's completion contract: the result is complete when the call returns. On the SM100 TCGEN05 path the lowering now posts completion to `mbar` and inserts the matching mbarrier_wait_parity with the phase LowerTileOp derives from the enclosing loop, exactly as the dense _gemm_ss lowering does; the explicit T.tcgen05_gemm_blockscaled (is_tcgen05) still never waits. No C++ change is needed: InjectPipeline's IsMbarPhaseConsumer and the loop-phase plumbing already match GemmBlockScaledNode through GemmNode. The SM100 correctness kernel now follows each entry point's contract. The synchronous entry waits on a single per-iteration barrier and the MMA warp releases the smem stage itself afterwards, so a missing wait would race the producer's TMA against the in-flight MMA. A hardware-free lowering test pins the wait (with the loop phase threaded through) for T.gemm_blockscaled and its absence for T.tcgen05_gemm_blockscaled. * [Language][CUDA] Move explicit CUDA GEMM variants into the CUDA dialect wgmma_gemm, tcgen05_gemm, tcgen05_gemm_blockscaled, mma_gemm_blockscaled and make_blockscaled_gemm_layout pin a CUDA instruction family and have no target-neutral meaning, but were defined in the common tilelang/language/gemm_op.py and only re-exported from tilelang/cuda/language/intrinsics.py. Define them in tilelang/cuda/language/gemm_op.py next to the CUDA gemm / gemm_blockscaled shadows and import them from there. The common module now holds only gemm, gemm_blockscaled and the shared _impl helpers, and no longer reaches into tilelang.cuda.intrinsics for the TMEM layout helper. Function bodies are relocated verbatim; the dialect export surfaces are unchanged. * [Test] Drop redundant gemm_api parametrization from SM120 NVF4 tests T.mma_gemm_blockscaled and T.gemm_blockscaled build structurally identical tl.tileop.gemm_blockscaled calls, so parametrizing the NVF4 codegen and correctness tests over both entry points compiled the same IR twice on SM120 hardware. The dispatch contract for the unified entry on SM120 is already covered hardware-free by test_blockscaled_instruction_selection. Restore the file to main. * [Op] Require exactly 13 positional slots for tl.tileop.gemm tl.tileop.gemm is registered with variable arity and Gemm::Gemm only bounded the argument count from above, so a call with fewer than 13 slots reached InitFromDenseArgs and indexed past the end of the array. Check for exactly 13 slots up front and report the actual count; the block-scaled hint in the message is kept for the 16-slot case. Cover both the long and the short call in the parse test. * [Op] Lower block-scaled GEMM through the shared tl.gemm entry points GemmBlockScaledNode routed layout inference and lowering through its own tl.gemm_blockscaled.{infer_layout,lower} global functions, selected by virtual InferLayoutGlobalFunc/LowerGlobalFunc hooks on GemmNode. Those functions were copies of tl.gemm.{infer_layout,lower}: both only call infer_layout()/lower() on the Python object, and the object handed over the FFI boundary is already the GemmBlockScaled Python class, so the block-scaled behaviour comes from that class and from the instruction key the C++ selector returns. Drop the hooks and the duplicate global functions; a future GEMM flavour that needs different Python lowering overrides lower() on its class instead. * [CUDA] Split the TCGEN05 block-scaled implementation from the dense class The Python implementation layer still told dense and block-scaled GEMM apart by sniffing scale-factor fields: GemmBase carried SFARegion / SFBRegion / sf_k_start / is_blockscaled via getattr defaults, and GemmTCGEN5 branched on is_blockscaled at five points before diverting to an 81-line _lower_blockscaled. SM120 already had the right shape, with cuda.mma.blockscaled mapped by the registry to its own class. Give TCGEN05 the same shape. The C++ selector returns cuda.tcgen05.blockscaled, which the registry maps to GemmTCGEN5BlockScaled(GemmBlockScaledMixin, GemmTCGEN5). The dense GemmTCGEN5 exposes _warp_partition / _make_mma_emitter / _assign_layouts hooks and a tcgen05_allow_ws flag, and no longer knows about scale factors. GemmBlockScaledMixin (tilelang/tileop/gemm_blockscaled/gemm_blockscaled_base.py) owns the SFA/SFB regions, k_start and the sf_* annotation parsing that the TCGEN05 and SM120 lowerings each duplicated; which scale layouts a backend supports stays with that backend. GemmBase drops its block-scaled properties, matching the dense C++ node. Also drops an unused _FLOAT8_DTYPES constant from gemm_tcgen05.py. The instruction-selection test now also checks that each block-scaled key resolves to a GemmBlockScaledMixin implementation with the expected scale operands and granularities. * [Op] Register block-scaled GEMM backends separately from GemmImpl The block-scaled instruction selector was a slot on the dense GemmImpl struct, so the dense GEMM header forward-declared GemmBlockScaled and src/cuda/op/gemm.cc had to include the block-scaled selector just to fill that slot. That is the same dependency direction the rest of this change removed from GemmNode, GemmBase and GemmTCGEN5. Give block-scaled GEMM its own GemmBlockScaledImpl registry in src/op/gemm_blockscaled.{h,cc}. GemmBlockScaledNode::GetGemmInstructionKey resolves it and, when no backend is registered for the target, still names the dense backend that owns the target in the error. CUDA registers itself from src/cuda/op/gemm_blockscaled.cc with the same target predicate as its dense GemmImpl; the selector becomes internal and the cuda/op/gemm_blockscaled.h header goes away. src/op/gemm.h now has no block-scaled references. (cherry picked from commitd4787e9bb1) * [JIT] Add missing uint64 argument type mappings (#3229) (cherry picked from commite40e09e3e6) * [BugFix] Restore symbolic loop-layout injectivity proof; reject layouts on symbolic shared tiles (#3233) * [BugFix] Support symbolic shared layouts with mixed fixed and dynamic extents Related to tile-ai/tilelang#2906 and tile-ai/tilelang#2909. Credit Soham Panda for the original diagnosis and proposed defensive fix. Co-authored-by: Soham Panda <sohampanda1@gmail.com> * [Test] Minimize symbolic shared-layout regression coverage * [Layout] Document why both symbolic injectivity proofs are kept CanProveInjective and CanProveLeftInverse have disjoint blind spots: equality propagation proves (i, i + j) but cannot invert floordiv/floormod with a symbolic divisor, while the left-inverse decoder handles the padded partition ((i*n+j)%128, (i*n+j)//128) but has no candidate for (i, i + j). Record the counterexamples at the call site. * [BugFix] Reject layouts on symbolic shared tiles instead of remapping them makeBufferWithLayout dereferenced a null IntImm for a shared buffer with a symbolic extent (#2906). Rather than deriving a symbolic replication factor, keep the constant arithmetic and raise a ValueError naming the buffer and the offending extent. The check sits at the remap site so that layouts inferred by ops (scan) are covered as well as T.annotate_layout. The left-inverse injectivity proof stays: it fixes T.Parallel loops over a mixed static/symbolic space, which #2719 broke independently of shared layouts. Its regression test is now a pure global-to-global loop, and the #2906 test asserts the error for both the annotated and the inferred path. --------- Co-authored-by: Soham Panda <sohampanda1@gmail.com> Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commitefe9654bf4) * [ROCm] Run portable example validation in CI (#3165) * [ROCm] Run portable example validation in CI Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Make portable example checks fail reliably Signed-off-by: andyluo7 <andy.luo@amd.com> --------- Signed-off-by: andyluo7 <andy.luo@amd.com> (cherry picked from commit48cd23e181) * [BugFix][CUDA] Only emit 256-bit global load/store on SM100+ targets (#3248) (cherry picked from commit4bc6c32a63) * [Metal] Support 32-bit integer atomic add (#3211) (cherry picked from commit901f941c0d) * [Testing] Drop duplicated codegen-smoke tests; fix fastmath self-comparison assertions (#3252) (cherry picked from commit054bcf1de3) * [ROCm] Add GLM-5.3 k-pool Top-K transform (#3254) * [ROCm] Add GLM-5.3 k-pool cache writer Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Match GLM-5.3 k-pool geometry Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Add GLM-5.3 k-pool decode tail Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Bind GLM-5.3 tail boundary count Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Add GLM-5.3 paged k-pool logits Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Guard GLM-5.3 k-pool page bounds Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Add GLM-5.3 k-pool Top-K transform Signed-off-by: andyluo7 <andy.luo@amd.com> --------- Signed-off-by: andyluo7 <andy.luo@amd.com> (cherry picked from commiteab74a4ae5) * [Frontend] Support Python iterables and comprehensions (#3230) * [Frontend] Support Python iterables and comprehensions * [Frontend] Avoid excessive nesting in generated loop code * [Frontend] Reject TIR values and non-iterables in compile-time for loops The Python-iterable path in ctx_for consumed anything that was not a loop frame. Var carries an __iter__ shim for single-binding unpacking, so `for i in n` with a symbolic n expanded once with i aliased to n and renamed the shape variable; Buffer.__getitem__ never raises IndexError, so `for x in A` looped forever. Reject PrimExpr and Buffer up front and keep the original diagnostic for objects that are not iterable at all. * [Frontend] Validate Python iterable sources and comprehensions --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit6ba187e20d) * [Frontend] Remove warnings for immutable variable rebinding (#3262) Remove warnings for immutable variable rebinding (cherry picked from commit195b6ebbf3) * [BugFix] Reject non-power-of-two AllReduce thread strides (#3266) The XOR butterfly in tl::AllReduce pairs thread t with t ^ (scale * k), which only moves the reduce coordinate when `scale` (the thread stride between consecutive reduce participants) is a power of two. #2611 added the check for the logical width (threads / scale) but not for the stride, so reducing a (32, 3) fragment along dim 0 with its default inferred layout lowered to AllReduce<MaxOp, 96, 3> and silently mixed columns (and replicas); with exactly 96 or 192 threads the shared-memory exchange also read past the workspace. - CheckAllReduceWidth, shared by tl.reduce and tl.finalize_reducer, now requires the stride to be a power of two and points the user at padding the non-reduced fragment extent. - The CUDA AllReduce template carries the matching static_assert. - The reducer v2 narrow-plan gate rejects such strides alongside the existing width check, so the planner falls back to the wide plan instead of emitting the broken collective. (cherry picked from commit1b908fc0eb) * [NVRTC] Fix warp reduction compilation (#3260) Fixes https://github.com/tile-ai/tilelang/issues/3259 (cherry picked from commit261a9e4bb1) * [BugFix][CUDA] Lower a kernel-body assert to a device-legal check (#3206) * [BugFix][CUDA] Lower a kernel-body assert to a device-legal check A plain assert in a kernel body is parsed into a tirx.AssertStmt. The CUDA codegen did not override that visitor, so it inherited CodeGenC's, which streams a host-only TVMFFIErrorSetRaisedFromCStrParts call plus `return -1` into the emitted __global__ kernel. nvcc then rejects the file with "identifier TVMFFIErrorSetRaisedFromCStrParts is undefined", so a valid kernel refused to build. Override VisitStmt_(AssertStmtNode) in CodeGenTileLangCUDA and lower to the device helpers the T.device_assert intrinsic path already uses (device_assert / device_assert_with_msg). Every function this codegen emits is a __global__ kernel, so the override cannot misroute a host-side assert; host and CPU code have their own codegens with their own visitors. Fixes #3019 * [Test][CUDA] Gate kernel-body assert regressions on CUDA --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commitb7bfe82807) * [BugFix][JIT] Allocate a dynamic-shape output that precedes its sizing input (#3207) * [BugFix][JIT] Allocate a dynamic-shape output that precedes its sizing input A @tilelang.jit kernel whose output carries a symbolic dimension and appears before the input supplying that dimension compiled fine but crashed at call time with "IndexError: list index out of range" in the host wrapper. Two things had to line up. _process_dynamic_symbolic walked the parameters in signature order and recorded each symbolic dimension against the first parameter mentioning it, outputs included, so for main(B(N,), A(N,)) with out_idx=[0] the owner of N was B, the output being allocated. And the caller assembled its tensor list in a single pass, so an output at parameter 0 would have read tensor_list[1] before it was filled even with the owner pointing at an input. Visit inputs first when building the symbolic map, and place every input tensor before allocating the outputs, in both the tvm_ffi and cython adapters. Fixes #3017 * fix(jit/tvm_ffi): resolve scalar dimensions against the parameter-aligned list `_process_dynamic_symbolic` records parameter indices taken from the PrimFunc signature: a scalar variable parameter is stored as `(2, param_index, -1, 1)`. The output-allocation loop resolved that branch as `inputs[ref_tensor_idx]`, but `inputs` holds only the non-output parameters, so for a signature whose output precedes its inputs the index points at the wrong slot and for `[B_output, A_input, N_scalar]` it runs off the end of the list. Resolve the scalar branch against `tensor_list` as well, which is sized and indexed by parameter position, and report a clear error if the referenced scalar slot is still unset instead of raising IndexError. The non-scalar branch already gained the same treatment in the parent commit; this makes the two branches consistent. --------- Co-authored-by: xy200303 <xy200303@users.noreply.github.com> (cherry picked from commit6679f91387) * [CUDA] Fuse exact FP4 to FP8 conversion through FP32 (#3204) * [CUDA] Fuse exact FP4 to FP8 conversion through FP32 * [CUDA] Use PascalCase for the exact FP4 conversion helper * [CUDA] Route the exact FP4 to FP8 transcode through __tl_cvt intrinsics Separate the algebraic fold from the conversion intrinsic. The codegen now rewrites Cast(e4m3, Cast(f32, e2m1)) with default rounding into a plain Cast(e4m3, e2m1), and the direct pair is handled like every other cast in VisitExpr_(CastNode): a scalar ::bitcast helper and PrintVectorizedCast with a new chunk_lanes parameter so four lanes go through one __byte_perm pair. The byte-level plumbing moves into cuda_fp4.h as __tl_cvt_e2m1x4_to_e4m3x4 with x2 and scalar wrappers, matching the header's existing helpers. A direct T.Cast("float8_e4m3fn", fp4) now takes the same fast path instead of the elementwise float fallback, and the pair is registered in IsCudaVectorizableCast. Tests cover both the folded chain and the direct cast. SASS for sm_90a is unchanged in size with no stack frame. --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit8cf0e7eff4) * [Fix]Clamp T.copy/T.async_copy coalesced_width to achievable vector size instead of LOG(FATAL) (#3246) * [Fix]Clamp T.copy/T.async_copy coalesced_width to achievable vector size instead of LOG(FATAL) * add type cast * format code and modify warning message * [Fix] Clamp coalesced_width to the widest achievable width instead of gcd Treat the hint as an upper bound on the per-thread vector width: take the widest width the geometry supports without exceeding the request, stepping down to a divisor of the geometry-derived width so alignment and contiguity stay proven. gcd collapsed requests such as 257 or 3 to a scalar copy even though float4 was achievable. Pin the oversized test to the clamped width and cover the realistic coalesced_width=8 case alongside 257. --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit227515d27b) * [BugFix] Bind loop targets independently of mutable scalar variables (#3232) * [BugFix] Bind loop targets independently of mutable scalar variables * [BugFix] Only store into a Ref/alloc_var whose binding region is still open The store-vs-fresh decision in Builder.bind read the Python locals without checking whether the TIR region that bound the name had already closed, so a write to an expired alloc_var was silently emitted into the hoisted buffer while a read of the same name was rejected. Treat an expired binding as absent (the rule rval already applies to reads) and share the check between the two. The loop_target flag keeps its remaining role: a live alloc_var shadowed by a for target still becomes a fresh induction binding. Also stop resolving the rewriter's `_` temporary against locals, which made tuple loop targets alias an alloc_var named `_`, and skip the "re-bound" shadow warning for loop targets, which stepped loops triggered with wrong advice. Tests cover the expired-binding path structurally and at runtime, the live nested-region store, the `_` tuple target, and the absent warning. * [Frontend] Shorten the store-target comment in Builder.bind --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commite4e14156d7) * [BugFix][Language] Lower NaN-propagating clamp through device templates (#3205) * [BugFix][Language] Make T.clamp propagate NaN T.clamp composed min(max(x, lo), hi). On CUDA those lower to the fmaxf/fminf family, which returns the non-NaN operand, so clamp(NaN, lo, hi) silently returned lo instead of NaN, unlike torch.clamp and numpy.clip. Re-inject the input NaN through an if_then_else. tir.isnan is implemented for only a subset of the float dtypes, so the predicate is evaluated on an fp32 cast, which preserves NaN; dtypes that cannot hold a NaN skip the guard entirely. T.max and T.min keep their existing fmaxf/fminf behaviour. Fixes #3024 * [BugFix][Language] Lower clamp through device templates * [Test][ROCm] Use mcpu for the HIP clamp codegen target --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit73c8fa0579) * [Fix] Reject unsupported reduction NaN propagation dtypes (#3273) * [Fix] Reject unsupported reduction NaN propagation dtypes * [Refactor][Reduce] Name the min/max NaN guard condition --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commitea42ce56f4) * [BugFix] Fix parallel loop lowering with let inlining disabled (#3269) (cherry picked from commit7763c88000) * [Transform][CUDA] Fix async copy lowering with partitioned layouts (#3278) [Transform] Refresh let bindings during loop partitioning (cherry picked from commit134ade7d81) * [CUDA] Keep FP8 vector copies packed (#3276) * [CUDA][Codegen] Preserve packed FP8 vector copies * [CUDA] Keep packed FP8 copy change focused on codegen * [CUDA] Define packed copy operations on FP8 vector types (cherry picked from commit356f309621) * [CUDA] Support SM120 block-scaled GEMM fragments and odd warp atom grids (#3257) * [CUDA] Support SM120 block-scaled GEMM fragments and odd warp atom grids * [Test] Consolidate SM120 block-scaled GEMM regression coverage * [CUDA] Consolidate SM120 block-scaled MMA selection conditions --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit7a5f446fde) * [CUDA] Reject conflicting SM120 scale fragment layouts (#3284) (cherry picked from commit74369d9e34) * [JIT] Add missing uint64 argument type mappings (#3229) (cherry picked from commite40e09e3e6) * [BugFix] Restore symbolic loop-layout injectivity proof; reject layouts on symbolic shared tiles (#3233) * [BugFix] Support symbolic shared layouts with mixed fixed and dynamic extents Related to tile-ai/tilelang#2906 and tile-ai/tilelang#2909. Credit Soham Panda for the original diagnosis and proposed defensive fix. Co-authored-by: Soham Panda <sohampanda1@gmail.com> * [Test] Minimize symbolic shared-layout regression coverage * [Layout] Document why both symbolic injectivity proofs are kept CanProveInjective and CanProveLeftInverse have disjoint blind spots: equality propagation proves (i, i + j) but cannot invert floordiv/floormod with a symbolic divisor, while the left-inverse decoder handles the padded partition ((i*n+j)%128, (i*n+j)//128) but has no candidate for (i, i + j). Record the counterexamples at the call site. * [BugFix] Reject layouts on symbolic shared tiles instead of remapping them makeBufferWithLayout dereferenced a null IntImm for a shared buffer with a symbolic extent (#2906). Rather than deriving a symbolic replication factor, keep the constant arithmetic and raise a ValueError naming the buffer and the offending extent. The check sits at the remap site so that layouts inferred by ops (scan) are covered as well as T.annotate_layout. The left-inverse injectivity proof stays: it fixes T.Parallel loops over a mixed static/symbolic space, which #2719 broke independently of shared layouts. Its regression test is now a pure global-to-global loop, and the #2906 test asserts the error for both the annotated and the inferred path. --------- Co-authored-by: Soham Panda <sohampanda1@gmail.com> Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commitefe9654bf4) * [ROCm] Run portable example validation in CI (#3165) * [ROCm] Run portable example validation in CI Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Make portable example checks fail reliably Signed-off-by: andyluo7 <andy.luo@amd.com> --------- Signed-off-by: andyluo7 <andy.luo@amd.com> (cherry picked from commit48cd23e181) * [BugFix][CUDA] Only emit 256-bit global load/store on SM100+ targets (#3248) (cherry picked from commit4bc6c32a63) * [Metal] Support 32-bit integer atomic add (#3211) (cherry picked from commit901f941c0d) * [Testing] Drop duplicated codegen-smoke tests; fix fastmath self-comparison assertions (#3252) (cherry picked from commit054bcf1de3) * [ROCm] Add GLM-5.3 k-pool Top-K transform (#3254) * [ROCm] Add GLM-5.3 k-pool cache writer Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Match GLM-5.3 k-pool geometry Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Add GLM-5.3 k-pool decode tail Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Bind GLM-5.3 tail boundary count Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Add GLM-5.3 paged k-pool logits Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Guard GLM-5.3 k-pool page bounds Signed-off-by: andyluo7 <andy.luo@amd.com> * [ROCm] Add GLM-5.3 k-pool Top-K transform Signed-off-by: andyluo7 <andy.luo@amd.com> --------- Signed-off-by: andyluo7 <andy.luo@amd.com> (cherry picked from commiteab74a4ae5) * [Frontend] Support Python iterables and comprehensions (#3230) * [Frontend] Support Python iterables and comprehensions * [Frontend] Avoid excessive nesting in generated loop code * [Frontend] Reject TIR values and non-iterables in compile-time for loops The Python-iterable path in ctx_for consumed anything that was not a loop frame. Var carries an __iter__ shim for single-binding unpacking, so `for i in n` with a symbolic n expanded once with i aliased to n and renamed the shape variable; Buffer.__getitem__ never raises IndexError, so `for x in A` looped forever. Reject PrimExpr and Buffer up front and keep the original diagnostic for objects that are not iterable at all. * [Frontend] Validate Python iterable sources and comprehensions --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit6ba187e20d) * [Frontend] Remove warnings for immutable variable rebinding (#3262) Remove warnings for immutable variable rebinding (cherry picked from commit195b6ebbf3) * [BugFix] Reject non-power-of-two AllReduce thread strides (#3266) The XOR butterfly in tl::AllReduce pairs thread t with t ^ (scale * k), which only moves the reduce coordinate when `scale` (the thread stride between consecutive reduce participants) is a power of two. #2611 added the check for the logical width (threads / scale) but not for the stride, so reducing a (32, 3) fragment along dim 0 with its default inferred layout lowered to AllReduce<MaxOp, 96, 3> and silently mixed columns (and replicas); with exactly 96 or 192 threads the shared-memory exchange also read past the workspace. - CheckAllReduceWidth, shared by tl.reduce and tl.finalize_reducer, now requires the stride to be a power of two and points the user at padding the non-reduced fragment extent. - The CUDA AllReduce template carries the matching static_assert. - The reducer v2 narrow-plan gate rejects such strides alongside the existing width check, so the planner falls back to the wide plan instead of emitting the broken collective. (cherry picked from commit1b908fc0eb) * [NVRTC] Fix warp reduction compilation (#3260) Fixes https://github.com/tile-ai/tilelang/issues/3259 (cherry picked from commit261a9e4bb1) * [BugFix][CUDA] Lower a kernel-body assert to a device-legal check (#3206) * [BugFix][CUDA] Lower a kernel-body assert to a device-legal check A plain assert in a kernel body is parsed into a tirx.AssertStmt. The CUDA codegen did not override that visitor, so it inherited CodeGenC's, which streams a host-only TVMFFIErrorSetRaisedFromCStrParts call plus `return -1` into the emitted __global__ kernel. nvcc then rejects the file with "identifier TVMFFIErrorSetRaisedFromCStrParts is undefined", so a valid kernel refused to build. Override VisitStmt_(AssertStmtNode) in CodeGenTileLangCUDA and lower to the device helpers the T.device_assert intrinsic path already uses (device_assert / device_assert_with_msg). Every function this codegen emits is a __global__ kernel, so the override cannot misroute a host-side assert; host and CPU code have their own codegens with their own visitors. Fixes #3019 * [Test][CUDA] Gate kernel-body assert regressions on CUDA --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commitb7bfe82807) * [BugFix][JIT] Allocate a dynamic-shape output that precedes its sizing input (#3207) * [BugFix][JIT] Allocate a dynamic-shape output that precedes its sizing input A @tilelang.jit kernel whose output carries a symbolic dimension and appears before the input supplying that dimension compiled fine but crashed at call time with "IndexError: list index out of range" in the host wrapper. Two things had to line up. _process_dynamic_symbolic walked the parameters in signature order and recorded each symbolic dimension against the first parameter mentioning it, outputs included, so for main(B(N,), A(N,)) with out_idx=[0] the owner of N was B, the output being allocated. And the caller assembled its tensor list in a single pass, so an output at parameter 0 would have read tensor_list[1] before it was filled even with the owner pointing at an input. Visit inputs first when building the symbolic map, and place every input tensor before allocating the outputs, in both the tvm_ffi and cython adapters. Fixes #3017 * fix(jit/tvm_ffi): resolve scalar dimensions against the parameter-aligned list `_process_dynamic_symbolic` records parameter indices taken from the PrimFunc signature: a scalar variable parameter is stored as `(2, param_index, -1, 1)`. The output-allocation loop resolved that branch as `inputs[ref_tensor_idx]`, but `inputs` holds only the non-output parameters, so for a signature whose output precedes its inputs the index points at the wrong slot and for `[B_output, A_input, N_scalar]` it runs off the end of the list. Resolve the scalar branch against `tensor_list` as well, which is sized and indexed by parameter position, and report a clear error if the referenced scalar slot is still unset instead of raising IndexError. The non-scalar branch already gained the same treatment in the parent commit; this makes the two branches consistent. --------- Co-authored-by: xy200303 <xy200303@users.noreply.github.com> (cherry picked from commit6679f91387) * [CUDA] Fuse exact FP4 to FP8 conversion through FP32 (#3204) * [CUDA] Fuse exact FP4 to FP8 conversion through FP32 * [CUDA] Use PascalCase for the exact FP4 conversion helper * [CUDA] Route the exact FP4 to FP8 transcode through __tl_cvt intrinsics Separate the algebraic fold from the conversion intrinsic. The codegen now rewrites Cast(e4m3, Cast(f32, e2m1)) with default rounding into a plain Cast(e4m3, e2m1), and the direct pair is handled like every other cast in VisitExpr_(CastNode): a scalar ::bitcast helper and PrintVectorizedCast with a new chunk_lanes parameter so four lanes go through one __byte_perm pair. The byte-level plumbing moves into cuda_fp4.h as __tl_cvt_e2m1x4_to_e4m3x4 with x2 and scalar wrappers, matching the header's existing helpers. A direct T.Cast("float8_e4m3fn", fp4) now takes the same fast path instead of the elementwise float fallback, and the pair is registered in IsCudaVectorizableCast. Tests cover both the folded chain and the direct cast. SASS for sm_90a is unchanged in size with no stack frame. --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit8cf0e7eff4) * [Fix]Clamp T.copy/T.async_copy coalesced_width to achievable vector size instead of LOG(FATAL) (#3246) * [Fix]Clamp T.copy/T.async_copy coalesced_width to achievable vector size instead of LOG(FATAL) * add type cast * format code and modify warning message * [Fix] Clamp coalesced_width to the widest achievable width instead of gcd Treat the hint as an upper bound on the per-thread vector width: take the widest width the geometry supports without exceeding the request, stepping down to a divisor of the geometry-derived width so alignment and contiguity stay proven. gcd collapsed requests such as 257 or 3 to a scalar copy even though float4 was achievable. Pin the oversized test to the clamped width and cover the realistic coalesced_width=8 case alongside 257. --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit227515d27b) * [BugFix] Bind loop targets independently of mutable scalar variables (#3232) * [BugFix] Bind loop targets independently of mutable scalar variables * [BugFix] Only store into a Ref/alloc_var whose binding region is still open The store-vs-fresh decision in Builder.bind read the Python locals without checking whether the TIR region that bound the name had already closed, so a write to an expired alloc_var was silently emitted into the hoisted buffer while a read of the same name was rejected. Treat an expired binding as absent (the rule rval already applies to reads) and share the check between the two. The loop_target flag keeps its remaining role: a live alloc_var shadowed by a for target still becomes a fresh induction binding. Also stop resolving the rewriter's `_` temporary against locals, which made tuple loop targets alias an alloc_var named `_`, and skip the "re-bound" shadow warning for loop targets, which stepped loops triggered with wrong advice. Tests cover the expired-binding path structurally and at runtime, the live nested-region store, the `_` tuple target, and the absent warning. * [Frontend] Shorten the store-target comment in Builder.bind --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commite4e14156d7) * [BugFix][Language] Lower NaN-propagating clamp through device templates (#3205) * [BugFix][Language] Make T.clamp propagate NaN T.clamp composed min(max(x, lo), hi). On CUDA those lower to the fmaxf/fminf family, which returns the non-NaN operand, so clamp(NaN, lo, hi) silently returned lo instead of NaN, unlike torch.clamp and numpy.clip. Re-inject the input NaN through an if_then_else. tir.isnan is implemented for only a subset of the float dtypes, so the predicate is evaluated on an fp32 cast, which preserves NaN; dtypes that cannot hold a NaN skip the guard entirely. T.max and T.min keep their existing fmaxf/fminf behaviour. Fixes #3024 * [BugFix][Language] Lower clamp through device templates * [Test][ROCm] Use mcpu for the HIP clamp codegen target --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit73c8fa0579) * [Fix] Reject unsupported reduction NaN propagation dtypes (#3273) * [Fix] Reject unsupported reduction NaN propagation dtypes * [Refactor][Reduce] Name the min/max NaN guard condition --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commitea42ce56f4) * [BugFix] Fix parallel loop lowering with let inlining disabled (#3269) (cherry picked from commit7763c88000) * [Transform][CUDA] Fix async copy lowering with partitioned layouts (#3278) [Transform] Refresh let bindings during loop partitioning (cherry picked from commit134ade7d81) * [CUDA] Keep FP8 vector copies packed (#3276) * [CUDA][Codegen] Preserve packed FP8 vector copies * [CUDA] Keep packed FP8 copy change focused on codegen * [CUDA] Define packed copy operations on FP8 vector types (cherry picked from commit356f309621) * [CUDA] Support SM120 block-scaled GEMM fragments and odd warp atom grids (#3257) * [CUDA] Support SM120 block-scaled GEMM fragments and odd warp atom grids * [Test] Consolidate SM120 block-scaled GEMM regression coverage * [CUDA] Consolidate SM120 block-scaled MMA selection conditions --------- Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> (cherry picked from commit7a5f446fde) * [CUDA] Reject conflicting SM120 scale fragment layouts (#3284) (cherry picked from commit74369d9e34) * [Ascend] Adapt upstream clamp lowering and regression coverage --------- Signed-off-by: andyluo7 <andy.luo@amd.com> Co-authored-by: Anders <anders@magnitude.dev> Co-authored-by: Sepcnt <30561671+sepcnt@users.noreply.github.com> Co-authored-by: Карим <kareemm@yandex-team.ru> Co-authored-by: xuebozhang525-alt <xuebozhang525@gmail.com> Co-authored-by: penguin_wwy <940375606@qq.com> Co-authored-by: Alfred <166222074+Dino1844@users.noreply.github.com> Co-authored-by: Soham Panda <sohampanda1@gmail.com> Co-authored-by: andyluo7 <43718156+andyluo7@users.noreply.github.com> Co-authored-by: Xiangwen Wang <77378439+LJC00118@users.noreply.github.com> Co-authored-by: 小云 <130276203+xy200303@users.noreply.github.com> Co-authored-by: xy200303 <xy200303@users.noreply.github.com> Co-authored-by: Ziming Wang <125807850+ZenAlexa@users.noreply.github.com> Co-authored-by: edragain <3425282590@qq.com> * [Ascend][Docs] Extend npusim cycle measurements beyond VFs (#526) * [Ascend][Docs] Extend npusim cycle measurements beyond VFs * [Ascend][Docs] Document task cost overrides and SF copy cycles * [Ascend][Docs] Keep cycle guidance focused on kernel authoring * [Fix][TVM] Update TVM to preserve dynamic reduction tail guards (#3294) (#538) [Fix][TVM] Update modular remainder bounds and preserve tail guards * [Ascend] Support post-update SIMD loads (#537) * [Ascend] Support post-update SIMD loads * [Ascend] Infer SIMD pointer types and validate post-update loads * [Ascend] Trim redundant post-update checks and tests * [Ascend] Rename SIMD load increment to post_inc * [Ascend] Calibrate blockscaled GEMM and scale-load costs (#528) * [Ascend] Calibrate blockscaled GEMM latency and II * [Ascend][Tests] Focus blockscaled GEMM cost coverage * [Ascend] Keep latency parameter comments focused on semantics * [Ascend] Model MX scale-factor copy latency and II * [Ascend] Add DeepGEMM-compatible GEMM kernels (#291) * [Ascend] Add DeepGEMM frontend kernels Implement dense, batched, M-grouped, and K-grouped BF16/FP8 APIs with MNK and KMN kernels, tail handling, and explicit L0 scale-factor buffers. * [Ascend] Add DeepGEMM tests and benchmarks Add focused numerical coverage and benchmark entry points, replace the various-shapes example, and update the Ascend performance regression runner. * [Ascend] Limit DeepGEMM launches to active output tiles * [Ascend] Reuse MNK L0 operands across output tiles Reuse only when the K iteration count equals the L0 stage count. Preserve read-only K owners and the unrolled producer/consumer order. * [Ascend] Refactor DeepGEMM APIs and compile heuristics with Cython Move host configuration selection into a Cython extension built and installed with TileLang, preserving the upstream cost model and candidate ordering. Keep hashable configuration metadata in Python and reuse descriptor validation across GEMM families. Bind layout-specific APIs through factories while preserving public signatures and defaults. Validate layouts before copying C and reject malformed scale recipes and unsupported output descriptors explicitly. Validation: - 2002 heuristic comparisons against Python and upstream C++ with the existing TileLang launch cap normalized - 108 API metadata, scale, and launch-argument differential cases - Public signature, default, name, and serialization compatibility checks - 4 existing DeepGEMM NPU tests - Scoped repository format and staged whitespace checks * [Ascend] Remove DeepGEMM heuristic cache and refresh comments Call the Cython selector directly for each configuration request. Describe current layout and launch contracts, use DeepGEMM-Ascend naming, and clarify that configuration cache hints refer to hardware L2 behavior. Validation: staged API import and uncached-call check, scoped repository pre-commit checks, and staged diff checks. * [Ascend] Integrate SIMD scale conversion into DeepGEMM * [Ascend] Refactor DeepGEMM scale transform configuration Select scale tiles, launch size, and load/store cache policies in a dedicated host config. Keep the group count dynamic and bound grouped metadata storage by tile geometry. * [Ascend] Simplify DeepGEMM scale-transform scheduling * [Build] Enable ROCm and Ascend by default on Linux (#544) * [Ascend] Curate examples and consolidate feature tests (#545) * [Ascend] Curate examples and consolidate feature tests * [Ascend] Report example throughput and remove Top-K gate Enable example performance reporting by default, with TFLOPS for matrix kernels and logical I/O bandwidth for vector kernels. Add measured FP8 VF latencies and remove the Top-K gate example, test, and references. * [Ascend] Batch high-level SIMT RMSNorm row tiles Amortize VF launches and small RSTD transfers by processing adjacent rows together. Keep the computation in Parallel loops, fragments, and reducers so thread mapping and collective synchronization remain compiler-generated. Use compact UB tiles with two input versions, reuse the input for output when the UB budget requires it, and retain weights in registers across rows. Cache RSTD tiles for a strided writeback, with a bounded-cache fallback for large batches. Preserve the latest benchmark output and command-line behavior. Validation on Ascend950 with the existing local TileLang runtime: - The existing RMSNorm tests and additional shape, zero-row, tail, cache-limit, and repeated-launch checks passed: 18 cases, with unchanged tolerances. - Scoped pre-commit checks and git diff --check passed. - FP32 batch=4096 measured 50.5/61.0/85.1 us for d=4096/5120/7168 with msprof, 30 warmups and 50 measured iterations per case (one full profiling round). Only the RMSNorm kernel example is changed; tests and benchmark artifacts are not included in this commit. * [Ascend] Optimize SIMT per-token FP8 quantization Process adjacent 128-element groups in contiguous DMA tiles and write scales through UB instead of scattered scalar stores. Select 32, 64, or 128 groups per tile while keeping up to 64 cores busy, and explicitly double-buffer the input, quantized output, and scale buffers. Use a float2 fragment layout for contiguous UB accesses, explicit abs plus max reduction, and the saturating FP8 cast without a redundant clamp. Enable TileLang fast math and remove the VF latency measured for the old layout. The SIMD implementation and example harness remain unchanged. Validation: all four existing FP8 example tests and changed-file formatting hooks pass on Ascend 950 with the existing local TileLang build. Three interleaved msprof rounds on 8192x8192 FP32 input show median kernel latency falling from 308.32 us to 111.05 us (2.78x), with TileLang fast math enabled for both the original implementation and the optimized kernel. * [Ascend] Improve high-level SIMT RMSNorm layout Use a unit coalescing width for the column-parallel input, reduction, weight, and output loops. Keep the reducer and thread mapping expressed through high-level TileLang operations. Copy the row reciprocal standard deviations from the result fragment to UB at the end of the VF. This lets code generation select the writing thread and removes redundant stores and a synchronization point. Validation: 18 RMSNorm correctness cases passed, including narrow and odd dimensions, the bounded RSTD cache fallback, and repeated launches. Scoped pre-commit checks passed using the existing local TileLang build. On Ascend950DT_9581, three-round msprof medians for batch 4096 and widths 4096/5120/7168 were 45.67/53.60/76.08 us, versus 48.75/58.11/82.88 us for the fastest explicit thread-level implementation in the same run. This reduces latency by 6.3%/7.8%/8.2%; batch 128 remains within 0.10 us of it. * [Ascend] Translate FlashAttention README to English * Update README.md --------- Co-authored-by: Denver Jin <denverjin@outlook.com> Co-authored-by: silentCoder-dev <silentcoder@foxmail.com> * fix(ascend): correct swapped ROUND_F/ROUND_C rounding descriptions (#547) The ``round`` parameter documentation of ``T.simd.vcvt`` described ``"ROUND_F"`` as rounding toward +inf (ceiling) and ``"ROUND_C"`` as rounding toward -inf (floor). The two descriptions were swapped. CANN's vconv wrappers pair ``ROUND_F`` with ``CastFloor`` and ``ROUND_C`` with ``CastCeil``, i.e. ROUND_F rounds toward -inf (floor) and ROUND_C rounds toward +inf (ceiling). Fix the docstring to match the hardware modes. Docstring-only change; no functional impact. * [CI] Restore upstream multi-backend checks Restore the upstream lint, CUDA, ROCm, Metal and CuTeDSL jobs in place of the fork-specific Ascend workflow. Defer dedicated Ascend CI integration. Exclude examples/ascend from the CUDA and CuTeDSL example sweeps so those jobs do not collect tests that require an NPU. * [CI] Add Ascend tests on the self-hosted runner * [CI] Restore the upstream distribution workflow * [CI] Restore the upstream PR reminder workflow * [Fix] Keep relaxed copy shape checks in the Ascend dialect * [Fix] Preserve shape and stride source kinds in Cython arguments * [Maint] Restore upstream tooling and defer the Ascend performance bot * [Fix] Avoid narrowing conversion in vector size constraint * [Docs][Ascend] Update backend documentation and benchmarks * [Docs][Ascend] Refine backend acknowledgements * [Fix] Restore broadcast vectorization and scalar reductions --------- Signed-off-by: andyluo7 <andy.luo@amd.com> Co-authored-by: LeiWang1999 <leiwang1999@outlook.com> Co-authored-by: matt-tian <81284512+matt-tian@users.noreply.github.com> Co-authored-by: xuruifan <xuruifan@deepseek.com> Co-authored-by: silentCoder-dev <silentcoder@foxmail.com> Co-authored-by: Denver Jin <denverjin@deepseek.com> Co-authored-by: Yongqi Zhuo <Yongqi-Zhuo@users.noreply.github.com> Co-authored-by: Meng Yuhao <42602106+AutumnKite@users.noreply.github.com> Co-authored-by: Denver Jin <45557821+Denverjin@users.noreply.github.com> Co-authored-by: mengyuhao <mengyuhao@deepseek.com> Co-authored-by: Lei Wang <34334180+LeiWang1999@users.noreply.github.com> Co-authored-by: Denver Jin <denverjin@outlook.com> Co-authored-by: cj <erhsh_165@126.com> Co-authored-by: caojian5 <caojian5@huawei.com> Co-authored-by: Anders <anders@magnitude.dev> Co-authored-by: Sepcnt <30561671+sepcnt@users.noreply.github.com> Co-authored-by: Карим <kareemm@yandex-team.ru> Co-authored-by: xuebozhang525-alt <xuebozhang525@gmail.com> Co-authored-by: penguin_wwy <940375606@qq.com> Co-authored-by: Alfred <166222074+Dino1844@users.noreply.github.com> Co-authored-by: Soham Panda <sohampanda1@gmail.com> Co-authored-by: andyluo7 <43718156+andyluo7@users.noreply.github.com> Co-authored-by: Xiangwen Wang <77378439+LJC00118@users.noreply.github.com> Co-authored-by: 小云 <130276203+xy200303@users.noreply.github.com> Co-authored-by: xy200303 <xy200303@users.noreply.github.com> Co-authored-by: Ziming Wang <125807850+ZenAlexa@users.noreply.github.com> Co-authored-by: edragain <3425282590@qq.com>
39 lines
1.7 KiB
CMake
39 lines
1.7 KiB
CMake
# Resolve each backend independently. Linux builds include ROCm and Ascend by
|
|
# default: vendored/local headers and runtime stubs avoid a build-time dependency
|
|
# on either SDK. CUDA still needs a toolkit, while macOS defaults to Metal.
|
|
set(TILELANG_BACKENDS CUDA ROCM METAL LLVM ASCEND)
|
|
|
|
set(TILELANG_BACKEND_DOC_CUDA "Enable CUDA backend (ON/OFF/or CUDA SDK path)")
|
|
set(TILELANG_BACKEND_DOC_ROCM "Enable ROCm backend (ON/OFF/or ROCm SDK path)")
|
|
set(TILELANG_BACKEND_DOC_METAL "Enable Metal backend")
|
|
set(TILELANG_BACKEND_DOC_LLVM "Enable LLVM backend")
|
|
set(TILELANG_BACKEND_DOC_ASCEND "Enable Ascend backend")
|
|
|
|
foreach(BACKEND IN LISTS TILELANG_BACKENDS)
|
|
set(_backend_var "USE_${BACKEND}")
|
|
set(_doc "${TILELANG_BACKEND_DOC_${BACKEND}}")
|
|
set(_default OFF)
|
|
if(BACKEND STREQUAL "CUDA" AND NOT APPLE AND TILELANG_CUDA_TOOLKIT_AVAILABLE)
|
|
set(_default ON)
|
|
elseif(BACKEND STREQUAL "METAL" AND APPLE)
|
|
set(_default ON)
|
|
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND
|
|
(BACKEND STREQUAL "ROCM" OR BACKEND STREQUAL "ASCEND"))
|
|
set(_default ON)
|
|
endif()
|
|
|
|
# CMake variables (including cached values and -D arguments) take precedence
|
|
# over environment variables. Environment variables initialize a fresh cache.
|
|
if(DEFINED ${_backend_var})
|
|
set(_default "${${_backend_var}}")
|
|
elseif(DEFINED ENV{${_backend_var}})
|
|
set(_default "$ENV{${_backend_var}}")
|
|
endif()
|
|
# STRING preserves SDK paths as well as ON/OFF values.
|
|
set(${_backend_var} "${_default}" CACHE STRING "${_doc}")
|
|
|
|
# TVM's config.cmake redefines USE_* options later. Save the resolved value
|
|
# so the caller can restore it after including that configuration.
|
|
set(TILELANG_OPTION_${_backend_var} "${${_backend_var}}")
|
|
endforeach()
|