Repository navigation
Fix fp8 MoE on the sparse_matmul path - #4957
copybara-service[bot] merged 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces safety checks and integration tests for Mixture of Experts (MoE) models using the sparse_matmul path with FP8 quantization. It adds a warning log when FP8 quantization is combined with sparse_matmul because the expert matmuls remain unquantized on this path. It also updates get_quantization_dtypes to safely access quant_dg using getattr to prevent errors when using FP8 schemes. Finally, it adds integration tests to ensure that the MoE layer builds and trains successfully under these configurations. There are no review comments, so I have no feedback to provide.
6926628 to
2bc82fd
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Codecov flagged one partial on the patch. The two fp8 tests only ever take the branch where there is no |
|
Hi @gulsumgudukbay, Here is the intended behavior:
I accidentally changed Behavior 3: #4451 (assert against empty gmm qwix rule) and #4842 (allow non- |
Thanks @shuningjin, that's very helpful. I had this on the wrong side of behavior 1. just pushed 5bcaf7f, which changes the fix.
On restoring behavior 3, it seems to me like the gmm dtypes already satisfy it, since
|
| @@ -288,6 +317,21 @@ def test_gpu_fp8(self): | |||
| def test_gpu_nanoo_fp8(self): | |||
| train_main(TrainTests.CONFIGS["nanoo_fp8"] + ["attention=dot_product"]) | |||
|
|
|||
| # No hardware marker: the fp8 schemes do not reach the expert matmuls on this path on any | |||
There was a problem hiding this comment.
I'm surprised this is true - isn't train_main being invoked which will run on the backend?
There was a problem hiding this comment.
sorry you are right, that comment was stale it predates 5bcaf7f, where fp8 stopped falling past the gmm quantization and started feeding it a dtype like every other scheme. i fixed this in 664b181, along with the int8 one next to it.
regarding the marker: conftest auto-marks unmarked tests cpu_only and skips those on accelerator testbeds, so these only run in the CPU job, which is fine/enough because the failure is an attribute read during tracing, and fp8 is emulated in XLA, so the quantized gmm still runs under megablox's interpret mode.
664b181 to
cb96f5d
Compare
| @@ -288,6 +317,22 @@ def test_gpu_fp8(self): | |||
| def test_gpu_nanoo_fp8(self): | |||
| train_main(TrainTests.CONFIGS["nanoo_fp8"] + ["attention=dot_product"]) | |||
|
|
|||
| # No hardware marker, so conftest runs these cpu_only. The failure they cover is an attribute | |||
There was a problem hiding this comment.
no, it is not a typo, conftest.py is the pytest file that auto-marks unmarked tests cpu_only. i just reworded now for it to say so properly, since "conftest runs these" was misleading anyway. it marks them, and the CPU job is what runs them sorry for the confusion.
cb96f5d to
ad68c32
Compare
`get_quantization_dtypes` reads `self.quant.quant_dg` whenever a quantization is set, but only AQT carries one, so a MoE model with `sparse_matmul=True` dies before it ever reaches the gmm: fp8 AttributeError: 'Fp8Quantization' object has no attribute 'quant_dg' nanoo_fp8 AttributeError: 'NANOOFp8Quantization' object has no attribute 'quant_dg' Since `sparse_matmul=True` is the default, this is what a user hits first when combining fp8 with a MoE model. `quant_dg` is how AQT spells its numerics, not what the gmm needs. What the hand-crafted rule wants is a forward dtype, which the fp8 schemes can simply name, so they do: float8_e4m3fn for fp8 and float8_e4m3fnuz for nanoo_fp8. The read becomes "AQT describes itself through a `quant_dg`, everyone else names a dtype", and the gmm ends up quantized for every scheme rather than only for AQT. That keeps the three intended behaviours intact. With `use_qwix_quantization=false` every scheme reaches the hand-crafted rule, fp8 included, where it used to crash. With qwix and `fp8_full` the gmm takes the qwix rule, untouched. With qwix and any other scheme `configure_quantization` returns None, so there is no dtype to hand over and the gmm is left alone, which is what a scheme declaring no gmm rule should get. `SparseMatmulQuantizationTest` asserts the dtypes the grouped matmul is actually handed across all six cases, including the two qwix ones that must stay None; it intercepts `mblx.gmm`, so it costs no kernel time. int8 is in there because it is the scheme whose dtypes reach the gmm, covering the `quant_dg` branch the fp8 cases never take. The integration tests take the MoE model the dense-path tests already use and put it back on sparse_matmul with megablox, so the rule is built and used for real rather than falling back to ragged_dot -- which is also what confirms qwix accepts the fnuz dtype AMD needs. A downscaled model needs tiles small enough to divide its operands, hence the tile overrides. They carry no hardware marker, for the same reason the dense ones do not: the failure is an attribute read during tracing rather than anything a kernel does, and fp8 is emulated in XLA, so the quantized gmm still runs on CPU through megablox's interpret mode. The fp8 cases fail before this change.
ad68c32 to
b5cb2dc
Compare
d3197ae
into
AI-Hypercomputer:main
Problem
Any MoE model with
sparse_matmul=Trueand an fp8 quantization crashes while the layer is being traced:get_quantization_dtypesreadsself.quant.quant_dgwhenever a quantization is configured, butquant_dgis an AQTnotion — the fp8 classes have never had one. Since
sparse_matmul=Trueis the default, this is what a user hits firstwhen combining fp8 with a MoE model.
Fix
quant_dgis how AQT spells its numerics, not what the gmm needs. All the hand-crafted rule wants is a forward dtype,which the fp8 schemes can simply name, so they do:
float8_e4m3fnforfp8andfloat8_e4m3fnuzfornanoo_fp8. Theread becomes "AQT describes itself through a
quant_dg, everyone else names a dtype", and the gmm ends up quantized forevery scheme rather than only for AQT.
This keeps the three intended behaviours intact, per @shuningjin in the thread below:
use_qwix_quantization=false: every scheme quantizes the gmm through the hand-crafted rule. That now includes thefp8 schemes, which previously crashed here.
use_qwix_quantization=truewithfp8_full: the gmm takes the qwix rule, untouched by this change.use_qwix_quantization=truewith any other scheme:configure_quantizationreturnsNone, so there is no dtype tohand over and the gmm is left unquantized, which is what a scheme declaring no gmm rule should get.
An earlier revision of this PR read
quant_dgdefensively, which stopped the crash but left fp8 with an unquantizedgmm and a warning saying so. That was the wrong side of case 1, so both are gone.
Tests
SparseMatmulQuantizationTestinmoe_test.pyasserts thelhs_quantize_dtypeandrhs_quantize_dtypethe groupedmatmul is actually handed, across all six cases above:
fp8,nanoo_fp8,int8, unquantized, andfp8/int8underqwix, the last two of which must stay
None. It interceptsmblx.gmm, so it costs no kernel time. The two fp8 casesfail without this change.
Three integration tests on a tiny Mixtral with
sparse_matmul=Truecoverfp8,nanoo_fp8andint8end to end, andthey run megablox rather than
ragged_dotso the rule is built and used for real — which is also what confirms qwixaccepts the fnuz dtype AMD needs. A downscaled model needs tiles small enough to divide its operands, hence the tile
overrides. The two fp8 ones fail before this change, and the three take about 33s on CPU. No hardware marker: the fp8
schemes are emulated in XLA and the failure is backend independent.
Related
dense_matmulpath, where every quantization fails for an unrelated reason(Linen einsums with no scope to bind to after the NNX migration). The two are independent, but together they make
quantized MoE work on both paths. There is a small textual overlap in
tests/integration/train_tests.pyandtests/unit/moe_test.py; happy to rebase whichever lands second.Checklist
Before submitting this PR, please make sure (put X in square brackets):
gemini-reviewlabel.