Skip to content

[Fix] Keep the source register in the int8/int4b to float16 cast - #171

Merged
yaoyaoding merged 2 commits into
NVIDIA:mainfrom
0z5a:fix-cast-source-clobber
Nov 10, 2026
Merged

yaoyaoding merged 2 commits into
NVIDIA:mainfrom
0z5a:fix-cast-source-clobber

Conversation

@0z5a

@0z5a 0z5a commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

What

Casting an int8 or int4b register to float16 changes the source register. Anything that reads the register after the cast gets wrong values. This PR leaves the source intact.

Why

cast_i8_to_f16 and cast_i4_to_f16 bias the input to unsigned before the conversion. They do it in place:

self.buffer_store(buf=src_uint32, indices=[i], value=src_uint32[i] ^ uint32(0x80808080))

src_uint32 points at the source register, so the emitted kernel contains src_uint32[0] = (src_uint32[0] ^ ...). The cast result is correct, but a later read of the same register sees every element with its sign bit flipped. For int4b the mask is 0x88888888. No error is raised. A second .to(...) or a store of the register silently produces wrong data.

Only these two specialized paths are affected. The other paths in cast.py write only to the destination and their own temporaries.

How

Put the biased value in a local variable and read that in the following prmt and shift steps. The arithmetic is otherwise the same.

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.

New tests in tests/kernels/test_cast.py. Each script casts the register, then stores both the result and the register itself:

test needs main this PR
test_cast_to_float16_does_not_write_source_register_codegen (int8, int4b): no store into src_uint32 in the generated source no GPU, no nvcc 2 fail 2 pass
test_cast_to_float16_keeps_source_register (int8, int4b): checks the cast values and the stored register GPU 2 fail 2 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.

cast_i8_to_f16 and cast_i4_to_f16 biased the source in place with buffer_store(src_uint32, ...). Any instruction that read the register after the cast saw the values with their sign bits flipped. Put the biased value in a temporary instead.

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.

- Remove the hand-rolled codegen test. It drove optimize_program,
  generate_ir_module, optimize_ir_module and codegen directly, which is
  brittle against internal APIs. The normal build path cannot replace it
  without losing its no-GPU value, because compile_source requires CUDA.
  The GPU test covers the behaviour.
- Use "cuda" directly in the GPU test. The "cpu" fallback was dead:
  compile-only mode also needs CUDA to build.
- Add larger vectors to CAST_REUSE_CASES so the non-unrolled loop body
  of the int8/int4b to float16 cast is compiled and run.

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

Copy link
Copy Markdown
Member

/ok to test c7147f8

@yaoyaoding

Copy link
Copy Markdown
Member

thank you for the recent 4 PRs! @0z5a

@yaoyaoding
yaoyaoding merged commit d8e6cc6 into NVIDIA:main Nov 10, 2026
9 of 10 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