Skip to content

[BugFix] Use masked warpsync in in-warp allreduce - #2865

Merged
LeiWang1999 merged 2 commits into
tile-ai:mainfrom
jjppp:fix-allreduce-syncwarp
Aug 4, 2026
Merged

LeiWang1999 merged 2 commits into
tile-ai:mainfrom
jjppp:fix-allreduce-syncwarp

Conversation

@jjppp

@jjppp jjppp commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

The in-warp reduction loop separated each load/store pair with SyncThread("warp") -> tvm_storage_sync("warp"), which is a no-op in CUDA.
This leads to datarace and silently produces incorrect results.

Note that simply emitting __syncthreads() does not help as there might be a surrounding divergent if condition guarding the sync.
To fix this, we compute the participant mask via __ballot_sync() before entering the branch, and emit __syncwarp(participant_mask) (tl::sync_warp) between stages.

Fixes #2628

Summary

  • Fixed incorrect in-warp tvm_thread_allreduce results caused by unsynchronized shared-memory stages.
  • Replaced warp-wide no-op barriers with masked tl::sync_warp calls.
  • Computed participant masks with __activemask() and __ballot_sync().
  • Added regression coverage for float, integer, and bfloat16 reductions.

C++ style / lint notes

  • The change does not modify docs/developer_guide/cpp_style.md or introduce a new public API.
  • The C++ API Style Audit remains warning-only.
  • No correctness or build issue is indicated by the style checks. Advisory TLCPP003 or TLCPP004 findings should not block this change without a clear API, FFI, or maintainability risk.

@github-actions

github-actions Bot commented Aug 4, 2026

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 4, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change updates in-warp tvm_thread_allreduce lowering to use masked warp synchronization. A regression test covers single-warp sum and product reductions for float, integer, and bfloat16 inputs.

Changes

Warp allreduce synchronization

Layer / File(s) Summary
Masked warp reduction lowering
src/transform/lower_thread_allreduce.cc
The lowering pass computes active-lane and participant masks. It replaces warp-wide barriers with SyncWarp(participant_mask) and emits tl::sync_warp.
Allreduce regression coverage
testing/python/issue/test_tilelang_issue_2628.py
The test builds a synchronized single-warp allreduce kernel and validates sum and product results across six numeric dtypes against PyTorch.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TileLangKernel
  participant lower_thread_allreduce
  participant SharedMemory
  participant CUDAWarpSync
  TileLangKernel->>lower_thread_allreduce: Lower tvm_thread_allreduce
  lower_thread_allreduce->>lower_thread_allreduce: Compute activemask and ballot_sync masks
  lower_thread_allreduce->>SharedMemory: Store reduction values
  lower_thread_allreduce->>CUDAWarpSync: Emit tl::sync_warp(participant_mask)
  CUDAWarpSync-->>SharedMemory: Synchronize participating lanes
Loading

Possibly related PRs

