Skip to content
团子云技术 Lite 1.048596
Go back

Qwen3.5 推理全流程解析:基于 vLLM 源码的混合架构逐层拆解

团团虾声明:基于 vLLM 源码中 qwen3_5.pyqwen3_next.pygdn_linear_attn.py 的实现,逐层拆解 Qwen3.5 的混合注意力架构与推理计算流程。

Qwen3.5 推理全流程:从提示到输出的逐层计算解析

基于 vLLM 源码 (vllm/model_executor/models/qwen3_5.py, qwen3_next.py, gdn_linear_attn.py) 分析


总体架构概览

Qwen3.5 是一个 Hybrid(混合)架构 模型,核心特点是将 全注意力(Full Attention)门控增量网络线性注意力(Gated Delta Net Linear Attention) 交替混合使用。同时支持 Dense MLP 和 Sparse MoE 两种 FFN 变体。

graph TD
    A["Input Token IDs"] --> B["Embedding Layer"]
    B --> C["Decoder Layer 0"]
    C --> D["Decoder Layer 1"]
    D --> E["..."]
    E --> F["Decoder Layer N-1"]
    F --> G["Final RMSNorm"]
    G --> H["LM Head (Linear)"]
    H --> I["LogitsProcessor"]
    I --> J["Output Token Probabilities"]

    style C fill:#2d5aa0,color:#fff
    style D fill:#8b4513,color:#fff
    style F fill:#2d5aa0,color:#fff

其中每个 Decoder Layer 的注意力类型由 config.layer_types 列表逐层指定:


阶段一:输入嵌入 (Embedding)

入口: Qwen3_5Model.forward()embed_input_ids()

input_ids: [batch_size, seq_len] (int64)

VocabParallelEmbedding(vocab_size, hidden_size)

hidden_states: [batch_size × seq_len, hidden_size] (bf16/fp16)

计算:查表操作,将每个 token ID 映射为 hidden_size 维的稠密向量。


阶段二:Decoder 层堆叠

每个 Decoder Layer 的结构相同,但注意力模块根据层类型不同而不同。

整体流程(Pre-Norm 架构 + 残差连接):

graph TD
    IN["hidden_states, residual"] --> LN1["Input LayerNorm (GemmaRMSNorm)"]
    LN1 --> ATN{"layer_type?"}
    ATN -->|full_attention| FA["Full Attention"]
    ATN -->|linear_attention| LA["GatedDeltaNet Linear Attention"]
    FA --> LS1["Attention Layer Scale (可选)"]
    LA --> LS1
    LS1 --> LN2["Post-Attention LayerNorm (GemmaRMSNorm)"]
    LN2 --> MLP{"MLP 类型?"}
    MLP -->|Dense| DMLP["Dense MLP"]
    MLP -->|MoE| MOE["Sparse MoE Block"]
    DMLP --> LS2["FFN Layer Scale (可选)"]
    MOE --> LS2
    LS2 --> OUT["hidden_states, residual"]

源码:qwen3_next.py:393-450


2.1 Input LayerNorm (GemmaRMSNorm)

与标准 RMSNorm 的两点区别

  1. 权重使用 x * (1 + w) 而非 x * w(权重初始化为 0,而非 1)
  2. 类型转换顺序不同:(x * w).to(orig_dtype) 而非 x.to(orig_dtype) * w

数学公式

GemmaRMSNorm(x)=xmean(x2)+ϵ(1+w)\text{GemmaRMSNorm}(x) = \frac{x}{\sqrt{\text{mean}(x^2) + \epsilon}} \cdot (1 + w)

首层特殊处理residual is None):

residual = hidden_states          # 保存残差
hidden_states = layernorm(hidden_states)  # 仅归一化

后续层(fused add + norm):

hidden_states = hidden_states + residual  # 加残差
residual = hidden_states                  # 更新残差
hidden_states = layernorm(hidden_states)  # 归一化

源码:layernorm.py:354-401


2.2A Full Attention 层 (Qwen3NextAttention)

layer_type == "full_attention" 时执行。这是标准的 Transformer 自注意力,但包含多个 Qwen3.5 特有的改进。

源码:qwen3_next.py:197-312

Step 1: QKV 投影

qkv, _ = self.qkv_proj(hidden_states)
# hidden_states: [num_tokens, hidden_size]
# qkv: [num_tokens, (num_heads*2 + num_kv_heads*2) * head_dim]
#       当 attn_output_gate=True 时 Q 的通道数翻倍(含 gate)

特殊点attn_output_gate=True(默认)时,Q 投影的输出维度翻倍,额外部分作为输出门控。

Step 2: 拆分 Q/Gate/K/V

