Skip to content

[Refactor][BugFix] Refactor the loop vectorization plan with ConstraintKind - #2935

Merged
SiriusNEO merged 3 commits into
mainfrom
vectorize-hard-constraints
Aug 11, 2026
Merged

[Refactor][BugFix] Refactor the loop vectorization plan with ConstraintKind#2935
SiriusNEO merged 3 commits into
mainfrom
vectorize-hard-constraints

Conversation

@SiriusNEO

@SiriusNEO SiriusNEO commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • classify vectorization constraints explicitly as must-scalarize, call, cast, local, memory, or broadcast-load
  • keep non-local invariant loads as scalar broadcasts without reusing the local/fragment category
  • force ordinary loop-invariant stores to scalarize across global, shared, local, and fragment scopes while preserving the all-rep reducer exception
  • add deterministic global and local accumulation regressions

Motivation

ComputeBufferVectorSize already identifies ordinary invariant stores as requiring vector_size=1, but the old scope-based bucketing could discard that semantic requirement. The narrow fix in #2922 keeps global/shared stores in the memory bucket, while local/fragment stores can still lose the same constraint.

This change separates classification, aggregation, and strategy selection. Plan() now orchestrates those phases, broadcast loads have their own category, and a must-scalarize constraint returns width 1 before any memory/local strategy is selected.

Related: #2922

ConstraintKind

ConstraintKind Classification condition Typical example Effect on vector size
kMustScalarize requires_scalarization=true; an ordinary invariant/independent store, excluding all-rep reducers acc[0] += A[i], B[row] += A[row, i] Takes precedence over every strategy and selects vector_size=1
kCall Not associated with a concrete buffer and not produced by a cast A regular CallNode or condition expression Contributes to both call_min and non_cast_call_min
kCast Not associated with a concrete buffer and produced by a CastNode A conversion from float32 to float16 Contributes to call_min; may be deferred by DecoupleTypeCast in the simple memory strategy
kLocal Buffer scope is local, local.var, or fragment local[i] = value, fragment load/store Contributes to local_min; deferred and revalidated under the simple memory strategy
kMemory A non-local buffer access that is neither a broadcast load nor a must-scalarize store B[row, i] = A[row, i], regular global/shared load/store Contributes to memory_min and enables the memory vectorization strategy
kBroadcastLoad A non-local load whose address is invariant within the candidate vector boundary Reading C[row] inside a vectorized loop Treated as scalar load + broadcast; excluded from memory_min and revalidated if necessary

Testing

  • bash format.sh --files src/transform/loop_vectorize.cc testing/python/language/test_tilelang_language_vectorize.py
  • cmake --build build -j 32
  • python -m pytest -q testing/python/language/test_tilelang_language_vectorize.py — 36 passed
  • python -m pytest -q testing/python/transform/test_tilelang_transform_legalize_vectorized_loop.py::test_vectorize_access — passed

Summary

  • Refactored loop-vectorization constraint handling into explicit constraint kinds and summaries.
  • Preserved scalarization for loop-invariant stores across global, shared, local, and fragment buffers.
  • Kept non-local invariant loads as scalar broadcasts.
  • Preserved the all-rep reducer exception.
  • Updated Plan() to classify constraints, aggregate them, select a strategy, revalidate deferred accesses, and enforce loop-extent divisibility.
  • Added global and local invariant-store accumulation regression tests.

Validation

  • Formatting passed.
  • Build passed.
  • 36 language vectorization tests passed.
  • Vectorized-loop access test passed.

C++ style / lint notes

  • The PR changes C++ code in src/transform/loop_vectorize.cc.
  • No public or exported declarations changed.
  • The PR does not change documented rules in docs/developer_guide/cpp_style.md.
  • The C++ API Style Audit (warning only) is relevant to C++ changes. Any TLCPP003/TLCPP004 findings are advisory and do not block merging unless they indicate an API, FFI, or maintainability risk.

Classify vectorization constraints explicitly so broadcast loads, memory accesses, local accesses, and semantic scalarization requirements cannot be conflated by scope-based bucketing.
@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the TileLang project.

Please remember to run pre-commit run --all-files in the root directory of the project to ensure your changes are properly linted and formatted. This will help ensure your contribution passes the format check.

