Add LP relaxation and eager TRW-S solvers - #521
Conversation
d6f8359 to
4d617b2
Compare
|
CI note for reviewers: the repo-wide lint job reaches and passes isort, Black, and flake8, then remains red only for mypy errors in autoparallel/tools/overlap_simulator/run.py at lines 255 and 402. PR514/base is already red in that unrelated file (see #514), and this stack does not modify it. The PR514 TorchTitan integration check is also already red upstream; layer validation and the saved full-suite evidence are linked from #519. |
There was a problem hiding this comment.
Pull request overview
This PR extends AutoParallel’s sharding optimizer with additional solver backends over the same eagerly-constructed optimization problem: an LP-relaxation path (used directly when integral) and an approximate TRW-S-based solver, while keeping ILP/CBC as the default. This fits into the core “build ILP once, solve with selectable backend” workflow of autoparallel/api.py + autoparallel/optimize_sharding.py.
Changes:
- Add an eager TRW-S approximate solver (
ApproximateShardingSolver) that reuses the existing PuLP-built optimizer and writes back assignments for exact scoring/validation. - Add LP-relaxation solving and lower-bound certification utilities in the sharding optimizer, plus API plumbing to select
"ilp" | "lp" | "approx". - Add targeted tests for LP integrality/lower-bound certification and approximate-solver faithfulness + constraint adherence.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
autoparallel/api.py |
Adds solver selection to AutoParallel.optimize_placement() and optional LP lower-bound optimality checking/logging. |
autoparallel/optimize_sharding.py |
Adds LP relaxation/lower-bound routines, prunes infinite-cost edges, and updates constraint/objective handling to support alternate solvers. |
autoparallel/approximate_sharding.py |
Introduces TRW-S + local search approximate solver that operates on the existing optimizer’s decision vars/constraints. |
tests/test_approximate_sharding.py |
New tests covering approx-vs-ILP gap bounds, feasibility/constraint validity, LP==ILP on the fixture, and optimality-check logging. |
tests/test_lp_relaxation.py |
New test validating LP relaxation provides a certified lower bound and restores optimizer state afterward. |
tests/conftest.py |
Adds an autouse fixture to reset placement-options caching between tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for eqs in arg_vars.values(): | ||
| self.prob += ( | ||
| pulp.lpSum(arg_vars.get(argi, [])) == 1, | ||
| pulp.lpSum(eqs) == 1, | ||
| self._get_next_name("unique_decision"), | ||
| ) |
| opt = self.sharding_optimizer | ||
| if opt.prob is None: | ||
| logger.warning( | ||
| "optimality_check skipped: solver=%r build has no PuLP problem; " | ||
| "construct with solver='ilp' or 'lp' to enable it.", | ||
| self.solver, | ||
| ) | ||
| return | ||
| achieved = opt._safe_float(pulp.value(opt.prob.objective)) | ||
| lb_res = opt.get_lower_bound(verbose=verbose) | ||
| lb = lb_res.objective | ||
| if not lb or lb <= 0 or achieved is None: | ||
| logger.warning( | ||
| "optimality_check inconclusive: lower_bound=%s achieved=%s", | ||
| lb, | ||
| achieved, | ||
| ) | ||
| return |
4d617b2 to
518188a
Compare
518188a to
704dba7
Compare
9e6181f to
64c12c9
Compare
Keep ILP as the default while supporting integral LP extraction and lower-bound certificates on the same optimizer problem. Authored with Claude.
Build a pairwise factor graph from the existing optimizer costs and constraints, then solve it with TRW-S and constrained local-search polish. Authored with Claude.
Authored with Claude.
Authored with Claude.
Authored with Claude.
Use a non-colliding invalid-cost marker and make approximate solving and serialization honor the active memory constraint state after constraints are removed. Authored with Claude.
d246df3 to
5fa1e9d
Compare
Summary
Second PR in the PR519 review stack. It adds LP relaxation and eager full-cost TRW-S over the same optimizer problem; ILP remains the default. Lazy/no-PuLP construction and split-dimension seed search are intentionally left to PR522 and PR523.
Current head
5fa1e9df8e39eb0a272b903ff4fbe4ba353d8f5dalso replaces the colliding invalid-cost sentinel and makes approximate solving and serialization honor removal of active memory constraints without changing the search space.Interface
The supported entry point is
AutoParallel:ilp: exact PuLP/CBC solve.lp: solve the continuous relaxation and extract it only when integral; otherwise raise and direct the caller toilp.approx: eager pairwise-factor TRW-S followed by constrained local-search polish.approximate_optionsis forwarded to this solver.optimality_check=True: solve the LP relaxation as a lower bound and report the certified gap. It requires an ILP/LP-backed optimizer build.ShardingOptimizer.solve_lp_relaxation()remains the lower-level diagnostic interface. It restores binary variable categories after the relaxation so a later ILP solve is unaffected.Reproducible tests
The harness and its tests are checked in as
tests/search_profile.pyandtests/test_search_profile.py; there is no separate profiling directory.Unit and fake-process-group coverage:
PYTHONPATH=. python -m pytest -q \ tests/test_search_profile.py \ tests/test_approximate_sharding.py \ tests/test_lp_relaxation.py \ tests/test_optimize_placement.py::test_invalid_strategies_are_pruned \ -k "not ilp_and_approx_match"Real four-GPU end-to-end behavior, also run by the Test CUDA multi-GPU job:
This test enters through
AutoParallelfor both ILP and approximate solvers, adds the same memory/input/output constraints, solves, applies the placement, initializes identical weights, runs forward, reconstructs the full output, computes the same scalar loss, and runs backward. It compares the objective, full output, and every parameter gradient and emits per-rank phase/memory breakdown asAUTOPARALLEL_E2E_BREAKDOWN.Full suite:
Search-only reproduction
The following matrix uses meta models, a fake process group, and explicit H100 properties. It measures placement search only, not distributed model execution. Each JSON records the expanded config, environment, Git commit/status, constraints, solver status, objective, placement hash, node/variable counts, phase timings, and process peak RSS.
Add
--detailed-solutionto emit per-node placement and component cost contributions. Reproduction should use a clean worktree and verify that the recordedgit.statusis empty.Search settings:
2.14.0.dev20260629+cu130, CUDA 13.0, PuLP 3.3.2; AMD EPYC 9654 host.PYTHONHASHSEED=0,torch.manual_seed(0), repeated subgraphs, default NCCL cost model, parameterbfloat16, reductionfloat32, and the same parameter memory/input/output constraints. Dynamic tracing is enabled only for DSv3.(8,8): dim 2048, 16 layers, 32 heads, 8 KV heads, FFN multiplier 1.5, FFN multiple 256, RoPE theta 500000, vocab 128256, sequence 2048, global batch 16. Mesh axes are(dp,tp); input is(Shard(0), Replicate())and output is(Shard(0), Shard(2)).(8,8): dim 4096, 32 layers, 32 heads, 8 KV heads, FFN multiplier 1.3, FFN multiple 1024; remaining settings and placements as LLaMA1B.(8,8): dim 256, 6 layers (1 dense), 16 heads, 64 experts, vocab 2048, sequence 2048, global batch 512; degrees(8,8,1,1,8)for(dp_replicate,dp_shard,cp,tp,ep). Input and output areShard(0)on both mesh axes.Search-only results
The matrix was run once per row at
1b92f5fb7d81315b9a9d417d25eb4c213498b9a4. The final PR head moves the harness totests/, extracts the same validation checks for unit coverage, and adds the real E2E/CI invocation; it does not change the solver implementation. Timings usetime.perf_counter; RSS uses processru_maxrss.factor buildandsolver coreare contained insolve call.All nine jobs returned success, finite objectives, nonempty placements, and zero constraint violations. ILP and LP report
Optimal; approximate reportsHeuristicandSolution Found. ILP, LP, and approximate objectives match for all three workloads. LLaMA8B placements are identical. LLaMA1B differs at 16 of 4,299 equal-costviewplacements and DSv3 differs at 10 of 2,288 equal-costpermuteplacements; detailed compute, communication, transition, and total cost contributions match at recorded precision.These are single observations with no run-to-run variance. They are diagnostic breakdowns, not stable latency, speedup, throughput, or memory-improvement claims.
Real 2x2 GPU E2E result
Run at final head
9e6181f5bae8ff1fb02f75416b672f1c84e531c5with Python 3.12.13, PyTorch2.14.0.dev20260629+cu130, CUDA 13.0, NCCL 2.30.7+cuda13.3, and four NVIDIA H100 GPUs.Configuration: DeepSeekV3 debug model, dim 256, 6 layers (1 dense), 16 heads, 8 experts, vocab 2048, sequence 2048, global/local batch 32/8, compute
bfloat16, reductionfloat32; mesh(2,2)withdp_replicateanddp_shard_in_ep, degrees(2,2,1,1,2). Inputs and outputs areShard(0)on both mesh axes. Runtime collectives are real NCCL operations. The non-canonical four-GPU topology uses the repository's documented default cost-model fallback.Behavior result:
34091.15472077961, 2,288 solution nodes, zero constraint violations.rtol=atol=0.015625; observed max/mean absolute difference0.03125/0.0004442967.4.6566e-10/1.1185e-12.Breakdown method:
time.perf_counterper rank, with CUDA synchronization around materialization, forward, full-output construction, and backward. Values are mean[min, max]over four ranks from one ILP run followed by one approximate run.[0.008, 0.009][0.007, 0.008][12.477, 13.212][10.559, 11.154][18.342, 21.626][14.417, 18.362][0.001, 0.001][0.001, 0.001][13.906, 16.092][12.857, 14.611][0.972, 1.207][10.871, 12.347][9.267, 9.754][8.229, 9.201][8.642, 9.103][0.833, 6.531][0.064, 6.765][1.114, 1.117][0.203, 0.205][0.003, 0.004][0.002, 0.005][1.072, 1.083][0.189, 0.196][62.854, 63.014][53.935, 54.583]Mean
[min, max]CUDA peak allocated memory is 4.829[4.821, 4.839]GiB for ILP and 4.904[4.896, 4.914]GiB for approximate. Peak reserved memory is 4.992[4.992, 4.992]GiB and 5.062[5.061, 5.063]GiB, respectively. This is one ordered run, so later execution can benefit from process, compiler, allocator, and filesystem caches; no performance improvement is claimed.Published evidence
41aac1783a5c67acccdf570f64d16b51ea0188b2455beb34b62b79a0815e6e4b).Validation status
autoparallel/tools/overlap_simulator/run.pyas the PR base. TorchTitan integration remains red on the same upstreamconfig_registryimport error as the prior PR head.Authored with Claude.
Stack