Skip to content

Refactor DeepSeek adapter around native vLLM lifecycle#151

Open
jiaran-king wants to merge 1 commit into
mainfrom
codex/issue143-refactor
Open

Refactor DeepSeek adapter around native vLLM lifecycle#151
jiaran-king wants to merge 1 commit into
mainfrom
codex/issue143-refactor

Conversation

@jiaran-king

@jiaran-king jiaran-king commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

DeepSeek V2 native lifecycle refactor

Background, objective, and scope

The previous DeepSeek adapter copied the ordinary Model and Decoder forward
paths and most of the native weight-loader loop. AFD role selection,
communication, model lifecycle, and backend-specific weight handling were
therefore coupled in one adapter. Changes to upstream deepseek_v2.py had to
be merged manually, and native fixes such as new parameter mappings could
drift from the plugin.

Issue #143 changes that ownership boundary:

Native vLLM owns the ordinary DeepSeek model lifecycle and weight mappings.
AFD owns role-aware construction, checkpoint role filtering, Attention/FFN
communication, Attention-side gate policy, and async CAM scheduling.

The refactor targets only this pinned stack:

vLLM          0.19.1
vLLM-Ascend   0.19.1rc1

It covers synchronous AFD, the existing Attention-side-gate path, async CAM
integration, and role-aware checkpoint filtering. It does not add
sequence-parallel MoE support, adapt another upstream version, redesign
connectors or workers, or remove existing process-wide runtime compatibility
patches. Native model registration also remains protected by the existing
AFD-prefixed alias mechanism introduced for Issue #141.

Architecture before and after

Before the refactor, the common adapter owned both AFD composition and ordinary
DeepSeek semantics. After the refactor, the adapter inherits native lifecycle
behavior and inserts AFD at explicit stage boundaries.

flowchart LR
    subgraph Before["Before: copied lifecycle"]
        BC["AFD CausalLM"]
        BM["Standalone AFD Model"]
        BD["AFD DecoderLayer"]
        BF1["Copied Model.forward"]
        BF2["Copied DecoderLayer.forward"]
        BL["Copied load_weights loop"]

        BC --> BM
        BC --> BL
        BM --> BF1
        BM --> BD
        BD --> BF2
    end

    subgraph After["After: native lifecycle"]
        AC["AFD CausalLM<br/>native subclass"]
        AM["AFD Model<br/>native subclass"]
        AD["AFD DecoderLayer<br/>native forward"]
        AP[".mlp stage proxy"]
        AL["Role filter<br/>native loader"]
        AA["Async CAM<br/>schedule helper"]

        AC --> AM
        AC --> AL
        AM --> AD
        AM --> AA
        AD --> AP
    end
Loading

The resulting responsibility boundary is:

Owner Responsibilities
Native vLLM Model/Decoder forward, residual and normalization order, Attention, native MLP/MoE execution, PP/Indexer behavior, and native weight mappings
AFD Role-aware constructors, .mlp stage proxies, send/yield/receive, checkpoint role filtering, Attention-side gate policy, and async CAM scheduling
vLLM-Ascend NPU operators, quantization contracts, mix-placement activation, and fused shared-expert behavior for the pinned runtime

Only DeepseekV2Model.__init__ and
DeepseekV2DecoderLayer.__init__ remain as pinned upstream copies because
vLLM 0.19.1 hard-codes layer construction. Their signatures match upstream,
AFD differences are marked with PATCH START/END, and the construction unit tests
check the non-patch constructor digest.

Core changes and effects

Role-aware construction

Modules are now selected during construction instead of constructing a full
Attention/MoE stack and deleting the unused half.

Mode Layer type Attention worker .mlp FFN worker .mlp
Standard AFD Dense RemoteFFNProxy native DeepseekV2MLP
Standard AFD MoE RemoteFFNProxy native DeepseekV2MoE
Attention-side gate Dense native DeepseekV2MLP MissingFFNStage
Attention-side gate MoE GateOnlyRemoteMoE native DeepseekV2MoE

The Attention worker constructs native Attention; the FFN worker uses
MissingAttentionStage and does not allocate a real Attention/KV module. The
DeepSeek V3.2 Indexer buffer is allocated only on the Attention role.
Attention-side gate parameters retain the native
model.layers.<idx>.mlp.gate path. In that mode gate weights may be loaded on
both roles to keep the FFN MoE structure native, but routing executes only on
Attention.

This removes unused large modules and their parameter, cache, quantization,
and backend initialization side effects from the wrong role.

Synchronous forward

The synchronous path calls native DeepseekV2Model.forward() and inherits
native DeepseekV2DecoderLayer.forward(). Communication occurs when the native
Decoder calls self.mlp(hidden_states).

sequenceDiagram
    participant Native as Native Model/Decoder
    participant Proxy as AFD .mlp proxy
    participant Connector
    participant FFN as FFN worker

    Native->>Proxy: self.mlp(hidden_states)
    Proxy->>Connector: send_attn_output
    Proxy->>Proxy: optional DBO yield
    Connector->>FFN: hidden states and optional gate payload
    FFN->>FFN: native MLP or MoE
    FFN-->>Connector: FFN output
    Connector-->>Proxy: recv_ffn_output
    Proxy-->>Native: remote FFN result
    Native->>Native: continue native residual/layer flow
Loading

AFD no longer copies the ordinary residual, RMSNorm, Attention, or layer-loop
logic. RemoteFFNProxy is not a DeepseekV2MLP, so native Decoder code does
not apply Dense FP16 output scaling on Attention. The FFN-side
compute_ffn_output() applies that scaling once for native Dense MLP and does
not repeat native MoE scaling.

Role filtering and native weight loading

The old adapter performed role filtering and then reimplemented native
projection, expert, quantization, PP, and scale loading. The new adapter
answers only whether a checkpoint entry belongs to the current role:

flowchart LR
    CKPT["One-shot checkpoint iterator"] --> FILTER["AFD role filter"]
    FILTER --> LOADER["Native DeepSeek load_weights"]
    LOADER --> MAP["QKV / MLA / Indexer mappings"]
    LOADER --> MOE["Experts / shared experts / scales"]
    LOADER --> PP["PP missing parameters"]
    LOADER --> RESULT["Native loaded_params"]
Loading

Common parameters remain visible to both roles. Attention parameters are
visible only to Attention. Dense and MoE ownership follows the construction
table above; in Attention-side-gate mode MoE gate parameters are visible to
both roles while expert/shared-expert parameters remain FFN-owned.

The filter is a generator, consumes the checkpoint iterator once, and returns
the exact native loaded_params result. Native vLLM remains responsible for
MHA QKV packing, MLA fusion, Indexer loading, quantization scales, expert
mapping, EPLB/redundant experts, PP-missing parameters, and KV-scale remapping.

For vLLM-Ascend 0.19.1rc1, NPUModelRunner.load_model() enables vLLM's fused
MoE/shared-expert probes before model construction when mix-placement is
active. The vLLM 0.19.1 native loader consequently performs widened
shared-expert splitting and appended-expert mapping. The adapter must not
duplicate that loader behavior. This describes the pinned design path;
mix-placement and W8A8 still require real NPU validation.

Async CAM integration

The async CAM wavefront, dual-ubatch ordering, and connector semantics remain
AFD-owned. What changed is how that schedule enters the Model lifecycle:

flowchart TD
    FORWARD["AFD Model.forward"] --> MODE{"Async CAM active?"}
    MODE -- "No" --> NATIVE["Native Model.forward"]
    MODE -- "Yes" --> ASYNC["NPU async CAM helper"]
    ASYNC --> SCHEDULE["Existing cross-layer / dual-ubatch schedule"]
    SCHEDULE --> STAGES["Native stage modules and AFD send/receive"]
Loading

The Model override is a thin dispatcher. Synchronous execution never enters
the async Attention fragment. The NPU helper continues to own ubatch metadata,
cross-layer scheduling, PP first/last-rank handling, and ForwardContext
installation/restoration without forcing the synchronous path to maintain a
second Model/Decoder forward.

Validation status and remaining work

Validation layer Status Evidence and boundary
Static contract Passed Native inheritance, exact signatures, constructor drift digest, no copied synchronous forward/loader loop, native gate path, and alias-only registration
CPU focused tests Passed Role inventory, V3.2 allocation, role filter, one-shot iterator, native QKV loading, send/yield/receive, Dense scaling, PP boundaries, and ForwardContext restoration
GPU synchronous path Passed DeepSeek V2 Lite eager and graph execution with matching output
NPU basic path Pending post-refactor hardware evidence Synchronous Attention/FFN execution on the pinned Ascend stack
NPU specialized paths Pending Attention-side gate, mix-placement/shared experts, W8A8, EPLB/redundant experts, and real dual-ubatch async CAM
Future architecture work Out of scope Complete exact-version adapter isolation, next-version upgrade proof, sequence-parallel MoE, and runtime-patch governance

The static, CPU, and GPU results establish the new ownership and synchronous
execution contracts. They do not prove NPU fused MoE, quantization, or real
async communication behavior. Issue #143 should not claim complete NPU
validation until the pending hardware paths have recorded evidence.

@jiaran-king
jiaran-king force-pushed the codex/issue143-refactor branch from c891e66 to 8e2216f Compare July 24, 2026 03:04

@jiangkuaixue123 jiangkuaixue123 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.

Please remove the unnecessary harness artifacts from this PR.

Comment thread issue143-harness/.gitignore Outdated
@jiaran-king
jiaran-king marked this pull request as ready for review July 25, 2026 07:05
Comment thread afd_plugin/model_executor/models/deepseek_v2.py Outdated
@jiaran-king
jiaran-king force-pushed the codex/issue143-refactor branch from 8e2216f to 3db61ea Compare July 25, 2026 07:50

Copy link
Copy Markdown
Collaborator

Nice work — the current structure is much simpler and cleaner than before.

One idea for consideration: since the expert components currently use either SharedFusedMoE or FusedMoE, could we introduce an AFDFusedMoE wrapper or subclass that encapsulates the AFD send/receive logic on the Attention side, and then use it in place of the original model's SharedFusedMoE or FusedMoE?

I'm not sure yet whether this is technically feasible, especially given the backend-specific behavior, but if it works, it could be reused across other models and significantly reduce the amount of model-specific adaptation required. It may be worth doing a quick feasibility check first.

Signed-off-by: zzh <jiaranran2@gmail.com>
@jiaran-king
jiaran-king force-pushed the codex/issue143-refactor branch from 3db61ea to 88fb906 Compare July 25, 2026 08:06
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