We appreciate you taking this step! Our team will review your contribution, and we look forward to your awesome work! 🚀

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 79be3245-9e6e-4b87-802b-667af399d2a5

📥 Commits

Reviewing files that changed from the base of the PR and between 28ea4ca and 842429c.

📒 Files selected for processing (1)
  • src/transform/loop_vectorize.cc
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/transform/loop_vectorize.cc

📝 Walkthrough

Walkthrough

Vectorization planning now uses explicit constraint metadata, deferred-access validation, and scalarization requirements. Buffer constraints propagate into planning state. New CUDA tests cover invariant-store accumulation through global and local buffers.

Changes

Vectorization constraints

Layer / File(s) Summary
Constraint classification and vector-size selection
src/transform/loop_vectorize.cc
The planner classifies vector constraints, aggregates summaries, revalidates deferred accesses, and selects vector sizes by strategy.
Buffer constraints and scalarization propagation
src/transform/loop_vectorize.cc
Buffer-size computation reports scalarization requirements. Invariant and independent stores require scalarization except for all-rep reducer buffers.
Invariant-store vectorization tests
testing/python/language/test_tilelang_language_vectorize.py
CUDA tests cover accumulation through global memory and local scalar buffers. Each output is checked against the reduction width.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: leiwang1999

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: refactoring loop vectorization planning to use ConstraintKind.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vectorize-hard-constraints

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@SiriusNEO SiriusNEO changed the title [BugFix] Preserve invariant store scalarization across buffer scopes [Refactor][BugFix] Refactor the loop vectorization plan with ConstraintKind Aug 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
testing/python/language/test_tilelang_language_vectorize.py (1)

150-158: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a generated-source assertion to pin the planning decision.

The numeric check catches a regression that changes the result. It does not distinguish a scalarized plan from a vectorized plan that happens to produce the same value. Other tests in this file assert on jit_kernel.get_kernel_source(). Add the same check so the test fails on the planning decision, not only on the numeric result.

♻️ Proposed addition
 def run_vectorize_invariant_store_accumulate(kernel_factory):
     M, K = 128, 4
     kernel = kernel_factory(M, K)
     a = torch.ones((M, K), device="cuda", dtype=torch.float32)
     b = torch.empty((M,), device="cuda", dtype=torch.float32)

     kernel(a, b)

     torch.testing.assert_close(b, torch.full_like(b, float(K)), rtol=0, atol=0)
+
+    code = kernel.get_kernel_source()
+    assert "float4" not in code and "float2" not in code, code
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/python/language/test_tilelang_language_vectorize.py` around lines 150
- 158, Update run_vectorize_invariant_store_accumulate to inspect the generated
kernel source through the established jit_kernel.get_kernel_source() pattern
used elsewhere in the file, and assert that it contains the expected vectorized
planning construct. Keep the existing numeric assertion and obtain the source
from the created kernel using the test’s existing kernel-access convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/transform/loop_vectorize.cc`:
- Around line 284-295: The offset logic in IsBroadcastLoad and
RevalidateDeferredAccesses must use transformed indices consistently with
ComputeBufferVectorSize. In src/transform/loop_vectorize.cc#L284-L295 and
src/transform/loop_vectorize.cc#L378-L403, extract a shared helper that applies
TransformIndices, obtains GetBufferStrides, validates the
transformed-index/stride size relationship, and computes the offset; replace
both inline computations with that helper.
- Around line 925-931: The invariant-store branch in SelectVectorSize currently
forces the entire loop to scalarize by returning `{1, true}`. Return the reduced
invariant try_vec_size for non-all-rep reducer buffers, and reserve
requires_scalarization for cases that genuinely require whole-loop scalar
execution; otherwise apply scalarization only to that store operation.

---

Nitpick comments:
In `@testing/python/language/test_tilelang_language_vectorize.py`:
- Around line 150-158: Update run_vectorize_invariant_store_accumulate to
inspect the generated kernel source through the established
jit_kernel.get_kernel_source() pattern used elsewhere in the file, and assert
that it contains the expected vectorized planning construct. Keep the existing
numeric assertion and obtain the source from the created kernel using the test’s
existing kernel-access convention.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 706f315b-ba75-45a5-b7fa-7200dc0c5e2d

📥 Commits

Reviewing files that changed from the base of the PR and between 7cabeed and 8f6d9d1.

