| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
CommsLogger stores parallel lists per message size, [count, latencies, algbws, busbws], where index i is the i-th recorded op. get_operation_summary() and log_all() pass each of those lists to trim_mean, which sorted in place, so summarising reordered all three independently and destroyed the correspondence between them. get_raw_data() afterwards pairs the fastest op with the lowest bandwidth, which calc_bw_log makes impossible by construction, and a read-only summary call silently rewrites what was recorded. get_operation_summary() already takes a .copy() of comms_dict to avoid exactly this, but that is a shallow copy and the inner lists are shared. Use sorted() instead, which fixes every caller at the shared function. The trimmed mean itself is unchanged. Signed-off-by: Vineeth Sai <vineethsai4444@gmail.com>
There was a problem hiding this comment.
This also fixes the straggler breakdown, which I think is worth saying in the PR body because it is a bigger effect than the reordering itself.
log_all() reads vals[1] twice. The main loop calls trim_mean(vals[1], 0.1) at comms_logging.py:306, and the show_straggler block re-reads that same list at comms_logging.py:351 to build lats/min_lats. dist.all_reduce(min_lats, op=ReduceOp.MIN) is elementwise, so it assumes index i is the same collective on every rank. With the in-place sort, every rank has independently sorted its list by then, so index i is "the i-th fastest op on that rank" and the reduction compares unrelated operations. That is exactly the correspondence the docs define the metric on:
straggler = sum(t_collectives - allreduce(t_collectives, MIN))
Measured on 5 all_reduce ops at one message size, rank 0 [3.0, 1.0, 2.0, 4.0, 1.5] against rank 1 [1.0, 3.0, 2.5, 2.0, 4.0]:
upstream this PR correct total_comm_lat_ms 10.50 7.50 7.50 total_straggler_ms 1.00 4.00 4.00
The direction is not random. Sorting every rank's list ascending maximizes the elementwise minima, so it can only shrink the reported straggler: over 20k random trials (2 to 6 ranks, 2 to 40 ops) it never once over-reported, and it went as far as reporting 0.00 for a rank with a real straggler effect. A tool for finding stragglers that silently rounds them toward zero seems worth calling out.
How I ran it, since it is not a real distributed job: python:3.12-slim, real torch 2.13.0+cpu, the real comms_logging.py and timer.py from this branch at 1df1171 loaded from a mounted checkout, with deepspeed.comm stubbed so all_reduce(..., MIN) takes the elementwise minimum against a fixed peer vector. Same arrangement as yours, then reverted timer.py alone to e2aae1b0 for the upstream column.
Two suggestions, both optional:
For what it is worth I could not find a prior attempt at this either. git log -G 'data\.sort\(\)' --follow on timer.py returns only #1554, which is the commit that introduced the current form.
Sorry, something went wrong.
|
You are right, and it is worse than I had it. Added to the PR body. I checked the path you describe rather than taking it: comms_dict_snapshot = self.comms_dict.copy() at the top of log_all() is shallow, so the summary loop's trim_mean(vals[1], 0.1) sorts the same list the show_straggler block later turns into both lats and min_lats. Every rank has sorted independently by then, so the elementwise all_reduce(..., MIN) takes the minimum across unrelated collectives, exactly as you say. What I had not appreciated is that the error has a direction. Sorting both ranks aligns them as closely as their values permit, so the pairwise differences shrink and the straggler effect is systematically under-reported, which is the failure mode you least want from this metric. Two ranks, four index-aligned ops: rank0 = [2.30, 1.60, 3.60, 1.29] rank1 = [3.14, 2.46, 1.23, 3.03] true total_straggler per rank [2.37, 3.44] with the in-place sort [0.52, 1.59] Over 20000 random four-op cases, 16246 report a different total. It is also not reliably wrong, which is part of why it survived: a three-op example I tried first happened to agree by coincidence. I have left the tests as they are, since show_straggler needs a real process group and the existing file is deliberately dist-free, and the one-line sorted() change fixes both call sites at the shared function. Happy to add a multi-rank test under tests/unit/comm/ if you would rather have the straggler path pinned directly; I cannot run it here, so I would be writing it for CI. |
Sorry, something went wrong.
|
@vineethsaivs This looks good to me, but can you fix the formatting issue? |
Sorry, something went wrong.
Signed-off-by: Vineeth Sai <vineethsai4444@gmail.com>
Head branch was pushed to by a user without write access
|
Fixed, thank you. It was one missing blank line between the two module-level tests in tests/unit/comm/test_comms_logger.py; yapf 0.40.0 with the repo's .style.yapf now reports no diff on both changed files, and flake8 with the repo's .flake8 is clean. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
The bug
CommsLogger.comms_dict stores parallel lists per message size, [count, latencies, algbws, busbws], where index i is the i-th recorded op. get_operation_summary() and log_all() hand each of those lists to trim_mean, which sorted in place:
So summarising sorts the three lists independently and destroys the correspondence between them. Driving the real CommsLogger with latencies [3.0, 1.0, 2.0]:
algbw is computed from latency in calc_bw_log, so latency[i] * algbw[i] is constant for a fixed message size. After summarising it is not, because row 0 now pairs the fastest op (1.0 ms) with the lowest bandwidth. get_raw_data() hands that out to anyone consuming the log.
Two things make this look unintended rather than a quirk:
It also breaks the straggler breakdown, which is the bigger effect
Raised in review and worth stating here, because it is worse than the reordering above.
log_all() reads vals[1] twice. The summary loop calls trim_mean(vals[1], 0.1), which sorts it, and the show_straggler block afterwards builds both lats and min_lats from that same, now-sorted list:
all_reduce(..., MIN) is elementwise, so it assumes index i is the same collective on every rank. Each rank has sorted its own list independently by then, so index i is "the i-th fastest op on that rank" and the reduction takes the minimum across unrelated operations. comms_dict_snapshot = self.comms_dict.copy() at the top of log_all() does not help, for the same reason the copy in get_operation_summary() does not: it is shallow.
That is not a cosmetic reordering, it changes the number. Two ranks, four index-aligned ops:
Over 20000 random four-op cases, 16246 report a different total, and the error has a direction: sorting both ranks aligns them as closely as their values allow, so the differences shrink and the straggler effect is systematically under-reported. Small cases can coincide (a three-op example I tried happened to agree), which is part of why this is easy to miss.
Fix
data = sorted(data). That fixes every caller at the shared function rather than patching log_all and get_operation_summary separately, and the trimmed mean it returns is unchanged.
Tests
Added to tests/unit/comm/test_comms_logger.py:
Both are dist-free, like the existing test in that file. comms_dict is populated directly rather than through append() because append() calls calc_bw_log, which needs a live process group.
Fail-before / pass-after against the unmodified timer.py:
The pre-existing test passing either way is deliberate: this is a distinct failure from the one it covers.
How these were run, since I would rather say than imply a normal pytest run: I do not have a GPU or a built DeepSpeed here, so I executed deepspeed/utils/timer.py and deepspeed/utils/comms_logging.py from source against stub deepspeed.comm / deepspeed.accelerator modules and ast-extracted the tests. That exercises the real CommsLogger and the real trim_mean; CI is the runner for the suite proper.
No existing issue or PR covers this; searching trim_mean across open and closed returns only the merged PR that introduced the current form.