Conversation
register_replicated_grad_hooks() logs its summary via print_dist, which exists in deepspeed/utils/logging.py but was not imported in deepspeed/module_inject/auto_tp.py. Any model whose HuggingFace tp_plan contains replicated_with_grad_allreduce entries (e.g. Qwen3's q_norm / k_norm with recent transformers) hits NameError at deepspeed.initialize() whenever tensor-parallel size > 1. Verified with the AutoTP equivalence check from deepspeedai/DeepSpeedExamples#1008 (Qwen3-0.6B, tp=3 uneven / tp=4 even, 500 steps each): tp=3 and tp=4 both crashed at initialize before this fix and complete with agreeing loss curves after it. Same missing import is fixed in passing on the #8241 branch; this is the minimal standalone hotfix so the crash is not blocked on that refactor. Signed-off-by: Guokai Ma <guokai.ma@intel.com>
Two experiment lines for the kernel-injection discussion: 1. Old KI (v1 container path): Qwen2/Qwen3.5 policies + containers with a per-instance should_replace hook for hybrid GDN/full-attention layers. GPU evidence: takeover works (containers=24/8, generate wrapped) but the path hits legacy-stack bugs independent of Qwen3.x - GQA qkv merge assumes MHA layout (fixed), transformers>=5 layer tuple protocol (adapter added), KV-cache protocol divergence (opt-1.3b bisect), and AutoTP collision. 2. Segment KI (new architecture probe): segment_ki.py applies fused_glu on comm-free segments of the AutoTP-sharded tree; collective-bearing modules are hard boundaries and are delegated untouched. CPU evidence: bit-exact greedy vs native HF on Qwen2.5-0.5B and cross-family on Qwen3.5-0.8B-Base with zero core changes. Note: the two lines are mutually exclusive at runtime; segment-KI tests need DS_QWEN2_INJECTION=0. Journals and harnesses under experiments/. Signed-off-by: Guokai Ma <guokai.ma@intel.com>
Segment KI line update: - csrc/module_inject/fused_glu.cu: native CUDA fused silu-mul kernel (halves layout, no chunked views; fp32/bf16; op_builder JIT via FusedGLUBuilder). fp32 max diff 2.4e-7 vs torch composite; bf16 ~1-2 ULP (single rounding vs the oracle's double rounding). - segment_ki.apply_segment_ki(backend='auto'): dispatches to the CUDA op on non-cpu accelerator backends, torch composite otherwise. - perf_ki_matrix.py: hf.generate (eager + torch.compile reduce-overhead), AutoTP TP=2 with/without segment KI, old-KI modes. GPU results (Qwen2.5-0.5B-Instruct, bf16, batch 1, greedy 128 tok): hf eager 50.0 tok/s; hf+graph 50.4 (capture region misses the HF generate python loop); hf+segKI 51.6 (+3.2%); AutoTP 24.1; AutoTP+segKI 33.2 (+38% over AutoTP). Old-KI numbers withheld pending its numerics fix (KV write-back landed in the old-KI line; single-layer divergence remains under investigation). Signed-off-by: Guokai Ma <guokai.ma@intel.com>
…ecision diagnosis Old-KI line update from the 4h GPU debugging window: - hybrid_engine: DS layers now write their full-history presents back into the HF DynamicCache (sliced to the tokens introduced by each call; the layer index comes from the engine enumeration since transformers>=5 decoder layers no longer store layer_idx). Verified by probe: cache length advances 5->6->7 in lockstep with position_ids. Also neutralize SDPA-style bool masks for the v1 ops (they read masks as additive floats; None selects their internal triangular causal path). - pt_binding.cpp: the legacy binding at :197 used alpha=norm_factor (head_dim^-1/4) while the fallback at :374 squares it (head_dim^-1/2); align the CUDA path. The main softmax_context path dispatches to the correctly-squaring template, so this is a latent-bug fix. - Numerics remain broken and are now precisely diagnosed (probes 6-14 under experiments/, using get_accelerator() throughout): every layer component is individually correct (weights 0.0 diff, embed/lm_head wrappers 0.0, attention corr 0.999997, layer corr 0.99974) yet full-model logits diverge (argmax agreement 0%). Root cause: v1 csrc kernel bf16 precision (RMSNorm max err 2x HF: 0.082 vs 0.043) compounds across 24 layers and Qwen2's ~700x deep-layer activation dynamic range into ~20% logit error. Fixing requires fp32 accumulation in the csrc RMSNorm kernel (kernel engineering, follow-up), not an adapter-level patch. Signed-off-by: Guokai Ma <guokai.ma@intel.com>
Second segment kernel: the GatedDeltaNet middle segment of hybrid qwen3_5-family models. - find_gdn_segments: structural detection of the in_proj_qkv/z/b/a projection set. Under AutoTP the projections become sharded wrappers while the grouped conv1d keeps its full-channel layout, so fusion safely degrades to zero segments there (documented limitation; single-device native layout only). - _fused_gdn_forward: collapses the four input projections into one GEMM (weight concat, [qkv|z|b|a] layout) and delegates conv1d, the FLA delta-rule scan, the gated norm, and the collective-bearing out_proj to the untouched original submodules. Scan inputs are whitelisted: transformers threads unrelated layer kwargs (use_cache, cache_position, cache_params) into every block and the fused kernel signatures reject unknown names - four such delegation traps were cleared during bring-up (cu_seqlens, cache_params, use_cache). - csrc fused_glu.cu: new gdn_gates kernel fusing beta=sigmoid(b) and g=-exp(A_log)*softplus(a+dt_bias) with fp32 math and the numerically stable softplus branch (large-x without it loses significant digits; relmax 0.39% == one bf16 ULP vs the composite oracle). - apply_segment_ki now takes kernel='all'|'fused_glu'|'fused_gdn'. Results (Qwen3.5-4B-Base, bf16, batch 1, greedy): - CPU 0.8B oracle: 18/18 GDN + 24/24 GLU bit-exact on first run. - GPU 4B end-to-end: MATCH_REF=True - greedy identical to plain HF eager with the native kernel path. - Performance flat (22.6 vs 22.9 tok/s): the fused-output slicing chain (slice+transpose+contiguous measured at 403us per call at prefill sizes, x24 layers) eats the 4->1 GEMM gain; the FLA scan dominates GDN layer time, so glue-fusion headroom is small by construction. Follow-up: pre-arranged fused weight layout and strided-input gates kernel, then re-measure. Signed-off-by: Guokai Ma <guokai.ma@intel.com>
… +10.3% on Qwen3.5-4B Root cause of the earlier -6% regression: transformers 5.14 binds the GDN kernels onto the module instance in __init__ (recurrent_gated_delta_rule = fused_recurrent_gated_delta_rule or torch_recurrent_gated_delta_rule), so the FLA fused kernel is selected per instance. The segment-KI delegation called the module-level torch_* reference directly, silently forcing the slow pure-torch recurrence on every GDN layer - visible in the profiler as +604 elementwise launches per decode step (the mul/add/sum of the un-fused delta-rule math) and a halved gdn_core bucket. Fix: delegate through the instance attributes so the replacement follows transformers' kernel selection automatically. Also lands the layout round: strided gdn_gates (row-stride input, no contiguous copies; fixes a stride(0)-vs-stride(-2) out-of-bounds), and an explicit single qkv repack before conv1d. Final numbers (Qwen3.5-4B-Base, bf16, batch 1, single GPU, 3-run means): HF eager 22.5 +/- 0.2 tok/s; segment KI 24.8 +/- 0.4 (+10.3%). Gain decomposition (profiler evidence): device time only -3% (30.3->29.4ms/step; the GEMM merge saves launches but no weight bytes, and the FLA scan is delegated untouched), the remaining ~7% is CPU-side dispatch elimination (~250 fewer aten/python traversals per step). Correctness: divergence position identical to the historical GLU-era ULP fork (position 75 = 68 generated tokens), non-structural. Signed-off-by: Guokai Ma <guokai.ma@intel.com>
…DN gap; 3.7x on 0.5B Three findings from the graph-capture experiments: 1. transformers >= 5 cache protocol drift had silently broken the whole graph path: DeepSpeedStaticCache lacked get_query_offset (crashes for every model, with or without segment KI). Fixed by delegating to get_seq_length, matching HF semantics for non-MTP layers. With the fix, Qwen2.5-0.5B + use_graph_capture reaches 182-193 tok/s vs ~50 eager (3.7x), correct output. 2. Graph capture crashes on hybrid GDN models: the _generate_graph KV-copy loop assumes every cache slot has keys/values, but Qwen3.5's GDN slots are LinearAttentionLayer objects holding recurrent_states + conv_states. Not a GDN limitation - GDN state is fixed-shape (more graph-friendly than growing KV); vLLM/SGLang already run hybrid GDN models under CUDA graphs via separate static state allocators with in-place updates. 3. Graph capture and segment KI harvest the same CPU-dispatch pool: with graphs enabled, segKI's ~7% CPU gain vanishes and its residual slice overhead turns net-negative (-9% on 0.5B). They substitute, not stack; post-graph, segKI's value moves to the device side (fewer kernels per replay) once the slicing chain is eliminated. Journal carries the 3-4 day design note for GDN graph support: slot-type-aware DeepSpeedStaticCache (DSStaticGDNLayer with static recurrent/conv buffers), in-place update_recurrent_state (HF rebinds, which breaks replay), MTP layers excluded in v1. test_graph.py is the reproduction harness for the follow-up. Signed-off-by: Guokai Ma <guokai.ma@intel.com>
The final three bugs in the chain (each instrument-verified): 1. Prefill with a 2D attention mask corrupts GDN conv-state init: generate passes a per-type mask dict (GDN=None); switch the prefill call to the same kwargs. 2. DeepSpeedStaticLayer.get_seq_length returned write_position+1 while HF cumulative_length semantics make the write index equal the cached count - the +1 shifted every model-derived decode position and mis-roped stored keys (KV@5 maxdiff 2.52 vs generate). Fixed by returning write_position itself (kept tensor-typed: read inside captured regions, must not sync). 3. The 3 warmup forwards before capture advance GDN conv/recurrent states by 3 steps, so every replay starts from corrupted state. Snapshot GDN slots after prefill; copy_ restore after warmup, right before capture. Results (Qwen3.5-4B-Base, bf16, batch 1, single GPU): eager 22.5 tok/s; graph 30.4 tok/s (+35%, 128-token) / 54.8 (2.4x, 64-token); graph+segKI 31.4 (+3% in-graph, ULP fork at ~68 tokens, coherent - known fused-rounding characteristic). Total GDN graph enablement: 6 protocol alignments + these 3 bugs, ~80 lines of Python, zero csrc changes. The journal records the full 11-layer bisection chain with evidence at each step. Signed-off-by: Guokai Ma <guokai.ma@intel.com>
Three-level decode optimization for HybridEngineRollout, each level
validated on Qwen3.5-4B (b=1, single GPU):
Level 1 - fused step kernel (decode_step):
argmax(vocab) + token write + write_pos advance + mask reveal in one
CUDA kernel via op_builder. Replaces 6 Python->CUDA dispatches with
one kernel call.
Level 2 - C++ decode loop (decode_loop.cu, DecodeLoopBuilder):
The entire decode loop runs in a single C++ function call. Graph
replay via pybind11 method invocation + fused step kernel. Python
calls decode_loop() once for N steps.
Level 3 - full-step CUDA graph (decode_step_graph):
Forward + argmax + all buffer updates captured in ONE CUDA graph.
Each decode step is a single graph.replay() - architecturally
equivalent to vLLM's decode loop. Key design: token_buf indexed by
GPU-resident write_pos (kernel advances it), eliminating host-side
step parameters for graph capture compatibility.
nsys deep profiling findings (with --cuda-graph-trace=node):
- Our GPU kernel time (8.8ms/step) is competitive with vLLM
- The 48% CPU gap (8.1ms/step) was the real bottleneck on fast GPUs
- 146 GEMV calls/step vs vLLM's fewer large-tile calls
- GDN core (FLA) only 0.8% of GPU time (0.07ms/step)
- vLLM uses custom GDN decode kernel + Flash Attention + Triton
fused activation (not FLA)
Measured on same-instance controlled comparison (slow instance):
Python loop 28.1 -> C++ loop 28.1 -> full-step graph 30.0 tok/s
vLLM: 72.3 tok/s (gap: 2.5x, remaining = GPU kernel efficiency)
Correctness: 64-token and 128-token outputs token-identical to golden.
Signed-off-by: Guokai Ma <guokai.ma@intel.com>
…7.8% with vLLM decode
segKI is now a config option instead of an external call:
rollout = HybridEngineRollout(engine, tok,
cfg=HybridEngineRolloutConfig(use_graph_capture=True, use_segki=True))
- use_segki/segki_kernel/segki_backend config fields
- _apply_segki() runs at rollout construction; unsupported architectures
pass through silently
- sync_segki_weights() re-builds fused weight copies in-place (copy_)
after optimizer steps, keeping CUDA graph buffer addresses stable
- refresh_fused_weights() in segment_ki.py walks GLU+GDN segments and
re-concatenates from the latest training weights
Definitive E2E comparison (same instance, 5 warmup generates, Qwen3.5-4B,
b=1, 128 tok greedy):
segKI+full-step-graph: 60.9 tok/s
vLLM 0.29.0: 72.4 tok/s (gap: 1.19x)
Gap decomposition (CUDA Events measurement, zero profiler overhead):
GPU kernel difference: 2.2% (14.28 vs 13.97 ms/step)
Prefill amortization: ~10% (eager 200ms vs vLLM chunked ~50ms)
Generate-path Python: ~7%
Key methodology finding: nsys profiling adds 2.2x overhead which inflated
all previous gap measurements (2.5x -> actual 1.19x). CUDA Events are the
only reliable GPU timeline measurement (100% utilization confirmed, zero
replay gaps).
Signed-off-by: Guokai Ma <guokai.ma@intel.com>
…remove markdown-breaking pipe chars New files created on this branch incorrectly carried the Microsoft copyright line (copied from neighboring files). Per project convention, new files use: # SPDX-License-Identifier: Apache-2.0 # DeepSpeed Team Also replaces pipe characters in code comments/docstrings that broke the GitHub-style diff rendering (interpreted as markdown table cell delimiters inside the diff viewer). Signed-off-by: Guokai Ma <guokai.ma@intel.com>
Signed-off-by: Guokai Ma <guokai.ma@intel.com>
These were prototype artifacts from the v1 container-injection line. The integrated segKI path (segment_ki.py forward replacement) does not use containers; these files are dead code. Signed-off-by: Guokai Ma <guokai.ma@intel.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Enables CUDA graph capture for hybrid-architecture models (GDN + full-attention, e.g. Qwen3.5) in
HybridEngineRollout, adds segment-KI (kernel injection) as a config option, and reaches 97.8% of vLLM decode performance on Qwen3.5-4B.Model measured: Qwen3.5-4B-Base (32 layers: 24 GatedDeltaNet + 8 full-attention, head_dim=256, vocab 248K)
What's Included
1. Full-Step CUDA Graph (
hybrid_engine_rollout.py)graph.replay()token_bufindexed by GPU-residentwrite_pos(kernel self-advances), eliminating host-side step counters for graph capture compatibility2. Segment-KI Integration (
segment_ki.py+ config)HybridEngineRolloutConfig(use_segki=True)— one-line enablefused_silu_mul_halves,gdn_gatessync_segki_weights()for RL loops (in-place weight refresh, graph-address stable)3. Hybrid-Slot Static Cache (
static_cache.py)4. Bug Fixes (upstream-valid)
ds_attention.py)get_query_offsetmissing from static cache (breaks all graph capture on tf 5.x)pt_binding.cpp:197alpha not squaredE2E Performance (Qwen3.5-4B-Base, bf16, b=1, 512 tok greedy)
Gap Decomposition (CUDA Events, zero profiler overhead)
Correctness
Architecture Notes
Test Plan
📄 Full experiment journal:
experiments/segment-ki-proto.md(11-layer bisection chain with evidence at each step)