Suggested reviewers: siriusneo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The synchronization fix and regression test address issue #2628, but the summary does not confirm int8, int16, float8, or multi-warp coverage. Add or document regression coverage for the remaining sub-4-byte dtypes and multi-warp power-of-two reductions.
✅ 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 describes the main change: masked warp synchronization for in-warp allreduce.
Out of Scope Changes check ✅ Passed The changes are limited to masked warp synchronization and regression coverage for the linked allreduce issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@testing/python/issue/test_tilelang_issue_2628.py`:
- Around line 34-50: Expand test_thread_allreduce_syncwarp into a parameterized
regression matrix covering power-of-two single- and multi-warp thread counts,
every supported reducer combiner, and supported float8 dtypes. Reuse the
existing make_kernel execution and reference validation for each case, and add
explicit rejection coverage for float8 variants when the target does not support
them.
🪄 Autofix (Beta)

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: 1ab10aca-30b7-4c14-a397-ea50d1241351

📥 Commits

Reviewing files that changed from the base of the PR and between 66e8e47 and a4aec2c.

📒 Files selected for processing (2)
  • src/transform/lower_thread_allreduce.cc
  • testing/python/issue/test_tilelang_issue_2628.py

Comment on lines +34 to +50
def test_thread_allreduce_syncwarp():
# Fixed exact-integer inputs so the reported values reproduce byte-for-byte.
INT_VALS = [10, 25, 40, 30, 15, 42, 20, 20] # sum = 202 (-54 for int8)
BF16_VALS = [3.0, 8.0, 5.0, 2.0, 7.0, 6.0, 4.0, 6.0] # sum = 41 (exact in bf16)
for dt, vals in [
("float32", INT_VALS),
("int32", INT_VALS),
("float16", INT_VALS),
("bfloat16", BF16_VALS),
("int16", INT_VALS),
("int8", INT_VALS),
]:
dtype = getattr(torch, dt)
A = torch.tensor(vals, device="cuda").to(dtype)
got = make_kernel(8, dt)(A).to(dtype)[0].item()
ref = A.sum().to(dtype).item()
torch.testing.assert_close(got, ref, rtol=1e-3, atol=1e-3)

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Expand the regression matrix before merge.

This test executes only an eight-thread + reduction. It does not execute multi-warp reductions, non-additive supported combiners, or float8_* paths. A regression in those required paths can still merge.

Add parameterized power-of-two single- and multi-warp cases for each supported combiner. Test supported float8 variants, or add an explicit rejection test where the target does not support them.

Based on PR objectives, coverage is required for supported reducer operations and single- and multi-warp power-of-two thread counts.

🤖 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/issue/test_tilelang_issue_2628.py` around lines 34 - 50,
Expand test_thread_allreduce_syncwarp into a parameterized regression matrix
covering power-of-two single- and multi-warp thread counts, every supported
reducer combiner, and supported float8 dtypes. Reuse the existing make_kernel
execution and reference validation for each case, and add explicit rejection
coverage for float8 variants when the target does not support them.

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@testing/python/issue/test_tilelang_issue_2628.py`:
- Around line 39-55: Expand test_thread_allreduce_syncwarp_sum to cover 8-, 32-,
and 64-thread power-of-two reductions, creating input values sized for each
thread count and preserving the existing dtype assertions. Extend the dtype
matrix with every supported float8 variant; for unsupported float8 types, add an
explicit test that verifies the documented rejection behavior.
- Around line 35-36: Replace the INT_VALS and BF16_VALS fixtures with values
whose product remains finite and representable for the correctness checks,
avoiding overflow or wrapping in int8, float16, and int16 references. Keep any
intentional overflow coverage in separate dedicated fixtures or cases.
🪄 Autofix (Beta)

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: fa8d4686-cf30-4217-9712-057fdda43e9b

📥 Commits

Reviewing files that changed from the base of the PR and between a4aec2c and 8995305.

📒 Files selected for processing (1)
  • testing/python/issue/test_tilelang_issue_2628.py

Comment on lines +35 to +36
INT_VALS = [10, 25, 40, 30, 15, 42, 20, 20] # sum = 202 (-54 for int8)
BF16_VALS = [3.0, 8.0, 5.0, 2.0, 7.0, 6.0, 4.0, 6.0] # sum = 41 (exact in bf16)

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the referenced test file and relevant sections.
if [ -f testing/python/issue/test_tilelang_issue_2628.py ]; then
  echo "== file exists =="
  wc -l testing/python/issue/test_tilelang_issue_2628.py
  echo "== lines 1-120 =="
  cat -n testing/python/issue/test_tilelang_issue_2628.py | sed -n '1,120p'
else
  echo "== missing target file =="
  fd -a test_tilelang_issue_2628.py . || true
fi

echo "== related references =="
rg -n "INT_VALS|BF16_VALS|product|float16|int8|reduce|TileLang|tilelang|issue_2628|2628" testing/python/issue -S || true

Repository: tile-ai/tilelang

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
vals = [10, 25, 40, 30, 15, 42, 20, 20]
product = 1
for v in vals:
    product *= v

import math
print("product (Python int) =", product)

import struct

def f16_to_float(h):
    return math.copysign(2 ** (struct.unpack(">B", struct.pack(">H", h & 0x7fff) >> 10 & 0x1f)[0] - 15), struct.pack(">H", h & 0x8000))

bf16_vals_float = []
for v in vals:
    as32 = struct.unpack(">I", struct.pack(">f", float(v)))[0]
    bf16 = (as32 >> 16) & 0xffff
    bf16_vals_float.append(f16_to_float(bf16))

prod_f16 = 1.0
for v in vals:
    prod_f16 *= v

print("bf16 values approximated via Python:", bf16_vals_float)
print("product of bf16 approximations:", prod_f16)
print("is product of bf16 approximations finite:", math.isfinite(prod_f16))
print("INT_VALS sum:", sum(vals))
PY

Repository: tile-ai/tilelang

Length of output: 371


🌐 Web query:

bfloat16 float8 format Python struct f16 conversion

💡 Result:

Bfloat16 and float8 are specialized floating-point formats used primarily in machine learning to optimize memory and computational efficiency [1]. As of August 2026, Python's built-in struct module does not provide native support for either format [2][3]. ### Format Overview - Bfloat16 (Brain Floating Point 16): A 16-bit format with 1 sign bit, 8 exponent bits, and 7 mantissa bits [4][1]. Its exponent range matches IEEE 754 single precision (FP32), making it stable for deep learning, though it has lower precision [4][5]. - Float8: A family of 8-bit formats, most commonly E4M3 (4 exponent, 3 mantissa) and E5M2 (5 exponent, 2 mantissa) [6][1]. These provide significant memory bandwidth savings but require careful scaling strategies to manage their narrower dynamic range [1][7]. ### Python struct Limitations The struct module supports standard IEEE 754 formats via specific conversion codes: - e: IEEE 754 binary16 (half-precision) [2][8]. - f: IEEE 754 binary32 (single-precision) [2][8]. - d: IEEE 754 binary64 (double-precision) [2][8]. There is currently no corresponding format character for bfloat16 or float8 in the struct module [2][3]. Conversion between these formats and standard Python types typically requires specialized libraries rather than the standard library's struct module [9][10]. ### How to Handle Conversions Because struct does not support these formats, you must use dedicated tools designed for numerical computing: - For PyTorch users: High-level utilities such as torch.Tensor.to(torch.bfloat16) or specialized modules like torchao.float8 are recommended for model training and inference [11][12][10]. - For numerical conversion: Libraries like ml_dtypes extend NumPy to support bfloat16, allowing you to interface between PyTorch and NumPy more easily [9]. - For low-level binary data: If you need to manually pack or unpack binary data that is not supported by struct, you may need to use bitwise manipulation or third-party packages designed for exotic floating-point representations [13]. Top results: [1][2][3]

Citations:


Use a non-overflowing product fixture.

INT_VALS multiplies to 75_600_000_000. The int8 reference wraps to 0, and the float16/int16 product reference overflows. A correct product reduction can already match these references, so it can pass without catching the synchronization issue. Use a finite, non-overflow product fixture for correctness cases, and keep overflow cases separate.

Applies to lines 35-36 also.

🤖 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/issue/test_tilelang_issue_2628.py` around lines 35 - 36,
Replace the INT_VALS and BF16_VALS fixtures with values whose product remains
finite and representable for the correctness checks, avoiding overflow or
wrapping in int8, float16, and int16 references. Keep any intentional overflow
coverage in separate dedicated fixtures or cases.

