Skip to content

zero3: SDMA allgather via mori (sdma_allgather) - #7999

Merged
delock merged 37 commits into
deepspeedai:masterfrom
inkcherry:sdma_ag_
May 14, 2026
Merged

zero3: SDMA allgather via mori (sdma_allgather) #7999
delock merged 37 commits into
deepspeedai:masterfrom
inkcherry:sdma_ag_

Conversation

@inkcherry

@inkcherry inkcherry commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

RFC: #7884

Wire sdma_allgather into ZeRO-3's parameter prefetch path
(_dist_allgather_fn). When enabled, ZeRO-3 allgather routes through
mori_cpp.AllGatherIntoTensor (intra-node SDMA copy on AMD MI300), with a
transparent fallback to dist.allgather_fn (RCCL/NCCL) on init failure.

End-to-end demo + repro steps + verified numbers live in
examples/sdma_allgather/README.md.

Headline (8x MI300X, DeepSpeed default ZeRO-3 buckets, 100 steps):

GPT-7B-ish Qwen3-32B
SDMA off 697.7 ms / step 1402.5 ms / step
SDMA on 622.0 ms / step 1263.2 ms / step
gain +10.85 % +9.93 %

Loss curves match off ↔ on, peak memory unchanged.

Speedup is workload-dependent — gains shrink (or invert) when allgather can't be overlapped with compute

Co-authored-by: wuyl1 yangwu@amd.com

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66673546b5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".



def _dist_allgather_fn(input_tensor: Tensor, output_tensor: Tensor, group=None):
work = mori.allgather_into_tensor(input_tensor, output_tensor)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Honor ZeRO's process group before using SDMA

When ZeRO is initialized with a non-WORLD data-parallel group, or with a secondary zero-param group, _all_gather passes that group down as ds_process_group (partition_parameters.py:1463-1471), but this new SDMA call ignores the group argument and uses mori's WORLD-backed default process group. In those model/tensor-parallel configurations mori gathers from more ranks than the caller allocated output_tensor for, which can corrupt fetched parameters or write past the expected buffer; fall back unless group is WORLD or make mori initialize/use the matching group.

Useful? React with 👍 / 👎.

Comment on lines +1282 to +1283
handle = instrument_w_nvtx(dist.allgather_fn)(
flat_tensor, partitions[rank_in_group], group=ds_process_group, async_op=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route coalesced allgathers through the SDMA wrapper

With the default stage3_allgather_sequential=false, any ZeRO-3 fetch containing more than one parameter takes _all_gather_dtype, but this path now calls dist.allgather_fn directly instead of _dist_allgather_fn. As a result, enabling sdma_allgather has no effect for the common coalesced prefetch path (including the added sample config, which does not enable sequential allgather), so the advertised optimization is skipped for most multi-parameter buckets.

Useful? React with 👍 / 👎.

wuyl1 and others added 21 commits May 8, 2026 05:47
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: wuyl1 <yangwu@amd.com>
Move all mori-specific code (handle, dtype map, transit buffer sizing,
PG-registration helper, Work wrapper) out of partition_parameters.py
into a dedicated runtime/comm backend module:

  deepspeed/runtime/comm/mori.py
    mori.init(max_numel)               # one-shot, idempotent, exception-safe
    mori.is_enabled()                  # cheap predicate
    mori.allgather_into_tensor(in, out)
        -> Work-compatible object on success, None on fallback

The new backend uses mori_cpp.AllGatherIntoTensor (NCCL/RCCL-style
flat->flat C++ dispatcher) instead of the old mori.ccl.AllgatherSdma
templated Python wrapper, so DeepSpeed no longer has to pre-convert
numel into uint32 lane counts or template the C++ class on dtype.

partition_parameters.py is now agnostic to the SDMA path:

    def _dist_allgather_fn(input_tensor, output_tensor, group=None):
        work = mori.allgather_into_tensor(input_tensor, output_tensor)
        if work is not None:
            return work
        return instrument_w_nvtx(dist.allgather_fn)(...)

Init failure (mori missing, non-AMD/ROCm runtime, shmem init error)
leaves the handle unset and logs a single rank-0 warning, so the SDMA
path silently no-ops and dist.allgather_fn (RCCL/NCCL) takes over —
no hard fail.

Net change: partition_parameters.py shrinks by 79 lines; one new
self-contained module under runtime/comm/.

Signed-off-by: inkcherry <mingzhi.liu@amd.com>
_SdmaWork.wait() previously blocked the CPU on _event.synchronize()
before issuing the stream-level dependency.  RCCL's Work.wait() only
records a stream-level wait (cudaStreamWaitEvent / hipStreamWaitEvent)
and does NOT block the CPU, which is what the ZeRO-3 prefetch pipeline
relies on: while bucket N is in flight on the GPU, the CPU is free to
queue bucket N+1 so it can overlap with the trailing compute of N.

The CPU-blocking variant turned out to be a per-step critical-path tax
that wiped out SDMA's headroom on workloads that issue many small
allgathers per step.  Concretely, on Qwen3-32B + ZeRO-3 + seq_len=128,
8x MI300X, ~6400 prefetch buckets per step:

  before:  SDMA  1014 ms / step  (1009 tok/s)
           RCCL   932 ms / step  (1099 tok/s)   -> SDMA -8.0%
  after:   SDMA   927 ms / step  (1104 tok/s)
           RCCL   929 ms / step  (1100 tok/s)   -> within noise

Loss curve is bit-identical with and without the CPU sync, so this is
purely a CPU-pipelining fix.  is_completed() is unchanged (it polls
via _event.query() without blocking, same as before).

Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Move the zero3_overlap demo dir into examples/sdma_allgather/ (the name
that matches the feature being demoed) and add a Qwen3-32B + ZeRO-3
trainer that reproduces the +9.93% end-to-end speedup of this PR on
8x MI300X with the default DeepSpeed bucket sizes.

Layout:
  ds_config_zero3_{sdma,nosdma}.json   ZeRO-3 + bf16 + DS-default buckets
  run_gpt_sdma_{on,off}.sh             GPT-7B-ish demo (existing trainer)
  run_qwen3_sdma_{on,off}.sh           Qwen3-32B demo (new trainer)
  train_qwen3_zero3.py                 self-contained Qwen3 trainer
  README.md                            feature overview + repro steps
  train_zero3.py                       unchanged (renamed only)
  test_sdma_allgather_zero3.py         unchanged (renamed only)

train_qwen3_zero3.py inlines a minimal wikitext-103 dataloader so the
benchmark has no dependency on external benchmark repos.  Loading via
AutoConfig + from_config keeps the example weight-free; only the model
config and tokenizer are pulled from HuggingFace.

The configs use DeepSpeed's default ZeRO-3 bucket sizes
(stage3_prefetch_bucket_size = 5e7, etc.) so the published numbers
in README.md are reproducible without any tuning.

Verified on 8x MI300X, two fresh rounds:
  Qwen3-32B + ZeRO-3 + DP=8, seq_len=1024, micro_bs=1, 100 steps
    SDMA off : 1402.5 ms / step   (5841 tok/s)
    SDMA on  : 1263.2 ms / step   (6486 tok/s)   -> +9.93% e2e

  GPT-7B + ZeRO-3 + DP=8, 100 steps                -> +5.9% e2e

Loss curves match across the two backends, peak memory is identical
(96.45 GB), per-step jitter is 1.4-2.7%, so the ~140 ms gap is well
above noise.

Drops:
  examples/zero3_overlap/run.sh                   superseded by run_gpt_*
  examples/zero3_overlap/ds_config_zero3.json     superseded by *_sdma.json
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
inkcherry added 3 commits May 12, 2026 06:47
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
@delock

delock commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Hi @inkcherry @wuyl1, thanks for your contributed code. I have read this PR and your discussion with @tjruwase in RFC 7884. And I have some high level comments before going into detailed review.

  1. I saw SDMA allgather is exposed in deepspeed config file. If I understand correctly, this is HW specific option, which should be turn on by default (or huristic) with AMD GPU backend, thus a flag in config file may not be needed. Config file may be convienient for debugging, but make it transparent to user looks like a better choice.
  2. I agree with @tjruwase 's comments in RFC 7884 that Mori should be integrated into deepspeed.comm as an alternative path to all_gather, which should be transparent (zero modification) to ZeRO3 code. Is this possible?

The following are current diagram and the ideal diagram

                                                                                      
 ┌─────────────────────────────────────────────────────────────────────┐              
 │                        ZeRO-3 (partitioned_param_coordinator)       │              
 │                                                                     │              
 │   with allgather_stream:                                            │              
 │       handle = _dist_allgather_fn(input, output)                    │              
 │       handle.wait()                                                 │              
 └──────────────────────────────┬──────────────────────────────────────┘              
                                │                                                     
                                │  当前 PR 的做法(hardcoded hook)                   
                                │                                                     
                     ┌──────────▼──────────┐                                          
                     │ _dist_allgather_fn  │  ← partition_parameters.py:109           
                     │                     │                                          
                     │  if mori:           │                                          
                     │    sdma allgather   │──── mori.py ──► SDMA 引擎 (硬件)         
                     │  else:              │                                          
                     │    dist.allgather   │──── RCCL ─────► CU (GPU)                 
                     └─────────────────────┘                                          
                     
 ┌─────────────────────────────────────────────────────────────────────┐           
 │                     理想架构(透明路由)                              │         
 │                                                                     │           
 │   ZeRO-3 只调用:  dist.allgather_into_tensor(output, input)         │           
 └──────────────────────────────┬──────────────────────────────────────┘           
                                │                                                  
                                │                                                  
                     ┌──────────▼──────────┐                                       
                     │   deepspeed.comm    │  ← Backend 选择层                     
                     │                     │                                       
                     │  NVIDIA ──► NCCL    │                                       
                     │  AMD ────► MoriBackend (自动检测)                           
                     └──────────┬──────────┘                                       
                                │                                                  
                     ┌──────────▼──────────┐                                       
                     │    MoriBackend      │  extends TorchBackend                 
                     │                     │                                       
                     │  all_gather_into_tensor()  ──► SDMA 引擎 (硬件)             
                     │  all_reduce()              ──► RCCL ──► CU                  
                     │  reduce_scatter()          ──► RCCL ──► CU                  
                     │  broadcast()               ──► RCCL ──► CU                  
                     │  ...                       ──► RCCL ──► CU                  
                     └─────────────────────┘                                       

inkcherry and others added 3 commits May 13, 2026 04:07
Per @delock's review (PR deepspeedai#7999) and @tjruwase's RFC 7884 feedback, the
SDMA allgather should be an internal optimisation inside deepspeed.comm,
not a user-facing ZeRO-3 config knob.  This commit reshapes the
integration accordingly:

  - TorchBackend.all_gather_into_tensor() now attempts a mori SDMA
    fast-path before delegating to torch.distributed.all_gather_into_tensor.
    Detection is automatic at backend init (AMD/ROCm + mori importable
    + shmem init succeeds); failure leaves the standard RCCL/NCCL path
    untouched.

  - ZeRO-3 partition_parameters.py becomes mori-agnostic: _dist_allgather_fn
    is just a thin wrapper around dist.allgather_fn.  The dedicated mori
    init in Init.__init__ is gone.

  - DeepSpeedZeroConfig.sdma_allgather and sdma_allgather_max_numel are
    removed (no ds_config field — transparent).  An opt-out env var
    DS_DISABLE_SDMA_ALLGATHER=1 is provided for A/B baselines and
    debugging, plus DS_SDMA_ALLGATHER_MAX_NUMEL=N to override the
    transit buffer size (default 64M elements).

  - mori.supports() now refuses the SDMA path when the call is on a
    non-WORLD process group, when the shard is larger than the
    pre-allocated transit buffer, or when the dtype isn't in the
    mori dtype map.  This fixes the Codex P1 review note: a ZeRO
    instance initialised with a sub-PG no longer dispatches to a
    WORLD-bound mori handle.

  - Because ZeRO-3's coalesced allgather path (_all_gather_dtype, the
    default with stage3_allgather_sequential=False) calls dist.allgather_fn
    directly, it now ALSO benefits from SDMA.  This fixes the Codex P2
    review note that the original hook only intercepted the sequential
    path.

Examples:
  - ds_config_zero3.json is now a single file (no _sdma / _nosdma split).
  - run_*_sdma_off.sh export DS_DISABLE_SDMA_ALLGATHER=1.
  - run_*_sdma_on.sh export MORI_ENABLE_SDMA=1 (still required for
    uncached transit buffers on the mori side).
  - test_sdma_allgather_zero3.py exercises the transparent path via
    dist.allgather_fn instead of the old private hook.

Quick test on 8x MI300X (GPT-7B-ish, ZeRO-3 default coalesced path,
100 steps, wikitext-2):
  off (DS_DISABLE_SDMA_ALLGATHER=1) : 705.0 ms / step (11.5 samples/s)
  on  (MORI_ENABLE_SDMA=1)          : 649.8 ms / step (12.5 samples/s)
  -> +8.5% e2e, mori log "AllgatherSdma initialized: PE 0 of 8",
     loss bit-identical at every checkpoint.

The +8.5% gain on the coalesced path is direct evidence Codex P2 is
fixed: before this commit the same config would have shown ~0% gain
because the SDMA hook only sat on the sequential path.

Signed-off-by: inkcherry <mingzhi.liu@amd.com>
…ched

Follow-up to the TorchBackend refactor (606f309) in response to
@delock's review.  Two changes:

1. User-facing control surface is a single opt-in env var.

   DS_SDMA_ALLGATHER=1 is now the only switch.  Default is OFF even
   when mori is installed, so users explicitly choose the
   hardware-specific fast-path (per the discussion under PR deepspeedai#7999:
   users want to keep control over a HW-specific optimisation even
   when the runtime can auto-detect it).

   When set, mori.init() auto-exports MORI_ENABLE_SDMA=1 on the
   user's behalf (via os.environ.setdefault), so the SDMA kernel
   gets its uncached transit buffers without the user having to
   know mori's internal env var.  DS_SDMA_ALLGATHER_MAX_NUMEL=N
   stays as the transit-buffer-size override.

   The previous opt-out DS_DISABLE_SDMA_ALLGATHER is gone (default
   off already covers the A/B baseline scenario).

   mori.init() is now a silent no-op when the user did not opt in,
   so backend init on machines that happen to have mori installed
   no longer prints anything.

2. ZeRO-3 hot path is bit-identical to upstream.

   partition_parameters.py is now 0 diff against the upstream
   pristine version (commit 57b10d5).  All earlier cosmetic
   churn from the original SDMA branch (AllGatherHandle multi-line
   signature, gathered=... temp var, total_numel=... temp var,
   _dist_allgather_fn inlining, and notably collapsing the
   `if original_dtype == allgather_dtype:` fast-path that upstream
   explicitly keeps separate "for safety" re: tied-parameter
   post_accumulate_grad_hook re-entry) is reverted.

   The SDMA acceleration entirely lives inside
   TorchBackend.all_gather_into_tensor; ZeRO-3 just calls
   dist.allgather_fn and lands on the fast-path transparently
   when it is enabled and the call is on the WORLD group with a
   fitting shard size and supported dtype.

   Net core-file footprint of this PR is now just
   deepspeed/comm/torch.py (TorchBackend) + the new
   deepspeed/runtime/comm/mori.py module.

Examples and README updated accordingly: run_*_sdma_on.sh export
DS_SDMA_ALLGATHER=1 (no MORI_ENABLE_SDMA needed); run_*_sdma_off.sh
just don't export anything (default).

Smoke test on 8x MI300X (GPT-7B-ish, ZeRO-3 default coalesced path,
100 steps, wikitext-2):

  off (default, no env var) : 686.2 ms / step (11.8 samples/s)
  on  (DS_SDMA_ALLGATHER=1) : 645.9 ms / step (12.5 samples/s)
  -> +6.2% step time, +5.9% throughput
     mori reports "AllgatherSdma initialized: PE X of 8" on all
     ranks of the on run and nothing on the off run; loss curves
     overlap (off step 50 loss=7.28 vs on step 50 loss=7.25,
     within data-loader jitter).

Co-authored-by: wuyl1 <yangwu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Forgot to put a concrete number in the GPT column.  Both off and on
runs report Max_MA = 12.12 GB on rank 0 (DeepSpeed's memory_status
report), so the table now matches the Qwen3 column's format.

Signed-off-by: inkcherry <mingzhi.liu@amd.com>
inkcherry added a commit to inkcherry/DeepSpeed that referenced this pull request May 13, 2026
…ched

Follow-up to the TorchBackend refactor (606f309) in response to
@delock's review.  Two changes:

1. User-facing control surface is a single opt-in env var.

   DS_SDMA_ALLGATHER=1 is now the only switch.  Default is OFF even
   when mori is installed, so users explicitly choose the
   hardware-specific fast-path (per the discussion under PR deepspeedai#7999:
   users want to keep control over a HW-specific optimisation even
   when the runtime can auto-detect it).

   When set, mori.init() auto-exports MORI_ENABLE_SDMA=1 on the
   user's behalf (via os.environ.setdefault), so the SDMA kernel
   gets its uncached transit buffers without the user having to
   know mori's internal env var.  DS_SDMA_ALLGATHER_MAX_NUMEL=N
   stays as the transit-buffer-size override.

   The previous opt-out DS_DISABLE_SDMA_ALLGATHER is gone (default
   off already covers the A/B baseline scenario).

   mori.init() is now a silent no-op when the user did not opt in,
   so backend init on machines that happen to have mori installed
   no longer prints anything.

2. ZeRO-3 hot path is bit-identical to upstream.

   partition_parameters.py is now 0 diff against the upstream
   pristine version (commit 57b10d5).  All earlier cosmetic
   churn from the original SDMA branch (AllGatherHandle multi-line
   signature, gathered=... temp var, total_numel=... temp var,
   _dist_allgather_fn inlining, and notably collapsing the
   `if original_dtype == allgather_dtype:` fast-path that upstream
   explicitly keeps separate "for safety" re: tied-parameter
   post_accumulate_grad_hook re-entry) is reverted.

   The SDMA acceleration entirely lives inside
   TorchBackend.all_gather_into_tensor; ZeRO-3 just calls
   dist.allgather_fn and lands on the fast-path transparently
   when it is enabled and the call is on the WORLD group with a
   fitting shard size and supported dtype.

   Net core-file footprint of this PR is now just
   deepspeed/comm/torch.py (TorchBackend) + the new
   deepspeed/runtime/comm/mori.py module.

Examples and README updated accordingly: run_*_sdma_on.sh export
DS_SDMA_ALLGATHER=1 (no MORI_ENABLE_SDMA needed); run_*_sdma_off.sh
just don't export anything (default).

Smoke test on 8x MI300X (GPT-7B-ish, ZeRO-3 default coalesced path,
100 steps, wikitext-2):

  off (default, no env var) : 686.2 ms / step (11.8 samples/s)
  on  (DS_SDMA_ALLGATHER=1) : 645.9 ms / step (12.5 samples/s)
  -> +6.2% step time, +5.9% throughput
     mori reports "AllgatherSdma initialized: PE X of 8" on all
     ranks of the on run and nothing on the off run; loss curves
     overlap (off step 50 loss=7.28 vs on step 50 loss=7.25,
     within data-loader jitter).

Co-authored-by: wuyl1 <yangwu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
baseline_pp.py was a stray 257 KB UTF-16 + CRLF copy of
deepspeed/runtime/zero/partition_parameters.py left at the repo root by
an earlier development snapshot.  It is not referenced from anywhere in
the codebase and shows up as a binary blob in the PR diff against
upstream master.  Drop it.

Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Comment thread deepspeed/comm/ccl.py Outdated
import torch
from deepspeed.accelerator import get_accelerator
from deepspeed.ops.op_builder import NotImplementedBuilder
try:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is the condition of this fallback? Is this fallback related to Mori library?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removed try/except here.

@@ -0,0 +1,229 @@
# Copyright (c) Microsoft Corporation.

@delock delock May 13, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should replace Microsoft Corporation in licence header with DeepSpeed team.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

thanks, fixed.

Comment thread deepspeed/comm/mori.py

User-visible controls (env vars, no ``ds_config`` field):

* ``DS_SDMA_ALLGATHER=1`` opt in to the SDMA path. Required:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we use DS_DMA_ALLGATHER and DS_DMA_ALLGATHER_MAX_NUMEL as behavior control environment variable? I'm thinking about in the future other accelerators wants to use similiar techniques, and can share the same environment variable name that is not brand binded.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I initially wanted to use a config file, but considering that currently only the AMD path is available, I set it as an environment variable.
I suggest adding it to the config file in the future if more accelerators support this approach, because environment variables tend to be more specific.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@inkcherry I feel that SDMA implementation should be a drop in replacement of RCCL implementation. So maybe it should be turn on by default. In comments, I saw descriptions that Mori allgather may not be bit by bit identical to RCCL implementation. However allgather has no computation, how could it be not bit by bit identical?

I agree for *MAX_NUMEL option, if in the future more accelerator use it, we need to move it as a config controled variable and give it a generic name.

@inkcherry inkcherry May 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For now, set it as an environment variable. we don't want DeepSpeed's collective backend to silently change behaviour based on which extra packages happen to be installed — mori may end up on the system as a transitive dep of some other framework, not because the user explicitly chose to enable SDMA.

Allgather itself will not have changes. Due to timing variations (data dependencies are correct), end-to-end results might have bit-level differences at the tail due to the order of additions (which can be considered noise, and this is normal in training). However, the loss remains unchanged. Thanks, I have revised the wording to prevent ambiguity.

Comment thread deepspeed/comm/torch.py
# SDMA unsafe (non-WORLD group, oversized shard, unsupported dtype,
# mori unavailable) yields None and we fall through to RCCL/NCCL.
from deepspeed.runtime.comm import mori as _mori
sdma_work = _mori.allgather_into_tensor(input_tensor, output_tensor, group=group)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is the overhead here if the hardware does not support mori? I'm a little bit concerned in potential overhead into this common path. How long does it take on a CUDA system without Mori library, for example?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactored to a lazy check at TorchBackend.init

inkcherry added 2 commits May 13, 2026 08:55
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
inkcherry added 3 commits May 13, 2026 10:00
Three small fixes for the format pipeline that started failing after
the recent comment cleanup:

  - mori.py: restore the standard DeepSpeed license header.  The
    earlier "update" commit collapsed
        # Copyright (c) Microsoft Corporation.
        # SPDX-License-Identifier: Apache-2.0
        #
        # DeepSpeed Team
    into a single "# Copyright (c) DeepSpeed Team." line, which makes
    scripts/check-license.py fail (it greps for the literal lines
    "^# SPDX-License-Identifier: Apache-2.0$" and "^# DeepSpeed Team$").
    Header is back in the canonical form used everywhere else in the
    repo.

  - mori.py: yapf-reformat three call sites the editor had wrapped
    too aggressively for the project's 119-column limit
    (allgather_into_tensor signature, the _handle(...) call, the
    rank-0 warning guard).

  - .pre-commit-config.yaml: add deepspeed/runtime/comm/mori.py to
    the check-torchdist exclude list.  Like its sibling
    deepspeed/runtime/comm/coalesced_collectives.py (already on the
    list), mori is an intentionally-low-level comm backend that
    needs torch.distributed primitives (group.WORLD identity,
    is_initialized(), get_rank()) directly; the higher-level
    deepspeed.comm wrappers aren't a substitute here.

Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
@delock

delock commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Hi @inkcherry can you resolve merge conflicts? Thanks!

inkcherry added 2 commits May 14, 2026 05:59
mori is a backend helper for TorchBackend (selected at backend init,
exposed via TorchBackend.all_gather_into_tensor), so it belongs under
deepspeed/comm/ alongside the other backend implementations rather
than under deepspeed/runtime/comm/ which holds higher-level comm
utilities.

This also drops the explicit deepspeed/runtime/comm/mori.py entry
from the check-torchdist exclude list, since deepspeed/comm/ is
already covered by the broader parent-directory exclude pattern.

Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Brings the SDMA allgather branch up to date with upstream master
(57 commits since the last sync, including the v0.19.0 release).

Only conflict was the Latest News section of the top-level README,
which now lists [2026/05] SDMA above the [2026/03] entries added
upstream.

Signed-off-by: inkcherry <mingzhi.liu@amd.com>
@inkcherry

Copy link
Copy Markdown
Contributor Author

Hi @inkcherry can you resolve merge conflicts? Thanks!

@delock resolved.

