Repository navigation
[OMNIML-6052] Add the distribution plan, a run-scoped process group, and collectives that fail together - #2736
Open
shengliangxu wants to merge 60 commits into
Open
shengliangxu wants to merge 60 commits into
shengliangxu wants to merge 60 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. |
Contributor
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2736 +/- ##
==========================================
+ Coverage 73.82% 74.14% +0.32%
==========================================
Files 662 650 -12
Lines 75949 72948 -3001
==========================================
- Hits 56069 54087 -1982
+ Misses 19880 18861 -1019
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:
|
Collaborator
Author
|
/ok to test 2dcf64f |
shengliangxu
force-pushed
the
shengliangx/hfep-1-session
branch
2 times, most recently
from
October 13, 2026 01:32
a97136a to
fc0ed3b
Compare
Contributor
|
Collaborator
Author
|
/ok to test fc0ed3b |
Collaborator
Author
|
/ok to test cdc1b5c |
Collaborator
Author
|
/ok to test 485c0f5 |
Collaborator
Author
|
/ok to test c4581d0 |
Collaborator
Author
|
/ok to test 7612c2e |
Collaborator
Author
|
/ok to test b63b579 |
Collaborator
Author
|
/ok to test 680dc30 |
shengliangxu
force-pushed
the
shengliangx/hfep-1-session
branch
from
October 15, 2026 02:53
680dc30 to
351470c
Compare
broadcast_from and all_reduce checked that a value pickles with torch.save, protocol 2, and then handed the value itself to the object collective, which pickled it again with plain pickle's default protocol, outside the guard. A value that passed the check could then fail inside the collective on one rank alone, and the other ranks waited for it. Each value is now serialized once, inside the guard, and loaded back there too, so a value the other ranks could not load fails alike; the collective carries the bytes, which always pickle, and every receiving rank loads them. Signed-off-by: Shengliang Xu <[email protected]>
…n every rank The serialized value is loaded back before it is sent, so a value that pickles but cannot be loaded fails inside the guard, which sends the failure to every rank. Nothing tested that half: without the load-back the sending rank returned and only the receiving ranks raised. A value whose reduction raises on load now crosses both broadcast_from and all_reduce on two ranks. Signed-off-by: Shengliang Xu <[email protected]>
gloo's new_group can return on one rank while a peer is still connecting to the group. A pooled job that runs no collective on its bounded group (one that mocks barrier, or one refused before it calibrates) destroys the group at once, and the peer's new_group then fails with "Gloo connectFullMesh failed ... Connection closed by peer". That is the "Worker(s) failed" flake of the bounded-barrier and expert-parallel calibration tests in CI. bounded_collectives now runs a barrier on the new group before the job's body, with the real barrier, which the job may have mocked by then. On a two-rank gloo pool under load (six job loops pinned to two cores), the bounded-barrier job failed 12 of 3000 times without the barrier and 0 of 3000 with it. Signed-off-by: Shengliang Xu <[email protected]>
Loading the bytes back on the computing rank proves they load there: a value whose load depends on the loading rank, a tensor's, which lands on the device it was saved from, can still fail on a peer alone. The collectives carry small values, and the check holds one more copy of the value while it runs. The full load's reason is that the values are of any class, as through torch's object collectives. Signed-off-by: Shengliang Xu <[email protected]>
The sending rank returned the object fn built, still shared with whatever fn closed over, while every other rank got a copy loaded from the bytes sent: a caller mutating the result would change state on rank 0 alone. Every rank now returns the loaded copy, as all_reduce does. Signed-off-by: Shengliang Xu <[email protected]>
broadcast_from's docstring promised every rank the value loaded from the bytes sent, and all_reduce's that it shares nothing with the callers', but with no distributed run both hand back fn's own value, neither serialized nor copied. The docstrings now say which path gives which. Signed-off-by: Shengliang Xu <[email protected]>
…he load-back costs Before a launch sets its group up, each process runs alone with a model of its own, so a model with no plan reads as a single process's there; the load-back check in _encoded loads the value once more on the sending rank, so a load with side effects has them twice there. Signed-off-by: Shengliang Xu <[email protected]>
distributed_run aborts only in a run of several ranks on a group that was not there when it began; a run that found the caller's group leaves the error to the caller, which owns the peers. Signed-off-by: Shengliang Xu <[email protected]>
_on_group reads the caller's group from the keyword only; a group passed by position would also get the bound one, and fail as a duplicate argument. Signed-off-by: Shengliang Xu <[email protected]>
Both collectives read the launch's environment now, so an inherited WORLD_SIZE would make them refuse; the test says it runs alone, as its siblings do. Signed-off-by: Shengliang Xu <[email protected]>
…e the collective _sendable formatted the error outside the guarded block, so an exception whose __str__ raises, or a SystemExit whose code does, failed its rank before the broadcast or gather and left the other ranks waiting. Its text now falls back to a stand-in naming the type. Signed-off-by: Shengliang Xu <[email protected]>
load_pretrained records the plan only from the load routing on; until then the advice to load through it would leave a multi-rank model as planless as before. Signed-off-by: Shengliang Xu <[email protected]>
… asks Signed-off-by: Shengliang Xu <[email protected]>
Signed-off-by: Shengliang Xu <[email protected]>
The derivations behind them (why a SystemExit travels as its code, what the load-back costs and why a rank-dependent load can still fail on a peer alone) move to the PR description, as CONTRIBUTING.md asks of rationale. Signed-off-by: Shengliang Xu <[email protected]>
The report of the body's exception in distributed_run's finally formatted it with str(); an exception whose __str__ raises escaped there, skipping abort() and leaving the other ranks to wait out the group's timeout. Signed-off-by: Shengliang Xu <[email protected]>
A SystemExit out of the body is not reported: the group is destroyed as on a normal exit, whose barrier waits on every rank. The collectives carry one rank's exit to all of them; a rank that exits alone waits in that barrier until the group times out. The docstring now states that precondition. Signed-off-by: Shengliang Xu <[email protected]>
broadcast_from's label check runs on the receivers only, so the sending rank returns its value when the labels differ; and _encoded's load-back proves a value loads on the producing rank only. Both caveats went to the PR description with the rest of the rationale; they are part of the contract, so they are back beside the code. Signed-off-by: Shengliang Xu <[email protected]>
CONTRIBUTING.md has test filenames mirror the production module they cover, and these tests cover modelopt/torch/utils/distributed.py. This completes the move the previous commit started, which removed test_distributed_run.py along with its docstring change. The moved helper that builds a model for the plan tests is renamed _plan_model, beside the file's own _model. Signed-off-by: Shengliang Xu <[email protected]>
_decoded loaded a tensor in a value onto the device it was saved from, the producing rank's, so on every other rank it landed on another rank's GPU, or failed where that index is absent. It now loads with map_location=cpu; _encoded's caveat names what can still fail on a peer alone, a class only the producer can import. No collective in the series sends a tensor today: they carry flags, sizes, plans and names. Signed-off-by: Shengliang Xu <[email protected]>
broadcast_from and all_reduce are the public entry points, and _decoded's map_location makes the CPU the device of any tensor they return, on every rank, the producing one too. Signed-off-by: Shengliang Xu <[email protected]>
A group's collective timeout is fixed when it is set up, so a run that finds the caller's group already there cannot honor a timeout passed to it; it now warns instead of dropping it silently. The found-group test passes one and fails without the warning. Signed-off-by: Shengliang Xu <[email protected]>
… torch.device _decoded loaded what the ranks send with weights_only=False, under a SECURITY.md exception. It now loads with weights_only=True, as _deserialize does: built-in types and tensors load as they are, and DistributionPlan, the one class the collectives carry, is registered with add_safe_globals. A value of an unregistered class fails its load-back on the producing rank, inside the guard, so every rank raises; the tests register their protocol-2 class and expect that refusal for the unloadable one. The public docstrings state the requirement. DistributedState.device is a torch.device on every path, not the caller's string outside a launch; the tests compare it directly. Signed-off-by: Shengliang Xu <[email protected]>
… tests _encoded now builds on _serialize, and _decoded calls _deserialize, which takes the collectives' map_location, so one loader sets the load flags (review). broadcast_from's rank parameter is src: it shadowed this module's rank(), which it now uses. Tests, per review: the stand-in broadcast and gather tests and their helpers are gone, the real two-rank jobs cover the same paths; the encoding's failure modes are tested once, single-process, with one integration case per collective; the bounded_collectives helper's own test is gone (the helper stays: later tests rely on its 10 s bound, which keeps the pool alive where the pool's job timeout kills it); the plan tests that restated the dataclass are gone, the invalid-plan cases cover each branch once, and the one-line plan checks fold into the record-once test; the launch-state test folds into the end-the-group matrix, whose entered-in-except variant runs on two rows; test docstrings are one line. Signed-off-by: Shengliang Xu <[email protected]>
The launch-state test folded into the end-the-group matrix ran as rank 2 of 4 on local rank 1, which is what pinned the device to the local rank and is_main to rank 0. The matrix's launch rows now run so too, and check the state after the run, so a row that aborts cannot swallow a failed check; picking the device by the global rank, or is_main always true, fails every launch row. Signed-off-by: Shengliang Xu <[email protected]>
The plan is the one class the collectives carry, and its registration with add_safe_globals is what lets it load back under weights_only; the test fails without it. Signed-off-by: Shengliang Xu <[email protected]>
hf_ptq passes one on every run, for its launches; a run of one waits on no peer, so the timeout has nothing to bound there. The single-process test passes one and fails if the run warns about it. Signed-off-by: Shengliang Xu <[email protected]>
A single process in a one-rank group of the caller's waits on no peer either, so distributed_run takes its timeout without a warning there too, as the docstring now says. The single-process test gains that row, which fails with the warning. Signed-off-by: Shengliang Xu <[email protected]>
The warning's condition is is_distributed_run() before the run sets a group up. Comments say a loaded plan skips __post_init__, as the sending rank built and checked it, and that the object collective pickles the message between the job's ranks while weights_only bounds its value. Signed-off-by: Shengliang Xu <[email protected]>
Collaborator
Author
|
/ok to test cf4597d |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
The expert-parallel PTQ series is now a tree instead of one stack (re-cut 2026-10-27, from main 1521053), so PRs that do not depend on each other can be reviewed and merged in parallel. This PR was [4/21]. Rebased onto main 7c3c868 later the same day, after #2739 merged, and onto main f03651e that night.
Base:
main. It can be reviewed and merged on its own.Re-cut: this PR's diff is unchanged; only its base moved. Review fixes since are listed at the end of Testing.
Series layout
mainWhat does this PR do?
Type of change: New feature
This PR opens the series' second phase, the distribution plan and the loads it routes to (the series layout above maps the whole series). It adds the distributed basics that phase builds on, all in
modelopt.torch.utils.distributed:is_distributed_launch()reads the launch environment, so argument checks work before any process group exists;is_distributed_run()reads the live group.DistributionPlanis how a model is distributed over a run's ranks, MoE or dense: the world size, the expert-parallel degree, and whether the rest of the model is replicated on every rank or FSDP2-sharded (dense). A single process is the default plan. An EP degree that cannot divide the world, or any degree below 1, is rejected when the plan is built, rather than as a mesh error once every rank is up or as a silent replication.set_distribution_plan(model, plan)records the plan on a model and every submodule, anddistribution_plan(model)reads it back. The plan says how the model is placed over the run; theParallelStatea quantized module holds names the process groups its collectives run on. Later steps that depend on how the model is distributed read it there rather than inspecting the model or the process group: [OMNIML-6054] Decide how a model is distributed, from the model built on the meta device #2737 decides the plan, [OMNIML-6054] Route load_pretrained by the distribution plan #2779 records it at load. A model is distributed once, and its plan recorded once: recording a plan on a model that already records one, or whose submodule does, raises, the same plan included, and leaves the model as it was. The refusal names the submodule that already records a plan by its qualified name. A model with none recorded is a single process's, unless it is FSDP2-sharded or held in a run of several ranks, which raises rather than be taken for one. That includes a module added to a model after its plan was recorded, which has none and cannot reach the model it was added to: read the plan off that model, asdistribution_plan's docstring and its refusal say.distributed_run(timeout)owns the run's process groups for a block.abort()instead, which prints the traceback; the launcher then tears the job down. Destroying the group would wait on peers that may sit in a collective this rank never reaches, so a rank that fails alone would look hung. A single process and an intentional exit (SystemExit) clean up as on success, and the exit is not reported as an error. Its one-line report of the error names the rank by the live group (rank(), not theRANKvariable), and the run prints no traceback of its own:abort()prints it, and anywhere else the error goes on to the caller with it.sys.exc_info(), which also returns an exception a caller up the stack is handling, so a run entered in anexceptblock aborted a healthy rank on a clean exit, or destroyed its group without the barrier. The teardown is a private_destroy_group(exc), whichcleanup()calls withsys.exc_info()[1]as before: a clean exit from a run entered in anexceptblock runs the barrier before it destroys the group.distributed_runyields this process'sDistributedState: its rank and the run's size, read from the live group the block's collectives run on (0 and 1 without a group), and the device it computes on.device. In a group the caller set up without the launch'sWORLD_SIZE, each rank gets its own rank and the group's size, on the caller'sdevicetoo.is_mainis read from the rank. The class moves here fromexamples/hf_ptq, which [OMNIML-6085] Run expert-parallel PTQ from hf_ptq.py #2754 switches over.broadcast_from, only the receiving ranks fail, as the sending rank sees no other rank's label.fn's value must pickle.broadcast_from(label, fn, src=0)runsfnonsrconly and returns its result on every rank. It is for a value that depends on what each rank reads for itself, so ranks computing their own could differ.all_reduce(label, fn, combine)runsfnon every rank and returnscombineof all the results on every rank. Withmin, for example, each rank's memory bounds a batch size, and no rank's choice runs another out of memory.fnraises on any rank, aSystemExitorKeyboardInterruptincluded, or returns a value that does not pickle, every rank raises instead of waiting in a collective that rank never joins. The error goes with its kind: aSystemExitgoes as its exit code, and every other rank raisesSystemExitwith that code, so every rank takesdistributed_run's exit path (barrier, then destroy) together; as aRuntimeErrorit would abort the peers while the exiting rank waits in the exit barrier. Any other error reaches the other ranks as aRuntimeErrornaming the failing rank and its error. When ranks fail differently in oneall_reduce, every rank still fails alike: an error other than aSystemExiton any rank makes every rank raise an error (a rank with one its own, every other rank, an exiting one included, aRuntimeErrornaming the first), so no rank waits in the exit barrier for an aborted one; only when every failing rank exits does every rank exit. Unlikeallreduce,broadcastandmaster_only, the two runfnthemselves, so that a rank whosefnfails still joins;all_reduce's docstring says so. Both collectives picklefn's value inside itstry(torch.saveinto anio.BytesIO; bandit refusesimport pickleundermodelopt/), so a value that does not pickle fails as that rank's error on every rank, rather than leaving the others waiting in the collective. Outside a distributed run,fnruns alone; on a rank of a multi-rank launch whose process group is not set up yet, both raise instead, since each rank would compute its own value.Under a launch,
distributed_runrefuses any device but plaincuda(LOCAL_RANKpicks the index), a pinnedcuda:Nincluded, instead of ignoring it, andbroadcast_fromchecks the rank range in a single process too.The test helper
bounded_collectivesruns a pooled job's object collectives on a group with a short timeout; it bounds those on the default group only, and keeps a group the caller names.Usage
Testing
New tests in
tests/unit/torch/utils/test_distributed.py, the module's test file. The collectives and the plan guard run on two real gloo ranks (cpu_dist_workers), over a group with a 10 s timeout, so a rank left waiting fails the test instead of hanging the session:fully_shard, and of a module added to a model after its plan was recorded;broadcast_from, on the two ranks: only the sending rank runsfn; any rank can send, and a rank outside the run is refused; the sending rank's failure raises everywhere, itsSystemExitexits every rank with its code, and a value that does not pickle raises on every rank (each fails without its fix); ranks out of step fail;all_reduce, on the two ranks: every rank gets the combined value; another rank's failure raises everywhere, naming it, aSystemExiton one rank exits every rank with its code, one rank exiting while the other errors makes both raise errors, the exiting one aRuntimeErrornaming the other (fails without the fix), and a value that does not pickle raises on every rank (each fails without its fix); ranks out of step fail;fnalone, with no collective;distributed_run: a launch read from the environment, not from a live process group; a single process sets no group up; a distributed launch sets it up with the timeout and tears it down; a rank of several whose body raised aborts instead, while a single process and an intentional exit clean up, the exit unreported. The teardown runs for real, recording its barrier anddestroy_process_group: the barrier first, unless the body raised. Each of these cases also runs entered inside anexceptblock, where only the body's own exception counts (the clean-exit cases fail without the fix). The state it yields: the rank's, on its CUDA device, under a launch; rank 0 of one, on the given device, in a single process. A group the body sets up is destroyed with the run, and a group the caller had is left alone, even when the body raises. Its report of a body's error names the stubbed group's rank, and nothing prints a traceback (each fails without its fix).Mutation-checked: each mutant below fails a test.
broadcast_from: every rank runningfn, always sending from rank 0, no range check onsrc, raising before the broadcast, ignoring the sent error, ignoring out-of-step labels, a collective even in a single process.all_reduce: returning this rank's value uncombined, raising before the gather, ignoring another rank's failure, a failing rank raising before it reads the other ranks' failures, ignoring out-of-step labels, a collective even in a single process.distributed_run: a raising rank of several cleaning up instead of aborting, a single process or an intentional exit aborted, the caller's process aborted, a group set up in a single process, the timeout dropped, no group set up on a launch, a group the body set up left behind, the caller's group destroyed, the group's presence not read at entry, the report's rank read fromRANK, the traceback printed on the caller's path.is_mainalways or never true.distributed_runleaves a group the caller set up before the run, under a distributed launch too, without setting it up again, and yields that group's rank and size (rank 1 of 2), also when the caller set it up without the launch's environment (those single-process cases fail without the fix).Near-duplicate tests are merged into one test per behavior, and the launch-detection test, which the
distributed_runtests cover, is dropped. Each drop is checked by mutation.The launch tests use CUDA, a test covers the refusal of a non-CUDA device and of a pinned
cuda:N, and a single process refuses rank 1.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.Exercised end to end by the series' multi-node runs on earlier revisions; GPU tests were not re-run on this one. See [OMNIML-6085] 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: the rank's CUDA device is bound under a launch also when the caller set the group up;
broadcast_fromandall_reducerefuse a multi-rank launch whose process group is not set up, instead of computing per rank; comments on how the collectives check and unpickle values. 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). The line this PR's rewrite of the run's cleanup deletes is main's
sys.exception()form now ([OMNIML-5802] Drop Python 3.10 support and adopt Python 3.11+ features #3007).Review fixes, 2026-10-27 (bot review):
broadcast_fromandall_reduceserialize each value once, inside the guard that sends a failure to every rank, and the collective carries the bytes, so a value that passes the check can no longer fail later in the collective on one rank; two-rank tests with a value only pickle protocol 2 takes fail without the change.CI flake fix, 2026-10-27:
bounded_collectivesruns a barrier on its new group before the job's body. A pooled job that runs no collective on the group (one that mocksbarrier, or one refused before it calibrates) destroyed it at once, and a peer still connecting to it then failed innew_groupwith "Connection closed by peer", the "Worker(s) failed" flake of the pooled tests in CI. The bounded-barrier job on the 2-rank gloo pool, under load (six job loops pinned to two cores):Review suggestions, 2026-10-27 evening:
broadcast_fromreturns, on the sending rank too, the value loaded from the bytes it sent, so no rank's result shares state withfn's;_encoded's docstring says what its load-back proves.Review suggestion, 2026-10-27 night:
broadcast_from's andall_reduce's docstrings say what each rank gets back, on a distributed run and in a single process.Review suggestions, 2026-10-27, last round:
distribution_plan's docstring says a launch's processes read as single processes before their group is set up, and_encoded's says its load-back runs the value's load once more on the sending rank.Review fixes, 2026-10-27, last round: a failure whose text cannot be formatted (an exception whose
__str__raises) still reaches the broadcast or gather, and both two-rank jobs cover it; the advice for a model with no plan points atset_distribution_plan()only, asload_pretrainedrecords plans from [OMNIML-6054] Route load_pretrained by the distribution plan #2779 on;distributed_run's docstring limitsabortto the run's own group;_decodedcarries the inline justificationSECURITY.mdasks for.Review fix, 2026-10-27, last round:
set_distribution_planrefuses a plan for another number of ranks than the run it is recorded in, and records nothing then, when the run has more than one rank; a run of one can still stand in for several, as the export tests' single-process runs do. The two-rank job's new check fails without the change.Review suggestion, 2026-10-28: the private collective helpers' docstrings state their contracts only. The rationale they carried:
distributed_runends an exit with a barrier but an error withabort, so a rank exiting while its peers abort would wait in that barrier for ranks that are gone; if any rank's failure is not an exit, every rank raises an error instead._encodedloads each value back on the sending rank, so a value that cannot round-trip fails inside the caller's guard, which tells every rank. That costs one more copy and a second load on the sender. A load that depends on the loading rank (a class only the sending rank can import) can still fail on a peer alone, which is acceptable for the small values these collectives carry: a plan, a flag, a size.fnreturned.Review fix, 2026-10-28, second round:
distributed_run's report of the body's exception formats it through_texttoo, so an exception whose__str__raises still reachesabort()instead of escaping thefinally;test_the_run_ends_the_group_it_set_upgains that case, which fails without the change.Review suggestion, 2026-10-28, fifth round:
distributed_run's docstring says aSystemExithas to be every rank's, as the collectives make it, since the exit path waits on every rank in the group's teardown.Rebased onto main
7092db6ac5bon 2026-10-28 (no conflicts). Bot review fixes, same day: two contract caveats are back in the docstrings (a broadcast's label mismatch fails only the receivers, and the load-back proves a value loads on the producing rank only), and the tests moved intotest_distributed.py, as CONTRIBUTING.md has test files mirror their module.Review fixes, 2026-10-28, last round:
_decodedloads withmap_location="cpu", as_deserializedoes, so a tensor in a value lands on the CPU on every rank, the sending one included, instead of on the sending rank's device, which on a peer is another rank's GPU or absent;broadcast_from's andall_reduce's docstrings say so. No test pins the mapping: a single process returns before any value is serialized, so observing it takes a two-rank run with a CUDA tensor, and no caller in the series sends a tensor (they send aDistributionPlan, a flag, a size). pre-commit, mypy included, passes at this head.Review fix, 2026-10-28, evening:
distributed_runwarns when atimeoutis passed but the caller's group is already set up, since a group's timeout is fixed when it is set up, instead of dropping it silently.test_the_run_leaves_a_group_it_found_alonepasses a timeout in all four of its cases and fails without the warning. pre-commit, mypy included, passes at this head.Review changes (kevalmorabia97), 2026-10-28, night:
_decodedloads withweights_only=True, withDistributionPlanregistered viaadd_safe_globals. A value of an unregistered class fails its load-back on the producing rank, inside the guard, so every rank raises; the two-rank tests now expect that refusal, and register their protocol-2 class.DistributedState.deviceis atorch.deviceon every path. Each test fails without its change; this PR's tests pass (52), and pre-commit, bandit included, passes against main.Rebased on 2026-10-28, night, onto main
bc0b6e03237, without conflicts; this PR's own diff is unchanged. pre-commit passes against main, and this PR's tests pass (52).Review changes (kevalmorabia97), 2026-10-28, night:
_encodedbuilds on_serializeand_decodedcalls_deserialize, which takes the collectives'map_location, so one loader sets the load flags.broadcast_from'srankparameter issrc, as it shadowedrank()(Claude). The tests are trimmed: the stand-in broadcast and gather tests and their helpers are gone, since the real two-rank jobs cover those paths. The encoding's failure modes are tested once in a single process, with one integration case per collective.bounded_collectivesloses its own test but stays, because later tests rely on its 10 s bound, which keeps the worker pool alive where the pool's job timeout would kill it. The plan tests that restated the dataclass are gone, and the invalid-plan cases cover each branch once. The launch-state test is folded into the end-the-group matrix, whose launch rows still tell the local rank from the global one; its entered-in-except variant now runs on two rows. Test docstrings are one line. 36 tests pass, and pre-commit passes against main.Rebased on 2026-10-28, night, onto main
e9f432affbb(Bump minimum torch to 2.10 and drop older-torch code paths #3091, the torch 2.10 floor). Bump minimum torch to 2.10 and drop older-torch code paths #3091 made_get_fsdp2_meshcallfully_shard.state, so [OMNIML-6073] [1/3] Gather only FSDP2's own parameters in its weight-access window #3088's two ownership tests, which counted those calls, fail on main. This PR carries the fix, counting_fsdp2_param_groups, the scan itself, instead. The same commit is in [OMNIML-6052] Add the distribution plan, a run-scoped process group, and collectives that fail together #2736 and [OMNIML-6073] [2/3] Quantize fused experts under grouped_mm; leave transposed layouts unquantized #2745: whichever merges first lands it, and the other's copy drops out on rebase. This PR's tests andtest_utils.pypass (63), and pre-commit passes against main.Review suggestions (Claude), 2026-10-28, late night: a test round-trips a
DistributionPlanthrough the collectives' encoding, the one class they carry; it fails with theadd_safe_globalsregistration removed. The carriedtest_utils.pyfix stays until [OMNIML-6052] Add the distribution plan, a run-scoped process group, and collectives that fail together #2736 or [OMNIML-6073] [2/3] Quantize fused experts under grouped_mm; leave transposed layouts unquantized #2745 merges. It counts the ownership scan itself, which is narrower than countingfully_shard.state(since Bump minimum torch to 2.10 and drop older-torch code paths #3091 also part of the mesh lookup), and that scan is what the cache saves. 37 tests pass, and pre-commit passes against main.Review suggestion (Claude), 2026-10-28, late night:
distributed_run's docstring says a single process takes atimeoutwithout a warning, since a run of one waits on no peer andhf_ptq.py([OMNIML-6085] Run expert-parallel PTQ from hf_ptq.py #2754) passes one on every run, for its launches. The single-process test passes a timeout and fails if the run warns about it. 37 tests pass, and pre-commit passes against main.Review suggestion (Claude), 2026-10-28, late night: the unused-timeout warning is only for a caller's group of several ranks, so a single process in a one-rank group of the caller's takes a timeout quietly too; the single-process test gains that row, which fails with the warning. 38 tests pass, and pre-commit passes against main.
Review suggestions (Claude), 2026-10-28, late night: the timeout warning's condition is
is_distributed_run(), the same test before the run sets a group up; comments say a loaded plan skips__post_init__(the sending rank built and checked it), and that the object collectives pickle the message whileweights_onlybounds its value, which the Security section now says too. Kept: the process-wideadd_safe_globalsregistration, as the collectives ask of a caller's classes, and theall_reducename (open for the code owners). 38 tests pass, and pre-commit passes against main.Rebased on 2026-10-28, late night, onto main
1ca8bd1fc4dafter [OMNIML-6073] [2/3] Quantize fused experts under grouped_mm; leave transposed layouts unquantized #2745 merged: thetest_utils.pyfix both PRs carried landed with [OMNIML-6073] [2/3] Quantize fused experts under grouped_mm; leave transposed layouts unquantized #2745, so its copy here dropped out, and this PR's diff isdistributed.pyand its tests again. 65 tests pass (this PR's andtest_utils.py), and pre-commit passes against main.Security
The value loads with
weights_only:_decodedloads each collective's payload withtorch.load(..., weights_only=True), as_deserializedoes. Built-in types and tensors load as they are;DistributionPlan, the one class the collectives carry, is registered withtorch.serialization.add_safe_globals, and a caller that sends another class registers it. The transport is torch's object collectives (broadcast_object_list,all_gather_object), which pickle the message envelope (label, payload bytes, error text) between the job's ranks, one trust boundary;weights_onlybounds the value inside it. The earlierweights_only=Falseexception is gone (review, 2026-10-28).Before your PR is "Ready for review"
CONTRIBUTING.md: N/Ac3576fe83b5; the predicate and comment change after it is from that review.Additional Information
Part of the expert-parallel PTQ series for Hugging Face MoE models. Based on main and independent of #2745; the series' other PRs build on it.
Summary by CodeRabbit