[apply][2/2] Fused qk_norm_rope for Qwen3-MoE (#13998)
Co-authored-by: luoyuan.luo <luoyuan.luo@antgroup.com>
This commit is contained in:
@@ -18,10 +18,12 @@
|
|||||||
"""Inference-only Qwen3MoE model compatible with HuggingFace weights."""
|
"""Inference-only Qwen3MoE model compatible with HuggingFace weights."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
import math
|
||||||
|
from typing import Any, Dict, Iterable, List, Optional, Tuple, TypeVar
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from torch import nn
|
from torch import nn
|
||||||
|
from transformers import PretrainedConfig
|
||||||
|
|
||||||
from sglang.srt.distributed import (
|
from sglang.srt.distributed import (
|
||||||
get_moe_expert_parallel_world_size,
|
get_moe_expert_parallel_world_size,
|
||||||
@@ -73,6 +75,13 @@ from sglang.srt.utils import (
|
|||||||
is_npu,
|
is_npu,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_is_cuda = is_cuda()
|
||||||
|
|
||||||
|
if _is_cuda:
|
||||||
|
from sgl_kernel import fused_qk_norm_rope
|
||||||
|
|
||||||
|
TConfig = TypeVar("TConfig", bound=PretrainedConfig)
|
||||||
|
|
||||||
Qwen3MoeConfig = None
|
Qwen3MoeConfig = None
|
||||||
|
|
||||||
_is_flashinfer_available = is_flashinfer_available()
|
_is_flashinfer_available = is_flashinfer_available()
|
||||||
@@ -85,6 +94,118 @@ if _is_npu:
|
|||||||
from sgl_kernel_npu.norm.split_qkv_rmsnorm_rope import split_qkv_rmsnorm_rope
|
from sgl_kernel_npu.norm.split_qkv_rmsnorm_rope import split_qkv_rmsnorm_rope
|
||||||
|
|
||||||
|
|
||||||
|
def compute_yarn_parameters(
|
||||||
|
config: PretrainedConfig,
|
||||||
|
) -> tuple[float, float, float, float]:
|
||||||
|
"""
|
||||||
|
Refer to https://github.com/huggingface/transformers/blob/main/src/transformers/modeling_rope_utils.py#L197C1-L288C1
|
||||||
|
Computes the inverse frequencies with NTK scaling. Please refer to the
|
||||||
|
[original paper](https://huggingface.co/papers/2309.00071)
|
||||||
|
Args:
|
||||||
|
config ([`~transformers.PretrainedConfig`]):
|
||||||
|
The model configuration.
|
||||||
|
Returns:
|
||||||
|
factor: float, the scaling factor for the RoPE embeddings
|
||||||
|
low: float, the lower bound of the dimension range
|
||||||
|
high: float, the upper bound of the dimension range
|
||||||
|
attention_factor: float, the post-processing scaling factor applied to the computed cos/sin
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The config does not contain rope_scaling, which means the model is not using yarn
|
||||||
|
rope_scaling = getattr(config, "rope_scaling", None)
|
||||||
|
if rope_scaling is None:
|
||||||
|
return 1.0, 0, 0, 1.0
|
||||||
|
|
||||||
|
base = config.rope_theta
|
||||||
|
partial_rotary_factor = (
|
||||||
|
config.partial_rotary_factor
|
||||||
|
if hasattr(config, "partial_rotary_factor")
|
||||||
|
else 1.0
|
||||||
|
)
|
||||||
|
head_dim = getattr(
|
||||||
|
config, "head_dim", config.hidden_size // config.num_attention_heads
|
||||||
|
)
|
||||||
|
dim = int(head_dim * partial_rotary_factor)
|
||||||
|
factor = getattr(rope_scaling, "factor", 1.0)
|
||||||
|
attention_factor = rope_scaling.get("attention_factor")
|
||||||
|
mscale = rope_scaling.get("mscale")
|
||||||
|
mscale_all_dim = rope_scaling.get("mscale_all_dim")
|
||||||
|
|
||||||
|
if "original_max_position_embeddings" in rope_scaling:
|
||||||
|
original_max_position_embeddings = rope_scaling[
|
||||||
|
"original_max_position_embeddings"
|
||||||
|
]
|
||||||
|
factor = config.max_position_embeddings / original_max_position_embeddings
|
||||||
|
else:
|
||||||
|
original_max_position_embeddings = config.max_position_embeddings
|
||||||
|
|
||||||
|
def get_mscale(scale, mscale=1):
|
||||||
|
if scale <= 1:
|
||||||
|
return 1.0
|
||||||
|
return 0.1 * mscale * math.log(scale) + 1.0
|
||||||
|
|
||||||
|
# Sets the attention factor as suggested in the paper
|
||||||
|
if attention_factor is None:
|
||||||
|
if mscale and mscale_all_dim:
|
||||||
|
attention_factor = float(
|
||||||
|
get_mscale(factor, mscale) / get_mscale(factor, mscale_all_dim)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
attention_factor = get_mscale(factor)
|
||||||
|
|
||||||
|
# Optional config options
|
||||||
|
# beta_fast/beta_slow: as suggested in the paper, default to 32/1 (correspondingly)
|
||||||
|
beta_fast = rope_scaling.get("beta_fast") or 32
|
||||||
|
beta_slow = rope_scaling.get("beta_slow") or 1
|
||||||
|
|
||||||
|
# Compute the inverse frequencies
|
||||||
|
def find_correction_dim(num_rotations, dim, base, max_position_embeddings):
|
||||||
|
"""Inverse dimension formula to find the dimension based on the number of rotations"""
|
||||||
|
return (
|
||||||
|
dim * math.log(max_position_embeddings / (num_rotations * 2 * math.pi))
|
||||||
|
) / (2 * math.log(base))
|
||||||
|
|
||||||
|
def find_correction_range(
|
||||||
|
low_rot, high_rot, dim, base, max_position_embeddings, truncate
|
||||||
|
):
|
||||||
|
"""Find dimension range bounds based on rotations"""
|
||||||
|
low = find_correction_dim(low_rot, dim, base, max_position_embeddings)
|
||||||
|
high = find_correction_dim(high_rot, dim, base, max_position_embeddings)
|
||||||
|
if truncate:
|
||||||
|
low = math.floor(low)
|
||||||
|
high = math.ceil(high)
|
||||||
|
return max(low, 0), min(high, dim - 1)
|
||||||
|
|
||||||
|
truncate = rope_scaling.get("truncate", True)
|
||||||
|
low, high = find_correction_range(
|
||||||
|
beta_fast, beta_slow, dim, base, original_max_position_embeddings, truncate
|
||||||
|
)
|
||||||
|
|
||||||
|
# These parts are implemented in the fusedQKNormRopeKernel.cu
|
||||||
|
# # def linear_ramp_factor(min, max, dim):
|
||||||
|
# # if min == max:
|
||||||
|
# # max += 0.001 # Prevent singularity
|
||||||
|
|
||||||
|
# # linear_func = (torch.arange(dim, dtype=torch.float32) - min) / (max - min)
|
||||||
|
# # ramp_func = torch.clamp(linear_func, 0, 1)
|
||||||
|
# # return ramp_func
|
||||||
|
|
||||||
|
# # Note on variable naming: "interpolation" comes from the original technique, where we interpolate the position IDs
|
||||||
|
# # to expand the possible context length. In other words, interpolation = apply scaling factor.
|
||||||
|
# # pos_freqs = base ** (torch.arange(0, dim, 2).to(device=device, dtype=torch.float) / dim)
|
||||||
|
# # inv_freq_extrapolation = 1.0 / pos_freqs
|
||||||
|
# # inv_freq_interpolation = 1.0 / (factor * pos_freqs)
|
||||||
|
|
||||||
|
# # # Get n-dimensional rotational scaling corrected for extrapolation
|
||||||
|
# # inv_freq_extrapolation_factor = 1 - linear_ramp_factor(low, high, dim // 2).to(device=device, dtype=torch.float)
|
||||||
|
# # inv_freq = (
|
||||||
|
# # inv_freq_interpolation * (1 - inv_freq_extrapolation_factor)
|
||||||
|
# # + inv_freq_extrapolation * inv_freq_extrapolation_factor
|
||||||
|
# # )
|
||||||
|
# # return inv_freq, attention_factor
|
||||||
|
return factor, low, high, attention_factor
|
||||||
|
|
||||||
|
|
||||||
class Qwen3MoeSparseMoeBlock(nn.Module):
|
class Qwen3MoeSparseMoeBlock(nn.Module):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -286,6 +407,7 @@ class Qwen3MoeAttention(nn.Module):
|
|||||||
head_dim: Optional[int] = None,
|
head_dim: Optional[int] = None,
|
||||||
rms_norm_eps: float = 1e-06,
|
rms_norm_eps: float = 1e-06,
|
||||||
attention_bias: bool = False,
|
attention_bias: bool = False,
|
||||||
|
config: Optional[TConfig] = None,
|
||||||
quant_config: Optional[QuantizationConfig] = None,
|
quant_config: Optional[QuantizationConfig] = None,
|
||||||
prefix: str = "",
|
prefix: str = "",
|
||||||
dual_chunk_attention_config: Optional[dict[str, Any]] = None,
|
dual_chunk_attention_config: Optional[dict[str, Any]] = None,
|
||||||
@@ -297,6 +419,7 @@ class Qwen3MoeAttention(nn.Module):
|
|||||||
attn_tp_rank = get_attention_tp_rank()
|
attn_tp_rank = get_attention_tp_rank()
|
||||||
attn_tp_size = get_attention_tp_size()
|
attn_tp_size = get_attention_tp_size()
|
||||||
|
|
||||||
|
self.config = config
|
||||||
self.total_num_heads = num_heads
|
self.total_num_heads = num_heads
|
||||||
assert self.total_num_heads % attn_tp_size == 0
|
assert self.total_num_heads % attn_tp_size == 0
|
||||||
self.num_heads = self.total_num_heads // attn_tp_size
|
self.num_heads = self.total_num_heads // attn_tp_size
|
||||||
@@ -352,6 +475,14 @@ class Qwen3MoeAttention(nn.Module):
|
|||||||
self.compatible_with_fused_kv_buffer = (
|
self.compatible_with_fused_kv_buffer = (
|
||||||
False if isinstance(self.rotary_emb, MRotaryEmbedding) else True
|
False if isinstance(self.rotary_emb, MRotaryEmbedding) else True
|
||||||
)
|
)
|
||||||
|
self.compatible_with_fused_qk_norm_rope = (
|
||||||
|
not isinstance(self.rotary_emb, MRotaryEmbedding)
|
||||||
|
) and self.head_dim in (64, 128, 256)
|
||||||
|
self.use_fused_qk_norm_rope = (
|
||||||
|
get_global_server_args().enable_fused_qk_norm_rope
|
||||||
|
and self.compatible_with_fused_qk_norm_rope
|
||||||
|
)
|
||||||
|
self._used_fused_qk_norm_rope_last_call = False
|
||||||
|
|
||||||
self.attn = RadixAttention(
|
self.attn = RadixAttention(
|
||||||
self.num_heads,
|
self.num_heads,
|
||||||
@@ -379,6 +510,9 @@ class Qwen3MoeAttention(nn.Module):
|
|||||||
k_by_head = k.reshape(-1, self.head_dim)
|
k_by_head = k.reshape(-1, self.head_dim)
|
||||||
k_by_head = self.k_norm(k_by_head)
|
k_by_head = self.k_norm(k_by_head)
|
||||||
current_stream.wait_stream(self.alt_stream)
|
current_stream.wait_stream(self.alt_stream)
|
||||||
|
q = q_by_head.view(q.shape)
|
||||||
|
k = k_by_head.view(k.shape)
|
||||||
|
return q, k
|
||||||
else:
|
else:
|
||||||
q_by_head = q.reshape(-1, self.head_dim)
|
q_by_head = q.reshape(-1, self.head_dim)
|
||||||
q_by_head = self.q_norm(q_by_head)
|
q_by_head = self.q_norm(q_by_head)
|
||||||
@@ -433,6 +567,41 @@ class Qwen3MoeAttention(nn.Module):
|
|||||||
forward_batch: ForwardBatch,
|
forward_batch: ForwardBatch,
|
||||||
):
|
):
|
||||||
qkv, _ = self.qkv_proj(hidden_states)
|
qkv, _ = self.qkv_proj(hidden_states)
|
||||||
|
|
||||||
|
q, k, v = self.apply_qk_norm_rope(qkv, positions, forward_batch)
|
||||||
|
|
||||||
|
inner_state = q, k, v, forward_batch
|
||||||
|
return None, forward_batch, inner_state
|
||||||
|
|
||||||
|
def apply_qk_norm_rope(self, qkv, positions, forward_batch):
|
||||||
|
use_fused = self.use_fused_qk_norm_rope and qkv.dtype == torch.bfloat16
|
||||||
|
if use_fused:
|
||||||
|
theta = getattr(self.config, "rope_theta", 10000.0)
|
||||||
|
positions = (
|
||||||
|
positions.view(-1).to(dtype=torch.int32, device=qkv.device).contiguous()
|
||||||
|
)
|
||||||
|
factor, low, high, attention_factor = compute_yarn_parameters(self.config)
|
||||||
|
fused_qk_norm_rope(
|
||||||
|
qkv,
|
||||||
|
self.num_heads,
|
||||||
|
self.num_kv_heads,
|
||||||
|
self.num_kv_heads,
|
||||||
|
self.head_dim,
|
||||||
|
self.q_norm.variance_epsilon,
|
||||||
|
self.q_norm.weight,
|
||||||
|
self.k_norm.weight,
|
||||||
|
theta,
|
||||||
|
self.rotary_emb.is_neox_style,
|
||||||
|
positions,
|
||||||
|
factor,
|
||||||
|
low,
|
||||||
|
high,
|
||||||
|
attention_factor,
|
||||||
|
)
|
||||||
|
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
|
||||||
|
self._used_fused_qk_norm_rope_last_call = True
|
||||||
|
else:
|
||||||
|
# Fallback to non-fused QK Norm & RoPE implementation
|
||||||
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
|
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
|
||||||
q, k = self._apply_qk_norm(q, k)
|
q, k = self._apply_qk_norm(q, k)
|
||||||
q, k = self.rotary_emb(
|
q, k = self.rotary_emb(
|
||||||
@@ -450,9 +619,8 @@ class Qwen3MoeAttention(nn.Module):
|
|||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
self._used_fused_qk_norm_rope_last_call = False
|
||||||
inner_state = q, k, v, forward_batch
|
return q, k, v
|
||||||
return None, forward_batch, inner_state
|
|
||||||
|
|
||||||
def forward_prepare(
|
def forward_prepare(
|
||||||
self,
|
self,
|
||||||
@@ -482,15 +650,17 @@ class Qwen3MoeAttention(nn.Module):
|
|||||||
|
|
||||||
q, k, v, fb = inner_state
|
q, k, v, fb = inner_state
|
||||||
|
|
||||||
|
must_save_kv = self._used_fused_qk_norm_rope_last_call
|
||||||
|
save_kv_cache = must_save_kv or not (
|
||||||
|
enable_fused_set_kv_buffer(forward_batch)
|
||||||
|
and self.compatible_with_fused_kv_buffer
|
||||||
|
)
|
||||||
attn_output = self.attn(
|
attn_output = self.attn(
|
||||||
q,
|
q,
|
||||||
k,
|
k,
|
||||||
v,
|
v,
|
||||||
fb,
|
fb,
|
||||||
save_kv_cache=not (
|
save_kv_cache=save_kv_cache,
|
||||||
enable_fused_set_kv_buffer(forward_batch)
|
|
||||||
and self.compatible_with_fused_kv_buffer
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
output, _ = self.o_proj(attn_output)
|
output, _ = self.o_proj(attn_output)
|
||||||
return output
|
return output
|
||||||
@@ -543,6 +713,7 @@ class Qwen3MoeDecoderLayer(nn.Module):
|
|||||||
head_dim=head_dim,
|
head_dim=head_dim,
|
||||||
rms_norm_eps=rms_norm_eps,
|
rms_norm_eps=rms_norm_eps,
|
||||||
attention_bias=attention_bias,
|
attention_bias=attention_bias,
|
||||||
|
config=config,
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
prefix=add_prefix("self_attn", prefix),
|
prefix=add_prefix("self_attn", prefix),
|
||||||
dual_chunk_attention_config=dual_chunk_attention_config,
|
dual_chunk_attention_config=dual_chunk_attention_config,
|
||||||
|
|||||||
@@ -542,6 +542,7 @@ class ServerArgs:
|
|||||||
enable_attn_tp_input_scattered: bool = False
|
enable_attn_tp_input_scattered: bool = False
|
||||||
# Context parallelism used in the long sequence prefill phase of DeepSeek v3.2
|
# Context parallelism used in the long sequence prefill phase of DeepSeek v3.2
|
||||||
enable_nsa_prefill_context_parallel: bool = False
|
enable_nsa_prefill_context_parallel: bool = False
|
||||||
|
enable_fused_qk_norm_rope: bool = False
|
||||||
|
|
||||||
# Dynamic batch tokenizer
|
# Dynamic batch tokenizer
|
||||||
enable_dynamic_batch_tokenizer: bool = False
|
enable_dynamic_batch_tokenizer: bool = False
|
||||||
@@ -3738,6 +3739,11 @@ class ServerArgs:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Enable context parallelism used in the long sequence prefill phase of DeepSeek v3.2.",
|
help="Enable context parallelism used in the long sequence prefill phase of DeepSeek v3.2.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--enable-fused-qk-norm-rope",
|
||||||
|
action="store_true",
|
||||||
|
help="Enable fused qk normalization and rope rotary embedding.",
|
||||||
|
)
|
||||||
|
|
||||||
# Dynamic batch tokenizer
|
# Dynamic batch tokenizer
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|||||||
Reference in New Issue
Block a user