Fix comms logger KeyError when log_name is omitted - #8267
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
7dc2dcc to
657bd1a
Compare
* add missing log_name for all_to_all & broadcast_object_list * add fallback log_name for time_op * add ut Signed-off-by: iLeGend <824040212@qq.com>
ebarkhordar
left a comment
There was a problem hiding this comment.
prof_ops never matches an op that takes its log_name from the signature default. The gate at comm.py:111-113 tests 'log_name' in kwargs, so it fires only when a caller passes the name explicitly, while config-json.md documents "prof_ops": ["all_reduce", "all_gather"] against ordinary calls.
At 1f95164 in a clean container, CPU torch and a stub cdb:
prof_ops = ['all_reduce'] prof_all = False
A. dist.all_reduce(t) -> comms_dict keys: []
B. dist.all_reduce(t, log_name=...) -> comms_dict keys: ['all_reduce']
Your setdefault is one step from covering this. Resolving the name once per op keeps the per-call fast path at two conditions:
def timed_op(func):
default_log_name = get_default_args(func).get('log_name', func.__name__)
def log_wrapper(*args, **kwargs):
if comms_logger.enabled:
selected = kwargs.get('log_name', default_log_name)
if kwargs.get('prof') or comms_logger.prof_all or selected in comms_logger.prof_ops:then func_args['log_name'] = selected in place of the setdefault, and the same condition in the finally gate. With that, A logs and tests/unit/comm/test_comms_logger.py is still 4 passed. It is a separate bug from the KeyError you are fixing, so it may belong in its own PR.
Wow, that's a very insightful observation! I agree that |
|
Your call as the author, but I would take it in this PR. The repair replaces the Either way I am not going to open a competing PR for it. One thing to keep if you do take it: the I re-read |
Thank you for your patience and guidance. I’ve implemented your suggestions and pushed the changes. Could you please take a look when you have a chance? I’d really appreciate your feedback. |
Co-authored-by: Ehsan Barkhordar <realbarkhordar@gmail.com> Signed-off-by: iLeGend <824040212@qq.com>
FU-max-boop
left a comment
There was a problem hiding this comment.
Independent re-review of exact head 90d37a2bf484749a95ab2674459a5b9f89c5242e against base 715965e027894a2e72ac2e27f2daed2c599e99f0: the two focused behaviors look correct, and tests/unit/comm/test_comms_logger.py is 5/5 green locally on CPU.
One hot-path performance issue remains in this revision. selected_log_name and should_profile are now computed before the comms_logger.enabled gate, so the default-disabled path pays the kwargs lookup, logger attribute reads, and prof_ops membership test on every decorated communication call. That also contradicts the nearby comment that the disabled overhead is at most the enabled check.
A same-host synthetic no-op wrapper benchmark, intended only to isolate Python dispatch overhead rather than claim end-to-end collective latency, produced:
base 715965e: median 88.39 ns/call
head 90d37a2: median 141.39 ns/call
+53.00 ns, about +60%
Each result is the median of 9 repeats × 5,000,000 calls under the same Python 3.12.13 / Torch 2.13 environment with enabled=False, prof_all=False, and prof_ops=[]. A non-empty prof_ops list makes the new disabled-path work grow further.
The narrow fix is to initialize should_profile = False, then resolve selected_log_name and the selection expression only inside if comms_logger.enabled:. That retains the default-log-name repair while restoring the disabled fast path. I would keep the existing broader synchronization semantics out of this PR; this finding is only about overhead introduced by the current diff.
|
Nice catch! @FU-max-boop Thank you very much, I pushed the fix. |
Signed-off-by: iLeGend <824040212@qq.com>
FU-max-boop
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 9a92148c4b8a0b807421403c2639c269b5512d30 against base 715965e027894a2e72ac2e27f2daed2c599e99f0.
The disabled-path performance blocker from my previous review is resolved: selected_log_name resolution and the prof_ops membership test now occur only under comms_logger.enabled, while the single should_profile decision is reused by the finally gate.
Validation:
tests/unit/comm/test_comms_logger.py: 5/5 passed locally on CPU.- DCO, test collection, and the full Modal CI job: 3/3 passed on this exact head.
- An interleaved same-process synthetic no-op benchmark of the exact
timed_opsource on Python 3.12.13 (31 ABBA/BAAB rounds, 2,000,000 calls per sample, two samples per variant per round) measured a base median of 128.375 ns/call and a head median of 135.398 ns/call, with a paired median delta of +6.694 ns/call. The host was noisy, so the absolute timing is directional; static disassembly shows that the residual disabled-path difference is only two bytecodes,LOAD_CONST FalseandSTORE_FAST should_profile, rather than the unconditional kwargs lookup and logger membership work in the prior revision. That residual is non-blocking.
git diff --check is clean, and I found no remaining correctness, API, or performance blocker in this revision. No remaining blockers from me.
tohtana
left a comment
There was a problem hiding this comment.
Hi @jinyouzhi,
Thank you for the improvement! I think the overall direction of this PR looks good, but there seems to be a few small issues we should fix before merge. I left comments on them.
|
|
||
| @timed_op | ||
| def broadcast_object_list(object_list, src, group=None, device=None): | ||
| def broadcast_object_list(object_list, |
There was a problem hiding this comment.
This should be added to calc_bw_log.
There was a problem hiding this comment.
prof and log_name will be ignored if they are passed as positional args.
There was a problem hiding this comment.
Fixed — added broadcast_object_list and all_to_all to calc_bw_log. Thanks!
|
|
||
| @timed_op | ||
| def all_to_all(output_tensor_list, input_tensor_list, group=None, async_op=False): | ||
| def all_to_all(output_tensor_list, |
There was a problem hiding this comment.
This should be added to calc_bw_log.
There was a problem hiding this comment.
prof and log_name will be ignored if they are passed as positional args.
There was a problem hiding this comment.
Fixed — positional prof and log_name arguments are now handled correctly. Thanks!
|
I noticed the above might be preexisting issues, but it would be great if we could address in this PR. |
142dab2 to
09ec337
Compare
|
Thanks for catching these, @tohtana! Both issues are now addressed:
Argument binding remains inside the comms_logger.enabled guard, preserving the disabled-logging fast path. |
Honor positional prof and log_name arguments using a cached signature without changing collective APIs or the disabled-logging fast path. Reuse bound arguments when recording operations. Add broadcast_object_list and all_to_all bandwidth calculations, with regression coverage for real wrappers, profiling selection, debug names, and world-size scaling. Signed-off-by: iLeGend <824040212@qq.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tohtana
left a comment
There was a problem hiding this comment.
Thank you for the update, @jinyouzhi!
Looks good to me.
|
Failed with MPS CPUAdam JIT: @PKUWZP Should we modify the CPUAdam opbuilder cpp version for MPS. Like #8466 DeepSpeed/op_builder/mps/cpu_adam.py Lines 48 to 53 in 02b5350 cpp_standard = '-std=c++20' if (TORCH_MAJOR, TORCH_MINOR) >= (2, 12) else '-std=c++17' |
Summary
Fix a
KeyError: 'log_name'raised by the DeepSpeed communication loggerwhen a wrapped collective is called without an explicit
log_name.This is exposed by multi-rank AutoTP input consistency checks, which call
broadcast_object_listwithout passing profiling metadata. Single-rank TPdoes not exercise this communication path.
Changes
broadcast_object_listandall_to_allfunc.__name__as the defaultlog_nameto cover missing statusValidation
python -m pytest -q tests/unit/comm/test_comms_logger.py