feat(quantization): add native FP8 inference (serve_fp8_weight) for DenseGeneral and GMM v2 - #5207
Shuwen-Fang wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a native FP8 inference feature (quantization="serve_fp8_weight") to load and execute FP8 checkpoints directly on TPU without on-the-fly dequantization, yielding significant performance improvements. It adds native_fp8_dot_general utilizing qwix and updates DenseGeneral and MoE layers to bypass standard dequantization when this mode is active. Feedback on the changes highlights two critical runtime issues: a dimension mismatch in moe.py where per_expert_scale is incorrectly reshaped to 4D instead of 3D for a 3D kernel, and a potential ValueError in quantizations.py when unconditionally reshaping kernel_scale when its size is greater than 1.
| if kernel_scale.ndim != quantized_kernel.ndim: | ||
| kernel_scale = kernel_scale.reshape((1,) * quantized_kernel.ndim) |
There was a problem hiding this comment.
Unconditionally reshaping kernel_scale to (1,) * quantized_kernel.ndim when kernel_scale.ndim != quantized_kernel.ndim will raise a ValueError if kernel_scale has a size greater than 1 (e.g., a 1D per-channel or per-expert scale of size > 1). We should only reshape to all-1s if kernel_scale.size == 1. If kernel_scale.size > 1 and its leading dimensions match the kernel, we should expand it by appending trailing dimensions of size 1 to match the kernel's rank.
| if kernel_scale.ndim != quantized_kernel.ndim: | |
| kernel_scale = kernel_scale.reshape((1,) * quantized_kernel.ndim) | |
| if kernel_scale.ndim != quantized_kernel.ndim: | |
| if kernel_scale.size == 1: | |
| kernel_scale = kernel_scale.reshape((1,) * quantized_kernel.ndim) | |
| elif kernel_scale.ndim < quantized_kernel.ndim and quantized_kernel.shape[: kernel_scale.ndim] == kernel_scale.shape: | |
| kernel_scale = kernel_scale.reshape(kernel_scale.shape + (1,) * (quantized_kernel.ndim - kernel_scale.ndim)) |
ef0c61e to
ed2e194
Compare
…enseGeneral and GMM v2 - Implements quantization="serve_fp8_weight" to load and execute FP8 weights directly at inference time without on-the-fly dequantization. - DenseGeneral: Dispatches single-contraction GEMMs to native_fp8_dot_general using qwix.quantize (dynamic activation FP8 quantization) and qwix.dot_general, supporting per-tensor, per-channel, and block-wise scaling. - RoutedMoE: Dispatches per-tensor and per-channel FP8 MoE weights wrapped in qpl.QArray directly to gmm_v2 (tokamax v2 FP8 GMM kernel), completely eliminating weight dequantization. - linears.py: Guard against square 2D matrix broadcasting and handle 3D MoE per-expert scaling in dequantize_weight. - Adds comprehensive unit test suites in tests/unit/native_fp8_dot_general_test.py and tests/unit/nnx_decoders_test.py. - Validated on TPU v6e-8 with Qwen3.5-35B-FP8 per-tensor checkpoint, achieving 1.81x step speedup (471.59ms -> 259.80ms) and doubling operational intensity (1.11 -> 2.03 FLOP/byte).
ed2e194 to
7f1db21
Compare
Description
Implements native FP8 inference (
quantization="serve_fp8_weight") for Qwen3.5 (and other FP8-quantized architectures) to execute forward passes directly using quantized FP8 weights without on-the-fly dequantization (b/557372531).Previously, weights stored/loaded in FP8 were dequantized on-the-fly to BF16 prior to compute, causing substantial memory bandwidth and runtime overhead during inference and RL rollouts. This change allows weights to stay in FP8 and compute directly via native FP8 GEMMs across DenseGeneral (supporting per-tensor, per-channel, and block-wise scaling) and RoutedMoE (dispatching directly to tokamax v2 FP8 GMM kernel).
Benchmark & Performance Results
Evaluated on TPU v6e-8 (8 chips, GhostLite) using the per-tensor quantized
qwen3.5-35b-a3b-fp8checkpoint (/dev/shm/maxtext_qwen3.5_35b_fp8_pertensor_v3), running a standalone autoregressive decode forward loop simulating RL rollout forward inference (batch size = 1, sequence length = 64 tokens,scan_layers=True,attention="dot_product").Performance Summary
quantization="")quantization="serve_fp8_weight")XProf Trace Viewer Links
quantization=""):http://xprof.corp.google.com/?session_id=shuwenf-5004636990561406008
Session ID:
shuwenf-5004636990561406008quantization="serve_fp8_weight"):http://xprof.corp.google.com/?session_id=shuwenf-16130608832799170934
Session ID:
shuwenf-16130608832799170934Persisted GCS Traces:
gs://test-maxtext-output/shuwenf/xprof/20260911_run/xprof/baseline/gs://test-maxtext-output/shuwenf/xprof/20260911_run/xprof/diff/Tests
Unit Tests
python -m unittest tests/unit/native_fp8_dot_general_test.py(23/23 passed in 42.8s):self_attn,linear_attn, MoE expert gate/up/down).(embed, heads, head_dim)).DequantizeWeightPerChannelTest: square matrix 2D per-channel scaling & MoE 3D per-expert scaling.MoENativeFp8PerChannelTest: GMM v2 native FP8 execution with per-channelqpl.QArray.pytest tests/unit/moe_test.py -k "SparseMoeNativeGmmPerChannelAxisTest"(1/1 passed in 43.9s):RoutedMoE.python tests/unit/configs_value_test.py(4/4 passed in 7.2s):serve_fp8_weightconfiguration, FP8 weight requirement, Qwix mutual exclusivity, and fused MoE warning.python -m unittest tests/unit/nnx_decoders_test.py(53/53 passed in 91.9s):codespell,pyink,pylint(10.00/10), andyamllintall passed.Numerical Parity & Alignment (Native FP8 OFF vs. ON)
Evaluated full
NNXDecoderforward pass across 128 tokens (batch_size=4,seq_len=32,vocab_size=1024) on TPU v6e comparing Post-PR Native OFF (quantization=""/ dequantize) vs. Post-PR Native ON (quantization="serve_fp8_weight"):BUGS: b/557372531
Checklist