CUDA Kernel 可视化解析 — 对 Query 和 Key 施加旋转位置编码
对 head 内的每一对元素 (x, y),施加一个角度为 θ 的 2D 旋转。θ 由 position 和维度下标共同决定(通过预计算的 cos/sin cache 查表)。
配对方式不同,但旋转公式完全相同。head_size 内只有前 rot_dim 个元素参与旋转,后面的元素不动。
NeoX:前半和后半配对。 arr[i] 和 arr[i + embed_dim] 组成旋转对。LLaMA、Qwen、Baichuan 等主流模型使用此方式。
GPT-J:相邻元素配对。 arr[2i] 和 arr[2i+1] 组成旋转对。GPT-J、CodeGen 使用此方式。
float4 向量化 Load(4 个 fp16 元素)恰好覆盖 2 个旋转对,cos/sin 只需加载 HalfVecSize。相比 NeoX 的两次 Load,Interleaved 的内存访问效率更高。
Partial Rotary:只旋转前 rotary_dim 维度。 head_dim 中只有前 rotary_dim 个元素参与 NeoX 配对旋转,后面的维度保持原值。Phi-3、GLM-4 使用此策略。
partial_rotary_factor=0.5)、GLM-4/4.1V、ChatGLM 系列。保留部分维度不受位置编码干扰,让模型学习纯内容相关的 attention pattern。
Fused NeoX:从 packed QKV 中拆分并同时旋转。 输入是 Q/K/V 拼在一起的 packed tensor,kernel 一次完成 QKV Split + NeoX RoPE,减少 global memory 读写。
float 精度,bf16/fp16 数据在运算前 cast 到 float,运算后 cast 回来。避免低精度累积误差。
for (int i = threadIdx.x; i < nq; i += blockDim.x)
— 每个线程从自己的 threadIdx 开始,每次跨 blockDim 步长,直到覆盖全部 nq 个旋转对。这意味着一个线程可能跨越多个 head。同一组 cos/sin(同一 position)被所有 head 共享。
当 rot_dim < head_size 时,head 的前 rot_dim 个元素参与旋转,后面的元素保持不动。常见于部分旋转模型。
GQA (Grouped-Query Attention) 下 Q heads 和 KV heads 数量不同。Variable-Length 模式支持 batch 内每条序列长度不等(padding_offset 压缩存储)。kernel 需要同时处理 Q 和 K 的旋转,并正确映射 GQA 的 head 分组。
GQAVariableLengthRotarySplitKernel(GPT-J 配对)和 GQAVariableLengthNeoxPartialRotarySplitKernel(NeoX 配对 + Partial Rotary)。两者共享 GQA + Variable-Length 的线程映射逻辑,区别仅在配对方式和是否有 pass-through 区域。
最高级的融合方案:在一个 kernel 内完成 RMSNorm 归一化 + QKV 拆分 + NeoX RoPE 旋转。消除所有中间 tensor 的 HBM 读写,将 3 次 kernel launch 合并为 1 次。
__shfl_xor_sync 在 warp 的 32 个 lane 内完成规约,无需 __syncthreads() 和 shared memory。5 轮 shuffle(log2(32)=5)即可完成全 warp 归约。这是 fused kernel 的效率核心——把本需 shared memory 的 norm 压缩到纯寄存器操作。
所有频率缩放方法都是正交于配对方式的。它们只修改预计算的 cos_sin_cache(改变 θ 的计算方式),kernel 本身的旋转逻辑完全不变。这是 RoPE 系统设计中非常重要的解耦。
cos_sin_cache 的预计算逻辑,kernel 的四种配对模式可以复用。这也是为什么 PaddlePaddle / vLLM 等框架把 RoPE kernel 和 frequency scaling 完全解耦的原因。
| 特性 | NeoX (Half-Split) | GPT-J (Interleaved) | Partial Rotary | Fused NeoX |
|---|---|---|---|---|
| 配对规则 | arr[i] ↔ arr[i+d/2] | arr[2i] ↔ arr[2i+1] | NeoX 方式 (仅前 rotary_dim) | NeoX 方式 (从 packed QKV) |
| 代表模型 | LLaMA, Qwen, Mistral, Baichuan | GPT-J, CodeGen | Phi-2/3, GLM-4, ChatGLM | PaddlePaddle 推理优化 |
| 旋转维度 | 全部 rot_dim | 全部 rot_dim | 前 rotary_dim, 后面 pass-through | 全部 head_dim (Q,K); V 不旋转 |
| cos/sin cache 读取 | 2 次 Load (前半+后半分开) | 1 次 Load (交错对内连续) | 2 次 Load (同 NeoX) | 2 次 Load (同 NeoX) |
| HBM 读写次数 | 1R + 1W (in-place) | 1R + 1W (in-place) | 1R + 1W (in-place/split) | 1R + 1W (含 QKV split) |
| 融合程度 | 仅 RoPE | 仅 RoPE | 仅 RoPE | QKV Split + RoPE (可+RMSNorm) |
| GQA 支持 | 需额外 kernel 或参数 | GQAVariableLengthRotarySplitKernel | GQAVariableLength...PartialRotary... | 通过 num_heads 参数 |
| Variable-Length | padding_offset | padding_offset + seq_lens | padding_offset + seq_lens | 通常固定长度 |
| 向量化策略 | VecSize 对齐 head_dim | float4 覆盖 2 个旋转对 | VecSize 对齐, 分段判断 | VecSize=4, 前后半分别 Load |