Skip to content

[BugFix][CPU] Compute scalar GEMM products in accum dtype instead of input dtype - #2917

Merged
LeiWang1999 merged 1 commit into
tile-ai:mainfrom
Dino1844:fix/cpu-gemm-scalar-fp16-accum
Sep 2, 2026
Merged

[BugFix][CPU] Compute scalar GEMM products in accum dtype instead of input dtype#2917
LeiWang1999 merged 1 commit into
tile-ai:mainfrom
Dino1844:fix/cpu-gemm-scalar-fp16-accum

Conversation

@Dino1844

@Dino1844 Dino1844 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

GemmScalar — the CPU/LLVM fallback for T.gemm — evaluates each scalar multiply in the input dtype and only then widens the already-rounded product to the accumulator dtype:

C_buf[c0 + i, c1 + j] += T.cast(
    A_buf[...] * B_buf[...],   # fp16 x fp16 -> fp16 (rounded)
    accum_dtype,
)

For fp16 inputs this diverges from CUDA/ROCm mma, which computes fp16 products exactly (the 22-bit result fits in fp32's 24-bit mantissa) and accumulates in fp32. It also diverges from TileLang's own CUDA FMA fallback (tilelang/cuda/op/gemm/gemm_fma.py), whose docstring documents the correct convention: "Casts widen operands to accum_dtype before multiplication so narrow input dtypes stay numerically sound (e.g. BF16 to FP32 accumulation on Volta). This path favors correctness and coverage over peak throughput."

Problem

Rounding each product to fp16 before widening has two consequences:

  1. Accuracy. Every product carries an extra ~2^-11 relative rounding (fp32 accumulation is ~2^-24), which dominates the error for realistic K. Measured on normal-range inputs: the fp16 GEMM deviates from exact-product accumulation by up to ~1e-2 absolute (K=256), whereas exact products are accurate to fp32-accumulation level (~1e-5).

  2. Overflow -> NaN/Inf. Products with |a·b| > 65504 (fp16 max) overflow to ±inf; mixed-sign infs then produce NaN inside the fp32 accumulator. Measured with |a|,|b| ~ 600: 100% of outputs were NaN/Inf before the fix, while exact products stay finite (they fit in fp32).

Fix

Cast both operands to accum_dtype before multiplying (exact for fp16/bf16/fp8 products, all of which fit exactly in fp32):

C_buf[c0 + i, c1 + j] += T.cast(A_buf[...], accum_dtype) * T.cast(B_buf[...], accum_dtype)

This matches mma semantics and the convention already established in gemm_fma.py.

Verification

fp16 T.gemm on target="c" (compiled and executed): normal-range inputs match an exact-product fp64 reference to fp32-accumulation level (max abs err 2e-5 vs ~1e-2 before); large-magnitude inputs (products > 65504) no longer produce NaN/Inf. Existing CPU GEMM tests (test_tilelang_cpu_tgemm.py, test_tilelang_cpu_gemm.py) pass.

Summary

  • Fixed CPU/LLVM scalar T.gemm accumulation.
  • Cast both operands to accum_dtype before multiplication.
  • Prevented fp16 product rounding and fp16 overflow to Inf or NaN.
  • Aligned CPU behavior with CUDA/ROCm MMA and existing CUDA FMA fallback semantics.
  • Improved fp16 GEMM accuracy and preserved existing CPU GEMM test coverage.

…input dtype

The CPU fallback GEMM computed A*B in the input dtype (e.g. fp16) and then
widened the already-rounded product to the accumulator dtype:

    C[i, j] += (float)(A[i, k] * B[k, j])

This diverges from CUDA mma, which computes fp16 products exactly and
accumulates in fp32. Rounding each product to fp16 before widening adds up
to ~2^-11 relative error per product, which dominates the fp32 accumulation
error, and products with |a*b| > 65504 overflow the fp16 product to inf,
poisoning the fp32 accumulator with inf/nan.

Cast both operands to accum_dtype before multiplying (exact for fp16/bf16/
fp8 products, which fit exactly in fp32), matching mma semantics and the
behavior of torch CPU fp16 matmul and oneDNN (f32 accumulation).
@github-actions

github-actions Bot commented Aug 7, 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 7, 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: 85cb4725-c6c7-46f5-89ae-500077507f7f

📥 Commits

Reviewing files that changed from the base of the PR and between 12dbf3e and cd93ca1.

📒 Files selected for processing (1)
  • tilelang/cpu/op/gemm/gemm_scalar.py

📝 Walkthrough

Walkthrough

The scalar GEMM kernel now casts operands A and B to accum_dtype before multiplication. This replaces casting the multiplication result after computation.

Changes

Scalar GEMM

Layer / File(s) Summary
Cast operands before multiplication
tilelang/cpu/op/gemm/gemm_scalar.py
The GEMM computation casts both operands to accum_dtype before multiplication.

Estimated code review effort: 2 (Simple) | ~5 minutes

Suggested reviewers: leiwang1999

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main fix: computing scalar GEMM products in the accumulator data type on CPU.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
🧪 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.

@Dino1844

Dino1844 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Hi @LeiWang1999 and maintainers,

While auditing TileLang I focused on the CPU backend, and wanted to confirm the current support status and direction before investing effort, to avoid overlapping with community work and wasting your review time.

Observation: The CPU backend (target="c" / target="llvm") is marked Experimental in the README. From my testing:

  1. Some core ops are unimplemented: T.cumsum, T.atomic_add, finalize_reducer, gemm_sp fail with Unresolved call (CUDA has ~14 ops, CPU 5);
  2. Vectorization is incomplete: T.exp/min/max/cast etc. fail to compile in T.Parallel, vec_type only implements +-*/;
  3. Some semantics diverge from CUDA/torch, e.g. NaN / -0.0 in min/max.

Questions:

  1. Is CPU an actively developed direction? Any roadmap or in-progress work?
  2. If contributions are welcome, what to prioritize — unimplemented ops, or vectorization/math support?

Thanks for your time! If CPU isn't a near-term priority, please let me know and I'll adjust accordingly.

@LeiWang1999
LeiWang1999 merged commit 3330a37 into tile-ai:main Sep 2, 2026
3 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.

2 participants