# attn_output_gate=True:
q_gate, k, v = qkv.split([q_size*2, kv_size, kv_size], dim=-1)
q, gate = torch.chunk(q_gate, 2, dim=-1)  # 各 [num_tokens, q_size]

# attn_output_gate=False:
q, k, v = qkv.split([q_size, kv_size, kv_size], dim=-1)

Step 3: QK-Norm (GemmaRMSNorm per head)

对每个头单独做 RMSNorm(防止注意力 logit 爆炸):

qh=GemmaRMSNorm(qh),kh=GemmaRMSNorm(kh)q_h = \text{GemmaRMSNorm}(q_h), \quad k_h = \text{GemmaRMSNorm}(k_h)

q = self.q_norm(q.view(-1, num_heads, head_dim)).view(-1, q_size)
k = self.k_norm(k.view(-1, num_kv_heads, head_dim)).view(-1, kv_size)

Step 4: RoPE 旋转位置编码

q, k = self.rotary_emb(positions, q, k)

对 Q 和 K 施加旋转位置编码,使模型获得位置感知能力。

Step 5: Scaled Dot-Product Attention

Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V

attn_output = self.attn(q, k, v)  # 含 KV Cache 管理

vLLM 的 Attention 层内部处理:

Step 6: 输出门控 (Output Gate)

if self.attn_output_gate:
    gate = torch.sigmoid(gate)         # σ(gate)
    attn_output = attn_output * gate   # 逐元素门控

output=Attn(Q,K,V)σ(G)\text{output} = \text{Attn}(Q,K,V) \odot \sigma(G)

这是 Qwen3.5 的关键创新之一:对注意力输出施加可学习的 sigmoid 门控。

Step 7: 输出投影

output[:], _ = self.o_proj(attn_output)
# o_proj: RowParallelLinear(num_heads * head_dim → hidden_size)

2.2B GatedDeltaNet 线性注意力层

layer_type == "linear_attention" 时执行。这是 Qwen3.5 的核心创新——一种基于状态空间模型思想的线性注意力机制,用递推状态矩阵替代 KV Cache。

源码:gdn_linear_attn.py:214-595

整体计算流 (forward_cuda)

graph TD
    H["hidden_states"] --> P1["输入投影 in_proj_qkvz"]
    H --> P2["输入投影 in_proj_ba"]
    P1 --> S["拆分 Q, K, V, Z"]
    P2 --> S2["拆分 B, A"]
    S --> CONV["因果卷积 Conv1D"]
    S2 --> CORE["核心注意力计算"]
    CONV --> CORE
    CORE --> NORM["RMSNormGated(attn_out, z)"]
    NORM --> OUT["输出投影 out_proj"]

Step 1: 输入投影

# 投影 1: 生成 Q, K, V, Z
mixed_qkvz, _ = self.in_proj_qkvz(hidden_states)
# hidden_states: [num_tokens, hidden_size]
# mixed_qkvz: [num_tokens, key_dim*2 + value_dim*2]

# 拆分
mixed_qkv, z = mixed_qkvz.split([qkv_size, z_size], dim=-1)
# mixed_qkv: [num_tokens, key_dim*2 + value_dim]  (用于 Q, K, V)
# z: [num_tokens, value_dim]  (用于输出门控)

# 投影 2: 生成 B, A(控制遗忘和写入)
ba, _ = self.in_proj_ba(hidden_states)
b, a = ba.chunk(2, dim=-1)  # 各 [num_tokens, num_v_heads]

各投影的含义:

投影维度作用
Qkey_dim查询向量,用于读取状态
Kkey_dim键向量,用于写入状态
Vvalue_dim值向量,写入的内容
Zvalue_dim输出门控信号
Bnum_v_heads写入门控(beta = σ(B))
Anum_v_heads遗忘门控(与 A_log, dt_bias 结合)

Step 2: 因果卷积 (Causal Conv1D)

mixed_qkv 沿序列维度做短卷积(kernel_size 通常为 4),引入局部上下文:

# Prefill: 对整个序列做因果卷积
mixed_qkv = causal_conv1d_fn(mixed_qkv, conv_weights, bias, activation="silu",
                              conv_states=conv_state, ...)

# Decode: 仅更新单步(从 conv_state 缓存读取历史)
mixed_qkv = causal_conv1d_update(mixed_qkv, conv_state, conv_weights, bias, "silu", ...)

关键点:卷积状态 conv_state 持久化保存,类似 KV Cache 但体积极小(仅 kernel_size - 1 步)。

Step 3: 后卷积预处理 (fused_post_conv_prep)

将卷积输出和门控信号组合,生成核心注意力所需的全部输入:

