Skip to content

[Fix] Keep the fast_divmod multiplier within 32 bits - #170

Merged
yaoyaoding merged 2 commits into
NVIDIA:mainfrom
0z5a:fix-fast-divmod-multiplier
Nov 10, 2026
Merged

yaoyaoding merged 2 commits into
NVIDIA:mainfrom
0z5a:fix-fast-divmod-multiplier

Conversation

@0z5a

@0z5a 0z5a commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

What

fast_divmod returns wrong quotients for many divisors. fast_divmod(7, 7) gives 0. This PR changes how the multiplier is precomputed so that it is exact for every dividend fast_divmod accepts.

Why

cuda_fastdiv_precompute_m searched for the Hacker's Delight multiplier for the whole uint32 range. For some divisors that multiplier needs 33 bits, and return uint32(m) dropped the top bit. For b = 7 it computed m = 2^32 + 613566757, s = 3, and the kernel ran with m = 613566757.

Of the divisors 1 to 1024, 379 are affected. For example, b = 56 is wrong at a = 56, where it returns 0.

This reaches real kernels. hopper_matmul matmul_v4 to v6 and blackwell_matmul matmul_v4 to v6 divide linear_idx by num_m_blocks * swizzle_size. In hopper_matmul/matmul_v4 (block_m = 128, block_n = 256, swizzle_size = 4), m_size = 1792 and n_size = 4096 give a divisor of 56, and 168 of the 224 tiles get a wrong group_idx from the same multiplier.

How

The dividend of fast_divmod is a non-negative int32: cuda_fastdiv takes a: i32, and the docstring says "int32, must be >= 0". So the multiplier only has to be exact below 2^31, and then it fits in 32 bits:

  • p = 31 + ceil(log2(b))
  • m = ceil(2^p / b), which is below 2^32
  • shift = p - 32

This is the formula in find_divisor in CUTLASS fast_math.h. The runtime (mul.hi.u32 + shr.u32) and the power-of-2 path do not change.

Dividends of 2^31 and above stay out of contract, and the docstrings now say 0 <= a < 2^31. They were never reliable: the power-of-2 path shifts an int32 arithmetically. Every caller in the repo passes a non-negative int32 block index.

Testing

Host tests ran on the Grace CPU of a GH200: GCC 13.3, CUDA 13.2, PyTorch 2.14.1+cu130, tilus main at f23018a. The host-side numbers below run the real cuda_fastdiv_precompute_m/s, built into a cpu_kernel with nvcc, and evaluate mul.hi + shr in Python.

check (host, real precompute code) main this PR
Divisors 1 to 1024 with any wrong quotient (all a < 2^20, plus the top of the range) 379 0
Non-power-of-2 divisors below 2^20, wrong at the worst-case dividend (largest k*b - 1 < 2^31) 323313 of 1048555 0
b = 7, 56, 641, 1023, 2^31 - 1, all a in [0, 2^31) not run 0 wrong
matmul_v4 shape above, tiles with a wrong group_idx 168 of 224 0

tests/instructions/test_fast_divmod.py is new:

test needs main this PR
test_precomputed_multiplier_is_exact (1065 divisors, boundary dividends) nvcc, no GPU fails: 399 of 1065 wrong passes
test_fast_divmod[0-7], [0-56], [2147483392-7] GPU 3 fail 3 pass
test_fast_divmod[0-3], [0-64] (controls) GPU pass pass

GPU tests ran on GH200 (sm_90, CUDA 13.2). Not run on B300.

The rest of the CPU-runnable suite (tests/lang, tests/ir, tests/drivers, tests/transforms and the top-level tests) gives the same result on main and here: 192 passed, and the same 5 failed because they need a GPU.

ruff check and ruff format --check (0.11.0 from pyproject.toml, 0.12.3 from pre-commit), mypy (1.15.0) and the copyright check pass on the changed files.

The precomputed multiplier came from the Hacker's Delight search for the whole uint32 range, which needs 33 bits for some divisors. cuda_fastdiv_precompute_m truncated it to uint32, so fast_divmod(7, 7) returned 0 and 379 of the divisors 1-1024 gave wrong quotients.

The dividend of fast_divmod is a non-negative int32, so use p = 31 + ceil(log2(b)) and m = ceil(2^p / b) as CUTLASS FastDivmod does. This is exact for dividends below 2^31 and m always fits in 32 bits.

Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
@copy-pr-bot

copy-pr-bot Bot commented Nov 7, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

- State the dividend range as 0 <= a < 2^31 in the fast_divmod docstring.
- Move the fast_divmod primitive import to the top of the test file.
- Allocate test tensors on "cuda" directly under @requires.nvgpu_sm80.
- Note why the host reference a * multiplier fits in int64.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yaoyaoding

Copy link
Copy Markdown
Member

/ok to test e54034a

@yaoyaoding
yaoyaoding merged commit 8c49904 into NVIDIA:main Nov 10, 2026
9 of 10 checks passed
github-actions Bot added a commit that referenced this pull request Nov 10, 2026
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