Skip to content

[AffineIR] Analyze affine transfer batches before execution (Plan analysis & calibration Part-1) - #8967

Draft
jinyouzhi wants to merge 7 commits into
deepspeedai:masterfrom
jinyouzhi:affine-transfer-analysis
Draft

jinyouzhi wants to merge 7 commits into
deepspeedai:masterfrom
jinyouzhi:affine-transfer-analysis

Conversation

@jinyouzhi

Copy link
Copy Markdown
Contributor

Analyze affine transfer batches before execution

Stacked on #8809 (executor) and #8966 (calibration path labels). Only the last commit is new.

Why

Plans that move the same bytes can differ a lot in time (up to 1.75× on 8× B70 between replica choices), and executor measurements show that the round count under a StagingBudget dominates sync time. A policy (#8935/#8936) or cost model (#8252) needs these facts before it runs a plan.

What

deepspeed/checkpoint/affine_transfer_analysis.py:

  • analyze_transfers(plans, endpoints, element_size, budget, path_of) is a pure function with no communication. It replays the executor's build_operations and round packing and reports:
    • volume;
    • segment, chunk and message size distributions;
    • rounds;
    • per process: sent/received bytes, peers, messages, pack/unpack bytes, staging arena;
    • per-pair and per-path bytes.
  • expected_stats(report, process) predicts that process's TransferStats.
  • report_to_dict returns a JSON-ready summary.
  • Messages use the same path_of labels as calibrate_links, so per-path totals can be priced with fit_links.

It does not choose replicas, rank plans, or model time.

Note: it imports the executor's private _packed_rounds / build_operations. @0z5a, would a public dry-run schedule API on the executor be preferable?

Tests

  • Hand-counted rounds and bytes on an uneven handoff.
  • Staging of strided sources.
  • A batch with a shared plan.
  • Path and pair labels in the JSON summary.
  • 4 ranks: expected_stats == the executor's TransferStats, for a strided and a replicated parameter under budgets of 3 and 64 elements.

Results: 6 passed on 4× L20 (nccl) and on 4× Intel Arc Pro B70 (xccl). Mutation checks (dropping the receiver's round count, or the local bytes in the path summary) are caught.

0z5a and others added 7 commits October 12, 2026 15:40
Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
Address review on the affine transfer executor:

- Decide stream-ordered completion with is_synchronized_device()
  instead of the device type, for both the per-round handle and the
  final stream synchronization.
- Run the multi-rank executor tests on any accelerator. The
  disjoint-endpoint case now uses two or four ranks, so it fits the
  two-device PR runners and the four-rank CPU nightly.
- Move the executor documentation from the AutoEP page to a new
  Checkpointing API page, and record that the staging arena and
  StagingBudget belong to the current torch point-to-point transport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
RL frameworks build their trainer <-> engine weight-update group
outside the default world: each process keeps its own default group
(world size 1 for a TP=1 trainer or engine) and joins a separate group
created with _new_process_group_helper on its own store, as slime,
SGLang and vLLM do. torch's rank bookkeeping for such a group maps
every member's default-world rank 0 to group rank 0, so
dist.get_rank(group) returned 0 on both ends and the preflight refused
the whole batch ("target location 0 is not owned by this process").

When a group is passed, read the rank and size from the group itself.
Every other path is unchanged.

Add a CPU reproduction: two processes, each with a world-size-1
default group, joined by an externally created gloo group. It fails
before this change and passes after it. The new test file builds that
group with torch.distributed directly, so it joins the existing
check-torchdist exclusion next to tests/unit/comm/test_dist.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
The test builds the trainer <-> engine weight-update group the way RL
frameworks do, outside deepspeed.comm, so calling torch.distributed
directly is intended. Say so next to the import, as requested in review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
Add calibrate_links/fit_links: raw local, pairwise, concurrent, 1-to-N and
N-to-1 timings labelled by a path_of(from, to) hook, with a per-path
alpha + bytes/bandwidth summary, as measured inputs for transfer cost models.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com>
Add analyze_transfers/expected_stats/report_to_dict: a dry-run report of an
execute_transfers batch (volume, size distributions, rounds under a
StagingBudget, per-process load and staging, per-pair and per-path bytes),
labelled with the same path_of hook as calibrate_links.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com>
@jinyouzhi jinyouzhi changed the title Analyze affine transfer batches before execution [AffineIR] Analyze affine transfer batches before execution (Plan analysis & calibration Part-1) Oct 31, 2026

@0z5a 0z5a left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read the last commit (72081c1b). The report is what a policy or cost model needs: per-process sent and received bytes, rounds and messages give the bandwidth and per-message terms, and expected_stats matching the executor's TransferStats on 4 ranks is a good anchor. It passes on CPU (gloo, 4 ranks) here.

I checked the schedule reconstruction against the executor at e38477d7. The direct-send test (_is_packed versus region.is_contiguous()), the per-peer, per-direction arena sized by the largest staged round, and rounds as the maximum over a process's peer queues all match today. Two structural suggestions:

  • Share the schedule instead of re-deriving it. The module imports the executor's private _packed_rounds, and build_operations isn't in its __all__ either. It then re-implements the direct-versus-staged decision and the arena sizing. Those will drift when the executor's staging changes. staging_bytes in particular isn't covered by the expected_stats comparison, since TransferStats has no staging field. Would a public schedule helper in the executor work for you, returning each peer's rounds and whether each round is sent directly? Both sides could then call it.
  • Ordering. This stacks on #8966 only for LOCAL and default_path_of. Defining those here, or in a small shared module that calibration imports, would let this land first.

Minor: the report numbers processes by transport-group rank, while calibration uses default-group ranks, so the path labels join only when the two groups coincide. The docstring says so, but recording that assumption in the report itself would stop a consumer from mixing the two.

This branch has not been deployed

No deployments
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.

3 participants