Repository navigation
[AffineIR] Analyze affine transfer batches before execution (Plan analysis & calibration Part-1) - #8967
[AffineIR] Analyze affine transfer batches before execution (Plan analysis & calibration Part-1)#8967jinyouzhi wants to merge 17 commits into
Conversation
0z5a
left a comment
There was a problem hiding this comment.
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, andbuild_operationsisn'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_bytesin particular isn't covered by theexpected_statscomparison, sinceTransferStatshas 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
LOCALanddefault_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.
72081c1 to
8870b61
Compare
Add schedule_transfers, which returns what execute_transfers would run for a batch without buffers or communication: for each process, the rounds per peer and direction with their element and chunk counts and whether each is sent or received in place, the staging arena each peer direction allocates, the local copies, and the round count TransferStats reports. A replica policy or cost model can then price a plan before running it, without importing the executor's private helpers (deepspeedai#8967 asked for this). execute_transfers now builds its rounds and arenas from the same private computation, so the two cannot drift apart. The in-place decision is made from geometry, not from the buffer: _region is an as_strided view of a packed shard, so the view is contiguous exactly when (chunk_shape, strides) is row-major packed, extent-1 axes excepted. plan_digest, the batch fingerprint and TransferStats are unchanged, and build_operations and _packed_rounds keep their names and signatures. Tests: test_schedule_is_what_the_executor_runs (2 and 4 ranks, mixed packed and strided regions, 16-element messages) checks rounds, sent, received and local copy bytes against the executor's TransferStats, that both sides of every message agree, the arena sizes, and that the batch exercises both the in-place and the staged path. test_packed_regions_are_exactly_the_contiguous_views compares the geometric rule with Tensor.is_contiguous over 2000 random shapes and strides. A single-process case covers local-only batches and the argument checks. Forcing every round to staged, or dropping the extent-1 exception, fails these tests. On macOS arm64 CPU (Python 3.12, torch 2.15.0), the executor, external-group, planner and shard-map suites pass (263). All affine checkpoint tests pass except the four TestAffineUniversalCheckpointResume cases, which fail the same way on master here (uninstalled package version). pre-commit (yapf, flake8, codespell, license) is clean. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
|
@jinyouzhi on the schedule question: yes, I'd rather the executor own it, and I can add it. #8809 is in master now, and the executor is unchanged from the What I have in mind is a public, tensor-free helper in Separately, this head is stacked on |
…e executor would refuse schedule_transfers now checks the staging budget the same way execute_transfers does, so an empty batch can no longer be scheduled under a budget the executor refuses, and refuses a process the endpoints don't name instead of returning an all-zero schedule. _is_packed treats a zero-extent region as packed, matching Tensor.is_contiguous. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de>
8870b61 to
1ac1de1
Compare
1ac1de1 to
b97d6af
Compare
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> (cherry picked from commit 3cfa415)
…ive capacity Signed-off-by: Achyuthan Sivasankar <achyuthan.sivasankar@gmail.com> (cherry picked from commit dc1722e)
…ode-capped default - fit_links keys fan_out, fan_in, send and receive by (root, sorted peers), so fans of one width with different senders stay apart; the width is len(peers). Adds peer_pairs, a fit per ordered (from, to) pair. - calibrate_links takes fan_peers(root, width) to choose the peers, and an optional group whose ranks number the processes. - The default fan width is one less than the fewest processes on any node, and default peers are on the root's node first. - Docs state that width-keyed fits assume a uniform fabric, with B70 data. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com> (cherry picked from commit ba8b39f)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com> (cherry picked from commit f00ce17)
Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com> (cherry picked from commit 2c43824)
… a path A concurrent sample times a process's send to process + shift and its receive from process - shift together, but was labelled by the send path alone. Across nodes, an intra-node send paired with an inter-node receive takes the inter-node time, so the intra_node concurrent fit came out at 7.5 GB/s over TCP against 65 GB/s for clean intra-node processes on 2x8 H20. Record receive_paths and fit each path only from processes whose send and receive both took it. Document calibrating on the group and settings the transfer uses. Signed-off-by: iLeGend <824040212@qq.com> (cherry picked from commit 575bef3)
Weight transfers are one-way: a training process sends and a rollout process receives, so no process sends and receives at once. The concurrent shift pattern timed every process doing both, which matches no transfer, and needed per-direction path filtering to fit at all. Remove it with its fit, docs and tests, and drop the hardware-specific measurements from the docs. Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com> (cherry picked from commit 17aa897)
Some transfers have a process send and receive at the same time: colocated resharding, the all-gather stage of a relay, a pipelined relay, and moves between trainers of different DP sizes. peer samples gain both_ways. A two-way sample has a pair send to each other at once, with every other process idle, and gives one sample per direction labeled with that direction's path. fit_links keeps peer and peer_pairs one-way and adds peer_both_ways and peer_both_ways_pairs, so a policy can see what a link loses per direction when both directions are busy. The shift pattern returns as relay: every process receives from process - shift while sending to process + shift. Each sample is one process's own time, labeled with who it received from and sent to and with both the receive_path and the send_path, since that time covers both. fit_links fits relay per (receive_path, send_path), so no sample is filed under a path it only half used. Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com> (cherry picked from commit d00ba30)
Both directions of a both_ways sample carry the pair's completion time, so peer_both_ways_pairs[(a, b)] equals peer_both_ways_pairs[(b, a)]. Say so in fit_links and the docs, and compare against the slower of the two one-way fits. Keep the external-group executor test's assertion to one-way peer samples, since calibrate_links now also returns both_ways samples. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com> (cherry picked from commit 57a47ff)
A path_of that tells local devices apart, as on a host with unlike accelerators, had every local copy labeled 'local' and pooled into one fit. Local samples now carry path_of(process, process) like every other sample. communication_backend recorded the accelerator's preferred backend, which need not be the one the measured group runs on. It now comes from the group itself, through a new deepspeed.comm.get_backend that wraps the backend's existing get_backend. Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com>
b97d6af to
1da4d66
Compare
|
@0z5a Switched to |
fit_links reads peer samples' both_ways and the raw relay list. A raw calibration saved before those existed still claimed schema 1, so it passed the schema check and then raised KeyError. Schema 2 makes fit_links refuse it with a clear error instead. 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> (cherry picked from commit 1ac1de1)
…ysis Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com>
The analyzer rebuilt the executor's rounds with the private _packed_rounds and build_operations, and re-derived the direct-send rule and arena sizes, so staging_bytes could drift from what execute_transfers allocates. It now reads rounds, direct flags, arenas and local copies from deepspeedai#9027's public schedule_transfers. Per-chunk sizes are not part of that schedule, so the report keeps the chunk count and drops the chunk-size distribution. The summary also records process_numbering: processes are the transport-group ranks TransferEndpoints binds, which join with a calibration only when it measured the same group. Signed-off-by: Jin, Youzhi <youzhi.jin@intel.com>
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
StagingBudgetdominates 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'sbuild_operationsand round packing and reports:expected_stats(report, process)predicts that process'sTransferStats.report_to_dictreturns a JSON-ready summary.path_oflabels ascalibrate_links, so per-path totals can be priced withfit_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
expected_stats== the executor'sTransferStats, 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.