q, k, v, g, beta = fused_post_conv_prep(
    conv_output=mixed_qkv, a=a, b=b,
    A_log=self.A_log, dt_bias=self.dt_bias, ...
)

各输出的计算:

Step 4: 核心递推注意力

这是 GatedDeltaNet 的核心。与标准注意力不同,它维护一个递推状态矩阵 SRdk×dvS \in \mathbb{R}^{d_k \times d_v}

Prefill(分块计算)

output, final_state = chunk_gated_delta_rule(
    q, k, v, g, beta, initial_state, ...)

Decode(逐步递推)

output, final_state = fused_sigmoid_gating_delta_rule_update(
    A_log, a, b, dt_bias, q, k, v, initial_state, ...)

递推公式(每个时间步 t,每个头):

St=egt遗忘St1+βt写入门(vtktTSt1ktktT)S_t = \underbrace{e^{g_t}}_{\text{遗忘}} \cdot S_{t-1} + \underbrace{\beta_t}_{\text{写入门}} \cdot (v_t k_t^T - S_{t-1} k_t k_t^T)

ot=Stqt1dko_t = S_t \cdot q_t \cdot \frac{1}{\sqrt{d_k}}

其中:

与标准注意力对比

Full AttentionGatedDeltaNet
复杂度O(n2d)O(n^2 d)O(ndkdv)O(n d_k d_v)(线性)
状态KV Cache 随序列线性增长固定大小矩阵 dk×dvd_k \times d_v
信息整合显式查询所有历史递推压缩到状态矩阵

Step 5: 输出归一化与门控

# RMSNormGated: norm(attn_out) * activation(z)
core_attn_out = self.norm(core_attn_out, z)

output=RMSNorm(attn_out)act(z)\text{output} = \text{RMSNorm}(\text{attn\_out}) \odot \text{act}(z)

其中 act 可以是 silusigmoid(由 config.output_gate_type 决定)。

源码:layernorm.py:404-519

Step 6: 输出投影

output[:num_tokens], _ = self.out_proj(core_attn_out)
# out_proj: RowParallelLinear(value_dim → hidden_size)

2.3 Attention Layer Scale(可选)

config.layer_scale = True 时,对注意力输出施加可学习的缩放:

h=h(αattn+1)h = h \cdot (\alpha_{\text{attn}} + 1)

其中 αattn\alpha_{\text{attn}} 初始化为 0(即初始时为恒等变换),训练中逐渐学习合适的缩放。

if self.layer_scale:
    hidden_states = hidden_states * (self.attn_layer_scale + 1)

源码:qwen3_next.py:422-430


2.4 Post-Attention LayerNorm

与 2.1 相同的 GemmaRMSNorm,执行 fused add + norm:

hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
# hidden_states = GemmaRMSNorm(attn_output + residual)
# residual = attn_output + residual(更新后)

2.5 MLP / MoE 前馈网络

2.5A Dense MLP(qwen3_5_text 模型)

使用 SwiGLU 激活的标准 FFN:

gate_up, _ = self.gate_up_proj(x)      # [num_tokens, intermediate_size * 2]
out = self.act_fn(gate_up)             # SiLU(gate) * up = SwiGLU
out, _ = self.down_proj(out)           # [num_tokens, hidden_size]

数学公式

MLP(x)=Wdown[SiLU(Wgatex)(Wupx)]\text{MLP}(x) = W_{\text{down}} \cdot [\text{SiLU}(W_{\text{gate}} \cdot x) \odot (W_{\text{up}} \cdot x)]

源码:qwen2_moe.py:77-122

2.5B Sparse MoE(qwen3_5_moe_text 模型)

graph LR
    X["hidden_states"] --> GATE["Gate Linear"]
    GATE --> TOP["Top-K 选择"]
    X --> EXP["N 个 Expert MLP"]
    TOP --> EXP
    EXP --> AGG["加权聚合"]
    X --> SE["Shared Expert MLP"]
    SE --> SEG["Shared Expert Gate (sigmoid)"]
    SEG --> ADD["相加"]
    AGG --> ADD
    ADD --> OUT["output"]

计算流程

# 1. 路由器计算
router_logits, _ = self.gate(hidden_states)  # [num_tokens, num_experts]

# 2. FusedMoE: Top-K 选择 + 专家计算 + 加权聚合 + 共享专家
output = self.experts(hidden_states, router_logits)

数学公式

MoE(x)=iTopKwiExperti(x)+σ(g(x))SharedExpert(x)\text{MoE}(x) = \sum_{i \in \text{TopK}} w_i \cdot \text{Expert}_i(x) + \sigma(g(x)) \cdot \text{SharedExpert}(x)

