Repository navigation
[PyTorch] Fix RecursionError for ops on CPU-resident QuantizedTensors - #3812
Conversation
[Medium impact] The PR appears safe to merge; no new actionable issue was found. SummaryPrevents recursive dequantization when CPU-resident quantized tensors reach plain PyTorch operations.
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[PyTorch operation on CPU quantized tensor] --> B[Dequantize arguments]
B --> C[MXFP8 or NVFP4 moves buffers to CUDA]
C --> D[Handle aten.to overload directly]
D --> E[Rebuild quantized tensor with CUDA buffers]
E --> F[Dequantize on CUDA]
F --> G[Return dense result to CPU]
Reviews (6) · Last reviewed commit: "Merge branch 'main' into fix/cpu-quantiz..." · Reviewed by Greptile |
_FromNVFP4Func.forward() and _FromMXFP8Func.forward() call tensor.to(device=cuda) on the full QuantizedTensor when dequantizing a CPU-resident quantized tensor. This triggers __torch_dispatch__, which calls maybe_unwrap -> dequantize() -> forward() -> tensor.to() -> __torch_dispatch__ in an infinite loop. Move the raw inner data tensors to CUDA individually via get_metadata() and construct a plain TensorStorage for the tex.dequantize call. Plain torch.Tensor.to() on the inner buffers does not enter __torch_dispatch__, breaking the cycle. Fixes NVIDIA#3294 Signed-off-by: Siddhant Rajhans <51508945+siddhant-rajhans@users.noreply.github.com>
- MXFP8Quantizer requires fp8_dtype argument; pass kFloat8E4M3 - Skip NVFP4/MXFP8 tests on GPUs that lack support via is_nvfp4_available() / is_mxfp8_available() Signed-off-by: Siddhant Rajhans <51508945+siddhant-rajhans@users.noreply.github.com>
bf75cd5 to
1066e76
Compare
|
Thanks for the PR. There is a previously existing PR #3279 from @janekb04 to fix this problem. I think that PR also handles copy implementation for generic quantized_tensor. We also want to keep the test in test_quantized_tensor.py rather than introduce a new test file. Could you please use that as reference and modify this PR? Although, I dont see fix/implementation for Float8Tensor and Float8BlockWiseTensor there. My guess is that base implementation in QuantizedTensor already handles the copy method for them. And this makes me wonder whether we even need the standalone implementations for MXFP8/NVFP4. And base QuantizedTensor's implementation should be able to handle it by itself for all QuantizedTensor. |
Calling .to() on a QuantizedTensor from inside __torch_dispatch__ reaches the dispatcher as an aten.to.* overload rather than aten._to_copy, because the CompositeImplicitAutograd decomposition is skipped there. Those overloads fell through to the dequantizing fallback, so an op such as `x_cpu + 0` on a CPU-resident MXFP8 or NVFP4 tensor recursed: the fallback dequantizes, dequantization moves the tensor to the GPU with .to(), and .to() dispatches back into the fallback. Handle the aten.to.* overloads like aten._to_copy in the base class, as proposed in NVIDIA#3279. This replaces the MXFP8- and NVFP4-specific buffer copies from the earlier commits of this PR, and the test moves into test_quantized_tensor.py, parametrized over all quantization formats. Co-authored-by: Jan Bielak <jbielak@nvidia.com> Signed-off-by: Siddhant Rajhans <51508945+siddhant-rajhans@users.noreply.github.com>
|
Thanks for pointing me to #3279. I reworked the PR along those lines in f9380ee:
On your question: as you suspected, the MXFP8/NVFP4-specific changes aren't needed with the base-class fix, so I dropped them. The recursion comes from the base class. When the dequantizing fallback in I don't have a Blackwell GPU, so I couldn't run the MXFP8/NVFP4 cases here (details in the description). Could you trigger CI when you get a chance? |
f9380ee to
52c5faa
Compare
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Signed-off-by: vthumbe1503 <vthumbe@nvidia.com>
|
/te-ci pytorch |
Description
Ops on a CPU-resident
QuantizedTensorthat have no quantized implementation (for examplex_cpu + 0, ortorch.testing.assert_close(x_cpu, ref)) fail withRecursionErrorfor MXFP8 and NVFP4 tensors.QuantizedTensor.__torch_dispatch__dequantizes the arguments of such ops. MXFP8 and NVFP4 dequantize withtex.dequantize, which needs GPU buffers, so for a CPU tensor they first calltensor.to(device=...). Inside__torch_dispatch__the autograd keys are excluded, so that call reaches the dispatcher asaten.to.dtype_layoutinstead of being decomposed intoaten._to_copy. Onlyaten._to_copywas handled, so the call fell back into the dequantizing path and recursed. FP8 and FP8 block scaling are not affected because they dequantize the CPU buffers directly.The fix follows #3279 by @janekb04.
Fixes #3294
Type of change
Changes
QuantizedTensor.__torch_dispatch__handles theaten.to.*overloads the same way asaten._to_copy: the subclass is preserved and the internal buffers move to the target device.test_cpu_torch_opsintests/pytorch/test_quantized_tensor.py, parametrized over all quantization formats: arithmetic andassert_closeon a CPU tensor,.float(), and a round trip back to CUDA.Checklist:
I don't have a Blackwell GPU, so I could not run the MXFP8 and NVFP4 cases locally. I reproduced the recursion with a minimal wrapper subclass on CPU (handling only
aten._to_copyrecurses onx + 0; handling theaten.to.*overloads as well gives the expected results), and the changed files pass black and pylint with the repo's configuration.