Info

根据 SGL 仓库的 qwen2.py 解析

QWen2 特性

  • 参数
    • Hidden Size: 3584
    • Attention Heads: 28
    • KV Heads: 4
  • GQA 机制
  • RoPE

QWen2 Attention 机制

  • Attention 过程
    1. QKV 投影(也就是乘以
      • 输入的 hidden_states 维度为 [batch_size * seq_len, hidden_size],通过 QKVParallelLinear 投影得到 QKV 向量。
      • hidden_states 的大小通常是 [batch * seq_len, hidden_size]
      • qkv 向量大小通常是 [batch * seq_len, Q + K + V]
        • 此处
    2. QKV 向量拆分
      • q_size, kv_size * 2
    3. 旋转位置编码 RoPE
      • self.rotary_emb 使用 vLLM 的 get_rope ,只对 Q 和 K 向量应用 RoPE,V 向量保持不变。
        • get_rope 的实现在 rotary_embedding.py
        • 在初始化的时候会初始化 cossin cache,避免每次前向传播时重复计算三角函数
        • 接下来就会应用旋转(1386-1390)
    4. 注意力计算与 KV Cache 使用
      • 初始化使用了 GQA 分组
        • total_num_kv_heads >= tp_size 时,KV 头在多个 GPU 间分区
        • total_num_kv_heads < tp_size 时,KV 头在多个 GPU 间复制
        • num_kv_heads = max(1, total_num_kv_heads // tp_size) 确保每个 GPU 至少有一个 KV 头
      • self.attn 使用 SGL 自带的 RadixAttention,并且区分
        • Prefill
        • Decode
          • 进入 FlashInferAttnBackend,首先获取当前层的 decode_wrapper
            • forward_metadata 存储了每层的元数据信息
            • _get_wrapper_idx(layer) 根据层号获取对应的 wrapper 索引
          • 确定 cache 位置
            • 如果是自注意力,使用 out_cache_loc
            • 如果是交叉注意力(比如 encoder-decoder 结构),使用 encoder_out_cache_loc
          • 存储当前 token 的 K、V 到缓存(set_kv_buffer
          • 使用 decode 包装器从完整的 KV 缓存中计算 attention
            • 将 Q 从 [1, hidden_size][1, num_heads*head_dim] reshape 成 [1, tp_q_head_num, head_dim] (tensor parallel 后的 Q head num)
    5. 输出投影
      • ……