其中 wi=softmax(TopK(gate(x)))iw_i = \text{softmax}(\text{TopK}(\text{gate}(x)))_i

源码:qwen3_next.py:84-194


2.6 FFN Layer Scale(可选)

与 2.3 类似:

h=h(αffn+1)h = h \cdot (\alpha_{\text{ffn}} + 1)

if self.layer_scale:
    hidden_states = hidden_states * (self.ffn_layer_scale + 1)

阶段三:最终归一化

所有 Decoder 层执行完毕后,对最后的 hidden_states 做一次最终的 GemmaRMSNorm:

hidden_states, _ = self.norm(hidden_states, residual)
# 即: hidden_states = GemmaRMSNorm(last_layer_output + residual)

源码:qwen3_next.py:531


阶段四:LM Head + Logits 计算

# 1. 线性投影到词表空间
logits = self.lm_head(hidden_states)
# lm_head: ParallelLMHead(hidden_size → vocab_size)
# logits: [num_tokens, vocab_size]

# 2. LogitsProcessor 后处理
logits = self.logits_processor(self.lm_head, hidden_states)

如果 tie_word_embeddings=True,则 lm_head 直接复用 embed_tokens 的权重矩阵。

源码:qwen3_5.py:497-542


阶段五:采样输出

logits 经过温度缩放、Top-K/Top-P 等采样策略后,得到下一个 token 的 ID。这部分由 vLLM 的 Sampler 处理,不在模型代码内。


完整单层计算总结(伪代码)

def decoder_layer_forward(hidden_states, residual, positions):
    # ========== Pre-Norm ==========
    if residual is None:
        residual = hidden_states
        hidden_states = GemmaRMSNorm(hidden_states)
    else:
        tmp = hidden_states + residual
        residual = tmp
        hidden_states = GemmaRMSNorm(tmp)

    # ========== Attention ==========
    if layer_type == "full_attention":
        qkv = qkv_proj(hidden_states)
        q, gate, k, v = split(qkv)        # gate 仅当 attn_output_gate=True
        q = GemmaRMSNorm_per_head(q)       # QK-Norm
        k = GemmaRMSNorm_per_head(k)
        q, k = RoPE(positions, q, k)       # 旋转位置编码
        attn_out = FlashAttention(q, k, v)  # + KV Cache
        attn_out = attn_out * sigmoid(gate) # 输出门控
        hidden_states = o_proj(attn_out)

    elif layer_type == "linear_attention":
        qkvz = in_proj_qkvz(hidden_states)
        ba = in_proj_ba(hidden_states)
        qkv, z = split(qkvz)
        b, a = split(ba)
        qkv = CausalConv1D(qkv, conv_state)     # 因果卷积
        q, k, v = split(qkv)
        q, k = L2Norm(q), L2Norm(k)              # L2 归一化
        g = -exp(A_log) * softplus(a + dt_bias)  # 遗忘门
        beta = sigmoid(b)                         # 写入门
        attn_out = GatedDeltaRule(q, k, v, g, beta, ssm_state)  # 递推
        hidden_states = out_proj(RMSNormGated(attn_out, z))

    # ========== Layer Scale (Attention) ==========
    if layer_scale:
        hidden_states *= (attn_layer_scale + 1)

    # ========== Post-Norm ==========
    tmp = hidden_states + residual
    residual = tmp
    hidden_states = GemmaRMSNorm(tmp)

    # ========== FFN ==========
    if dense_model:
        hidden_states = SwiGLU_MLP(hidden_states)
    else:  # MoE
        router = gate(hidden_states)
        hidden_states = FusedMoE(hidden_states, router) + sigmoid_gate * SharedExpert(hidden_states)

    # ========== Layer Scale (FFN) ==========
    if layer_scale:
        hidden_states *= (ffn_layer_scale + 1)

    return hidden_states, residual

状态缓存对比

Qwen3.5 需要管理两套不同的状态缓存系统:

Full Attention 层Linear Attention 层
缓存类型KV CacheConv State + SSM State
缓存大小O(seq_len×d)O(\text{seq\_len} \times d),随序列增长O(dk×dv)O(d_k \times d_v) 固定 + O(d×w)O(d \times w) 固定
更新方式追加新 K/V递推更新状态矩阵
管理PagedAttentionMambaBase (conv_state + ssm_state)

这种混合设计使 Qwen3.5 在长序列推理时能节省显存(线性注意力层状态固定),同时通过全注意力层保持强大的全局建模能力。


Share this post on:

Previous Post
【转载】【美投晨报】川普持仓大公开!亚马逊冲三万亿!半导体和软件选谁?AI芯片新王登基?
Next Post
大模型的物理定律:Scaling Laws 从何而来,又往哪去