Skip to content

Fix Int4OpaqueTensor A16W4 crash on mismatched activation dtype - #4969

Open
tritsystem wants to merge 1 commit into
pytorch:mainfrom
tritsystem:fix-int4-opaque-tensor-activation-dtype-mismatch
Open

tritsystem wants to merge 1 commit into
pytorch:mainfrom
tritsystem:fix-int4-opaque-tensor-activation-dtype-mismatch

Conversation

@tritsystem

Copy link
Copy Markdown

Summary

Int4OpaqueTensor's A16W4 (weight-only) docstring documents support for
"float16/bfloat16/float32 activation + int4 weight", implying any of these
activation dtypes works regardless of the dtype the weight happened to be
in at quantization time. In practice, the aten.linear.default forward
implementation (torchao/prototype/quantization/int4/int4_opaque_tensor.py)
passed the activation straight into
torch.ops.aten._weight_int4pack_mm_for_cpu without casting it to match
scale_and_zero.dtype. That raw aten op requires an exact dtype match with
no implicit promotion at this boundary (unlike plain elementwise ops on
current PyTorch, which silently type-promote), so any mismatched
(weight_dtype, activation_dtype) pair raises:

RuntimeError: expected scalar type X but found Y

Mechanism

act_mat = input_tensor                      # whatever dtype the caller passes
...
act_mat = act_mat.reshape(-1, act_mat.shape[-1])
y = torch.ops.aten._weight_int4pack_mm_for_cpu(
    act_mat.contiguous(), packed_weight, groupsize, scale_and_zero
)

scale_and_zero is fixed at whatever dtype weight was quantized from
(Int4OpaqueTensor.from_hp), e.g. float32 if the nn.Linear wasn't cast
before quantize_() was called. If the caller later runs inference with a
different float dtype (a very normal thing to do — e.g. quantize a model
as-loaded, then call it with a bfloat16 activation), the op call above
crashes with a cryptic dtype error that gives no hint the real cause is a
weight/activation dtype mismatch, contradicting the class's own documented
contract.

The sibling CUDA tensor class, Int4TilePackedTo4dTensor, already handles
this correctly by casting its activation to its one supported dtype
(bfloat16) before calling its own tinygemm op. This PR applies the same
cast-before-op pattern to Int4OpaqueTensor, casting to scale_and_zero.dtype
(since this class supports three activation dtypes, not just one).

Verification

Directly exercised Int4OpaqueTensor.from_hp() + F.linear() for the full
3x3 (weight_dtype, act_dtype) grid over {float32, bfloat16, float16}:

  • Before the fix: only the 3 matching-dtype pairs succeed; all 6
    mismatched pairs raise RuntimeError: expected scalar type X but found Y.
  • After the fix: all 9 pairs succeed and return output in the caller's
    activation dtype.

Added test_linear_mismatched_activation_dtype to
test/prototype/test_int4_opaque_tensor.py, parametrized over all 9
(weight_dtype, act_dtype) pairs x both use_hqq settings (18 cases).

Red/green confirmed by reverting only the source fix: exactly the 12
mismatched-dtype cases fail (6 pairs x 2 algorithms); the 6 matching-dtype
cases pass regardless. With the fix, all 18 pass.

Ran the full existing test_int4_opaque_tensor.py suite before and after
this change: 36 pre-existing failures are present identically on unmodified
main (appear to be torch.compile-related in this local environment —
Python 3.14 / torch 2.13 CPU build — unrelated to this change) with no new
failures introduced by this fix.

Ran ruff check and ruff format on both changed files — clean.

Verification limitation

Could not test the from_hp_da8w4 (DA8W4, int8-activation) path, or any
CUDA/XPU/NPU int4 tensor class, since this change only touches the
CPU-only A16W4 path (_weight_int4pack_mm_for_cpu). This machine has no
CUDA-enabled PyTorch build installed, so GPU-only code paths in this repo
could not be exercised at all.

Int4OpaqueTensor's docstring documents A16W4 (weight-only) support for
"float16/bfloat16/float32 activation + int4 weight", implying any of
these activation dtypes works regardless of the dtype the weight
happened to be in at quantization time. The forward implementation
passed the activation straight into
torch.ops.aten._weight_int4pack_mm_for_cpu without casting it to match
scale_and_zero.dtype. That aten op requires an exact dtype match (no
implicit promotion at this boundary, unlike plain elementwise ops), so
any mismatched (weight_dtype, activation_dtype) pair raised:

    RuntimeError: expected scalar type X but found Y

Verified directly against Int4OpaqueTensor.from_hp() + F.linear() for
the full 3x3 dtype grid: only the 3 matching pairs worked before the
fix, all 6 mismatched pairs crashed. The sibling CUDA tensor class,
Int4TilePackedTo4dTensor, already casts its activation to its single
supported dtype (bfloat16) before calling its tinygemm op; this applies
the same cast-before-op pattern here, to scale_and_zero.dtype instead
of a hardcoded dtype, since Int4OpaqueTensor supports three.

Added test_linear_mismatched_activation_dtype, parametrized over all 9
(weight_dtype, act_dtype) combinations x both choose-qparams algorithms
(18 cases). Confirmed red/green: reverting only the source fix fails
exactly the 12 mismatched-dtype cases (6 pairs x 2 algorithms); the 6
matching-dtype cases pass either way. All 18 pass with the fix. Ran the
full existing test_int4_opaque_tensor.py suite before and after the fix:
36 pre-existing failures are present identically on unmodified main
(appear to be torch.compile-related issues in this environment, not
caused by or related to this change) with no new failures introduced.

Could not test the from_hp_da8w4 (DA8W4) path or any CUDA/XPU/NPU int4
tensor class, since this change only touches the CPU-only A16W4 path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@pytorch-bot

pytorch-bot Bot commented Oct 3, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/ao/4969

Note: Links to docs will display an error until the docs builds have been completed.

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 3, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant