Skip to content

feat: multi-GPU quantization improvements -- batched device-local sea… - #2357

Open
avtc wants to merge 1 commit into
intel:mainfrom
avtc:feature/improvements-to-multi-gpu-quantization
Open

avtc wants to merge 1 commit into
intel:mainfrom
avtc:feature/improvements-to-multi-gpu-quantization

Conversation

@avtc

@avtc avtc commented Sep 14, 2026 •

Copy link
Copy Markdown
Collaborator

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

  • Batched device-local quantization searches (iters=0 OptRTN and iters>0 SignRound V2). Scale searches for modules with identical shape and quantization config stack into one call per device; when a block's weights span several GPUs, the devices run their batches in parallel. In the iters=0 lane stacked batches may also run on other CUDA GPUs with free memory, including GPUs outside the device map.
  • --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).
  • Best-params snapshots next to their weights. The running-best snapshot lives on the GPU hosting its weight instead of host RAM, removing a host round trip per saved improvement; under memory pressure it falls back to host.
  • Reference outputs on the loss device. The fp reference pool was previously gathered across GPUs on every tune iteration; it now moves once to the loss device before the loop when it fits, eliminating the per-iteration gather.
  • FP input pool released. The fp input pool is released at tune-loop start, freeing its VRAM for the loop.
  • Background shard writes. Shard saves and the AR_RESUME_DIR marks are written in the background, overlapping with the next block's tuning.
  • Atomic expert placement. The data-driven allocator places whole experts on single devices; chunks split by whole experts when no single device has budget. Previously the projections of one expert landed on different GPUs because expert detection matched only nn.ModuleList containers, and the MoE unfuse path builds a plain container.
  • Per-device grouped-experts plans. The grouped-GEMM fast path ([high risk]Speedup 2.5-6X moe tuning and pin ngram to cpu #2303) required all active experts of a layer on one device; experts are now grouped by home device and run one stacked grouped GEMM per group, so the fast path stays active when a layer's experts span several GPUs.
  • 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 selects linear_loop for the run when grouped exceeds the free memory; explicit values take precedence.

Upstream fixes (single-GPU users benefit too)

  • layer_config rebuilt 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.
  • Immediate pack ran after the block moved to CPU; it now runs while the block is still on the GPUs, matching the model-free path. Packed artifacts still land in host RAM.
  • With --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.
  • The block memory estimator crashed on V2-wrapped modules (act_bits was read off the wrapper instead of the wrapped layer); it now reads quantization attributes through orig_layer.

Instrumentation (opt-in)

AR_PERF_COUNTERS emits 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:

Phase Before After Speedup
quant scale search (iters=0) 61.4 s 9.4 s 6.5x
wrap scale search (iters=20) 80 s 7.7 s 10.4x
tune loop (iters=20), block 1 62.8 s 38.1 s 1.6x
tune loop (iters=20), blocks 2+ 99.3 s 47.7 s 2.1x
pack (layer_config fix) 29 s 1.2 s 24x
move block to CPU (pack order) 4.5 s 1.2 s 3.8x

Full runs, same rig and model:

Configuration main this branch Speedup
8x3090, iters=0 120 s/block, 2.7 h 26 s/block, 34 min 4.6x
8x3090, iters=20 CUDA OOM in the first MoE block 112 s/block, 2.5 h unlocked
4x3090, iters=0 CUDA OOM in block 2 28.5 s/block, 38 min unlocked
4x3090, iters=20 - 166 s/block, 3.7 h (auto linear_loop) -

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 placement
  • AR_MOE_EXPERTS_IMPL=auto: new value of the existing env var, picks grouped vs linear_loop by predicted memory fit
  • AR_DISABLE_BATCHED_SEARCH=1: serial search fallback
  • AR_DISABLE_MULTIGPU_SEARCH=1: keeps search batches on the devices hosting the weights
  • AR_SEARCH_BATCH_GB: VRAM cap for stacked search batches
  • AR_PERF_COUNTERS=1: per-block phase timing

All documented in docs/environments.md and 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

  • My code has been tested locally.
  • Documentation has been updated as needed.
  • New or updated tests are included where applicable.
  • The CUDA CI has passed. You can trigger it by commenting /azp run Unit-Test-CUDA-AutoRound.

@AutoRoundBot

This comment has been minimized.

@avtc
avtc marked this pull request as draft September 14, 2026 19:28
@avtc
avtc force-pushed the feature/improvements-to-multi-gpu-quantization branch from 5b49ec9 to 0f0ea17 Compare September 14, 2026 21:32
@avtc
avtc marked this pull request as ready for review September 14, 2026 21:33
@AutoRoundBot

Copy link
Copy Markdown
Collaborator

/azp run Unit-Test-CUDA-AutoRound

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

…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>
@avtc
avtc force-pushed the feature/improvements-to-multi-gpu-quantization branch from 0f0ea17 to a44eb52 Compare September 15, 2026 09:19
@AutoRoundBot

Copy link
Copy Markdown
Collaborator

/azp run Unit-Test-CUDA-AutoRound

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@avtc
avtc removed the request for review from wenhuach21 September 16, 2026 08:03
avtc added a commit to avtc/auto-round that referenced this pull request Sep 18, 2026
…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>
avtc added a commit to avtc/auto-round that referenced this pull request Sep 19, 2026
…-> 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>
@avtc
avtc requested a review from wenhuach21 September 21, 2026 22:49
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.

2 participants