Repository navigation
[OMNIML-6062] Dequantize block-FP8 checkpoints as they load expert-parallel - #2743
shengliangxu wants to merge 5 commits into
Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## shengliangx/hfep-6b-ep-dense-sharding #2743 +/- ##
=========================================================================
+ Coverage 61.83% 61.86% +0.03%
=========================================================================
Files 649 649
Lines 72969 73059 +90
=========================================================================
+ Hits 45117 45195 +78
- Misses 27852 27864 +12
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
6eab845 to
d6cb219
Compare
f354b3b to
ed1ebbf
Compare
|
|
/ok to test ed1ebbf |
d6cb219 to
a5aecef
Compare
ed1ebbf to
6db2eb0
Compare
|
/ok to test 6db2eb0 |
a5aecef to
e8384c3
Compare
6db2eb0 to
6bcb1ca
Compare
e8384c3 to
ffbb850
Compare
6bcb1ca to
14f1610
Compare
|
/ok to test 14f1610 |
14f1610 to
f647855
Compare
|
/ok to test f647855 |
f647855 to
a40c380
Compare
a40c380 to
d407931
Compare
|
@kevalmorabia97, an update: the GPU path you couldn't run passed at c44d5c1, on GB200 with torch 2.11.0a0 and transformers 5.9.0. That commit includes the packed-FP4 fix.
This head, 54430ac, is only a rebase onto #2742's two new commits, and this PR's own diff is unchanged. The tied-head question above is still open: a traceback and your torch, accelerate and safetensors versions would let me reproduce it. |
|
/ok to test 54430ac |
kevalmorabia97
left a comment
There was a problem hiding this comment.
I reviewed this independently, with a focus on block-FP8 dequant correctness, per-rank expert and dense slices, and memory during load. It looks good to me at 54430ac.
- FP4 experts on transformers 5.9 (fixed in 54430ac). At e5ded19, the pre-5.10.1 override read packed-FP4 (int8) expert weights as plain integers and returned them at half width, with no error. Transformers 5.9's own dequant does unpack FP4, and DeepSeek-V4 stores its routed experts that way under a block-FP8 config. At this head, any weight that is not fp8 goes to transformers' own dequant. I checked on 5.9.0: a (256, 512) FP4 weight now matches stock up to bf16 rounding.
- The dequant matches a blockwise reference for a partial last block, a weight narrower than one block, and E8M0 scales.
- Under expert parallelism, each expert's weight and scale get the same expert index and are taken whole, and an expert held on another rank drops both. The dense part stays replicated for block FP8, both in the plan and in the loader's refusal, so a scale never meets a sharded tensor.
- Memory is about 6 B/elem per weight on the pre-5.10.1 path, and the 5.10.1+ path pads in fp8.
- Mutation checks: each of four deliberate breaks fails tests (the expert fuse, block-size choice, per-tensor refusal, partial-column scaling).
Ran on CPU (transformers 5.9.0, torch 2.12): test_fp8_dequant_patch, test_ep_loader, test_model_load_utils and test_ep_load_warmup at 54430ac (204 passed).
|
/ok to test a73d523 |
|
@kevalmorabia97 thanks for the re-review and the mutation checks. Your green run answers my tied-head question. This head, a73d523, only rebases onto #2742's docstring commit, so this PR's own diff is unchanged. |
|
/ok to test 09e3d73 |
|
/ok to test c884757 |
|
/ok to test 9c917ae |
|
/ok to test 7192e05 |
|
Blocker (probable): the block-FP8 path is admitted on transformers 5.7, where none of this PR's fixes install.
What 5.7 does instead: Who hits it silently: a block-FP8 MoE whose dimensions are all multiples of 128 (e.g. a Qwen3-MoE FP8 checkpoint). DeepSeek-V3 fails loudly instead, since 5.7's inline dequant raises on (576, 7168). Tests/GPU runs here cover 5.5, 5.9 and 5.18 only; the author's replies name 5.8-5.10.0 as the releases the fuse installs on, and 5.7 has not been discussed. I traced this from the transformers sources and did not run it, so confidence is medium. Suggested fix: require transformers >= 5.8 for |
|
/ok to test 7c83230 |
|
/claude review |
|
@juhi10071998 thanks, confirmed and fixed in 7c83230. transformers 5.7.0 has neither The expert-parallel load now refuses a block-fp8 checkpoint below transformers 5.8, inside the load's checks guard, so every rank refuses it before anything loads. |
| raw = json.load(f) | ||
| except (OSError, ValueError): | ||
| return None | ||
| for c in (raw, raw.get("text_config"), raw.get("llm_config")): |
There was a problem hiding this comment.
[SUGGESTION] raw.get(...) is evaluated eagerly to build the tuple, so a config.json whose top-level JSON is not an object escapes the guard below it. json.load accepts [], "x" or 3 as valid JSON, and the isinstance(c, dict) check on the next line only protects the inner levels — the two raw.get calls run first and raise AttributeError: 'list' object has no attribute 'get'.
Why it matters: the function is deliberately defensive (except (OSError, ValueError) → None) precisely so an unreadable or malformed config.json degrades to the "fp8-unverified" refusal with its actionable message. A top-level non-object skips that and surfaces a bare AttributeError from inside _ckpt_quant_method, on the path that now runs for every expert-parallel load that reads an FP8 config object — the one place the user has no hint what went wrong. Low likelihood, but the fix is one line and keeps all malformed-config cases on the same route.
| for c in (raw, raw.get("text_config"), raw.get("llm_config")): | |
| if not isinstance(raw, dict): | |
| return None | |
| for c in (raw, raw.get("text_config"), raw.get("llm_config")): |
There was a problem hiding this comment.
Taken in 5000023: _config_json_quantization now returns None for a config.json whose top level isn't a JSON object, so an FP8 config object meets the same fp8-unverified refusal as with an unreadable file. test_an_fp8_config_object_is_read_against_the_checkpoints_own_config gained a config-json-not-an-object row ([]), which fails without the check with the AttributeError you describe.
|
Claude review — block-FP8 dequant on the expert-parallel load Scope: full review (the trigger comment carried no scoping instructions). All 3 changed files reviewed against Findings: CRITICAL: 0, IMPORTANT: 0, SUGGESTION: 1 What changed since the last approved round The source diff is the previously reviewed arithmetic plus four deltas, all of which I re-derived:
Verified fresh this round (no finding; recorded so it isn't re-derived)
Suggestion (non-blocking, 1 inline)
What reads well
Risk Low. Additive and reached only on the expert-parallel load of a block-FP8 checkpoint; every path that could load wrong weights silently (empty grid, unplaceable grid, non-fp8 dtype, per-tensor scales, an unfusable expert converter) raises instead, and each is pinned by a test the author reports fails without the fix. The remaining item is a robustness one-liner on a malformed-config path. 🤖 Generated with Claude Code |
|
/ok to test 5000023 |
|
/ok to test 80cabec |
|
/ok to test 76e0338 |
|
/ok to test 7540a2e |
|
/ok to test b1cec70 |
|
/ok to test 6f5391d |
|
/ok to test 8787a4d |
|
/ok to test c032300 |
A native block-FP8 checkpoint, such as DeepSeek-V3's, is dequantized to bf16 while it loads expert-parallel, through transformers' FineGrainedFP8Config(dequantize=True); on transformers older than 5.10.1 the loader patches that dequantization for a partial last block, the destination dtype and the routed experts' fused converter. A block-FP8 checkpoint keeps its dense part replicated, since DCP reads it as stored. A per-tensor FP8 checkpoint, which names no weight_block_size, is refused, and so is an FP8 config object whose checkpoint has no config.json to say which it is. This is the PR's 25 commits in one, re-parented with #2742 onto the expert-parallel loader's [2/3]: the FP8 refusals, with their read of the checkpoint's config.json, run inside the loader's checks guard, so a rank refusing alone fails every rank. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Below transformers 5.10.1 the block-FP8 override replaced the dequant for every weight. From 5.9, transformers' own dequant unpacks packed FP4 (float4_e2m1fn_x2, or int8) with [1, 32] scales, as DeepSeek-V4 stores its routed experts under a block-FP8 config; the override read each packed byte as one value and returned the weight at half its width, without an error. It now hands any weight that is not fp8 to transformers' dequant, in the model dtype, as the 5.10.1 wrapper does, and the docstring no longer says these releases cannot unpack FP4. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
test_fp8_dequant_patch.py covers model_load_utils' block-FP8 dequant patch alone, so its tests join that module's test file, as CONTRIBUTING.md asks, as one section with their fixtures, marks and parametrization. Its module-level skip on a torch without float8_e4m3fn becomes a mark on its tests. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The expert-parallel load takes transformers 5.7, but the hooks block FP8 is dequantized through, Fp8Dequantize._dequantize_one and the FP8 quantizer's update_weight_conversions, first appear in 5.8. On 5.7 the experts' converter claims their fp8 weights before the dequant can, so a block-FP8 MoE whose dimensions divide into whole blocks (Qwen3-MoE's FP8, say) would load its experts unscaled. The load now refuses block FP8 below 5.8, before anything loads. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
… block _config_json_quantization returns None for a config.json it cannot read, so an FP8 config object meets the load's fp8-unverified refusal. A config.json holding valid JSON other than an object reached .get first and raised AttributeError instead; it now takes the same route. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
|
/ok to test 939bf6e |
Note
Re-parented on 2026-10-30 with #2742 onto #3136, #2741 [2/3] (the expert-parallel loader). #2741 was split into three PRs on review: #3131 [1/3], #3136 [2/3] and #2741 [3/3], its post-load refusals. This PR stays on #2742, whose dense-sharding rule it extends. The series first became a tree on 2026-10-27 (this PR was [11/21]).
Base: #2742. The diff below is this PR's alone, and it merges after #2742.
Re-parent: this PR's diff is unchanged, except that its FP8 refusals run inside #3136's checks guard, so a rank refusing alone fails every rank. That includes the per-tensor and unverifiable FP8-config refusals, with their read of the checkpoint's
config.json. The PR's 25 commits are condensed into one.Series layout
main([OMNIML-6073] [2/3] Quantize fused experts under grouped_mm; leave transposed layouts unquantized #2745, [OMNIML-6064] Load each rank's checkpoint shard straight into a meta-built model #2739, [OMNIML-6052] Add the distribution plan, a run-scoped process group, and collectives that fail together #2736, [OMNIML-6054] Decide how a model is distributed, from the model built on the meta device #2737, [OMNIML-6082] [1/3] Let a reverse conversion record a merge rule no key matched #3100, [OMNIML-6082] [2/3] Move each tensor of a sharded model whole to one owner rank #3101, [OMNIML-6081] [1/2] Pack sharded weights in FSDP2 weight-update windows, and NVFP4 DTensor shards #3099, [OMNIML-6073] [1/2] Calibrate and fold expert-parallel fused experts on each rank's own experts #3122, [OMNIML-6060] [1/3] Add require_transformers, and build a VLM without its LM head #3131 [1/3] and [OMNIML-6082] [3/3] Write a sharded model's HF checkpoint shards from each tensor's owner rank #2751 have merged)shengliangx/hfep-base-6a-ep-loader= [OMNIML-6054] [2/2] Route load_pretrained by the distribution plan #2779 + [OMNIML-6073] Quantize Nemotron-H's experts under grouped_mm, with their wrapper in its model folder #3067 + [OMNIML-6071] Calibrate a distributed model on per-rank data #2740What does this PR do?
Type of change: New feature
A native block-FP8 checkpoint, such as DeepSeek-V3, is dequantized to bf16 while it loads expert-parallel, through transformers'
FineGrainedFP8Config(dequantize=True). Quantizers attach to bf16 weights.A block-FP8 checkpoint keeps its dense part replicated (moved here from [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742, where block FP8 did not load at all): DCP, which loads a sharded dense part, cannot dequantize it: it reads the checkpoint as stored. Left to decide, the plan ([OMNIML-6054] Decide how a model is distributed, from the model built on the meta device #2737) replicates it and says so; an explicit
fsdpis refused by the decision, namingMODELOPT_FSDP_SIZE=1, and by the loader.Per-tensor FP8 is refused. AutoFP8's per-tensor checkpoints name the same
quant_methodwithout aweight_block_size; transformers' dequant would read them as 128x128 blocks and load their weights unscaled, without an error. The load refuses them, before anything loads, asfp8-per-tensor, naming theweight_block_sizethey lack. A config handed in as transformers' FP8 config object, which fills in a 128x128 block, is read against the checkpoint's ownconfig.json, and refused when the path holds none to read (a Hub ID the load has not made local yet). A tiny per-tensor FP8 Llama, loaded that way on transformers 5.18:On transformers older than 5.10.1, the loader also patches that dequantization in three ways:
kv_a_proj_with_mqa: (576, 7168) with a (5, 56) grid. It also raises on an empty scale grid. The patch tiles each block's scale and crops to the weight, using the checkpoint'sweight_block_size.MergeModulelist. The loader then assigns no per-expert index, and the expert-parallel shard slices each expert's rows instead of whole experts: a DeepSeek-V3gate_up_projloads as (256, 32, 7168) and fails calibration. The patch fuses the dequantization into the merge, so the converter starts with aMergeModulelistagain and runs as upstream intends.The patch installs once and survives a re-imported module. From transformers 5.10.1 it keeps upstream's dequantization, which takes the destination dtype and decodes E8M0 scales, and only pads a weight with a partial last block to whole blocks for it, in its own fp8 dtype, since upstream still infers the block from the scale grid: (448, 7168) under a (4, 56) grid would otherwise be scaled in blocks of 112 rows.
The pre-5.10.1 dequantization scales its own fp32 copy of the weight in place, through a view of its whole column blocks, against scales tiled over the rows only, so no full-size fp32 tile is built. It is rebound only once the expert converter's fuse is in, so a refused patch leaves transformers as it was. Against scaling by a full-size fp32 tile, on CPU with one thread:
Usage
Automatic:
load_and_prepare_epreads the checkpoint'squantization_configand picks this path forquant_method: fp8with aweight_block_size.Testing
In
tests/unit/torch/utils/test_model_load_utils.py(first written astest_fp8_dequant_patch.py), on CPU:MergeModulelist, and per-expert block-FP8 weights come out as the blockwise reference. It fails without the fusion, and if the fused op skips the dequantization or the merge. Skipped on transformers 5.5, which has noupdate_weight_conversions.Mutation-checked: each test fails on at least one deliberate break of the code.
The block-size cases join the blockwise-reference table, and dtype and version cases that took the same branch as a kept one are dropped.
A refused fuse leaves transformers' dequant as it was.
The block-FP8 dense layout: the loader refuses a plan that shards the dense part (
test_model_load_utils.py); the decision replicates it, or refuses an explicitfsdp, namingMODELOPT_FSDP_SIZE=1(test_model_load_utils.py).Per-tensor FP8 refused before anything loads and read as
fp8-per-tensor(test_ep_loader.py; both rows fail without the fix); the block-FP8 configs in the tests carry theirweight_block_size.Locally on this revision, the stack rebased on main c6fa613: pre-commit over the whole PR against main, at every PR of the stack; and at the stack's tip the full
tests/uniton transformers 5.18 and 5.5 and on torch 2.8, and the hf_ptq example tests, each with the same failures as main in that environment. CI runs the full unit suite.GPU results at a named head are under "GPU runs, 2026-10-30" at the end of this section; the other GPU results here were measured on earlier revisions of this branch.
On GPUs: DeepSeek-V3 (block FP8) loads, calibrates and exports expert-parallel through this path (see [OMNIML-6085] [3/3] Run expert-parallel PTQ from hf_ptq.py #2754).
Re-cut on 2026-10-27 (see the note at the top): pre-commit, mypy included, passes at this head over the whole change against main, and the series' PRs merged together give a tree byte-identical to the former stack's tip. This PR's own unit tests pass at this head on CPU (torch 2.14, transformers 5.18); CI runs the full unit suite.
Review fixes, 2026-10-27: a dequant dtype the config names as a string is read as the dtype it names; the test file's dead
Fp8Dequantizeskip is gone. Each behaviour fix has a test that fails without it; pre-commit, mypy included, and this PR's own unit tests pass at this head.Review fixes, 2026-10-27, round 2: the block-FP8 case of the replicated-dense-part test expects the out-of-memory warning [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 now gives when the plan replicates a dense part it cannot shard. Pre-commit, mypy included, and this PR's own unit tests pass at this head.
Review fixes, 2026-10-27, round 3: the pre-5.10.1 dequant override takes the destination dtype the loader passes (5.10.1 is the first release to pass one); a partial-block weight is padded in its own fp8 dtype; the 5.10.1 wrapper does not wrap itself on a re-import; a test pins that transformers' config merge keeps the checkpoint's
weight_block_size; a comment says why the block-fp8 dense rule applies under expert parallelism only; the per-tensor FP8 refusal names the missingweight_block_size. Each behaviour fix has a test that fails without it; pre-commit, mypy included, and this PR's own unit tests pass at this head.Review fixes, 2026-10-27, round 4: a per-tensor FP8 checkpoint whose config comes in as transformers' FP8 config object is refused too, its block read from the checkpoint's own
config.json; for a block-fp8 checkpoint, the several-replicas remedy names onlyMODELOPT_FSDP_SIZE=1; both dequant patches check a weight's dims before unpacking its shape, and the pre-5.10.1 override drops an FP4 branch it could not run. Each behaviour fix has a test that fails without it; pre-commit, mypy included, and this PR's own unit tests pass at this head.Review fixes, 2026-10-27, round 5: an FP8 config object whose checkpoint has no readable
config.json(a Hub ID not yet made local) is refused rather than read as 128x128 blocks; for a block-fp8 dense part within several replicas the remedy also names starting from an unquantized copy. Each behaviour fix has a test that fails without it; pre-commit, mypy included, and this PR's own unit tests pass at this head.Rebased on 2026-10-27 onto main 7c3c868, after [OMNIML-6064] Load each rank's checkpoint shard straight into a meta-built model #2739 merged: pre-commit, mypy included, and this PR's own unit tests pass at this head, and the series' PRs merged together add no unit-test failure over main (tests/unit/torch utils, export, quantization and models, on transformers 5.18 and 5.5.4 and on torch 2.8).
Re-cut on 2026-10-29 (see the note at the top): rebased onto the re-cut [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 with no source conflict. Its edits of the deleted
test_resolve_distribution_plan.pymoved totest_model_load_utils.py. Pre-commit, mypy included, passes at this head over the whole change against main. On CPU at this head (torch 2.14), this PR's tests withtest_ep_dense_sharding.py,test_ep_load_warmup.py,test_ep_loader.py,test_meta_dcp_load.pyandtest_model_load_utils.pypass on transformers 5.18 and 5.5.4.Rebased 2026-10-29 onto [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742's review fixes (0350840) with no conflict; this PR's own diff is unchanged. Pre-commit, mypy included, passes against main, and
tests/unit/torch/utilspasses on CPU at this head on transformers 5.18 and 5.5.4.Re-parented 2026-10-30 with [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 onto [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 ([OMNIML-6060] [3/3] Refuse an expert-parallel load that would calibrate the experts wrong #2741 [2/3]). The FP8 refusals now run inside the loader's checks guard. Pre-commit, mypy included, passes against main, and
tests/unit/torch/utilspasses on CPU at this head, on transformers 5.18 and 5.5.4.Rebased 2026-10-30 with [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 onto main 99c3834. The block-FP8 load notice now prints through
print_rank_0, since [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742's loader no longer importsis_master; the rest of this PR's diff is unchanged. Pre-commit, mypy included, passes against main, andtests/unit/torch/utilspasses on CPU at this head, on transformers 5.18 and 5.5.4.Rebased 2026-10-30 with [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 onto [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 96be72d; this PR's own diff is unchanged. Pre-commit, mypy included, passes against main, and
tests/unit/torch/utilspasses on CPU at this head, on transformers 5.18 and 5.5.4.Review fix, 2026-10-30 (kevalmorabia97's review): below transformers 5.10.1, the block-FP8 override hands any weight that is not fp8 to transformers' own dequant, in the model dtype. From 5.9 that dequant unpacks packed FP4 (
float4_e2m1fn_x2orint8, with[1, 32]scales, as DeepSeek-V4 stores its routed experts), which the override returned at half its width.test_a_weight_that_is_not_fp8_keeps_transformers_dequantcovers both dtypes and fails without the fix. Pre-commit, mypy included, passes against main, andtests/unit/torch/utilspasses on CPU at this head, on transformers 5.9.0, 5.18 and 5.5.4.Rebased 2026-10-30 onto [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 201e2a5 (module-scope imports, a hook-signature warning, one more remedy in a refusal); this PR's own diff is unchanged. Pre-commit, mypy included, passes against main, and
tests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4.GPU runs, 2026-10-30, at c44d5c1: NVIDIA GB200, torch 2.11.0a0 (nv26.02), transformers 5.9.0, pytest with
-p no:rerunfailures.tests/gpu/torch/utils/test_expert_parallel_load.pyThese results were taken at c44d5c1. Since then this PR moved its unit tests into
test_model_load_utils.pyand was rebased with [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742, last on 2026-10-30 onto the rebuilt base below; its source diff is unchanged, and the file was re-run at this head (the last entry here).Rebased 2026-10-30 onto [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 004de2a, which moved
test_ep_dense_sharding.pyandtest_ep_load_warmup.pyintotest_model_load_utils.py; this PR's edit of the block-FP8 dense-layout test moved with it, and its own diff is otherwise unchanged. Pre-commit, mypy included, passes against main, andtests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4.2026-10-30:
test_fp8_dequant_patch.py, which coveredmodel_load_utilsalone, moved intotest_model_load_utils.pyas one section with its fixtures, marks and parametrization, as CONTRIBUTING.md's naming rule asks (c884757). No test was added or dropped:tests/unit/torch/utilsgives the same counts on CPU on transformers 5.18, 5.9.0 and 5.5.4, and pre-commit, mypy included, passes against main.Rebased 2026-10-30 with [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 onto the rebuilt
shengliangx/hfep-base-6a-ep-loader, which after [OMNIML-6060] [1/3] Add require_transformers, and build a VLM without its LM head #3131 merged holds [OMNIML-6054] [2/2] Route load_pretrained by the distribution plan #2779 (on [OMNIML-6054] [1/2] Move fsdp2_wrap out of distributed.py into fsdp2_utils #3140, which movesfsdp2_wrapintomodelopt/torch/utils/fsdp2_utils.py), [OMNIML-6073] Quantize Nemotron-H's experts under grouped_mm, with their wrapper in its model folder #3067 and [OMNIML-6071] Calibrate a distributed model on per-rank data #2740 on main 62a5c48. This PR's own diff is unchanged. Pre-commit, mypy included, passes against main, andtests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4.GPU run, 2026-10-30, at this head, 9c917ae:
tests/gpu/torch/utils/test_expert_parallel_load.pyon transformers 5.18.0, the test file unchanged since the run at c44d5c1.On 1 GPU every case skips, so it is no evidence. On 2 GPUs the four cases with expert parallelism over half the GPUs skip; on 4 GPUs all 12 run: each rank's experts and the logits against the whole model, and FP8 and NVFP4 quantized training (scales and gradients), with the experts over every GPU or half of them and the dense part replicated or FSDP2-sharded.
Rebased 2026-10-30 with [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 onto [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 e251f6c (meenchen's review round); this PR's own diff is unchanged. Pre-commit, mypy included, passes against main, and
tests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4.Review fix, 2026-10-30 (juhi10071998's comment): the expert-parallel load refuses a block-fp8 checkpoint below transformers 5.8, inside its checks guard. 5.7.0 has neither
Fp8Dequantize._dequantize_onenorFineGrainedFP8HfQuantizer.update_weight_conversions, so its experts would load unscaled (7c83230).test_a_block_fp8_checkpoint_needs_the_transformers_that_dequantizes_its_expertspresents 5.7.0 and fails without the check.Rebased 2026-10-30 with [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 onto [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 1491b66, which refuses MXFP4:
_EP_SOURCE_FORMATSis{None, "fp8"}, this PR's MXFP4 branch went with [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136's, and the hub-id test lost itsmxfp4row. Pre-commit, mypy included, passes against main, andtests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4.GPU run, 2026-10-30, at 7192e05:
tests/gpu/torch/utils/test_expert_parallel_load.pyon transformers 5.18.0.On 4 GPUs every case runs, the four with expert parallelism over half the GPUs included, as at the run before. 1 GPU skips everything, so it is no evidence.
GPU run, 2026-10-30, at this head, 7c83230:
tests/gpu/torch/utils/test_expert_parallel_load.pyon transformers 5.18.0.The same result as the run before, with the same test file, so it speaks for the code underneath. The file loads an unquantized checkpoint on transformers 5.18, so it cannot show the MXFP4 refusal or the block-FP8 floor of 5.8 firing:
test_a_block_fp8_checkpoint_needs_the_transformers_that_dequantizes_its_experts(5.7.0) covers the block-FP8 floor, and [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136'smxfp4refusal row the MXFP4 refusal, on CPU.Review suggestion, 2026-10-30 (Claude's review, approved): a checkpoint
config.jsonholding valid JSON other than an object reads as one naming no FP8 block, so an FP8 config object meets thefp8-unverifiedrefusal instead of anAttributeError(5000023). The newconfig-json-not-an-objectrow fails without the check. Pre-commit, mypy included, passes against main, andtests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4. The GPU run above was at 7c83230; this one-line change is on a path the GPU test does not take.Rebased 2026-10-30 onto [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 a8ba054, with [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 ad9559d (meenchen's review, round 3, on the base rebuilt with [OMNIML-6054] [2/2] Route load_pretrained by the distribution plan #2779 47ca6d1); this PR's own diff is unchanged. Pre-commit, mypy included, passes against main, and
tests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4. The GPU tests have not run on this head.Rebased 2026-10-30 onto [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 6415c94, with [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 eb6c382 on main 059588b, on
shengliangx/hfep-base-6a-ep-loaderrebuilt there from [OMNIML-6054] [2/2] Route load_pretrained by the distribution plan #2779 3b814a6, [OMNIML-6073] Quantize Nemotron-H's experts under grouped_mm, with their wrapper in its model folder #3067 fbff3f3 and [OMNIML-6071] Calibrate a distributed model on per-rank data #2740 186efae. [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 now also refuses a rank not yet bound to its GPU. This PR's own diff is unchanged. Pre-commit, mypy included, passes against main, andtests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4. The GPU tests have not run on this head.Rebased 2026-10-30 onto [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 02388f9, with [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 56353ef, whose new commits change only tests and one comment; this PR's own diff is unchanged. Pre-commit, mypy included, passes against main, and
tests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4.GPU run, 2026-10-30, at [OMNIML-6062] Dequantize block-FP8 checkpoints as they load expert-parallel #2743's head 7540a2e, which contains this PR's own commits as they are at this head:
tests/gpu/torch/utils/test_expert_parallel_load.pyon transformers 5.18.0.The same result as the runs before. On 4 GPUs every case runs, the four with expert parallelism over half the GPUs included; 1 GPU skips everything, so it is no evidence. Since that head, [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 changed only tests and one comment, so the result speaks for this head's code.
Rebased 2026-10-30 onto [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 72f1e49, with [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 75bc2d6, which trims the docstring and comments this PR edits. This PR's own diff is unchanged but for its docstring lines, now on the trimmed docstring: it loads block-FP8 checkpoints on 5.8 or later, and a sharded dense part cannot load one. Pre-commit, mypy included, passes against main, and
tests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4. The GPU tests have not run on this head.At b1cec70 on a host with one visible GPU,
tests/unit/torch/utils/test_ep_loader.pypassed 57 of 57.Rebased 2026-10-30 onto [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 591b986, with [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 ab06860 on main d464d30, on
shengliangx/hfep-base-6a-ep-loaderrebuilt there from [OMNIML-6054] [2/2] Route load_pretrained by the distribution plan #2779 8f9d877, [OMNIML-6073] Quantize Nemotron-H's experts under grouped_mm, with their wrapper in its model folder #3067 d452768 and [OMNIML-6071] Calibrate a distributed model on per-rank data #2740 9a87d15; this PR's own diff is unchanged. Pre-commit, mypy included, passes against main, andtests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4.GPU runs, 2026-10-30, at an earlier head with the same own diff ([OMNIML-6062] Dequantize block-FP8 checkpoints as they load expert-parallel #2743's 6f5391d, which contains this PR's own commits as they are at this head), on transformers 5.18.0:
tests/gpu/torch/utils/test_expert_parallel_load.py:On 4 GPUs every case runs, the four with expert parallelism over half the GPUs included; 1 GPU skips everything, so it is no evidence.
tests/unit/torch/utils/test_ep_loader.pyon a host with one visible GPU: 59 of 59 passed.Rebased 2026-10-31 onto [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 00a592d, with [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 7747c5d, which moves
test_ep_loader.py's cases intotest_model_load_utils.pyand theexpert_parallel_meshones intotest_moe_a2a.py. This PR's test edits moved with those cases, unchanged, and its own source diff is unchanged. Pre-commit, mypy included, passes against main, andtests/unit/torch/utilscollects and passes the same cases as before on CPU, on transformers 5.18, 5.9.0 and 5.5.4.Rebased 2026-10-31 onto [OMNIML-6061] Load the dense part sharded, and size the load warmup to each rank #2742 7ea4ac7, with [OMNIML-6060] [2/3] Load an MoE expert-parallel: shard the experts at load time #3136 930bb0f on main e352cac, on
shengliangx/hfep-base-6a-ep-loaderrebuilt there from [OMNIML-6054] [2/2] Route load_pretrained by the distribution plan #2779 c78d2e5, [OMNIML-6073] Quantize Nemotron-H's experts under grouped_mm, with their wrapper in its model folder #3067 adb9757 and [OMNIML-6071] Calibrate a distributed model on per-rank data #2740 dea9b79; this PR's own diff is unchanged. Pre-commit, mypy included, passes against main, andtests/unit/torch/utilspasses on CPU at this head, on transformers 5.18, 5.9.0 and 5.5.4.GPU run, 2026-10-31, at [OMNIML-6062] Dequantize block-FP8 checkpoints as they load expert-parallel #2743's head 939bf6e on main e352cac, which contains this PR's head:
tests/gpu/torch/utils/test_expert_parallel_load.pyon transformers 5.18.0.The same 12 cases as before, now over the rebuilt base, which includes main's fused-MoE GPTQ change. On 4 GPUs every case runs, the dense-sharded ones included: each rank's experts and the logits against the whole model, and both quantized formats' training. 1 GPU skips everything, so it is no evidence.
Before your PR is "Ready for review"
CONTRIBUTING.md: N/Aa4609752e39. The 2026-10-29 re-cut only moved its edits of a deleted test file totest_model_load_utils.py(Testing).Additional Information
Part 11 of 21 of expert-parallel PTQ for Hugging Face MoE models; merges after #2742.
Summary by CodeRabbit