📒 Files selected for processing (2)
  • src/transform/loop_vectorize.cc
  • testing/python/language/test_tilelang_language_vectorize.py

Comment on lines +284 to +295
bool IsBroadcastLoad(const BufferVectorInfo &info) const {
if (info.is_store || info.indices.empty() || !inner_for_) {
return false;
}
Array<PrimExpr> strides = GetBufferStrides(info.buffer);
PrimExpr elem_offset = 0;
for (size_t i = 0; i < info.indices.size(); ++i) {
elem_offset += info.indices[i] * strides[i];
}
return IsExprInvariantInVectorBoundary(elem_offset, inner_for_->loop_var,
initial_vector_size_, analyzer_);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Two offset computations skip TransformIndices. UpdateVectorSize stores the raw indices in BufferVectorInfo (Line 947), but ComputeBufferVectorSize derives its offset from TransformIndices(indices, buffer). Both new helpers rebuild the offset from the raw indices and index strides[i] by the raw index count. When layout_map_ contains the buffer and layout forwarding changes the index count, the loop reads past the end of strides, and the offset disagrees with the offset that produced the recorded vector_size. Extract one helper that applies TransformIndices, calls GetBufferStrides, and checks the size relationship, then call it from both sites.

  • src/transform/loop_vectorize.cc#L284-L295: replace the inline offset loop in IsBroadcastLoad with the shared helper.
  • src/transform/loop_vectorize.cc#L378-L403: replace the inline offset loop in RevalidateDeferredAccesses with the same helper.
📍 Affects 1 file
  • src/transform/loop_vectorize.cc#L284-L295 (this comment)
  • src/transform/loop_vectorize.cc#L378-L403
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/transform/loop_vectorize.cc` around lines 284 - 295, The offset logic in
IsBroadcastLoad and RevalidateDeferredAccesses must use transformed indices
consistently with ComputeBufferVectorSize. In
src/transform/loop_vectorize.cc#L284-L295 and
src/transform/loop_vectorize.cc#L378-L403, extract a shared helper that applies
TransformIndices, obtains GetBufferStrides, validates the
transformed-index/stride size relationship, and computes the offset; replace
both inline computations with that helper.

Comment thread src/transform/loop_vectorize.cc
Move the original strategy rationale into the new classification helpers and retain the established verbose summary and strategy log messages while adding separate constraint details.
@SiriusNEO
SiriusNEO requested a review from LeiWang1999 August 10, 2026 10:52
@SiriusNEO

Copy link
Copy Markdown
Collaborator Author

@regression-perf

@github-actions

Copy link
Copy Markdown

Performance Regression Test Report

Triggered by: @SiriusNEO
Workflow run: https://github.com/tile-ai/tilelang/actions/runs/31385343688

Results

File Original Latency Current Latency Speedup
example_topk 0.058222 0.885271 0.0657674
example_tilelang_nsa_fwd 0.00394294 0.00406447 0.9701
block_sparse_attn_tilelang 0.00605677 0.0061914 0.978254
sparse_mla_bwd 0.137072 0.139051 0.98577
example_mha_sink_fwd_bhsd_sliding_window 0.00960595 0.00974256 0.985978
example_tilelang_sparse_gqa_decode_varlen_indice 0.0106992 0.0108404 0.986972
example_mha_sink_fwd_bhsd 0.00969099 0.00981875 0.986988
example_dequant_gemm_fp4_hopper 0.531658 0.538383 0.987509
example_mla_decode 0.297618 0.301213 0.988064
example_mha_bwd_bshd 0.0137935 0.0139297 0.990228
example_blocksparse_gemm 0.0116615 0.0117746 0.990388
example_mha_bwd_bhsd 0.0139521 0.0140699 0.991626
example_dequant_gemv_fp16xint4 0.0173398 0.0174516 0.993592
example_gqa_bwd_tma_reduce_varlen 0.0277113 0.0278529 0.994915
example_gqa_fwd_bshd 0.0295207 0.0296709 0.994937
example_gemm_intrinsics 0.0200305 0.0201279 0.995159
example_warp_specialize_gemm_barrierpipe_stage2 0.024758 0.0248608 0.995864
example_tilelang_nsa_decode 0.00417302 0.00418718 0.996618
example_elementwise_add 0.0690517 0.0691809 0.998133
fp8_lighting_indexer 0.0120128 0.0120347 0.99818
example_dequant_gemm_bf16_mxfp4_hopper 0.254961 0.255215 0.999006
example_mhc_pre 0.115229 0.115331 0.999114
example_gqa_sink_bwd_bhsd_sliding_window 0.0152765 0.0152787 0.999854
example_gqa_sink_bwd_bhsd 0.0250625 0.025062 1.00002
sparse_mla_fwd 0.0530143 0.0529986 1.0003
example_dequant_gemm_bf16_fp4_hopper 0.268503 0.268415 1.00033
example_tilelang_gemm_fp8 0.171175 0.171107 1.0004
example_fusedmoe_tilelang 0.0765218 0.0764712 1.00066
example_tilelang_gemm_fp8_2xAcc 0.0678948 0.067842 1.00078
example_linear_attn_fwd 0.0228309 0.0228121 1.00083
topk_selector 0.0272809 0.0272579 1.00085
example_warp_specialize_gemm_copy_0_gemm_1 0.0236678 0.0236476 1.00085
example_tilelang_gemm_splitk_vectorize_atomicadd 0.585114 0.584461 1.00112
example_dequant_gemm_w4a8 2.69498 2.6919 1.00114
example_mhc_post 0.0657032 0.065622 1.00124
example_convolution 0.583114 0.58229 1.00142
example_group_per_split_token_cast_to_fp8 0.00565377 0.00564454 1.00164
example_tilelang_gemm_splitk 0.590914 0.589911 1.0017
example_tilelang_sparse_gqa_decode_varlen_mask 0.0282575 0.0281872 1.00249
example_dynamic 0.388628 0.387553 1.00277
example_linear_attn_bwd 0.0970799 0.096784 1.00306
example_mha_inference 0.0330328 0.0329298 1.00313
example_gqa_bwd 0.0288299 0.0287377 1.00321
example_convolution_autotune 0.593456 0.591367 1.00353
example_gqa_decode 0.0306202 0.0305029 1.00384
example_mha_fwd_varlen 0.0206372 0.0205542 1.00404
example_mha_sink_bwd_bhsd 0.0409875 0.0408212 1.00407
example_vertical_slash_sparse_attn 0.135534 0.134766 1.0057
example_gemm 0.0148929 0.0148067 1.00583
example_mha_sink_bwd_bhsd_sliding_window 0.0263468 0.0261932 1.00586
example_mha_fwd_bshd 0.0148931 0.014805 1.00595
sparse_mla_fwd_pipelined 0.0345739 0.0343362 1.00692
example_gemv 0.149314 0.148082 1.00832
example_tilelang_block_sparse_attn 0.00576551 0.00570162 1.01121
example_per_token_cast_to_fp8 0.00439658 0.00433711 1.01371
example_mha_fwd_bhsd 0.00703848 0.00688103 1.02288
example_warp_specialize_gemm_copy_1_gemm_0 0.016173 0.0154917 1.04398
example_warp_specialize_gemm_softpipe_stage2 0.0161645 0.0154051 1.0493

Artifacts

  • regression_result.png (speedup plot) is attached as a workflow artifact. Download it from the workflow run page above.

@penguin-wwy

Copy link
Copy Markdown
Contributor

It seems this PR has fixed the vectorization issue. I will close #2922 and wait for this PR to be merged :)

@SiriusNEO

Copy link
Copy Markdown
Collaborator Author

@regression-perf

@github-actions

Copy link
Copy Markdown

Performance Regression Test Report

Triggered by: @SiriusNEO
Workflow run: https://github.com/tile-ai/tilelang/actions/runs/31454927106

Results

File Original Latency Current Latency Speedup
example_topk 0.0537084 1.21653 0.0441487
example_dequant_gemm_bf16_mxfp4_hopper 0.25286 0.257277 0.982834
sparse_mla_fwd_pipelined 0.0341743 0.0344521 0.991936
example_dequant_gemm_bf16_fp4_hopper 0.266337 0.268404 0.992297
example_dequant_gemm_fp4_hopper 0.535519 0.539382 0.992839
example_warp_specialize_gemm_barrierpipe_stage2 0.0247547 0.0249223 0.993275
example_mha_inference 0.0330068 0.0331768 0.994875
example_fusedmoe_tilelang 0.0762583 0.0766224 0.995249
example_tilelang_nsa_fwd 0.00405403 0.00407327 0.995275
example_mha_fwd_bshd 0.0147405 0.0148031 0.995772
example_gemm 0.0148691 0.0149311 0.995844
example_tilelang_block_sparse_attn 0.00567258 0.00569607 0.995876
fp8_lighting_indexer 0.0118871 0.0119309 0.996327
example_mha_bwd_bshd 0.0138733 0.0139212 0.996564
example_gqa_decode 0.0304774 0.0305798 0.996649
example_tilelang_sparse_gqa_decode_varlen_mask 0.028182 0.0282534 0.997472
example_tilelang_gemm_splitk 0.589415 0.590853 0.997566
example_mha_sink_bwd_bhsd 0.0407373 0.0408354 0.997596
example_mha_sink_fwd_bhsd 0.00981017 0.00982927 0.998057
sparse_mla_fwd 0.052515 0.052608 0.998233
example_gqa_bwd_tma_reduce_varlen 0.027842 0.0278828 0.998536
example_convolution 0.582336 0.583079 0.998725
example_mha_fwd_varlen 0.0205615 0.0205853 0.998841
example_elementwise_add 0.0690583 0.0691305 0.998956
example_mhc_post 0.0655605 0.065629 0.998957
example_warp_specialize_gemm_copy_1_gemm_0 0.0154877 0.0154978 0.999349
example_tilelang_gemm_splitk_vectorize_atomicadd 0.584121 0.584437 0.99946
example_group_per_split_token_cast_to_fp8 0.00563036 0.00563269 0.999586
example_mha_sink_fwd_bhsd_sliding_window 0.0097267 0.00972933 0.99973
example_gqa_sink_bwd_bhsd 0.0250328 0.0250367 0.999844
example_gqa_fwd_bshd 0.0297753 0.0297732 1.00007
example_gqa_sink_bwd_bhsd_sliding_window 0.015272 0.0152705 1.0001
example_mhc_pre 0.115312 0.115291 1.00018
example_tilelang_sparse_gqa_decode_varlen_indice 0.0108402 0.0108379 1.00021
example_convolution_autotune 0.591469 0.591341 1.00022
topk_selector 0.0272604 0.0272512 1.00034
example_warp_specialize_gemm_copy_0_gemm_1 0.0236613 0.0236514 1.00042
example_mha_bwd_bhsd 0.0140055 0.013998 1.00054
block_sparse_attn_tilelang 0.00616456 0.0061603 1.00069
example_per_token_cast_to_fp8 0.00432024 0.00431685 1.00079
example_tilelang_nsa_decode 0.00419256 0.00418891 1.00087
example_gemm_intrinsics 0.0201603 0.0201424 1.00089
sparse_mla_bwd 0.136786 0.136657 1.00095
example_linear_attn_fwd 0.0229229 0.0228887 1.00149
example_tilelang_gemm_fp8_2xAcc 0.0679276 0.0678258 1.0015
example_mla_decode 0.30124 0.300709 1.00177
example_blocksparse_gemm 0.0117528 0.0117313 1.00183
example_mha_sink_bwd_bhsd_sliding_window 0.0262123 0.0261371 1.00288
example_linear_attn_bwd 0.0969212 0.0966391 1.00292
example_gqa_bwd 0.0289117 0.0288118 1.00347
example_dynamic 0.388675 0.387319 1.0035
example_tilelang_gemm_fp8 0.170895 0.170022 1.00514
example_vertical_slash_sparse_attn 0.135546 0.134823 1.00536
example_mha_fwd_bhsd 0.00691941 0.00688059 1.00564
example_dequant_gemv_fp16xint4 0.0174725 0.0173719 1.00579
example_dequant_gemm_w4a8 2.69486 2.67921 1.00584
example_gemv 0.149109 0.148102 1.0068
example_warp_specialize_gemm_softpipe_stage2 0.0157873 0.0154881 1.01932

Artifacts

  • regression_result.png (speedup plot) is attached as a workflow artifact. Download it from the workflow run page above.

@SiriusNEO
SiriusNEO merged commit cd57a84 into main Aug 11, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants