Share one RNG stream across the bridged NVFP4 quantizers - #5160
Share one RNG stream across the bridged NVFP4 quantizers#5160ecnal-cienet wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request adds Gemma2-2b tokenizer configurations and refactors RNG handling in quantization backends by replacing the boolean needs_apply_rngs with an enum ApplyRngs (NONE, SHARED, PRIVATE). This change allows sharing RNG streams across unrolled layers to optimize state usage. Feedback on the changes suggests updating the type signature of the new share_rngs method in nnx_wrappers.py to accept Rngs | jax.Array | None to prevent potential static type-checking failures.
| """ | ||
| self.to_nnx__rngs = None | ||
|
|
||
| def share_rngs(self, rngs: Rngs): |
There was a problem hiding this comment.
The share_rngs method is annotated to accept only Rngs. However, ToNNX.__init__ accepts Rngs | jax.Array | None. To prevent static type-checking failures (e.g., with pytype or mypy) when rngs is None or a jax.Array, the type signature of share_rngs should be updated to match.
| def share_rngs(self, rngs: Rngs): | |
| def share_rngs(self, rngs: Rngs | jax.Array | None): |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
a304ead to
215adb0
Compare
TransformerEngine draws sr_rng for the NVFP4 DGRAD quantizer, so the bridge cannot drop its fork the way it does for the other recipes. It was keeping one fork per wrapper, which the unrolled decoder pays for per layer: llama3-8b te_nvfp4_no_rht carries 1351 u32 entry parameters against 7 for te_fp8_currentscaling. TE folds a per-quantizer hash into whatever it draws, so the wrappers do not need streams of their own. Replaces the needs_apply_rngs boolean with ApplyRngs, which distinguishes a backend that never draws from one that draws but can share, and adds ToNNX.share_rngs. AQT and Qwix keep their own streams, unchanged. __init__ still forks before the wrapper switches to the shared Rngs, so the caller's streams advance exactly as before and parameter initialization is unchanged.
215adb0 to
a2b6678
Compare
Description
PR #5027 stopped the Linen to NNX bridge keeping a forked
Rngsin every wrapper, which the unrolled decoder pays for once per layer. NVFP4 was left out: TransformerEngine drawssr_rngfor the DGRAD quantizer, so dropping the fork there raisedInvalidRngError. It still carries the whole cost, 1351 u32 entry parameters on llama3-8bte_nvfp4_no_rhtunrolled against 7 forte_fp8_currentscaling.TE folds a per-quantizer hash into whatever it draws, so the wrappers never needed streams of their own, and NNX stores a shared
Rngsonce however many modules reference it.needs_apply_rngsbecomesApplyRngs. A boolean could only say whether a backend draws. The enum separates one that never draws,NONE, from one that draws but does not need its own stream,SHARED.PRIVATEstays the default, and AQT and Qwix stay on it.ToNNX.share_rngsswaps the fork for the caller'sRngs.__init__still forks first, so the caller's streams advance as before and parameter initialization is unchanged.Tests
llama3-8b unrolled, 8xH100, entry parameters from
--xla_dump_to. NVFP4 kernels need Blackwell, but compilation succeeds on H100.887 / 7 is what
te_fp8_currentscalingalready gets, so nothing scales with the layer count. Throughput for NVFP4 itself needs GB300.te_fp8_currentscalingforced onto the shared path, to exercise it where it can run: 21 steps, loss 12.258 to 1.977 to 0.153, median 0.6570s, its usual numbers.scan_layers=Falseis expected to be the faster of the two. Both configs of a recipe, back to back on one node:te_fp8_currentscalingte_fp8_delayedscalingfp8fp8stayed on that side over two more repeats, so removing the per-layer state does not on its own reorder every recipe.te_mxfp8fails on H100 incublas_gemm, on main as well.Nine configurations against main across llama3-8b, llama2-7b, gemma2-2b, deepseek2-16b and gpt-oss-20b, covering each enum value plus the
moe.pycall site and the attention-sinks path: every loss lands inside main's own run-to-run range, which is 0.002 wide when one configuration is repeated on one image.SharedRngStateTestpins what this is for: a shared backend costs the same at 1, 2 or 8 layers, with a private backend alongside so it cannot pass by measuring nothing.Checklist
Before submitting this PR, please make sure (put X in square brackets):
gemini-reviewlabel.