Skip to content

feat: shard the AWQ pipeline under --data_parallel - #2575

Open
avtc wants to merge 3 commits into
intel:mainfrom
avtc:feature/ddp-awq-v2
Open

avtc wants to merge 3 commits into
intel:mainfrom
avtc:feature/ddp-awq-v2

Conversation

@avtc

@avtc avtc commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Follow-up to #2351. Under --data_parallel, AWQ (--algorithm awq, awq,auto_round) ran entirely on the home GPU while the other GPUs idled: its calibration forward, the smoothing-ratio grid search and the clip search. This PR shards all three across the same GPUs. The quantized model is bit-identical to the serial run. It also fixes a torch.compile crash in the thread ranks that #2351 left in place.

AWQ under --data_parallel

  • Calibration forward. The block forward that carries AWQ's hooks (activation statistics, clip features, cached parent-module calls) runs on one copy of the block per GPU, each over a contiguous slice of the samples. Each GPU keeps its captured calls. The captures are merged in sample order, and the activation sums are re-added call by call in that order, so every statistic equals the serial one. A serial run still parks the captured calls in host memory, as before. The pass uses one-pass copies of the block, not the block's mirror pool: AWQ changes the weights right after this pass, and the pool is reused by the passes after the transform.
  • Grid search. The cached parent calls are split across the GPUs. Each GPU replays its slice on its own copy of the mapping's parent module (the home GPU uses the parent itself). It returns the loss sum for every candidate plus its element count. The sums are added up, and the candidate is chosen exactly as in the serial loop, on sum / count.
  • Clip search. The per-layer searches are independent, so they are spread over the GPUs, each on a copy of the layer weight.
  • When it stays serial. A search falls back to the unchanged serial loop when there are too few calls, the parent module lies outside the block, a balance layer lies outside the parent, or no reference output survives. A calibration pass the engine would not shard (a non-list pool, fewer samples than GPUs) keeps the serial capture.
  • Interfaces. The engine side is a block-scoped search seam (auto_round/algorithms/parallel/search_shard.py); the composer attaches it around pre_quantize_block only. Preprocessors opt in through two BasePreprocessor hooks: set_parallel_search(seam) for the searches and sharded_calibration(devices) for the calibration pass. The defaults keep everything serial, so other transforms are unaffected.
  • Memory. The captured calls stay on their GPUs until the block's searches finish: about 1.5 GB more per GPU on a 27B-class block (peak 13.4 GB on the home GPU, 9.4 GB on the others, in the run below).

Fix: compiled calls in data-parallel threads

On torch < 2.14, torch.fx keeps its "symbolic tracing in progress" flag in a process-wide global (torch 2.14+ keeps it per thread, so the race below cannot happen there). The thread ranks compile some specializations of the quant functions after the serial first phase: a new shape, device or grad mode (for example the no-grad final QDQ at the end of a block). While one rank traces such a compile, a peer that enters a compiled function raises RuntimeError: Detected that you are using FX to symbolically trace a dynamo-optimized function. Reproduced on Qwen3.8-27B, --iters 10 --data_parallel 4: 3 of 5 runs failed on block 1, with or without AWQ. Each engine thread (spmd.run_ranks, run_threaded_spawn, the search seam) now runs with dynamo's error_on_nested_fx_trace off, so such a peer call runs eagerly for that one call. The tracing thread is unaffected (its dispatch modes are thread-local). The setting is applied per thread because dynamo keeps its config in context variables. On torch 2.14+ the guard is a no-op.

Measured (Qwen3.8-27B, W4A16 g128, --algorithm awq, RTN, --iters 0, --device_map 0, 128 samples, RTX 3090; times per block are medians):

serial --data_parallel 4
calibration forward (s/block) 10.2 1.1
grid search (s/block) 26.2 4.2
reference collection (s/block) 2.3 1.0
block total (s) 41.4 8.6
model total 45.1 min 10.1 min
quantized model reference identical (2013 tensors compared)

With --awq_apply_clip, the clip search went from 22.9 to 7.8 s per block before the calibration forward was sharded. On main, --data_parallel 4 left the AWQ phases serial: per-block time with clip stayed at the serial level (63-72 s against ~64 s serial). awq,auto_round with --iters 10 --data_parallel 4 and torch.compile passes with the fix (8-block smoke run); before the fix it failed on block 1.

