Skip to content

[OMNIML-6071] [8/21] Calibrate a distributed model on per-rank data - #2740

Open
shengliangxu wants to merge 29 commits into
shengliangx/hfep-4-meta-dcp-loadfrom
shengliangx/hfep-5-distributed-calibration
Open

shengliangxu wants to merge 29 commits into
shengliangx/hfep-4-meta-dcp-loadfrom
shengliangx/hfep-5-distributed-calibration

Conversation

@shengliangxu

@shengliangxu shengliangxu commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Type of change: New feature

The pieces a calibration loop needs once the model is spread across ranks.

  • shard_dataloader gives each rank its own shard of the data.
    • Map-style datasets use a DistributedSampler with drop_last off, so every rank runs the same number of steps; a rank that exits early hangs the others on the per-step collectives.
    • An IterableDataset gets a strided copy of the stream, which must yield the same samples in the same order on every rank and worker (a stream that shuffles needs a fixed seed of its own). The copy is split across the loader's worker processes so that each sample is still seen once. It is not main's vlm_dataset_utils._ShardedIterable, which pads each rank to a count its loader knows: this wraps any loader's stream, which may have no length, and splits it across that loader's workers.
  • distributed_forward_loop builds the forward_loop that mtq.quantize calls. It moves each batch to the device it is given and runs the full distributed forward, so the expert-parallel collectives fire inside it, with KV caching off as on the single-process path. Each batch is a mapping of the forward's keyword arguments; anything else is refused with a TypeError at the first batch.
    • A model whose distribution plan ([4/21]) is expert-parallel passes the ep_mesh its experts are sharded over, and the loop re-plants the torch_a2a forward wrappers that quantizer insertion clobbers on transformers >= 5.16. A module whose forward is still the wrapper planted there is skipped, so passes do not stack wrappers; one whose forward was rebound since (another conversion, an export) is planted again. Any other model passes none. A mesh missing for an expert-parallel model, or given for another, raises.
    • It returns the allocator's reserved pool before the first forward and after the last. NCCL's buffers live outside the torch allocator, so a full reserved pool starves them. A process that never started CUDA skips this, and the garbage collection it costs.
  • Every rank runs the same batch size. Equal step counts need equal batch sizes, and hf_ptq's default --batch_size 0 sizes calibration with get_max_batch_size on each rank: it measured every visible GPU, and on an out-of-memory error halved the batch and probed again, a different number of collective-bearing forwards per rank. For a model whose recorded plan spreads it over several ranks it is now collective: each rank measures its own GPU from one forward of the single-sample input, without probing the size it arrives at, and every rank takes the smallest size any rank allows, reduced with all_reduce(..., min) ([4/21]). A single process, and a model with no plan recorded (a Megatron model, say), are unchanged.
  • Calibration that keeps its statistics on the rank is refused. Every rank calibrates on its own data, so an algorithm must share what it gathers across the ranks or the ranks calibrate their copies, or their shards, of a weight differently. max all-reduces its amax, GPTQ merges its Hessians across data-parallel replicas, and MSE and SmoothQuant work from max's synced amax. AWQ does not: it averages its activation scales over data parallelism but picks each layer's scale from its own rank's search, and a 2-rank probe showed the replicas' weights and pre_quant_scale diverging. local_hessian and imatrix share nothing either, and neither do max and MSE with distributed_sync off. mtq.quantize, mtq.calibrate and mtq.auto_quantize refuse these (awq_lite, awq_clip, awq_full, svdquant, which runs awq_lite, local_hessian, imatrix, and any config that turns distributed_sync off, also as another algorithm's weight_scale_algorithm or scale_algorithm) on a model whose recorded plan spans several ranks, before converting anything (auto_quantize in its format check). A model with no plan recorded, such as a Megatron model, is unchanged. On an expert-parallel plan, any calibration is refused unless every module holding routed experts carries the experts' expert-parallel group and, as its data-parallel group, just the plan's edp ranks, those holding the same experts: each rank's per-expert quantizers hold its own experts, and max's and GPTQ's data-parallel sync would otherwise merge different experts' scales. The modules holding routed experts are those carrying an expert-parallel group and, once quantization has converted them, the quantized modules under a container named experts, as transformers' expert-parallel plans name it; unseeded experts beside a seeded router are refused once converted, before they calibrate. [9/21]'s expert-parallel loader gives the routed experts those groups. A module with no routed experts and no expert-parallel group, such as a VLM's vision tower that hf_ptq quantizes on its own with every quantizer disabled, passes.
  • hf_ptq's --use_fsdp2 load records its plan. The batch sizing above agrees across ranks only for a model that records its plan. hf_ptq loads FSDP2 through parallel_load_and_prepare_fsdp2, which records none, until [21/21] moves it onto load_pretrained; it now records the FSDP2 plan right after that load. On several GPUs, --use_fsdp2 therefore now refuses the calibrations above (AWQ, SVDQuant, local Hessian, imatrix, also as an AutoQuantize candidate), which calibrated the ranks' shards of a weight differently; the README's FSDP2 section and the changelog's Backward Breaking Changes say so.

shard_dataloader refuses a loader that picks its own samples (a custom batch_sampler, or a sampler other than sequential, random or distributed, also inside a plain BatchSampler or with auto-batching off) instead of rebuilding it, and keeps a plain BatchSampler's batch size and drop_last in the rank's shard; a loader with auto-batching off (batch_size=None) shards one item per step, as it loads. With one rank, shard_dataloader returns the loader itself, its sampler and shuffling included. distributed_forward_loop runs its forwards without gradients. get_max_batch_size reads the recorded plan, as the calibration guard does. distributed_forward_loop refuses an ep_mesh whose size is not the plan's ep_size.

Usage

from modelopt.torch.utils.distributed import distributed_forward_loop, rank, shard_dataloader, size

loader = shard_dataloader(calib_loader, rank(), size())
# ep_mesh: the mesh an expert-parallel model's experts are sharded over; None for any other model
forward_loop = distributed_forward_loop(model, loader, device=device, ep_mesh=ep_mesh)
model = mtq.quantize(model, quant_cfg, forward_loop=forward_loop)

Testing

  • New tests/unit/torch/utils/test_distributed_calibration.py (42 cases), on CPU, in one process except where noted. It covers:
    • strided shards that cover the data exactly once, with equal batch counts on uneven splits, for map-style and streaming datasets, including the split across DataLoader workers, run in-process with the worker info faked rather than by forking workers; map-style and streaming loaders with auto-batching off sharded one item per step;
    • every loader setting carried over, a plain BatchSampler's batch size and drop_last included;
    • the wrapper re-planting, on the planned modules only, without stacking, and again after another conversion drops it;
    • the loop running this rank's shard through the top-level model, with use_cache off and the allocator cache freed around it in a process that started CUDA, and not in one that did not; a batch that is not a mapping refused;
    • the ep_mesh refused when missing for an expert-parallel plan, given for another, or over the whole world for an ep_size=2 plan;
    • rank-local calibration refused through quantize, calibrate and auto_quantize (before conversion), also when another algorithm runs it for its weight scales or max is told not to sync, with the model left as it was;
    • on two gloo ranks, max calibration on an expert-parallel plan run when the experts carry their groups, and refused with no expert-parallel group anywhere or with the experts' data-parallel group over every rank (before conversion), and with the group on the router only (once the experts are converted, before they calibrate);
    • a module without routed experts calibrating under an expert-parallel plan, with every quantizer disabled (hf_ptq's vision-tower call) and quantized.
  • distributed_forward_loop runs on real GPU ranks in [10/21]'s expert-parallel calibration test, which checks each rank's per-expert scales against the whole model; this PR's own tests run it in one process.
  • tests/examples/hf_ptq/test_hf_ptq_args.py: hf_ptq's --use_fsdp2 load, stubbed, leaves the model recording its FSDP2 plan.
  • The worker-split test fails on the code before that fix.
  • tests/unit/torch/utils/test_dataset_utils.py, on two real gloo ranks (cpu_dist_workers) with only the CUDA memory queries faked per rank: each rank measures only its own GPU, probes once, and both take the smaller batch size. A rank keeping its own size, measuring every GPU, or skipping the reduction fails it, the last after the group's 10 s timeout rather than by hanging; main's single-process retry test still passes. The two-rank test also runs a model with no plan recorded, which each rank measures on every visible GPU, as a single process does, without a refusal.
  • Mutation-checked: deciding the reduction or the mesh other than by the plan fails a test (5 mutants), and so does dropping either refusal call, the nested-algorithm check, the distributed_sync check, hf_ptq's plan recording, the expert-parallel group refusal, or the ep_mesh size check. Refusing every expert-parallel calibration, or reading the plan strictly in get_max_batch_size, fails a test too. Dropping the check on converted experts or the data-parallel size check, keeping a replant flag once set, or planting on every pass each fail a test. Rebuilding a lone rank's loader, refusing a module without routed experts, or passing any module with no expert-parallel group each fail a test too.
  • The shard tests are one table over dataset kind and world size, the two replant tests are one, and cases a remaining test covers are dropped. Each drop is checked by mutation.
  • A subset sampler is refused, also under a plain BatchSampler and with auto-batching off, and so is a custom batch sampler; a lone rank keeps each such loader as it is. The sampler, batching, non-mapping-batch and auto_quantize cases each fail without their fix (mutation-checked).
  • 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.
  • GPU tests were not re-run on this revision.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ❌ for hf_ptq.py --use_fsdp2 on several GPUs with a calibration that keeps its statistics per rank (AWQ, SVDQuant, local Hessian, imatrix, also as an AutoQuantize candidate), now refused: these calibrated the ranks' shards of a weight differently (changelog, README). Otherwise additive; get_max_batch_size changes only for a model whose recorded plan spans several ranks.
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: N/A
  • Did you write any new necessary tests?: ✅
  • Did you update Changelog?: ✅ A Backward Breaking Changes entry for --use_fsdp2's refusal; the feature's entry lands with [21/21].
  • Did you get Claude approval on this PR?: ❌ Not yet.

Additional Information

Part 8 of 21 of expert-parallel PTQ for Hugging Face MoE models; merges after [7/21].

Summary by CodeRabbit

  • New Features
    • Distributed calibration now shards map-style and streaming datasets across ranks, keeping batch counts aligned and preserving data loader settings.
    • Calibration supports expert- and tensor-parallel models with improved device handling, cache management, and compatibility with parallel model wrappers.
    • Distributed batch-size detection probes each rank’s local GPU and selects the smallest supported batch size across ranks.
  • Bug Fixes
    • Calibration and quantization now reject selected rank-local calibration methods on distributed models before modifying model state.

@copy-pr-bot

copy-pr-bot Bot commented Oct 12, 2026

Copy link
Copy Markdown

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.

@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
Walkthrough

The changes add per-rank batch-size probing, distributed calibration data-loader sharding, and a calibration forward loop. They also reject selected rank-local calibration methods for distributed models.

Changes: Distributed Calibration

Layer / File(s) Summary
Per-rank batch-size probing
modelopt/torch/utils/dataset_utils.py, tests/unit/torch/utils/test_dataset_utils.py
Each rank probes its local GPU and contributes its regulated batch size to a minimum reduction. Device-specific probes do not retry smaller batches.
Calibration data sharding
modelopt/torch/utils/distributed.py, tests/unit/torch/utils/test_distributed_calibration.py
Adds map-style and iterable dataset sharding. The rebuilt loader preserves its settings and handles uneven or short inputs as tested.
Calibration forward loop and wrapper restoration
modelopt/torch/utils/distributed.py, tests/unit/torch/utils/test_distributed_calibration.py
Adds expert-parallel mesh validation, A2A wrapper restoration, temporary cache disabling, CUDA allocator cleanup, optional device movement, and calibration forwards.
Rank-local calibration guard
modelopt/torch/quantization/model_quant.py, modelopt/torch/utils/distributed.py, tests/unit/torch/utils/test_distributed_calibration.py
Rejects selected rank-local calibration methods when the model has a recorded multi-rank plan. The guard runs before calibration or quantization changes the model.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant distributed_forward_loop
  participant DataLoader
  participant Model
  Caller->>distributed_forward_loop: Create calibration callback
  distributed_forward_loop->>DataLoader: Read calibration batches
  distributed_forward_loop->>Model: Reinstate wrappers and run batch forward
  Model-->>distributed_forward_loop: Return from forward
Loading

Suggested reviewers: kevalmorabia97

Merge Risk: Moderate · up to 60b26

Some distributed calibration configurations can produce inconsistent scales, and automatic batch sizing fails for an unrecorded FSDP2 model. Address these paths before merging.

Pre-merge checks: Passed 5 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 56.92% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 65 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 5 passed
Check name Status Explanation
Title check Passed The title "[OMNIML-6071] [8/21] Calibrate a distributed model on per-rank data" names the main change. The PR adds distributed calibration utilities: sharding of map-style and iterable datasets across…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns Passed PASS. The pull request adds no listed security anti-pattern. Changed Python code does not add torch.load(..., weights_only=False), numpy.load(..., allow_pickle=True), hardcoded `trust_remote_code=…
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Full details: Title check

Explanation

The title "[OMNIML-6071] [8/21] Calibrate a distributed model on per-rank data" names the main change. The PR adds distributed calibration utilities: sharding of map-style and iterable datasets across ranks, a distributed forward loop, collective batch-size selection, and a guard that refuses rank-local calibration algorithms on distributed models with a recorded plan. The title is specific to the primary change. The ticket prefix and series marker "[8/21]" add noise but do not make the title unrelated or vague.

Full details: Security Anti-Patterns

Explanation

PASS. The pull request adds no listed security anti-pattern. Changed Python code does not add torch.load(..., weights_only=False), numpy.load(..., allow_pickle=True), hardcoded trust_remote_code=True, eval()/exec() on external input, or # nosec. No pyproject.toml or requirements.txt file changes add dependencies.

✨ Finishing Touches: 💡 2
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@shengliangxu
shengliangxu added this pull request to stack #2755 October 12, 2026 00:57
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-5-distributed-calibration branch from 7f91f94 to 963e78a Compare October 12, 2026 02:24
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://NVIDIA.github.io/Model-Optimizer/pr-preview/pr-2740/

Built to branch gh-pages at 2026-10-25 06:17 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@codecov

codecov Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.23664% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 61.54%. Comparing base (f9d1502) to head (29fcc46).

Files with missing lines Patch % Lines
modelopt/torch/utils/distributed.py 98.79% 1 Missing ⚠️
Additional details and impacted files
@@                         Coverage Diff                          @@
##           shengliangx/hfep-4-meta-dcp-load    #2740      +/-   ##
====================================================================
+ Coverage                             61.47%   61.54%   +0.06%     
====================================================================
  Files                                   649      649              
  Lines                                 72347    72469     +122     
====================================================================
+ Hits                                  44477    44598     +121     
- Misses                                27870    27871       +1     
Flag Coverage Δ
unit 61.54% <99.23%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 963e78a

@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-5-distributed-calibration branch from 963e78a to 935bd7f Compare October 12, 2026 08:39
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 935bd7f

@shengliangxu shengliangxu changed the title [5/19] Calibrate a distributed model on per-rank data [OMNIML-6071] [5/19] Calibrate a distributed model on per-rank data Oct 12, 2026
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-5-distributed-calibration branch from 935bd7f to c3ccb86 Compare October 13, 2026 00:53
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-5-distributed-calibration branch from c3ccb86 to 96159d7 Compare October 13, 2026 01:32
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 96159d7

@shengliangxu
shengliangxu removed this pull request from stack #2755 October 13, 2026 17:42
@shengliangxu
shengliangxu added this pull request to stack #2775 October 13, 2026 17:43
@shengliangxu shengliangxu changed the title [OMNIML-6071] [5/19] Calibrate a distributed model on per-rank data [OMNIML-6071] [5/20] Calibrate a distributed model on per-rank data Oct 13, 2026
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-5-distributed-calibration branch from 96159d7 to 540716e Compare October 13, 2026 19:38
@shengliangxu
shengliangxu removed this pull request from stack #2775 October 13, 2026 19:38
@shengliangxu
shengliangxu added this pull request to stack #2781 October 13, 2026 19:40
@shengliangxu shengliangxu changed the title [OMNIML-6071] [5/20] Calibrate a distributed model on per-rank data [OMNIML-6071] [6/21] Calibrate a distributed model on per-rank data Oct 13, 2026
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 540716e

@shengliangxu
shengliangxu removed this pull request from stack #2781 October 14, 2026 00:13
@shengliangxu
shengliangxu force-pushed the shengliangx/hfep-5-distributed-calibration branch from 540716e to 177c692 Compare October 14, 2026 00:14
@shengliangxu
shengliangxu added this pull request to stack #2786 October 14, 2026 00:14
The pieces a calibration loop needs once the model is spread across ranks:

- shard_dataloader gives each rank its own data shard: a DistributedSampler
  for map-style datasets, with drop_last off so every rank runs the same
  number of steps (a rank that exits early hangs the others on the per-step
  collectives), and a deterministic strided copy of the stream for an
  IterableDataset.
- distributed_forward_loop builds the forward_loop mtq.quantize calls: it
  moves each batch to this rank's device and runs the full distributed
  forward, so the expert-parallel collectives fire inside it, with KV caching
  off as on the single-process path. It re-plants the torch_a2a forward
  wrappers that quantizer insertion clobbers on transformers >= 5.16 (on the
  session's EP mesh), and returns the allocator's reserved pool both before
  the first forward and after the last -- NCCL's communication buffers live
  outside the torch allocator, so a full reserved pool starves them.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Cover how every rank calibrates on its own data and keeps the all-to-all
in place:
- shard_dataloader: strided shards that together cover the data exactly
  once, equal batch counts on an uneven split, and every loader setting
  carried over, for map-style and streaming datasets;
- _reinstall_a2a_forwards: re-planting the expert-parallel wrappers that
  quantizer insertion drops, on the planned modules only and without
  stacking;
- distributed_forward_loop: this rank's shard through the top-level model,
  use_cache off while it runs, the allocator cache freed around it.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Each worker process iterates its own copy of the stream, and every one of
them yielded the rank's whole shard, so with num_workers=2 each sample was
calibrated on twice. Have each worker take every num_workers-th group: the
shards still cover the data once, with equal counts on every rank.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
hf_ptq's default --batch_size 0 sizes calibration with
get_max_batch_size, which each rank ran on its own: it measured every
visible GPU, probed with a forward, and on an out-of-memory error
halved the batch and probed again. Under expert parallelism those
forwards hold all-to-alls, so a rank that retried ran more of them than
the rest, and ranks could settle on different batch sizes. Different
sizes mean different numbers of calibration steps, and a rank that
finishes early leaves the others waiting in a collective.

Inside a distributed session get_max_batch_size is now collective: each
rank measures its own GPU only and probes with a single forward, and
every rank takes the smallest size any rank allows, through the new
min-reduced session.calib_batch_size. Outside a session it is
unchanged.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Every rank takes the value get_max_batch_size returns, and nothing reads the batch size back from
the session, so the ranks agree on it (ModelOptSession.agree, combined with min) rather than
store it as a synced attribute. Every rank still gets the smallest size any rank's GPU allows.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…h, reduce over the live group

distributed_forward_loop takes the device to move batches to and the expert-parallel mesh whose
all-to-all forwards it re-installs, where it took the session that carried both. The calibration
batch size is still the smallest any rank allows, reduced with all_reduce over the live process
group, each rank measuring its own GPU.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…ibution plan

get_max_batch_size agrees on one batch size across ranks when the model's recorded plan spreads it
over several, rather than whenever a multi-rank process group is live. distributed_forward_loop
takes the ep_mesh exactly when the plan is expert-parallel: an expert-parallel model without its
mesh would run its experts without an EP group at the first step, and a mesh for any other model
would be ignored.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
… whole stream

The rank shard splits each stream's groups between the DataLoader workers,
so a dataset that already splits itself by get_worker_info() is split twice
and drops samples. hf_ptq loads with num_workers=0; say so for other callers.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
… CUDA

distributed_forward_loop collects garbage and empties the CUDA cache
before the first calibration forward and after the last, so NCCL can
allocate its buffers outside the caching allocator. A process that
never started CUDA has no pool to free, yet each call still paid a full
garbage collection, about a second on a large heap: nearly 12 s of the
unit run across this file's forward-loop tests. The release now runs
only once CUDA is initialized.

The forward-loop test that walked a 4-case shard grid keeps one case;
shard_dataloader's own tests cover the striding.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The test started two DataLoader workers per rank, forked from pytest's
multithreaded process, which warns that fork may deadlock; in a sandbox
where the workers could not use shared memory it hung until its timeout.
The split it guards is decided in the shard's __iter__ from
get_worker_info(), so the test now runs each worker's pass in this
process as the worker it is told it is.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
vlm_dataset_utils._ShardedIterable already strides an iterable
dataset across ranks, but it keeps ranks in step by padding each to a
per-rank count the VLM loader knows. shard_dataloader wraps any loader's
stream, which may have no length, so it drops the trailing partial
group instead, and it must also split across the loader's workers,
which the VLM loader never starts.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The test simulated rank 1 of two by stubbing all_gather_object and the
world size, and asserted the tuple this rank put on the wire. It now
runs get_max_batch_size on both ranks of the shared gloo workers, each
with its own GPU's memory faked, and checks that both take the smaller
size through the real reduction, measuring only their own GPU with one
probe forward. A rank left out of the reduction gives up on a group
with a 10 s timeout instead of hanging the run.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Merge the shard tests that differ only in dataset kind or world size:
- the strided map-style test, the iterable strided-and-replay test and
  the world-size-one test become one table over {map-style, iterable} x
  world size {1, 2, 3}, which also checks the replay for both kinds;
- the map-style padding test and the iterable trailing-drop test become
  one uneven-split test that keeps both kinds' asserts.

Fold the _reinstall_a2a_forwards replant test into the forward-loop
replant test (its router, colwise and children asserts; the rank-0 log
line it also checked is wording). Drop, each checked by breaking the
code it guards:
- the no-stacking test: the forward-loop replant test runs two passes
  and fails when a repeat call wraps again;
- the no-tp-plan no-op case: removing that early return changes
  nothing, so the case caught nothing (the no-mesh case stays: it is the
  only guard of its early return);
- the device test's no-device case: the shard-routing test passes no
  device and reads the values back.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The strided iterable shard drops a trailing incomplete group so every rank
yields the same count. A stream with fewer samples than ranks has no complete
group at all, so every rank silently calibrated on nothing. Refuse it instead,
as the VLM loader's own sharding already does for num_samples < dp_size. Every
rank sees the same stream, so all of them refuse it at the first batch, before
any forward.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shard_dataloader forwards the original loader's non-sampler settings to the
rebuilt per-rank DataLoader, but not drop_last, so a loader that drops its
incomplete final batch got one again. Forward it on both paths. Every rank
holds the same number of samples (the sampler pads, the stream keeps whole
groups), so ranks still run the same number of batches.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Each rank of a distributed model calibrates on its own data. AWQ (awq_lite, awq_clip, awq_full,
and SVDQuant, which runs awq_lite) averages its activation scales over data parallelism but picks
each layer's scale from its own rank's search loss; local_hessian and imatrix share nothing
between ranks. The ranks' copies of a weight would come out different, so quantize() and
calibrate() refuse these algorithms on a model whose recorded plan spans several ranks, before
anything changes.

Only a recorded plan counts: Megatron models record none, so their calibration is unchanged.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
nvfp4_act_headroom calibrates its weight scales with its weight_scale_algorithm, and lsq starts from
its scale_algorithm's; either may be local_hessian, which keeps its statistics on the rank as well.
The refusal now reads the methods those settings name too.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…ral ranks

Both take distributed_sync. Turned off, max_calibrate returns before its data-, expert- and
tensor-parallel amax syncs, and mse passes the option on, so ranks calibrating on their own data keep
different scales for the same quantizer. A config that turns it off, nested as another algorithm's
scale algorithm included, is refused like the rank-local algorithms.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…tion tests

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
From this PR on, get_max_batch_size, which hf_ptq calls with its default --batch_size 0, reads the
model's distribution plan, and from the no-gather export on so does export_hf_checkpoint. Each
refuses an FSDP2-sharded model with none recorded, and hf_ptq's --use_fsdp2 load,
parallel_load_and_prepare_fsdp2, records none: it loads the model whole until hf_ptq moves onto
load_pretrained. Record the FSDP2 plan right after that load.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The job reduced over a group with a 10-second timeout and left it registered on the session's
persistent cpu_dist_workers until the pool shut down; it now destroys it.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…dients

shard_dataloader replaces a map-style loader's sampler with the rank's DistributedSampler, so a
loader that picked its own samples (a SubsetRandomSampler, or a batch_sampler) silently calibrated on
the whole dataset; it is now refused. distributed_forward_loop runs its forwards under no_grad, as
the single-node _forward_loop does, and the two new public functions carry type annotations. The
comments on releasing the CUDA cache keep their reason and drop the measurements.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shard_dataloader took an exactly-BatchSampler batch sampler for the one DataLoader builds from
batch_size and checked the loader's sampler instead. A loader given
batch_sampler=BatchSampler(SubsetRandomSampler(...), 4, False) still carries a default
SequentialSampler, so its subset passed the picker refusal and was widened to the whole dataset,
and its batch_size of None rebuilt the shard with auto-batching off. The shard now takes the
picker from the plain BatchSampler itself, refusing a subset there too, and batches by that
sampler's batch_size and drop_last. A loader with auto-batching off is refused.

distributed_forward_loop refuses a batch that is not a mapping of forward keyword arguments by
name, rather than failing on batch.items() inside the loop. auto_quantize refuses a rank-local
calibration on a distributed model in its format check, before it converts the model, as quantize
and calibrate do. _max_batch_size builds its probe input only where it probes.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
A loader built with batch_size=None has auto-batching off: it reads one dataset item per step, a
pre-batched item included, and its rank's shard, rebuilt with batch_size=None, reads the same way.
Sharding it was correct, so refusing it only turned such loaders away. shard_dataloader shards it
again, on the map-style and the streaming path, and still refuses a sampler there that picks its
own samples. The plain-BatchSampler look-through and the custom batch sampler refusal stay. The
tests shard a map-style and a streaming loader with auto-batching off one item per step, and
refuse a subset sampler under one.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
Under expert parallelism each rank's per-expert quantizers hold its own experts, and max and GPTQ
sync them over the data-parallel group their module's ParallelState names, which defaults to every
rank: a slot holding expert 0 on one rank was merged with the slot holding another expert on the
next, silently. quantize, calibrate and auto_quantize now refuse any calibration on an
expert-parallel plan whose modules carry no expert-parallel group, before converting anything; a
model whose routed experts carry their ParallelState calibrates as before. The guard is renamed for
what it now refuses.

get_max_batch_size reads the recorded plan, as that guard does: a model with no plan recorded in a
run of several ranks (a Megatron model, say) is measured on every visible GPU again, as on main,
instead of being refused. distributed_forward_loop also refuses an ep_mesh whose size is not the
plan's ep_size, and the docstrings say that tensors nested in a batch value are not moved and what
each DataLoader worker of a streaming shard decodes.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The expert-parallel calibration guard passed once any module carried an expert-parallel group, an
expert-parallel group of None (every rank) included. A router seeded with the group passed it while
the routed experts kept the conversion default, a data-parallel group of every rank, which merges
different experts' scales; so did experts seeded with a data-parallel group over every rank. The
guard now checks every module holding routed experts: the modules carrying an expert-parallel
group and, once quantization has converted them, the quantized modules under a container named
experts, as transformers' expert-parallel plans name it. Each must carry an expert-parallel group
and, for data parallelism, just the plan's edp ranks.

_reinstall_a2a_forwards flagged a module once it re-planted its all-to-all forward and skipped it
from then on, so a forward rebound again, by another conversion or an export restoring an earlier
forward, kept no EP process group. It now skips a module only while its forward is still the
wrapper planted there.

get_max_batch_size's docstring no longer implies that a distributed model's probe retries after an
out-of-memory error, and says that the batch size it arrives at is not probed.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shard_dataloader(loader, rank, 1) now returns the loader itself, before the sampler check, so a
single process keeps its own sampler, a subset or a shuffling one included, rather than having it
refused or replaced by an unshuffled DistributedSampler.

Recording the FSDP2 plan in hf_ptq's --use_fsdp2 load makes the refusal of rank-local calibration
apply there on several GPUs: AWQ, SVDQuant, local Hessian and imatrix, also as an AutoQuantize
candidate. The FSDP2 section of the hf_ptq README and the changelog now say so, and the refusal's
message and docstring cover the ranks' shards of a weight as well as their copies.

shard_dataloader's docstring states that a stream must yield the same samples in the same order on
every rank and worker, a shuffling one with a fixed seed of its own; the strided shard's docstring
and its short-stream comment read that as the requirement it is.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
hf_ptq quantizes a VLM's vision tower and its other non-language modules apart from the language
model, with every quantizer disabled. Under an expert-parallel plan the calibration check refused
each of them, since no module there carries an expert-parallel group, though they hold no routed
experts whose scales could merge. A module with no expert-parallel group and no container named
experts now passes. Experts that carry no group are still refused before conversion, and converted
experts with a missing or wrong group before they calibrate.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
@shengliangxu

Copy link
Copy Markdown
Collaborator Author

/ok to test 29fcc46

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant