Skip to content

Enable support for Tutel when k != 1 for shared moe - #8174

Merged
tohtana merged 2 commits into
deepspeedai:masterfrom
jinyouzhi:tutel
Jul 26, 2026
Merged

Enable support for Tutel when k != 1 for shared moe#8174
tohtana merged 2 commits into
deepspeedai:masterfrom
jinyouzhi:tutel

Conversation

@jinyouzhi

Copy link
Copy Markdown
Contributor

Remove the limited on top2 and topk >2 for Tutel.

Signed-off-by: iLeGend <824040212@qq.com>

@tohtana tohtana left a comment

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.

Looks good to me, thank you @jinyouzhi!

Performance note from the validation on my side: on one 2×H100 BF16, the Tutel/native step-time ratio was 0.948 for k=2, but 1.119 for k=3. Correctness looks good so it is fine to merge, but Tutel might not have performance advantage for k=3 in this configuration.

@tohtana
tohtana enabled auto-merge July 26, 2026 22:02
@tohtana
tohtana added this pull request to the merge queue Jul 26, 2026
Merged via the queue into deepspeedai:master with commit dc81c9e Jul 26, 2026
13 checks passed
@jinyouzhi
jinyouzhi deleted the tutel branch July 27, 2026 06:17
@jinyouzhi

Copy link
Copy Markdown
Contributor Author

Looks good to me, thank you @jinyouzhi!

Performance note from the validation on my side: on one 2×H100 BF16, the Tutel/native step-time ratio was 0.948 for k=2, but 1.119 for k=3. Correctness looks good so it is fine to merge, but Tutel might not have performance advantage for k=3 in this configuration.

Thank you for your review and perf metrics. With your insights, I further tried to optimize the k=3 case to reduce overhead.

banxingmjj pushed a commit to openanolis/DeepSpeed that referenced this pull request Aug 2, 2026
…utel paths (deepspeedai#8195)

Follow-up to deepspeedai#8174. Three independent changes to
`deepspeed/moe/sharded_moe.py`.

### 1. Speed up Tutel route extraction for k > 2

`topkgating` rebuilt a dense `[s, e]` one-hot mask for every route, so
gating cost
grew linearly with `k`. On launch-bound configurations Tutel therefore
lost to the
native path once `k > 2`. Since the top-k columns already name the
selected experts,
the per-route index/location/gate values are now a `gather`. Routes are
handed to
Tutel as contiguous int32 so its own casts become no-ops, and the
capacity tensor is
resolved once instead of forcing a device-to-host sync per use.

Routing output is bit-identical.

### 2. Shard the Tutel dispatch buffer along capacity under tensor
parallelism

Tutel's `encode()` returns a flat `[e * c, m]` buffer, whereas the dense
path
produces `[e, c, m]`. The `drop_tokens(dispatched_input, dim=1)` that
follows
therefore sliced the **model** dim instead of the **capacity** dim. The
result still
reshaped cleanly into the expert input (`e * c * m / tp` divided by `e *
m` happens to
yield `c / tp`), so there was no error — experts silently ran on a
hidden-state slice
reinterpreted as capacity, producing wrong results.

Reshaping the buffer to `[e, c, m]` before sharding fixes it. This is a
pre-existing
bug on `master`, not introduced by deepspeedai#8174, but deepspeedai#8174 widens its reach by
lifting the
`k == 1` restriction on `use_tutel`.

### 3. Dispatch MoE tokens by index instead of a dense one-hot einsum

The native path materialised a dense `[s, e, c]` one-hot and ran
`einsum("sec,sm->ecm")`, costing `O(s * e * c * m)` to move only `s * k
* m` elements.
Tokens are now routed through their capacity slots directly, so dispatch
and combine
each become a single gather.

The sparse routing metadata already existed for Tutel, so the gate
always returns it
and `MOELayer` selects a backend. The dense return path is kept because
`deepspeed/ops/transformer/inference/moe_inference.py` still consumes
it.

Combine accumulates in fp32, matching the einsum it replaces — the
einsum accumulated
in fp32 inside the tensor-core matmul, so a naive low-precision gather
would have been
*less* accurate than the code it replaced.

## Results

Measured on 2 GPUs, bf16, forward+backward.

Tutel/native step-time ratio (change 1):

| k | before | after |
|---|--------|-------|
| 2 | 0.947  | 0.937 |
| 3 | 1.078  | 1.01  |
| 4 | 1.158  | 1.04  |

Native path (change 3), `s=8192 m=2048 e=16 k=3`:

| | step time | peak memory |
|---|-----------|-------------|
| before | 58.33 ms | 4353 MB |
| after  | 37.25 ms | 2918 MB |

The native path now matches Tutel (37.25 vs 37.49 ms).

## Testing

`tests/unit/moe/`: 50 passed with Tutel installed, 48 passed / 2 skipped
without.

- `test_sparse_dispatch_matches_dense` (k=1/2/3) checks the index path
against the
  dense einsum for both dispatch and combine.
- `TestMOETensorParallelTutel` checks Tutel against the dense path under
tensor
  parallelism; it fails without change 2 and passes with it.

Cross-checked end-to-end against `master` (fp32 max diff 1.2e-07) and
verified that
bf16/fp16 accuracy versus an fp32 golden reference does not regress.

> `TestMOETensorParallelTutel` requires Tutel and is skipped in CI,
which does not
> install it. Change 2 was validated locally on 2 GPUs: max diff
`7.27e-01` before the
> fix, `0.00e+00` after.

---------

Signed-off-by: iLeGend <824040212@qq.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Masahiro Tanaka <81312776+tohtana@users.noreply.github.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.

2 participants