Repository navigation
feat: multi-GPU quantization improvements -- batched device-local sea… - #2357
Conversation
This comment has been minimized.
This comment has been minimized.
5b49ec9 to
0f0ea17
Compare
|
/azp run Unit-Test-CUDA-AutoRound |
|
Azure Pipelines successfully started running 1 pipeline(s). |
0f0ea17 to
a44eb52
Compare
|
/azp run Unit-Test-CUDA-AutoRound |
|
Azure Pipelines successfully started running 1 pipeline(s). |
…eserved on the tune device Rework of the huge-layer snapshot parking along two axes. ONE HOME, STRUCTURAL (replaces the separate utils/snapshot_parking.py module): compressors/utils.py now carries collect_best_params_local and snapshot_best_params byte-identical to the multi-GPU improvements PR (intel#2357) plus this lane's ladder in the same file, so the two snapshot implementations meet in one place at merge time -- git, not a kit note, forces the analysis. SAME-GPU SLOT WITH WINDOW ACCOUNTING (the 24 GB expectation): the snapshot is now a BestParamsSlot reserved BEFORE the tune loop. The device ladder prefers the layer's own device whenever the snapshot fits beside the row-window floor (free - snapshot >= 1024 rows x in_features x fp32 x ~6 live arrays, the row-block machinery's own anchors), then an idle CUDA peer (10% headroom), then the host. Reserving upfront means row_block_bounds' per-forward free-memory probe sees the reduced pool from iteration 0: windows shrink in favor of keeping the snapshot, which removes both the multi-second host copy per improving iteration and the transient OOM of windows sized on the pre-snapshot pool. Refreshing an on-device slot copies into existing buffers; reservation failure degrades to the historical host path. quantize_layer_outside_block reserves the slot for layers above the chunking budget; small layers keep the exact cache_device path. _best_param_device is removed (the slot subsumes it); its contract tests move to the ladder tests. Signed-off-by: avtc <avtc@users.noreply.github.com>
…-> host) Adopting the streaming branch's fallback philosophy, one step further: the clone attempt itself is the test. The snapshot first duplicates beside the weights; on failure it falls through to the freest visible accelerator of the same type (most free first, every candidate still guarded by a real attempt-and-catch at clone time); when no accelerator can hold it, it parks on the host with a WARNING. The successful route is sticky per block (retried first on later improving iterations) and the host is terminal - no mid-tune flip-flopping back onto accelerators. The activation-floor routing is gone from the block path: placement no longer consults any prediction, so the estimator's tune-loop role ends (placement keeps it). act_floor_bytes stays in the signature for call-site compatibility and no longer routes anything. This also answers the observed gap: the floor-based ladder routed the predictor tree to an idle peer by prediction (its WARNING paths never fired in any real run), while the clone-time catches inherited from intel#2357 only covered the local-copy failure. Now every placement decision is made by attempting it. Signed-off-by: avtc <avtc@users.noreply.github.com>
a44eb52 to
7b11263
Compare
7b11263 to
d29d1f0
Compare
|
rebased on latest main, tested (with fixes) on qwen3.8-27B iters=0, iters>0, neuqi |
|
/azp run Unit-Test-CUDA-AutoRound |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
I just noticed this PR. Really nice work! The speedup looks very promising. I have a few questions, though:
|
|
@wenhuach21 Thanks! Answers to the three questions: 1. Where the speedup comes from The phase table maps each term to its source. The largest terms:
On the snapshot point: main's default cache device is the primary GPU ( 2. RAM and VRAM
3. Per-device grouped-experts plans Your model of main matches the intent for models whose experts sit in an This branch keeps the fit-driven spreading and changes the unit: whole experts place atomically on one device, and chunks split at whole-expert boundaries when the unit exceeds each device's remaining budget. The experts of a layer are then grouped per home device, and each group runs its own stacked grouped GEMM - the fast path stays active per group when the layer spans several GPUs. At run time the layer works like this: the router output is flattened into (token, top_k) pairs and sorted by expert id, so each active expert owns a contiguous range of rows. For a projection slot (gate/up/down), the rows of the experts hosted on one device travel to that device, pass through the activation-quantization wrapper, and run in a single grouped GEMM covering that device's experts - each expert keeps its own weights and its rows inside the call, addressed by per-expert offsets. The outputs return to a shared buffer on the layer's device, written back at their sorted positions. Gate and up run this way, the activation is applied, down runs the same way, and the final combine - multiply by the routing weights, sum over top_k - is the same code as the single-device path. One guard: experts sharing a group must agree on quantization scheme and weight layout; a layer that mixes them falls back to the per-expert loop. |
wenhuach21
left a comment
There was a problem hiding this comment.
Really appreciate this PR, though I don’t fully understand it yet!
Please expect a slower response from most of us, as we’ll be taking a week-long national holiday starting tomorrow.
7f820a9 to
9e87eb0
Compare
|
Follow-up:
I'll rebase onto main when requested or after approval. |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved batching correctness, memory-policy, and fallback errors can alter quantized weights or abort supported runs.
7 open issues
Batch stacking ignores wrapper layout and weight policies · New Unset signature marker accepts mixed activation policies · New Bulk pulls ignore low-memory and CPU placement policies · New Pulled pool copies leave original tensors resident · New CPU fallback loses optimized RTN and NeUQI settings · New OOM handler misses batch allocation failures · New Test uses unavailable third CUDA device · New
What changed in this PR
Improves AutoRound’s multi-GPU quantization throughput and memory use, extending the grouped-experts work from #2303.
Changes:
- Adds batched searches and device-aware calibration-pool handling.
- Improves expert placement, grouped execution, snapshots, and memory diagnostics.
- Adds regression tests and matching English/Chinese documentation.
| File | Description |
|---|---|
| test/unit/test_cpu/quantization/test_release_pool_inplace.py | Tests shared-pool release. |
| test/unit/test_cpu/quantization/test_pull_pool_if_fits.py | Tests pool-pull memory decisions. |
| test/unit/test_cpu/quantization/test_one_deep_writer.py | Tests background-write ordering and snapshots. |
| test/unit/test_cpu/quantization/test_neuqi.py | Tests compilation concurrency and batching parity. |
| test/unit/test_cpu/quantization/test_auto_linear_loop.py | Tests automatic expert implementation selection. |
| test/unit/test_cpu/quantization/test_attach_pool_placement.py | Tests calibration placement policies. |
| test/unit/test_cpu/modeling/test_grouped_experts_plans.py | Tests grouped routing and atomic placement. |
| test/unit/common/utils/test_device.py | Updates device-memory and compilation tests. |
| README.md | Documents calibration placement. |
| README_CN.md | Mirrors README documentation in Chinese. |
| docs/step_by_step.md | Adds calibration-placement usage. |
| docs/step_by_step_CN.md | Mirrors usage guidance in Chinese. |
| docs/environments.md | Documents search and memory controls. |
| docs/environments_CN.md | Mirrors environment documentation in Chinese. |
| auto_round/wrapper.py | Adds deferred searches and shared write-back helpers. |
| auto_round/utils/resume.py | Adds pool-container snapshots for asynchronous saves. |
| auto_round/utils/perf_counters.py | Adds optional phase timing. |
| auto_round/utils/oom.py | Adds memory-failure diagnostics. |
| auto_round/utils/device.py | Improves memory estimates and atomic allocation. |
| auto_round/utils/device_manager.py | Scales compilation limits by device count. |
| auto_round/modeling/fused_moe/grouped_experts.py | Adds per-device projection groups. |
| auto_round/export/formats/base.py | Instruments immediate packing. |
| auto_round/export/export_to_autoround/export.py | Records packing-phase timings. |
| auto_round/envs.py | Registers search and timing controls. |
| auto_round/data_type/utils.py | Exposes deferred scale-search callables. |
| auto_round/data_type/neuqi.py | Guards compilation setup and improves warnings. |
| auto_round/context/compress.py | Stores calibration-placement policy. |
| auto_round/compressors/utils.py | Adds device-local snapshots and packing counters. |
| auto_round/compressors/diffusion/tuning_cache.py | Explains rejected cache signatures. |
| auto_round/compressors/base.py | Accepts calibration-placement configuration. |
| auto_round/cli/parser.py | Adds the calibration-placement argument. |
| auto_round/cli/main.py | Forwards placement settings and installs diagnostics. |
| auto_round/autoround.py | Exposes placement through public APIs. |
| auto_round/algorithms/quantization/sign_roundv2/quantizer.py | Defers eligible initialization searches. |
| auto_round/algorithms/quantization/sign_round/quantizer.py | Integrates pool pulls, local snapshots, and timing. |
| auto_round/algorithms/quantization/rtn/quantizer.py | Dispatches staged RTN searches. |
| auto_round/algorithms/quantization/rtn/batched_search.py | Implements batched search and fallback execution. |
| auto_round/algorithms/quantization/base.py | Adds deferred RTN and memory-policy hooks. |
| auto_round/algorithms/composer.py | Releases consumed pools and records routing shapes. |
| auto_round/algorithms/block_runner.py | Supports distributed pool storage and batch gathering. |
Review effort: Balanced
|
The automated review's findings are addressed in a follow-up commit. |
…rches, memory-aware pool placement, auto experts-impl pick Device-local batched searches (iters=0 OptRTN and iters>0 SignRound): same-shape search calls stack per (device, shape, dtype, bits, group, resolved callable) and run on the GPUs the weights already live on -- idle devices take the overflow. Rows bit-identical by construction; mid-batch OOM keeps applied results; the serial lane's OOM->CPU fallback is honored at every site; singletons fall back to serial. Calibration-data pool placement (--calibration_data_device auto|off|cpu|csv): per-block water-fill placement across GPUs under a first-principles need model (computed working allowance, measured collection-window term, deduped 14 B/param tuning state, per-device activation charge with expert-home attribution). Unblocks MoE blocks whose primary cannot host the pools beside the activation window. Pure memory behavior; chunk values bit-identical. Atomic MoE expert placement + per-device grouped-experts plans: the data-driven allocator places whole experts (atomic groups) instead of scattering projection Linears; the grouped-GEMM fast path runs one stacked GEMM per (slot, device) group so it stays eligible under mapped multi-GPU placement. Alignment-only accelerate hooks are exempted from its hook check; calibration hooks still fall back by design. One-shot auto pick of the experts implementation: when grouped tuning cannot fit (per-device qdq stack retention + guaranteed in-loop state vs the live free probe), the run switches to linear_loop BEFORE the first MoE loop, with per-device term logging. Explicit AR_MOE_EXPERTS_IMPL values are never overridden. Upstream fixes surfaced by the new instrumentation: layer_config rebuilt the whole compression-plan dict per read (~27 s per MoE block of invisible dict-copying in the pack loops; now cached/hoisted, pack 29 -> 1.2 s); immediate pack ran after mv_module_from_gpu (CPU packing; reordered, mv 4.5 -> 1.2 s, bit-identical); compiled blocks pinned FX/dynamo example tensors (~2-4 GiB monotonic growth; reset at block end); estimator act_bits read off orig_layer for V2 wrappers. Instrumentation (opt-in): [perf] block-phase summary, OOM tensor census with holder attribution, AR_PERF_COUNTERS, routed-shape recorders. Measured on a 300B-A21B MoE (8x3090): iters=0 quant searches 61.4 -> 9.40 s, MoE block 123 -> ~26 s, full run ~34 min; iters=20 wrap search 80 -> 7.7 s, tune loop 62.8 -> 38.1 s (lean) / 99.3 -> 47.7 s (spread), full 80-block run 2 h 29 m; tuning losses identical to serial. 4x3090 capacity unlock via the auto pick (166 s/block, linear_loop). 3,100+ test lines across 10 files. Signed-off-by: avtc <tarasenkov@gmail.com>
Add type annotations across the tuning pipeline (batched search, search dispatch, pool placement, oom and resume, device and grouped experts, algorithm and wrapper layers, orchestration), drop the unreachable iters > 0 gate before the tune-loop pool pulls (iters = 0 selects RTN at config normalization, so SignRound's quantize_block always runs with iters > 0), and move the tune-loop memory policy into a shared tune_memory module exposed as BaseQuantizer hooks (maybe_adapt_moe_implementation, pull_tuning_pool) so algorithm implementations stay free of fleet-memory policy. Signed-off-by: avtc <tarasenkov@gmail.com>
Capacity-planned batched searches: chunk sizes derive from probed free VRAM with execute-time headroom re-checks and recursive halving, wrap/RTN/anchor lanes share one planner with a residency-aware spread, and NeuUQI grid anchors batch with a per-chunk VRAM cap. Review feedback from the rounds on the published head: the dead expert-batch path removed, find_best_device documented as never-None/never-veto, comment provenance purged to derivations, tune_memory failure paths at WARNING with the consequence named, the dynamo reset at the block boundary removed after an A/B measured its cost, and the dense allocator unified. Follow-ups validated on a full smoke run: one AR_PERF_COUNTERS recorder in auto_round/utils serves the SignRound tune phases, the RTN phases and the orchestrator block phases; the batched-search summaries share the wrap grammar and are gated behind the switch at INFO; unexpected-fallback handlers log at WARNING, expected version gates at debug once per process. Signed-off-by: avtc <tarasenkov@gmail.com>
- stack only row-group weight layouts in the batched RTN search: per-tensor scales, 2-D block groups, and activation-only layers finish per-module through the unwrapper, exactly like the serial lane (parity tested per case) - seed the grouped-experts signature check from the first projection so a plain expert preceding a quantized wrapper can no longer latch its signature without comparison and accept the mixed group - carry the optimized-RTN and NeuQI settings through the per-module CPU fallback so an OOM changes only where the search runs, not which search runs - move batch materialization inside the OOM guard and release the partial batch before the per-module retry - splice pulled tuning pools into the shared pool object so the source tensors are freed instead of staying resident as duplicates - keep pools host-parked when the memory policy asks for it (low-memory mode or host-pinned calibration data) - require three CUDA devices for the third-device routing test Signed-off-by: avtc <tarasenkov@gmail.com>
- f-string formatting in the tune-phase and rtn perf lines (UP031) - direct boolean return in the grouped hook gate (SIM103) - collections.abc/typing imports for annotations flagged by the stricter default rule set (F821/UP035) - .items()/.values() and dict-loop cleanups (PERF102/PLC0206) - merge nested with statements in tests (SIM117) - apply the rule set's autofixes across the touched files Signed-off-by: avtc <tarasenkov@gmail.com>
bbc1031 to
da75315
Compare
|
/azp run Unit-Test-CUDA-AutoRound |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Habana lazy tensors execute on per-thread device contexts, so a stacked search spread onto worker threads reaches the write-back host copy with lazy tensors that have no storage attached (data_ptr fails with 'no storage tensor attached to lazy tensor'). HPU lazy mode now collapses the search worker set to the home device, which also keeps every chunk inline on the calling thread -- the configuration the hpu lazy CI ran green before cross-device spreading generalized beyond cuda. Stacked batching still engages on hpu; compile mode (eager pipeline) keeps spreading. Signed-off-by: avtc <tarasenkov@gmail.com>
|
/azp run Unit-Test-CUDA-AutoRound |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
@xin3he Hi, i have fixed issues found by Copilot, rebased on main, fixed failed HPU test, CI should be green now. |
|
Hi @wenhuach21 |
I agree that the results are very promising, and I’m fine with merging this PR. I’ll leave the final decision to you, as I won’t be focusing on this project for a while. Given the size of this PR and our limited headcount and bandwidth, I’m a bit concerned about maintaining this feature long-term if @avtc is no longer available to support it. I suggest that one of us review the code carefully and develop a solid understanding of the implementation so we can maintain it going forward. |
|
@lvliang-intel Please have a look. |


Description
Multi-GPU tuning for large MoE models (validated end-to-end on a 300B-A21B MoE, 8x3090, W4A16): device-local batched quantization searches, calibration-data pool placement, atomic expert placement with per-device grouped-experts plans, and a memory-model-driven auto pick of the experts implementation. Plus four upstream bugs the new instrumentation surfaced (fixes benefit single-GPU users too).
Features
--calibration_data_device auto | off | cpu | cuda:1,cuda:2 | 1,2(CLI and Python API). Per-block placement of the calibration input/output pools: they stay on the primary GPU when they fit beside the activation window, and are spread across the other GPUs otherwise, placed so that every GPU's peak VRAM during tuning stays close to the same level. Placement decisions come from a memory model of the block (activation window, collection retention, tuning state, and activation estimates computed per GPU from the modules whose weights live there).--low_gpu_mem_usage) - a cross-device copy for each module hosted on another GPU, with all snapshot bytes concentrated on that one device. Each parameter's snapshot now stays on the device that already hosts it: every copy stays local, and the snapshot footprint spreads across the hosting GPUs like the weights themselves. Under memory pressure it falls back to host.nn.ModuleListcontainers, and the MoE unfuse path builds a plain container.AR_MOE_EXPERTS_IMPL=auto(new value of the existing env var, now the default). Predicts the per-device VRAM of grouped tuning before the first MoE block and selectslinear_loopfor the run when grouped exceeds the free memory; explicit values take precedence.Upstream fixes (single-GPU users benefit too)
layer_configrebuilt the full compression-plan dict on every read (~50 ms on a 300B plan); the pack loops read it once per module, which cost ~27 s per MoE block. The reads are hoisted.--enable_torch_compile, dynamo/FX caches retain tracing artifacts across blocks (~5 GiB in the block-3 OOM census on the 300B lane) until the recompile limit forces an eager fallback. Blocks wrap fresh modules, so no compiled graph is reused across blocks; the block-boundary dynamo reset (when compilation is enabled) frees the dead caches and keeps retention from accumulating.act_bitswas read off the wrapper instead of the wrapped layer); it now reads quantization attributes throughorig_layer.Instrumentation (opt-in)
AR_PERF_COUNTERSemits a per-block phase summary; an OOM tensor census with holder attribution is installed by the CLI; pool-placement decisions are logged. The three upstream fixes were found with these tools.Measured results (300B-A21B MoE, 80 blocks; iters=0 lane = optimized RTN, symmetric scheme; iters=20 lane = SignRound V2)
Per-block phase walls on 8x3090, serial baseline (with fixes unlocking iters=20) vs this branch:
Full runs, same rig and model:
VRAM stays flat across blocks on the branch's full runs. On 5 and more GPUs the grouped-experts path stays active.
Numerical parity: iters=0 outputs are bit-identical to the serial path; at iters=20 the tuning losses match the serial path within run noise (7.305e-11 to 2.670e-11 batched, 2.671e-11 serial).
New knobs
--calibration_data_device auto | off | cpu | cuda:1,cuda:2 | 1,2(CLI and Python API): calibration pool placementAR_MOE_EXPERTS_IMPL=auto: new value of the existing env var, picks grouped vs linear_loop by predicted memory fitAR_DISABLE_BATCHED_SEARCH=1: serial search fallbackAR_DISABLE_CROSS_DEVICE_SEARCH=1: keeps search batches on the devices hosting the weights instead of optimized search placementAR_SEARCH_BATCH_GB: VRAM cap for stacked search batchesAR_PERF_COUNTERS=1: per-block phase timingAll documented in
docs/environments.mdand its CN counterpart.Tests
About 3,100 lines of new CPU-runnable tests: search batching and dispatch, pool placement, grouped-experts plans and atomic placement (including a CUDA-gated two-device test), the auto linear_loop pick, pool release and writer ordering.
Type of Change
New feature (performance) + bug fix
Related Issues
Relates to #2303 (keeps the grouped-experts fast path active when experts span several GPUs)
Checklist Before Submitting
/azp run Unit-Test-CUDA-AutoRound.