-
Notifications
You must be signed in to change notification settings - Fork 552
Fix NVFP4 multi-GPU export device handling #2197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,8 @@ | |
| # limitations under the License. | ||
|
|
||
|
|
||
| from contextlib import contextmanager | ||
|
|
||
| import pytest | ||
| import torch | ||
| import torch.nn as nn | ||
|
|
@@ -102,6 +104,43 @@ def test_export_per_block_quantized_weight(): | |
| assert not hasattr(model.linears[2], quantizer_attrs.output_scale) | ||
|
|
||
|
|
||
| def test_export_quantized_weight_uses_weight_device_context(monkeypatch): | ||
| model = ToyModel(dims=[32, 32]) | ||
| mtq.quantize(model, mtq.NVFP4_DEFAULT_CFG, lambda m: m(torch.randn(1, 4, 32))) | ||
| linear = model.linears | ||
| entered = False | ||
|
|
||
| @contextmanager | ||
| def record_device_context(weight): | ||
| nonlocal entered | ||
| assert weight is linear.weight | ||
| entered = True | ||
| yield | ||
|
|
||
| monkeypatch.setattr( | ||
| "modelopt.torch.export.unified_export_hf.same_device_as", record_device_context | ||
| ) | ||
|
|
||
| _export_quantized_weight(linear, torch.float32) | ||
|
|
||
| assert entered | ||
|
Comment on lines
+107
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [SUGGESTION] This test asserts an implementation detail rather than behavior: it replaces The behavioral coverage already exists in |
||
|
|
||
|
|
||
| def test_export_quantized_weight_does_not_repr_input_quantizer(monkeypatch): | ||
| model = ToyModel(dims=[32, 256, 32]) | ||
| mtq.quantize(model, partial_fp8_config, lambda x: x(torch.randn(1, 4, 32))) | ||
| input_quantizer = model.linears[1].input_quantizer | ||
|
|
||
| monkeypatch.setattr( | ||
| input_quantizer, | ||
| "extra_repr", | ||
| lambda: pytest.fail("export should inspect is_enabled without formatting the quantizer"), | ||
| ) | ||
|
|
||
| _export_quantized_weight(model.linears[1], torch.float32, "weight") | ||
| assert hasattr(model.linears[1], "input_scale") | ||
|
Comment on lines
+129
to
+141
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [IMPORTANT TestCoverage] This test never reaches the line the PR changes, so it passes identically with and without the fix.
Why it matters: the PR's Testing section cites this test as the evidence for the Fix: drive a format that lands in the def test_export_quantized_weight_does_not_repr_input_quantizer(monkeypatch):
model = ToyModel(dims=[32, 256, 256, 32])
mtq.quantize(model, partial_w4a8_config, lambda x: x(torch.randn(1, 4, 32)))
input_quantizer = model.linears[2].input_quantizer
monkeypatch.setattr(
input_quantizer,
"extra_repr",
lambda: pytest.fail("export should inspect is_enabled without formatting the quantizer"),
)
_export_quantized_weight(model.linears[2], torch.float32, "weight")
assert hasattr(model.linears[2], "input_scale") |
||
|
|
||
|
|
||
| class QuantMoELinear(nn.Module): | ||
| def __init__(self): | ||
| super().__init__() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[SUGGESTION] Format-asymmetric coverage: this guard fixes
NVFP4QTensor.quantizeonly, while the sibling real-quantize paths keep the original exposure.Within this file the only current-device-sensitive allocation is
torch.ops.trtllm.fp4_quantize(_cast_fp4already keys its lookup tables offweight.deviceviaget_e2m1_bounds, and every other op derives its device frominput). The same class of exposure exists forMXFP8QTensor/FP8QTensor/INT4QTensorwhen reached throughTensorQuantizer.forward→_real_quantize, which — unlike_fake_quantize(tensor_quantizer.py:1205, already wrapped insame_device_as) — has no device guard:Adding
with same_device_as(inputs):aroundself._real_quantize(inputs)would make the compress/mtq.compresspath symmetric with fake-quant for every format in one place, rather than per-QTensor-class. Non-blocking, and it doesn't replace this hunk —to_quantized_weight()callsNVFP4QTensor.quantizedirectly, which is what the newtest_nvfp4_export_uses_input_deviceexercises.Minor: wrapping the whole body re-indents ~60 lines, which makes the actual one-line intent hard to see in the diff. An early
with same_device_as(input):scoped to just the trtllm branch (or anExitStack) would keep the diff surgical, per the "prefer simple, surgical changes" guidance in CONTRIBUTING.md.