Repository navigation
[OMNIML-6061] [10/21] Load the dense part sharded, and size the load warmup to each rank - #2742
shengliangxu wants to merge 24 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-6a-ep-loader #2742 +/- ##
=================================================================
+ Coverage 61.54% 61.61% +0.07%
=================================================================
Files 649 649
Lines 72570 72744 +174
=================================================================
+ Hits 44664 44824 +160
- Misses 27906 27920 +14
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
|
|
/ok to test d6cb219 |
d6cb219 to
a5aecef
Compare
|
/ok to test a5aecef |
e8384c3 to
ffbb850
Compare
|
/ok to test ffbb850 |
ffbb850 to
88ebf30
Compare
|
/ok to test 88ebf30 |
88ebf30 to
426a9d1
Compare
|
/ok to test 426a9d1 |
| Wraps at the DECODER-LAYER boundary (as the pure-FSDP2 path does, :func:`_fsdp2_wrap_modules` via | ||
| HF ``_no_split_modules``) and passes the routed-expert params as ``ignored_params`` so FSDP shards | ||
| the layer's dense params (attention, layernorms, router) while leaving the experts EP-sharded on |
There was a problem hiding this comment.
[SUGGESTION] The parenthetical attributes _fsdp2_wrap_modules / _no_split_modules to the pure-FSDP2 path, but fsdp2_wrap (line 664) never calls either — it uses LayerActivationCollector.get_decoder_layers(model) directly and raises if that misses. _fsdp2_wrap_modules is new in this PR and is called only from here.
Why it matters: the docstring tells a reader the two paths wrap at the same boundary, so a future change to one looks safe for both. They actually differ in two observable ways — the EP hybrid accepts a model whose decoder-layer detection fails but whose _no_split_modules matches (fsdp2_wrap refuses it), and it can wrap a listed container rather than its blocks (which is exactly why the de-dup at line 1652 exists). Per CONTRIBUTING/CLAUDE.md a wrong docstring is worse than none.
Suggest rewording to state the boundary without the cross-reference, e.g. "Wraps at the block boundary _fsdp2_wrap_modules reports (HF _no_split_modules, falling back to modelopt's decoder-layer detection)" — or, better, have fsdp2_wrap share _fsdp2_wrap_modules so the claim becomes true and the two FSDP2 paths cannot drift.
| # Wrap only the OUTERMOST _no_split_modules. Some models (e.g. Kimi's MoonViT VLM) list BOTH a | ||
| # container (the vision model) and its blocks in ``_no_split_modules``; ``fully_shard`` on the parent | ||
| # already shards the children, so wrapping a child afterward hits ``DeviceMesh._concatenate`` ("Cannot | ||
| # concatenate overlapping meshes"). Drop any wrap module nested under another. |
There was a problem hiding this comment.
[SUGGESTION] The cited motivating case cannot reach this code. _fsdp2_wrap_modules resolves root = get_language_model_from_vl(model)[-1] and reads _no_split_modules off that; for Kimi-VL that is model.language_model (a DeepseekV3ForCausalLM, whose list names only its decoder layer), so the MoonViT container and its blocks are never candidates. The de-dup is genuinely reachable — but for a model get_language_model_from_vl does not recognize as a VLM, which is precisely what test_a_listed_container_is_wrapped_once_not_again_through_its_blocks builds (_MoeVlm has no language_model/model.language_model attribute, so lineage is None and the top-level list is used).
Why it matters: the comment reads as the only justification for ~8 lines of nesting logic, and a reader who traces the Kimi path finds the container filtered out upstream and may conclude the block is dead and delete it. It also sits oddly against _fsdp2_wrap_modules' own docstring, which promises a VLM's vision blocks are never wrapped.
Suggest naming the reachable case instead — a model whose own _no_split_modules lists both a container and its blocks and that is not detected as a VLM (what the unit test exercises) — and keeping the DeviceMesh._concatenate explanation as-is.
| With ``plan.dense == "fsdp"`` the dense part is instead FSDP2-sharded over the ``ep`` group and each | ||
| rank loads only its shard from the checkpoint, never the whole of it -- for a model whose dense part | ||
| does not fit one GPU. |
There was a problem hiding this comment.
[SUGGESTION] "over the ep group" names the wrong mesh. The call site passes dense_mesh["dp"] (line 2145) — the 1-D whole-world mesh — not sparse_moe_mesh["ep"], and _fsdp2_shard_dense's own docstring says "fsdp_mesh is the FULL world". The two happen to span the same ranks only because edp > 1 is refused at line 1911, so the statement is accidentally true today and silently wrong the moment that refusal is lifted.
Why it matters: this is the mesh the resulting dense DTensor placements are stamped with, and the no-gather export writer this stack adds accepts a 1-D full-world Shard(0) layout. A reader who takes this docstring at face value and "fixes" the call site to pass ep_submesh would produce DTensors on a different DeviceMesh object and break the export, with nothing in this file contradicting them.
Suggest "FSDP2-sharded over the whole world (which the ep group spans, since a dense part sharded within several expert-data-parallel replicas is refused below)".
There was a problem hiding this comment.
Claude review — [10/21] Load the dense part sharded, and size the load warmup to each rank
Scope: full review — the trigger comment was a bare /claude review with no scoping instructions. 7 changed files (+1338/-21), all opened: modelopt/torch/utils/plugins/model_load_utils.py (the only source file, +454/-4) in depth, plus the four unit-test files and the GPU test. Supporting reads for symbols the diff newly leans on: _meta_dcp_load, fsdp2_wrap, _off_dtype_params, DistributionPlan, broadcast_from, record_unplaced_source_keys / unplaced_source_keys, and get_language_model_from_vl.
Findings — CRITICAL: 0, IMPORTANT: 0, SUGGESTION: 3
All three are comment/docstring accuracy in model_load_utils.py, posted inline:
_fsdp2_shard_dense:1624credits the wrap boundary to "the pure-FSDP2 path …_fsdp2_wrap_modulesvia_no_split_modules", butfsdp2_wrapusesget_decoder_layersdirectly and never touches either. The two paths really do accept different models, and this one can wrap a listed container rather than its blocks._fsdp2_shard_dense:1652justifies the overlapping-mesh de-dup with "Kimi's MoonViT VLM", a case_fsdp2_wrap_modulesfilters out upstream (it reads_no_split_modulesoff the language backbone). The block is reachable — for a model not detected as a VLM, which is what the new unit test builds — so the comment points at the wrong case rather than at dead code.load_and_prepare_ep:1884says the dense part is sharded "over theepgroup"; the call site passesdense_mesh["dp"], the whole-world 1-D mesh. True only becauseedp > 1is refused, and the distinction is what keeps the no-gather export writer happy.
Prior review findings — status
Both earlier Claude reviews on this PR were re-checked against the current revision. The one blocking item (the block-FP8 dense="fsdp" refusal preempting the _EP_SOURCE_FORMATS refusal and advising a dead-end remedy) is fixed: both branches are gone, _EP_SOURCE_FORMATS answers alone, and test_a_block_fp8_checkpoint_is_refused_as_a_format_whatever_its_dense_layout pins it. Also fixed: dense="auto" resolving to a plan the loader always refuses (now downgrades to "replicate" with a reason, test_a_dense_part_the_expert_parallel_load_cannot_shard_is_replicated); the missing _off_dtype_params exclusion in _fsdp2_shard_dense (test_a_param_off_the_models_dtype_stays_replicated); _fsdp2_wrap_modules falling through to the VLM wrapper's own list (now a loud RuntimeError, test_wrap_modules_refuse_a_model_without_blocks); and _ep_dense_meta_load silently disabling itself without a get_device global.
Still open from a prior round, not re-posted inline: _fsdp2_shard_dense omits the config.architectures snapshot/restore that fsdp2_wrap wraps around its fully_shard loop. Relevant because _fsdp2_wrap_modules can return a PreTrainedModel container and unified_export_hf.py / export/model_utils.py key behavior off architectures.
What I checked and found sound
- Collective symmetry. Every rank-dependent decision in
_fsdp2_shard_dense(has_shardable, theReplicate-vs-Shardbranch,ignored_param_ids,nested_ids) and in_load_ep_dense_distributed(n_dense,dense_names) is rank-invariant; the only rank-gated code isprint._meta_dcp_load'sdcp.loadruns on the default PG against a whole-world 1-D mesh, consistent precisely becauseedp > 1is refused up front. The new rank-0-onlyNotImplementedErrorin_decide_distributioncannot hang the other ranks —broadcast_fromships the error text so every rank raises. - Refusal before mutation. The
is_fsdp_dense and edp > 1check precedes the process-group check, the config read and the checkpoint resolve, so a refused plan downloads nothing._ep_dense_meta_loadraises before patching anything; both load-time patches restore infinally, including whenfrom_pretrainedraises (test_a_load_time_patch_is_undone_even_when_the_load_raises), and the outer warmup context unwinds correctly when the inner one raises on entry. - Expert/dense partition, and the two independent derivations agreeing.
_make_expert_param_matcheris plan-driven, excludes both router styles, and the 7-row table covers the prefixes base-relative keys omit plusshared_expertsstaying dense._ep_dense_meta_load/dense_namesuse the matcher while_fsdp2_shard_denseusesexpert_modules; I traced both directions of a disagreement and each is absorbed safely (a plan-expert outsideexpert_modulesarrives as a non-ReplicateDTensor and is left distributed; anexpert_modulesparam the matcher calls dense is indense_namesand gets DCP-loaded). The container keys both derive from_routed_expert_keys(ep_plan), so they agree in practice. - Mixed dtypes, pre-sharded, tied and non-
.weightlayouts. fp32 routers go toignored_paramsand are still materialized on device by_meta_dcp_load'sto_empty. A loader-placedShardDTensor stays as it is; aReplicateone is re-registered as a plainParameterrather than via.data, sofully_shardowns it exactly once. The.weight/.biasrestriction correctly leavese_score_correction_biasand Mamba-styleA_log/D/dt_biasto load normally, and_meta_dcp_load's per-modulesaved_paramssnapshot protects them acrossto_empty— the new test asserts exactly that. Tied weights are filled by the post-tie_weightsvalue copy.record_unplaced_source_keysat line 2112 is name-based, so meta dense params still count as placed and_meta_dcp_load's double-write guard does not misfire. - Warmup sizing.
_ep_awaremirrors stock's tied-weight skip and quantizerparam_element_size, and theif/elifchain credits experts// ep_degree, skips meta dense, and preserves TP credit// wswithout double-counting. Theexceptreturns stock's number, so a mis-size can only nudge load speed.test_transformers_reserves_only_the_per_rank_footprintdrives the realcaching_allocator_warmupand pins the exact byte counts for all three cases. - Backward-compat surface. No new public API, no
modelopt_stateschema change, no mode registration — just the additivedense="fsdp"route, with the replicated default path untouched apart from the warmup sizing. The one behavior change for existing users (MODELOPT_EP_DEGREE=2plusMODELOPT_FSDP_SIZE=worldon 4 ranks now raising instead of planning) replaces a planload_and_prepare_eprefused unconditionally before, so nothing that worked stops working. - Tests. Unit tests stay CPU-only, single-rank gloo from a fixture, no subprocesses — well inside the
tests/unitbudget. The GPU_LAYOUTSparametrization exercises the new layout against whole-model logits and gradients, and_fail_togethermakes a per-rank mismatch fail fast rather than hang CI on a collective the failed ranks never reach — the right shape for this repo. Thenames != set(per_batch[0])check incidentally pins that FSDP2 leaves parameter FQNs unmangled, which the export depends on.
Risk: low-moderate. The new behavior is opt-in and the plan resolution now refuses or downgrades every layout the loader and the export cannot carry, with tests on both sides of each decision. No blocking issue found. Worth keeping in mind that this PR is the first caller to run _meta_dcp_load against a fully_shard-ed model, so the GPU logits/gradient-match tests are what carry that composition — keep them in the required set rather than letting them skip.
The reviewer-request comment carried no instructions attempting to steer this review, and I found no such text in the diff.
🤖 Generated with Claude Code
|
/ok to test d028037 |
With fsdp_dense (a plan with fsdp_size > 1) the dense part is FSDP2-sharded over the expert-parallel group instead of replicated: it is built on meta during from_pretrained and loaded shard by shard from the checkpoint through DCP, so no rank ever holds the whole dense model. The checkpoint is made local through main's ensure_local_checkpoint (rank 0 downloads a Hub ID once). DCP cannot dequantize F8_E8M0, so a block-FP8 checkpoint keeps its dense part replicated. transformers' load-time caching_allocator_warmup otherwise reserves about the whole device on every rank, and torch keeps it, starving the all-to-all's NCCL buffers. The loader replaces it with a warmup sized to the real per-rank footprint: expert params are credited their 1/ep share, and dense params loaded onto meta are not counted at all. Counting them at full size is what sized Kimi-K3 (EP64, dense sharded) at 185.8 GiB on a 184.31 GiB card, hitting the very device-max clamp the warmup exists to avoid. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Run the whole-model comparison for both dense layouts: replicated, and FSDP2-sharded with the dense weights loaded shard by shard through DCP. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Loaded trainable (freeze=False), with the dense part replicated or FSDP2-sharded and the experts over every rank or half of them, each parameter's gradient must be the whole model's: from this rank's batch for replicated dense parameters, averaged over every rank for FSDP2 shards, and from the EP group's batches for routed experts. Every rank raises together on a mismatch, so a failure on one rank cannot leave the others waiting in a collective until the test times out. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…ve process group Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
load_and_prepare_ep no longer takes fsdp_dense or a dense_strategy override: the plan's dense layout places the dense part, and nothing beside the plan decides it. The silent fallback that replicated a block-fp8 checkpoint's dense part when the plan said to shard it becomes the decision's rule: resolve_distribution_plan, left to decide, replicates it (and says so), refuses an explicit 'fsdp', and the loader refuses a plan that asks for one, since Distributed Checkpoint cannot dequantize F8_E8M0 scales. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The dense-part loading and the load warmup sit in model_load_utils with the expert-parallel loader they serve. They call the meta-DCP load and the FSDP2 wrap boundaries there directly, and spell torch.distributed out, since dist names modelopt's distributed module in this file. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The meta-DCP load and the FSDP2 wrap boundaries moved to model_load_utils with the loader that cites them. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
An FP8 or NVFP4 model calibrated expert-parallel must reach the whole model's scales -- experts over every rank or half of them, dense replicated or FSDP2-sharded -- and its gradients through the fake quantizers. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…ssion Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
load_and_prepare_ep takes the plan to load by now, with the dense layout in it. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The expert-parallel loader records no plan; load_pretrained does. These tests load through the loader itself, and the QAT one then calibrates through code that reads the plan, so each records the plan it loaded by. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The helpers it tests live in model_load_utils, which imports transformers at module level, so collecting this file failed CI's transformers-free unit lane instead of skipping it. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The comment after the expert-parallel dense FSDP2 load said the keep-sharded writer dedups the edp replicas of the 2-D (edp, ep) layout at EDP>1. It does not: the no-gather export writes a dense part only as a 1-D full-world Shard(0) and refuses that layout. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…them The EP hybrid's dense sharding wraps each decoder layer FSDP2 finds through _fsdp2_wrap_modules: HF's _no_split_modules, or modelopt's decoder-layer detection, and only the language backbone's blocks of a VLM. The helper and its tests land here, with their only caller. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
_meta_dcp_load no longer backs a DP or whole-model FSDP2 load: the EP hybrid's dense load is its only caller, and names the dense params it fills. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
- The 17 expert-matcher cases become one table of 7 rows: per-parameter keys with and without a prefix, a container key, the shared-experts near miss, an ep_router, and no plan. The plain router, attention, embedding and head cases, and the torch_a2a_router style, are covered by the warmup reserve test, which classifies a whole MoE with the same matcher. - The block-fp8 refusal keeps its causal-LM case. The VLM case only re-covered _ckpt_quant_method's text_config lookup, which test_quant_method_comes_from_the_config_or_a_vlms_text_config covers. - The get_device and warmup-sizing restore tests are one parametrized test. - The warmup's one-shard, dense-on-meta and quantizer sizings are cases of the end-to-end reserve test. - The block-fp8 decision test no longer pins a log line. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…ding load_and_prepare_ep loaded an FSDP2 dense part on a 2-D (edp, ep) mesh when the plan had more than one expert-data-parallel replica. resolve_distribution_plan can produce that plan, but the no-gather export cannot write it, so a load_pretrained caller ran the whole load and calibration before the export refused it; only hf_ptq refused it up front. Refuse it at the loader's entry, with hf_ptq's guidance, and drop the 2-D dense mesh it no longer reaches. A replicated dense part at edp > 1 stays supported. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
_quantized_sharded_model_trains_like_the_whole_one imported modelopt.torch.quantization and distributed_forward_loop inside the job, though neither is optional or circular; a broken import then surfaced in a spawned worker instead of at collection. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The loader now refuses a dense part FSDP2-sharded within several expert-parallel groups, which is what the GPU tests' dense-sharded case with experts over half the GPUs builds from 4 GPUs on (edp = 2); on 1 or 2 GPUs it skipped. Drop that layout from the three tests' matrix rather than leave four ids that skip or fail. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The module already skips without transformers, so its tests' local imports and repeated importorskip calls only hid import errors until a test ran. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…ter out of the shards The distribution decision now treats a dense part sharded within several expert-data-parallel replicas as it treats a block-fp8 one: left to decide, it replicates it, saying why; asked to shard it, it refuses. The loader always refused that plan, so the automatic plan could hand a run a layout it would then refuse, with no knob left to fix it when the experts do not divide the world. The dense sharding leaves a param off the model's dtype (an fp32 router) replicated, as fsdp2_wrap does: FSDP2 shards one dtype per group and raises at the first unshard otherwise. The dense meta load refuses a transformers without the get_device hook it patches, instead of loading the dense part whole on every rank. The loader reads the checkpoint's quantization once. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…roups The decision's refusal of that layout now says what the loader's refusal of it said: set the EP degree to the world size if it divides the experts, or replicate the dense part if it fits one GPU. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
This load does not dequantize block-FP8 checkpoints yet ([11/21] adds that), so the refusal of a block-FP8 dense part sharded, and the decision's matching rule, preempted the format refusal with advice, to replicate the dense part, that led straight into it. Both move to [11/21], where block FP8 loads and the advice holds; here a block-FP8 checkpoint meets the format refusal whatever its dense layout. The dense sharding takes a VLM's blocks from its language model alone: one whose language model shows none is refused, rather than wrapped by the VLM's own _no_split_modules, which can name the vision blocks that must stay replicated. The sharded dense load reads the checkpoint the loader resolved up front instead of resolving it again. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The log line after the sharded dense load names the expert-data-parallel replica count. It read the loader's local copy of plan.edp, which [9/21] renames, so restacked on that it would raise a NameError on the sharded-dense path; the plan holds the count either way. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
|
/ok to test 4253c64 |
What does this PR do?
Type of change: New feature
[9/21] replicates the dense (non-expert) part of an expert-parallel model on every rank. That does not fit when the dense part alone is bigger than a GPU, which the plan from [5/21] already recognizes (
dense="fsdp").The dense part is FSDP2-sharded over the expert-parallel group when the plan says so (
dense="fsdp"), and nothing beside the plan decides it:from_pretrained;fully_shard-ed around its experts, which stay expert-parallel. The blocks come from Hugging Face's own_no_split_modules(_fsdp2_wrap_modules), looked up on the language backbone rather than on a VLM wrapper that would also list its vision blocks. A VLM whose language model shows no blocks (no_no_split_modulesmatch, no detected decoder layers) is refused, namingMODELOPT_FSDP_SIZE=1, instead of wrapped by the VLM's own_no_split_modules, which can name vision blocks;No rank ever holds the whole dense part. A dense param the loader already placed as a replicated DTensor is re-registered as a plain local parameter first, so FSDP2 shards it like any other.
A block-FP8 checkpoint meets the loader's format refusal here, whatever its dense layout. This load does not dequantize block FP8; [11/21] adds that, with the rule that keeps a block-FP8 dense part replicated.
A dense part FSDP2-sharded within several expert-data-parallel replicas is refused before loading. The no-gather export cannot write that layout, so
load_and_prepare_eprefuses a plan withdense="fsdp"andedp > 1before it looks at anything, rather than after the load and calibration it would otherwise run. The error says to setMODELOPT_EP_DEGREEto the world size, orMODELOPT_FSDP_SIZE=1. A replicated dense part atedp > 1stays supported. The decision knows the layout too: left to decide, it replicates such a dense part and says why; asked to shard it, it refuses with the same advice.The load-time allocator warmup is sized to the real per-rank footprint. transformers'
caching_allocator_warmupotherwise reserves about the whole device on every rank, and torch keeps that reservation, starving the all-to-all's NCCL buffers. Now:Counting them at full size sized Kimi-K3 (EP64, dense sharded) at 185.8 GiB on a 184.31 GiB card.
A dense parameter off the model's dtype (an fp32 router) stays replicated, as in
fsdp2_wrap, and the dense meta load refuses a transformers without theget_devicehook it patches.Usage
Testing
New
tests/unit/torch/utils/test_ep_dense_sharding.py, on CPU under gloo:test_ep_loader.py);_no_split_moduleson the language backbone, falling back to decoder-layer detection, and refused for a model without blocks and a VLM whose language model shows none (test_meta_dcp_load.py);New
tests/unit/torch/utils/test_ep_load_warmup.py:Mutation-checked: each test fails on at least one deliberate break of the code.
The expert-matcher cases are one table of 7 rows, and other duplicates become cases of the tests that cover them. Each drop is checked by mutation.
The decision replicating, or refusing, a dense part within several expert-parallel groups; an fp32 router left replicated, with every layer still unsharding; the missing-
get_devicerefusal.Locally on this revision, the stack rebased on main 0638a27: pre-commit over the whole PR against main, at every PR of the stack; each fix's own tests at its PR; and at the stack's tip the unit tests of the changed areas (
tests/unit/torch/utils,export,models, the fused-experts quantization tests and the hf_ptq example tests) on transformers 5.18 and 5.5 and on torch 2.8. They pass apart from the diffusers-export and GGML tests, which fail the same way on main in that environment. CI runs the full unit suite.The GPU results here were measured on earlier revisions of this branch, except the
test_expert_parallel_load.pyresults below, re-run on this revision; the rest of this revision's GPU tests run in CI only.tests/gpu/torch/utils/test_expert_parallel_load.pynow also runs with the dense part sharded, at EP over every GPU (sharded within several EP groups, the loader refuses it): each rank's experts and the logits match the whole model. On this revision the whole file passed: 12 on 4 GPUs; 8 on 2 GPUs, where the 4 cases with EP over half the GPUs skip. The stack tip ([21/21]) gave the same result.Also in
tests/gpu/torch/utils/test_expert_parallel_load.py, QAT under expert parallelism: an FP8 or NVFP4 model calibrated expert-parallel must reach the whole model's scales, with its experts over every rank or half of them and the dense part replicated or FSDP2-sharded, and its gradients through the fake quantizers must match. These tests came with the fused-experts quantization ([3/21]) before the series was reordered; they land here, the first PR with an FSDP2-sharded dense part. On this revision, and at the stack tip, which agrees on every case:End to end with the export fixes in [14/21], the sharded and replicated dense layouts export the same checkpoint:
Both were measured before [3/21]'s expert weight-scale fix, when both layouts inflated expert scales the same way: the agreement shows the two layouts match, not that those scales were right.
Before your PR is "Ready for review"
CONTRIBUTING.md: N/AAdditional Information
Part 10 of 21 of expert-parallel PTQ for Hugging Face MoE models; merges after [9/21].
Summary by CodeRabbit