After merging upstream/master, pre-commit run --all-files surfaced
three latent violations in the SDMA example scripts that the prior
DeepSpeedAI CI runs hadn't been exercising:

  - check-license : missing the standard DeepSpeed Apache-2.0 header
    on all three example .py files.
  - check-torchdist : direct use of torch.distributed.get_world_size,
    `import torch.distributed as dist`, `import torch.distributed as
    torch_dist`.  Routed through deepspeed.comm (`from deepspeed
    import comm as dist`), matching the rest of the examples in the
    repo.
  - check-torchcuda : torch.cuda.set_device / manual_seed_all /
    synchronize / reset_peak_memory_stats / max_memory_allocated /
    device_count / Event used directly.  Routed through
    `get_accelerator()` so the demos stay portable across CUDA, ROCm
    and other DeepSpeed accelerators (which matters here -- the
    SDMA fast-path is a ROCm-only feature in the first place).

yapf also re-wrapped a few argparse / torch.empty call sites to
the project's 119-column limit.

No behaviour change: the examples produce the same output and the
benchmark numbers in examples/sdma_allgather/README.md still apply.

Signed-off-by: inkcherry <mingzhi.liu@amd.com>
@delock
delock merged commit 66af8f0 into deepspeedai:master May 14, 2026
9 checks passed
nathon-lee pushed a commit to nathon-lee/DeepSpeed_woo that referenced this pull request Jul 1, 2026
## Summary
RFC: deepspeedai#7884

Wire `sdma_allgather` into ZeRO-3's parameter prefetch path
(`_dist_allgather_fn`).  When enabled, ZeRO-3 allgather routes through
`mori_cpp.AllGatherIntoTensor` (intra-node SDMA copy on AMD MI300), with
a
transparent fallback to `dist.allgather_fn` (RCCL/NCCL) on init failure.

End-to-end demo + repro steps + verified numbers live in

[`examples/sdma_allgather/README.md`](examples/sdma_allgather/README.md).

Headline (8x MI300X, DeepSpeed default ZeRO-3 buckets, 100 steps):

| | GPT-7B-ish | Qwen3-32B |
|---|---|---|
| SDMA off | 697.7 ms / step | 1402.5 ms / step |
| SDMA on  | 622.0 ms / step | 1263.2 ms / step |
| **gain** | **+10.85 %**    | **+9.93 %**      |

Loss curves match off ↔ on, peak memory unchanged.

Speedup is workload-dependent — gains shrink (or invert) when allgather
can't be overlapped with compute

Co-authored-by: wuyl1 <yangwu@amd.com>

---------

Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Co-authored-by: wuyl1 <yangwu@amd.com>
Signed-off-by: nathon-lee <leejianwoo@gmail.com>
nathon-lee pushed a commit to nathon-lee/DeepSpeed_woo that referenced this pull request Jul 1, 2026
## Summary
RFC: deepspeedai#7884

Wire `sdma_allgather` into ZeRO-3's parameter prefetch path
(`_dist_allgather_fn`).  When enabled, ZeRO-3 allgather routes through
`mori_cpp.AllGatherIntoTensor` (intra-node SDMA copy on AMD MI300), with
a
transparent fallback to `dist.allgather_fn` (RCCL/NCCL) on init failure.

End-to-end demo + repro steps + verified numbers live in

[`examples/sdma_allgather/README.md`](examples/sdma_allgather/README.md).

Headline (8x MI300X, DeepSpeed default ZeRO-3 buckets, 100 steps):

| | GPT-7B-ish | Qwen3-32B |
|---|---|---|
| SDMA off | 697.7 ms / step | 1402.5 ms / step |
| SDMA on  | 622.0 ms / step | 1263.2 ms / step |
| **gain** | **+10.85 %**    | **+9.93 %**      |

Loss curves match off ↔ on, peak memory unchanged.

Speedup is workload-dependent — gains shrink (or invert) when allgather
can't be overlapped with compute

Co-authored-by: wuyl1 <yangwu@amd.com>

---------

Signed-off-by: wuyl1 <yangwu@amd.com>
Signed-off-by: inkcherry <mingzhi.liu@amd.com>
Co-authored-by: wuyl1 <yangwu@amd.com>
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