Diagnostics. No new options. Under AR_PERF_COUNTERS there is a per-block [perf] pipeline phases line (calibration forward, pre-quantize transforms, reference and quantized-input collection, tuning, cascade), a per-block [perf] awq searches line, and a [perf] awq grid split line per grid search (documented in docs/environments.md). docs/step_by_step.md describes the sharded AWQ phases.

Tests.

  • test/unit/test_cpu/quantization/test_awq_sharded_search.py (new):
    • the seam (slicing, private replicas, no-grad workers, error propagation);
    • grid search parity with the serial search (losses and selected scales, for every duo_scaling mode and the micro-batched replay);
    • clip search parity;
    • the serial fallbacks;
    • capture merge order and bit-equal statistics;
    • composer wiring.
  • test/unit/test_cpu/quantization/test_spmd_ranks.py: compiled calls stay legal in rank and spawned threads while a peer traces. These tests fail without the fix.

Type of Change

New feature / Bug fix / Performance

Related Issues

Relates to #2351 (follow-up)

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.

🤖 Generated with Claude Code

avtc and others added 2 commits November 3, 2026 19:43
torch.fx keeps its "symbolic tracing in progress" flag in a process-wide
global. Under --data_parallel the thread ranks (and the sharded collection
threads) run compiled quant functions concurrently; when one of them
(re)compiles a specialization it meets first (a new shape, device or grad
mode -- e.g. the no-grad final qdq at the block tail, or a warm block whose
first phase runs concurrent), every peer entering a compiled function sees
the flag and dynamo raises "Detected that you are using FX to symbolically
trace a dynamo-optimized function". Observed on Qwen3.8-27B, iters 10,
--data_parallel 4, block 1 (nondeterministic).

Each engine thread now runs with dynamo's error_on_nested_fx_trace off, so
such a peer call runs eagerly instead (same result, no compile for that
one call); the tracing thread is unaffected (its dispatch modes are
thread-local). The setting is scoped per thread: dynamo's config lives in
context variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: avtc <tarasenkov@gmail.com>
Follow-up to intel#2351. Under --data_parallel the AWQ transform ran entirely on
the home device while the other devices idled: its calibration forward,
the smoothing-ratio grid search and the clip search. All three now shard
over the collection devices; the quantized model is bit-identical to the
serial run.

- Calibration capture (Step 1): the pass carrying the preprocessors' hooks
  shards over one-pass copies of the block (the mirror pool is built after
  the transform, whose weights it must carry) when every preprocessor in
  it opts in via the new BasePreprocessor.sharded_calibration(devices).
  AWQ routes each replica's captures into its device's buffer, keeps the
  captured parent calls on that device (copied, never aliasing a live
  activation) and merges in device order -- the sample order of the
  contiguous shards -- re-accumulating the per-call activation sums so
  every statistic equals the serial one. Passes the engine would not shard
  keep the serial, host-parked capture.
- Grid search: the cached parent calls split into contiguous slices; each
  device replays its slice on a private copy of the mapping's parent (the
  home slot on the parent itself) and returns the per-candidate loss sums
  plus its element count; the serial selection runs on sum / count.
- Clip search: the independent per-layer searches map over the devices on
  copies of the layer weights.

The engine side is a block-scoped search seam
(auto_round/algorithms/parallel/search_shard.py) that the composer attaches
around pre_quantize_block only; replica copies are built straight on their
device and all exist before any evaluation starts. Searches that cannot
shard (too few calls, a parent outside the block, a balance layer outside
the parent, an empty replay) keep the serial loop, which is unchanged.

AR_PERF_COUNTERS adds per-block "[perf] pipeline phases" and
"[perf] awq searches" lines and a "[perf] awq grid split" line per grid
search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: avtc <tarasenkov@gmail.com>
@avtc
avtc requested a review from wenhuach21 November 3, 2026 21:48
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
3 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@AutoRoundBot

This comment has been minimized.

…rocess-wide

torch>=2.14 keeps torch.fx's "tracing in progress" flag per thread
(_is_fx_tracing_tls); only torch<2.14 keeps it in the module global
_is_fx_tracing_flag that a peer thread's trace leaks through. On per-thread
torch there is no cross-thread race, so tolerate_concurrent_traces() is now
a no-op there instead of turning error_on_nested_fx_trace off.

The tests that simulate a peer's trace by setting the global flag skip on
per-thread torch (CI runs torch 2.15, where the attribute no longer exists
and the tests failed in setup); new tests pin the no-op guard and that a
per-thread flag hides a peer's trace from compiled calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: avtc <tarasenkov@gmail.com>
@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).

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