Comment on lines +39 to +55
@pytest.mark.parametrize(
"dt,vals",
[
("float32", INT_VALS),
("int32", INT_VALS),
("float16", INT_VALS),
("bfloat16", BF16_VALS),
("int16", INT_VALS),
("int8", INT_VALS),
],
)
def test_thread_allreduce_syncwarp_sum(dt, vals):
dtype = getattr(torch, dt)
A = torch.tensor(vals, device="cuda").to(dtype)
got = make_kernel(8, dt, lambda x, y: x + y, 0)(A).to(dtype)[0].item()
ref = A.sum().to(dtype).item()
torch.testing.assert_close(got, ref, rtol=1e-3, atol=1e-3)

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Expand the regression matrix before merge.

The previous coverage gap remains partly unresolved. Lines 53 and 72 test only an eight-thread reduction. Add full-warp and multi-warp power-of-two cases, such as 32 and 64 threads, with inputs sized for each reduction.

The matrix also has no float8_* case or explicit unsupported-type rejection. Add coverage for each supported variant, or assert the documented rejection.

Also applies to: 58-74

🤖 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/issue/test_tilelang_issue_2628.py` around lines 39 - 55,
Expand test_thread_allreduce_syncwarp_sum to cover 8-, 32-, and 64-thread
power-of-two reductions, creating input values sized for each thread count and
preserving the existing dtype assertions. Extend the dtype matrix with every
supported float8 variant; for unsupported float8 types, add an explicit test
that verifies the documented rejection behavior.

@LeiWang1999
LeiWang1999 self-requested a review August 4, 2026 09:28
@LeiWang1999
LeiWang1999 merged commit 03645a6 into tile-ai:main Aug 4, 2026
6 checks passed
LeiWang1999 pushed a commit that referenced this pull request Aug 5, 2026
#2865 fixed the warp-synchronous allreduce corruption from #2628 by
emitting `__syncwarp(mask)` around the in-warp reduction steps. On HIP that
call lowers to nothing, so the fix never took effect on ROCm and
`test_tilelang_issue_2628.py` fails on gfx942:

    test_thread_allreduce_syncwarp_sum[float32-vals0]
    E   AssertionError: Scalars are not close!
    E   Expected 202.0 but got 90.0.

The existing comment argues that AMD wavefronts execute in lockstep, so no
barrier is needed. That is true of *runtime* convergence but not of
compile-time scheduling: with nothing marking the synchronization point, the
backend is free to reorder or coalesce the LDS accesses that the
warp-synchronous reduction depends on, which is exactly the corruption
#2628 describes.

Emit `__builtin_amdgcn_wave_barrier()` instead. It generates no
instructions, so the lockstep reasoning still holds and there is no runtime
cost; it only constrains the scheduler. The mask argument remains ignored,
since a wavefront is always fully convergent.

Verified on 8x MI300X (gfx942), ROCm 7.2, torch 2.14.0.dev+rocm7.2:

* before: `test_tilelang_issue_2628.py` -> 3 failed (run aborted by --maxfail=3)
* after:  `test_tilelang_issue_2628.py` -> 12 passed
  (sum and mul across float32/int32/float16/bfloat16/int16/int8)
* full ROCm suite: 1809 passed, 1227 skipped, 0 failed
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.

[BUG][Fuzzer][wrong-code] Block T.tvm_thread_allreduce over sub-4-byte dtypes silently miscomputes (codegen drops the warp barrier)

2 participants