Skip to content

[Hybrid Engine] Enable graph capture for HybridEngine on non-CUDA accelerators - #8933

Open
VenusTZZ wants to merge 4 commits into
deepspeedai:masterfrom
VenusTZZ:npu-hybrid-engine
Open

VenusTZZ wants to merge 4 commits into
deepspeedai:masterfrom
VenusTZZ:npu-hybrid-engine

Conversation

@VenusTZZ

@VenusTZZ VenusTZZ commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The HybridEngine graph-capture paths (per-position decode graphs from #8271 and the continuous-rollout graphs from #8732) were gated on CUDA in several places: a device-name check, an input device-type check, and hard-coded "CUDA graph" naming. This PR replaces them with accelerator capability hooks so any backend that implements graph capture (create_graph() returning a graph, capture_to_graph, replay_graph, and is_current_stream_capturing) takes the same code path as CUDA.

Two accelerator-specific fixes ride along to make the NPU path actually work:

  • NPU inference attention switches to the graph-mode flash attention variant (npu_fusion_attention_v3): the v1/v2 variants launch work on an internal secondary stream that CANN capture rejects at capture_end (error 107025).
  • The NPU inference shim grows its KV-cache table on demand: layer_id is derived from DeepSpeedSelfAttention.num_layers, a process-wide counter that only ever grows, so any process that builds a second engine would otherwise index out of bounds.

Design

Capability gates instead of name gates. validate_cuda_graph_support becomes validate_graph_capture_support and rejects backends with get_accelerator().create_graph() is None instead of comparing device_name() with "cuda". The continuous-rollout path uses the same capability check. Its capture guard (is_current_stream_capturing, used by DeepSpeedStaticCache to skip host-side bounds checks while recording) already routes through the accelerator abstraction since #8732 (CUDA implementation, safe False default); this PR adds the NPU implementation.

Naming. Internal names, docstrings, and error messages drop the "CUDA" prefix (validate_graph_capture_support, "continuous graph capture", "per-position graph"). The config key is renamed to hybrid_engine.enable_graph_capture so the flag reads as a backend capability rather than a CUDA-specific feature, following the rename request in #8271's thread. The old hybrid_engine.enable_cuda_graph key still works as a deprecated alias: its value migrates to the new field with a warning (the existing DeepSpeed deprecated-field machinery), so existing config files keep working.

NPU flash attention. NPUInference._softmax_context calls torch_npu.npu_fusion_attention_v3, the graph-mode variant. The v1/v2 variants rejected capture with "capture model contains a stream that was not joined to the original stream" (107025); after the failed capture the fallback eager path then hit a secondary failure (561103) because host-side counters had already advanced. v3 captures cleanly in the BSH layout (the docs only promise BNSD), and both failure modes disappear together.

Safety

The existing refusals are preserved and remain device-agnostic: ZeRO stage 3, release_inference_cache, inference_tp_size > 1, and unpinned generation lengths all refuse graphs with a warning and fall back to eager, as do capture failures at runtime. The continuous-rollout path keeps its explicit rejections of aligned decode fronts, cache trimming, and flash_attention_2 implementations.

Results

Old container path (#8271 methodology)

facebook/opt-1.3b local weights, ZeRO-2, FP16, single Ascend NPU, 256-token prompt, 128 new tokens:

batch ms / decode step tok/s
1 7.16 140
4 10.86 368
8 12.27 652
16 15.77 1015
32 22.37 1431

Throughput scales 10.2× over a 32× batch increase. Batch 8, averaged over 4 measured iterations of an RLHF-style loop (eval → generate → train → forward/backward/step):

phase eager graph speedup
generate 5436.0 ms 1578.4 ms 3.44×
train step 797.7 ms 797.7 ms 1.00×
iteration 6233.7 ms 2376.1 ms 2.62×
  • Generated tokens are unchanged: 1024/1024 token agreement with the eager path (8 sequences × 128 tokens, same input).
  • Peak memory: 8.94 → 9.04 GiB (+0.10 GiB for 127 captured positions).
  • One-time capture cost: 8.9 s for the first generation; every later generation replays.

Continuous rollouts (#8732 methodology)

gpt2 local weights, FP16, batch 1, 32-token prompt, 32 new tokens, greedy, continuous batching on a single Ascend NPU, two warmup calls and five steady-state measurements:

Mode Median latency Throughput
Eager 1060.44 ms 30 tokens/s
Graph reuse 177.59 ms 180 tokens/s

That is a 5.97× steady-state speedup, with the first call (including capture) at 395.5 ms reported separately. Two different prompts (seeds 1234/1235) produced token-identical eager and graph outputs (32/32 tokens each) while reusing one capture.

Tests

tests/unit/hybrid_engine/test_he_graph.py (renamed from test_he_cuda_graph.py):

  • 16 CPU-only tests covering the generation-length gate, the ZeRO-3 / release_inference_cache / inference_tp_size rejections, the dispatch state machine, capture-failure fallback, and invalidation on length change.
  • One end-to-end test (seq_inference marker) asserting a graphed generation is token-identical to the same generation run eagerly.

Continuous-rollout suite, run on NPU with Transformers 4.57.6:

pytest -q \
  tests/unit/runtime/rollout/test_continuous_batching.py \
  tests/unit/runtime/rollout/test_hybrid_engine_rollout.py \
  tests/unit/v1/test_hybrid_engine_rollout_v1.py
# 55 passed, 3 skipped

NPU end-to-end validation ran locally on a single Ascend NPU (PyTorch 2.10 + torch_npu 2.10.0.post7; Transformers 4.57.6): both paths capture and replay correctly with token-identical outputs, and the eager path is unchanged. The three HybridEngine scripts in DeepSpeedExamples (TODO: fill in the script names) were also run end-to-end on NPU with graph capture enabled.

Scope

The graph flag remains opt-in and off by default; nothing changes for CUDA, whose eager path measured identically before and after. Only ZeRO-2 with inference_tp_size=1 was benchmarked on the container path, matching the upstream guards. Known limitations carried over or found while measuring:

Related work

Gate on the create_graph() capability instead of device names, route
is_current_stream_capturing through the accelerator API, and rename
validate_cuda_graph_support to validate_graph_capture_support with
matching cleanup of CUDA-only wording in internal names, docstrings,
and error messages. The user-facing hybrid_engine.enable_cuda_graph
config key is unchanged. Update the unit tests to capability-gated
skips and rename the test module accordingly.

Signed-off-by: Zizhao Tang <1601677496@qq.com>
Switch the attention call to npu_fusion_attention_v3: the v1/v2
variants launch on an internal secondary stream that CANN capture
rejects (107025), and the eager fallback then hits a lagging-mask
failure (561103). Grow the KV-cache table on demand because layer_id
is derived from DeepSpeedSelfAttention.num_layers, a process-wide
counter that only grows, so a second engine built in one process
indexes out of bounds.

Signed-off-by: Zizhao Tang <1601677496@qq.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-29T06:28:23.785292Z
We couldn’t complete this review. Comment @codex review to try again.
454611a Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

The flag now reads as a backend capability instead of a CUDA-specific
feature. The old key remains as a deprecated alias whose value migrates
to the new field with a warning, so existing configs keep working.

Signed-off-by: Zizhao Tang <1601677496@qq.com>
@VenusTZZ
VenusTZZ marked this pull request as ready for review October 29, 2026 04:24
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

An unknown error occurred
ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

1 similar comment
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

An unknown error occurred
ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

The config-json page still documented the pre-rename key; point it at
enable_graph_capture and note the deprecated enable_cuda_graph alias,
and drop the CUDA-specific wording in the graph description.

Signed-off-by: Zizhao Tang <1601677496@qq.com>
@delock
delock self-requested a review October 29, 2026 11:00

This branch has not been deployed

No deployments
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.

[REQUEST][Hybrid Engine] Enable graph capture for HybridEngine on non-CUDA accelerators

1 participant