[diffusion] optimize: fuse cosmos qk norm, rope, and kv packing (#34275)
This commit is contained in:
@@ -31,6 +31,25 @@ struct QKNormRopeParams {
|
||||
float eps;
|
||||
};
|
||||
|
||||
struct QKNormRopePackKVParams : QKNormRopeParams {
|
||||
const void* __restrict__ v_ptr;
|
||||
const void* __restrict__ k_prefix_ptr;
|
||||
const void* __restrict__ v_prefix_ptr;
|
||||
void* __restrict__ packed_k_ptr;
|
||||
void* __restrict__ packed_v_ptr;
|
||||
int64_t v_stride_bytes;
|
||||
int64_t k_prefix_stride_bytes;
|
||||
int64_t v_prefix_stride_bytes;
|
||||
int64_t packed_token_stride_bytes;
|
||||
int64_t packed_head_stride_bytes;
|
||||
uint32_t batch_size;
|
||||
uint32_t prefix_tokens;
|
||||
uint32_t suffix_tokens;
|
||||
};
|
||||
|
||||
template <bool kPackKV>
|
||||
using QKNormRopeParamsT = std::conditional_t<kPackKV, QKNormRopePackKVParams, QKNormRopeParams>;
|
||||
|
||||
constexpr uint32_t kThreadsPerBlock = 256;
|
||||
constexpr uint32_t kWarpsPerBlock = kThreadsPerBlock / device::kWarpThreads;
|
||||
|
||||
@@ -152,8 +171,9 @@ template <
|
||||
typename DType,
|
||||
typename CacheDType,
|
||||
bool kRoundNormBeforeRope,
|
||||
bool kPackKV,
|
||||
typename IdType>
|
||||
__global__ void fused_qknorm_rope_warp(const QKNormRopeParams __grid_constant__ params) {
|
||||
__global__ void fused_qknorm_rope_warp(const QKNormRopeParamsT<kPackKV> __grid_constant__ params) {
|
||||
using namespace device;
|
||||
|
||||
static_assert(std::is_same_v<DType, fp16_t> || std::is_same_v<DType, bf16_t>);
|
||||
@@ -181,23 +201,81 @@ __global__ void fused_qknorm_rope_warp(const QKNormRopeParams __grid_constant__
|
||||
using Storage = AlignedVector<Packed, kVecSize>;
|
||||
|
||||
const auto& [q_ptr, k_ptr, q_weight_ptr, k_weight_ptr, cos_sin_cache_ptr, positions, q_stride_bytes, k_stride_bytes, head_stride_bytes, num_qo_heads, num_kv_heads, num_tokens, eps] =
|
||||
params;
|
||||
static_cast<const QKNormRopeParams&>(params);
|
||||
|
||||
const uint32_t lane_id = threadIdx.x % kWarpThreads;
|
||||
const uint32_t warp_id = threadIdx.x / kWarpThreads;
|
||||
const uint32_t start_worker_id = blockIdx.x * kWarpsPerBlock + warp_id;
|
||||
const uint32_t num_workers = gridDim.x * kWarpsPerBlock;
|
||||
const uint32_t num_qk_heads = num_qo_heads + num_kv_heads;
|
||||
const uint32_t num_works = num_qk_heads * num_tokens;
|
||||
const uint32_t num_qk_works = num_qk_heads * num_tokens;
|
||||
uint32_t num_prefix_works = 0;
|
||||
uint32_t num_works = num_qk_works;
|
||||
if constexpr (kPackKV) {
|
||||
num_prefix_works = params.batch_size * params.prefix_tokens * num_kv_heads;
|
||||
num_works += 2 * num_prefix_works + num_tokens * num_kv_heads;
|
||||
}
|
||||
|
||||
PDLWaitPrimary<kUsePDL>();
|
||||
|
||||
for (uint32_t idx = start_worker_id; idx < num_works; idx += num_workers) {
|
||||
if constexpr (kPackKV) {
|
||||
if (idx >= num_qk_works) {
|
||||
const uint32_t copy_idx = idx - num_qk_works;
|
||||
const bool copy_k_prefix = copy_idx < num_prefix_works;
|
||||
const bool copy_v_prefix = copy_idx >= num_prefix_works && copy_idx < 2 * num_prefix_works;
|
||||
const uint32_t local_idx =
|
||||
copy_k_prefix ? copy_idx : (copy_v_prefix ? copy_idx - num_prefix_works : copy_idx - 2 * num_prefix_works);
|
||||
const uint32_t token_id = local_idx / num_kv_heads;
|
||||
const uint32_t head_id = local_idx % num_kv_heads;
|
||||
const bool copy_prefix = copy_k_prefix || copy_v_prefix;
|
||||
const uint32_t batch_id = token_id / (copy_prefix ? params.prefix_tokens : params.suffix_tokens);
|
||||
const uint32_t sequence_id = token_id % (copy_prefix ? params.prefix_tokens : params.suffix_tokens);
|
||||
const uint32_t packed_token_id = batch_id * (params.prefix_tokens + params.suffix_tokens) +
|
||||
(copy_prefix ? sequence_id : params.prefix_tokens + sequence_id);
|
||||
const void* input = nullptr;
|
||||
void* output = nullptr;
|
||||
if (copy_k_prefix) {
|
||||
input = pointer::offset(
|
||||
params.k_prefix_ptr, token_id * params.k_prefix_stride_bytes, head_id * head_stride_bytes);
|
||||
output = pointer::offset(
|
||||
params.packed_k_ptr,
|
||||
packed_token_id * params.packed_token_stride_bytes,
|
||||
head_id * params.packed_head_stride_bytes);
|
||||
} else {
|
||||
const void* v_ptr = copy_v_prefix ? params.v_prefix_ptr : params.v_ptr;
|
||||
const int64_t v_stride = copy_v_prefix ? params.v_prefix_stride_bytes : params.v_stride_bytes;
|
||||
input = pointer::offset(v_ptr, token_id * v_stride, head_id * head_stride_bytes);
|
||||
output = pointer::offset(
|
||||
params.packed_v_ptr,
|
||||
packed_token_id * params.packed_token_stride_bytes,
|
||||
head_id * params.packed_head_stride_bytes);
|
||||
}
|
||||
const auto copy_vec = load_as<Storage>(input, lane_id);
|
||||
store_as<Storage>(output, copy_vec, lane_id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t token_id = idx / num_qk_heads;
|
||||
const uint32_t head_id = idx % num_qk_heads;
|
||||
const bool load_q = head_id < num_qo_heads;
|
||||
const void* input = load_q ? pointer::offset(q_ptr, token_id * q_stride_bytes, head_id * head_stride_bytes)
|
||||
: pointer::offset(k_ptr, token_id * k_stride_bytes, head_id * head_stride_bytes);
|
||||
void* output = const_cast<void*>(input);
|
||||
if constexpr (kPackKV) {
|
||||
if (!load_q) {
|
||||
const uint32_t batch_id = token_id / params.suffix_tokens;
|
||||
const uint32_t sequence_id = token_id % params.suffix_tokens;
|
||||
const uint32_t kv_head_id = head_id - num_qo_heads;
|
||||
const uint32_t packed_token_id =
|
||||
batch_id * (params.prefix_tokens + params.suffix_tokens) + params.prefix_tokens + sequence_id;
|
||||
output = pointer::offset(
|
||||
params.packed_k_ptr,
|
||||
packed_token_id * params.packed_token_stride_bytes,
|
||||
kv_head_id * params.packed_head_stride_bytes);
|
||||
}
|
||||
}
|
||||
const void* weight_ptr = load_q ? q_weight_ptr : k_weight_ptr;
|
||||
|
||||
auto input_vec = load_as<Storage>(input, lane_id);
|
||||
@@ -246,7 +324,7 @@ __global__ void fused_qknorm_rope_warp(const QKNormRopeParams __grid_constant__
|
||||
}
|
||||
}
|
||||
}
|
||||
store_as<Storage>(const_cast<void*>(input), output_vec, lane_id);
|
||||
store_as<Storage>(output, output_vec, lane_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -315,7 +393,7 @@ __global__ void fused_qknorm_rope_warp(const QKNormRopeParams __grid_constant__
|
||||
for (uint32_t j = 0; j < kVecSize; ++j) {
|
||||
input_vec[j] = cast<Packed, fp32x2_t>({elems[2 * j], elems[2 * j + 1]});
|
||||
}
|
||||
store_as<Storage>(const_cast<void*>(input), input_vec, lane_id);
|
||||
store_as<Storage>(output, input_vec, lane_id);
|
||||
}
|
||||
|
||||
PDLTriggerSecondary<kUsePDL>();
|
||||
@@ -332,8 +410,16 @@ template <
|
||||
struct QKNormRopeKernel {
|
||||
static_assert(kHeadDim <= 256, "Only head_dim <= 256 is supported");
|
||||
template <typename IdType>
|
||||
static constexpr auto kernel =
|
||||
fused_qknorm_rope_warp<kHeadDim, kRopeDim, kIsNeox, kUsePDL, DType, CacheDType, kRoundNormBeforeRope, IdType>;
|
||||
static constexpr auto kernel = fused_qknorm_rope_warp<
|
||||
kHeadDim,
|
||||
kRopeDim,
|
||||
kIsNeox,
|
||||
kUsePDL,
|
||||
DType,
|
||||
CacheDType,
|
||||
kRoundNormBeforeRope,
|
||||
false,
|
||||
IdType>;
|
||||
|
||||
static void
|
||||
run(const tvm::ffi::TensorView q,
|
||||
@@ -405,4 +491,130 @@ struct QKNormRopeKernel {
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
int64_t kHeadDim,
|
||||
int64_t kRopeDim,
|
||||
bool kIsNeox,
|
||||
bool kUsePDL,
|
||||
typename DType,
|
||||
typename CacheDType,
|
||||
bool kRoundNormBeforeRope>
|
||||
struct QKNormRopePackKVKernel {
|
||||
template <typename IdType>
|
||||
static constexpr auto kernel = fused_qknorm_rope_warp<
|
||||
kHeadDim,
|
||||
kRopeDim,
|
||||
kIsNeox,
|
||||
kUsePDL,
|
||||
DType,
|
||||
CacheDType,
|
||||
kRoundNormBeforeRope,
|
||||
true,
|
||||
IdType>;
|
||||
|
||||
static void
|
||||
run(const tvm::ffi::TensorView q,
|
||||
const tvm::ffi::TensorView k,
|
||||
const tvm::ffi::TensorView v,
|
||||
const tvm::ffi::TensorView k_prefix,
|
||||
const tvm::ffi::TensorView v_prefix,
|
||||
const tvm::ffi::TensorView packed_k,
|
||||
const tvm::ffi::TensorView packed_v,
|
||||
const tvm::ffi::TensorView q_weight,
|
||||
const tvm::ffi::TensorView k_weight,
|
||||
const tvm::ffi::TensorView cos_sin_cache,
|
||||
const tvm::ffi::TensorView positions,
|
||||
int64_t batch_size,
|
||||
int64_t prefix_tokens,
|
||||
int64_t suffix_tokens,
|
||||
float eps) {
|
||||
using namespace host;
|
||||
|
||||
auto N = SymbolicSize{"num_tokens"};
|
||||
auto NP = SymbolicSize{"num_prefix_tokens"};
|
||||
auto B = SymbolicSize{"batch_size"};
|
||||
auto T = SymbolicSize{"packed_tokens"};
|
||||
auto Q = SymbolicSize{"num_qo_heads"};
|
||||
auto K = SymbolicSize{"num_kv_heads"};
|
||||
auto D = SymbolicSize{"head_dim"};
|
||||
auto R = SymbolicSize{"rope_dim"};
|
||||
auto Dq = SymbolicSize{"q_stride"};
|
||||
auto Dk = SymbolicSize{"k_stride"};
|
||||
auto Dv = SymbolicSize{"v_stride"};
|
||||
auto Dkp = SymbolicSize{"k_prefix_stride"};
|
||||
auto Dvp = SymbolicSize{"v_prefix_stride"};
|
||||
auto Dd = SymbolicSize{"head_stride"};
|
||||
auto device = SymbolicDevice{};
|
||||
auto id_type = SymbolicDType{};
|
||||
N.set_value(batch_size * suffix_tokens);
|
||||
NP.set_value(batch_size * prefix_tokens);
|
||||
B.set_value(batch_size);
|
||||
T.set_value(prefix_tokens + suffix_tokens);
|
||||
D.set_value(kHeadDim);
|
||||
R.set_value(kRopeDim);
|
||||
device.set_options<kDLCUDA>();
|
||||
|
||||
TensorMatcher({N, Q, D}).with_strides({Dq, Dd, 1}).with_dtype<DType>().with_device(device).verify(q);
|
||||
TensorMatcher({N, K, D}).with_strides({Dk, Dd, 1}).with_dtype<DType>().with_device(device).verify(k);
|
||||
TensorMatcher({N, K, D}).with_strides({Dv, Dd, 1}).with_dtype<DType>().with_device(device).verify(v);
|
||||
TensorMatcher({NP, K, D}).with_strides({Dkp, Dd, 1}).with_dtype<DType>().with_device(device).verify(k_prefix);
|
||||
TensorMatcher({NP, K, D}).with_strides({Dvp, Dd, 1}).with_dtype<DType>().with_device(device).verify(v_prefix);
|
||||
TensorMatcher({B, T, K, D}).with_dtype<DType>().with_device(device).verify(packed_k).verify(packed_v);
|
||||
RuntimeCheck(packed_k.is_contiguous(), "packed_k must be contiguous");
|
||||
RuntimeCheck(packed_v.is_contiguous(), "packed_v must be contiguous");
|
||||
TensorMatcher({D}).with_dtype<DType>().with_device(device).verify(q_weight).verify(k_weight);
|
||||
TensorMatcher({-1, R}).with_dtype<CacheDType>().with_device(device).verify(cos_sin_cache);
|
||||
TensorMatcher({N}).with_dtype<int32_t, int64_t>(id_type).with_device(device).verify(positions);
|
||||
|
||||
const auto num_tokens = static_cast<uint32_t>(N.unwrap());
|
||||
const auto num_qo_heads = static_cast<uint32_t>(Q.unwrap());
|
||||
const auto num_kv_heads = static_cast<uint32_t>(K.unwrap());
|
||||
if (num_tokens == 0 || (num_qo_heads == 0 && num_kv_heads == 0)) return;
|
||||
const auto head_stride_bytes = static_cast<int64_t>(Dd.unwrap() * sizeof(DType));
|
||||
const int64_t k_offset = static_cast<int64_t>(num_qo_heads) * head_stride_bytes;
|
||||
QKNormRopePackKVParams params{};
|
||||
params.q_ptr = q.data_ptr();
|
||||
params.k_ptr = pointer::offset(k.data_ptr(), -k_offset);
|
||||
params.q_weight_ptr = q_weight.data_ptr();
|
||||
params.k_weight_ptr = k_weight.data_ptr();
|
||||
params.cos_sin_cache_ptr = cos_sin_cache.data_ptr();
|
||||
params.positions = positions.data_ptr();
|
||||
params.q_stride_bytes = static_cast<int64_t>(Dq.unwrap() * sizeof(DType));
|
||||
params.k_stride_bytes = static_cast<int64_t>(Dk.unwrap() * sizeof(DType));
|
||||
params.head_stride_bytes = head_stride_bytes;
|
||||
params.num_qo_heads = num_qo_heads;
|
||||
params.num_kv_heads = num_kv_heads;
|
||||
params.num_tokens = num_tokens;
|
||||
params.eps = eps;
|
||||
params.v_ptr = v.data_ptr();
|
||||
params.k_prefix_ptr = k_prefix.data_ptr();
|
||||
params.v_prefix_ptr = v_prefix.data_ptr();
|
||||
params.packed_k_ptr = packed_k.data_ptr();
|
||||
params.packed_v_ptr = packed_v.data_ptr();
|
||||
params.v_stride_bytes = static_cast<int64_t>(Dv.unwrap() * sizeof(DType));
|
||||
params.k_prefix_stride_bytes = static_cast<int64_t>(Dkp.unwrap() * sizeof(DType));
|
||||
params.v_prefix_stride_bytes = static_cast<int64_t>(Dvp.unwrap() * sizeof(DType));
|
||||
params.packed_token_stride_bytes = static_cast<int64_t>(num_kv_heads * kHeadDim * sizeof(DType));
|
||||
params.packed_head_stride_bytes = static_cast<int64_t>(kHeadDim * sizeof(DType));
|
||||
params.batch_size = static_cast<uint32_t>(batch_size);
|
||||
params.prefix_tokens = static_cast<uint32_t>(prefix_tokens);
|
||||
params.suffix_tokens = static_cast<uint32_t>(suffix_tokens);
|
||||
|
||||
const auto is_int32 = id_type.is_type<int32_t>();
|
||||
const auto selected_kernel = is_int32 ? kernel<int32_t> : kernel<int64_t>;
|
||||
const uint32_t kNumSM = runtime::get_sm_count(device.unwrap().device_id);
|
||||
static const uint32_t kOccupancyTable[2] = {
|
||||
runtime::get_blocks_per_sm(kernel<int32_t>, kThreadsPerBlock),
|
||||
runtime::get_blocks_per_sm(kernel<int64_t>, kThreadsPerBlock),
|
||||
};
|
||||
const auto max_blocks = kOccupancyTable[is_int32 ? 0 : 1] * kNumSM;
|
||||
const uint32_t num_prefix_works = static_cast<uint32_t>(batch_size * prefix_tokens) * num_kv_heads;
|
||||
const uint32_t num_works =
|
||||
(num_qo_heads + num_kv_heads) * num_tokens + 2 * num_prefix_works + num_tokens * num_kv_heads;
|
||||
const auto needed_blocks = div_ceil(num_works, kWarpsPerBlock);
|
||||
const auto num_blocks = std::min(max_blocks, needed_blocks);
|
||||
LaunchKernel(num_blocks, kThreadsPerBlock, device.unwrap()).enable_pdl(kUsePDL)(selected_kernel, params);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sglang
|
||||
|
||||
@@ -31,6 +31,7 @@ def _jit_qknorm_rope_module(
|
||||
dtype: torch.dtype,
|
||||
cache_dtype: torch.dtype,
|
||||
round_norm_before_rope: bool,
|
||||
pack_kv: bool = False,
|
||||
) -> Module:
|
||||
args = make_cpp_args(
|
||||
head_dim,
|
||||
@@ -41,23 +42,24 @@ def _jit_qknorm_rope_module(
|
||||
cache_dtype,
|
||||
round_norm_before_rope,
|
||||
)
|
||||
op_name = "qknorm_rope_pack_kv" if pack_kv else "qknorm_rope"
|
||||
kernel_name = "QKNormRopePackKVKernel" if pack_kv else "QKNormRopeKernel"
|
||||
return load_jit(
|
||||
"qknorm_rope",
|
||||
op_name,
|
||||
*args,
|
||||
cuda_files=["diffusion/qknorm_rope.cuh"],
|
||||
cuda_wrappers=[("qknorm_rope", f"QKNormRopeKernel<{args}>::run")],
|
||||
cuda_wrappers=[(op_name, f"{kernel_name}<{args}>::run")],
|
||||
)
|
||||
|
||||
|
||||
@torch.compiler.assume_constant_result
|
||||
@cache_once
|
||||
def can_use_fused_inplace_qknorm_rope(
|
||||
def _can_use_fused_qknorm_rope(
|
||||
head_dim: int,
|
||||
rope_dim: int,
|
||||
is_neox: bool,
|
||||
dtype: torch.dtype,
|
||||
cache_dtype: torch.dtype = torch.float32,
|
||||
round_norm_before_rope: bool = False,
|
||||
cache_dtype: torch.dtype,
|
||||
round_norm_before_rope: bool,
|
||||
pack_kv: bool,
|
||||
) -> bool:
|
||||
if dtype not in _SUPPORTED_DTYPES or cache_dtype not in _SUPPORTED_CACHE_DTYPES:
|
||||
logger.warning(
|
||||
@@ -106,13 +108,37 @@ def can_use_fused_inplace_qknorm_rope(
|
||||
dtype,
|
||||
cache_dtype,
|
||||
round_norm_before_rope,
|
||||
pack_kv,
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load JIT fused QKNorm+RoPE kernel: {e}")
|
||||
suffix = "+KV pack" if pack_kv else ""
|
||||
logger.warning(f"Failed to load JIT fused QKNorm+RoPE{suffix} kernel: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@torch.compiler.assume_constant_result
|
||||
@cache_once
|
||||
def can_use_fused_inplace_qknorm_rope(
|
||||
head_dim: int,
|
||||
rope_dim: int,
|
||||
is_neox: bool,
|
||||
dtype: torch.dtype,
|
||||
cache_dtype: torch.dtype = torch.float32,
|
||||
round_norm_before_rope: bool = False,
|
||||
pack_kv: bool = False,
|
||||
) -> bool:
|
||||
return _can_use_fused_qknorm_rope(
|
||||
head_dim,
|
||||
rope_dim,
|
||||
is_neox,
|
||||
dtype,
|
||||
cache_dtype,
|
||||
round_norm_before_rope,
|
||||
pack_kv,
|
||||
)
|
||||
|
||||
|
||||
@register_custom_op(mutates_args=["q", "k"])
|
||||
def fused_inplace_qknorm_rope(
|
||||
q: torch.Tensor,
|
||||
@@ -139,3 +165,54 @@ def fused_inplace_qknorm_rope(
|
||||
round_norm_before_rope,
|
||||
)
|
||||
module.qknorm_rope(q, k, q_weight, k_weight, cos_sin_cache, positions, eps)
|
||||
|
||||
|
||||
@register_custom_op(mutates_args=["q", "packed_kv"])
|
||||
def fused_qknorm_rope_pack_kv(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
k_prefix: torch.Tensor,
|
||||
v_prefix: torch.Tensor,
|
||||
packed_kv: torch.Tensor,
|
||||
q_weight: torch.Tensor,
|
||||
k_weight: torch.Tensor,
|
||||
cos_sin_cache: torch.Tensor,
|
||||
positions: torch.Tensor,
|
||||
*,
|
||||
is_neox: bool,
|
||||
eps: float = 1e-6,
|
||||
head_dim: int = 0,
|
||||
rope_dim: int = 0,
|
||||
round_norm_before_rope: bool = False,
|
||||
) -> None:
|
||||
head_dim = head_dim or q.size(-1)
|
||||
rope_dim = rope_dim or cos_sin_cache.size(-1)
|
||||
batch_size, suffix_tokens = q.shape[:2]
|
||||
prefix_tokens = k_prefix.shape[1]
|
||||
module = _jit_qknorm_rope_module(
|
||||
head_dim,
|
||||
rope_dim,
|
||||
is_neox,
|
||||
q.dtype,
|
||||
cos_sin_cache.dtype,
|
||||
round_norm_before_rope,
|
||||
True,
|
||||
)
|
||||
module.qknorm_rope_pack_kv(
|
||||
q.view(-1, q.shape[-2], head_dim),
|
||||
k.view(-1, k.shape[-2], head_dim),
|
||||
v.view(-1, v.shape[-2], head_dim),
|
||||
k_prefix.view(-1, k_prefix.shape[-2], head_dim),
|
||||
v_prefix.view(-1, v_prefix.shape[-2], head_dim),
|
||||
packed_kv[0],
|
||||
packed_kv[1],
|
||||
q_weight,
|
||||
k_weight,
|
||||
cos_sin_cache,
|
||||
positions,
|
||||
batch_size,
|
||||
prefix_tokens,
|
||||
suffix_tokens,
|
||||
eps,
|
||||
)
|
||||
|
||||
@@ -972,8 +972,13 @@ def apply_qk_norm_rope(
|
||||
positions: Optional[torch.Tensor] = None,
|
||||
position_offset: int = 0,
|
||||
allow_inplace: bool = True,
|
||||
allow_strided_qk: bool = False,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Apply QK RMSNorm followed by RoPE, fusing both on supported CUDA/XPU shapes."""
|
||||
"""Apply QK RMSNorm followed by RoPE, fusing supported CUDA/XPU shapes.
|
||||
|
||||
Strided packed-QKV views require an explicit opt-in because selecting the fused
|
||||
kernel changes the numerical path for models that historically used the fallback.
|
||||
"""
|
||||
|
||||
from sglang.multimodal_gen.runtime.layers.rotary_embedding import (
|
||||
apply_flashinfer_rope_qk_inplace,
|
||||
@@ -1010,6 +1015,17 @@ def apply_qk_norm_rope(
|
||||
"off",
|
||||
"no",
|
||||
}
|
||||
q_has_supported_layout = q.is_contiguous()
|
||||
k_has_supported_layout = k.is_contiguous()
|
||||
if allow_strided_qk:
|
||||
q_has_supported_layout = (
|
||||
q.stride(-1) == 1
|
||||
and q.stride(-2) == k.stride(-2)
|
||||
and q.stride(0) == seq_len * q.stride(1)
|
||||
)
|
||||
k_has_supported_layout = k.stride(-1) == 1 and k.stride(
|
||||
0
|
||||
) == seq_len * k.stride(1)
|
||||
|
||||
if positions is None:
|
||||
pos_1d = torch.arange(
|
||||
@@ -1033,15 +1049,16 @@ def apply_qk_norm_rope(
|
||||
and allow_inplace
|
||||
and (q_eps == k_eps)
|
||||
and q.dtype in (torch.float16, torch.bfloat16)
|
||||
and k.dtype == q.dtype
|
||||
and q_norm.weight.dtype == q.dtype
|
||||
and k_norm.weight.dtype == k.dtype
|
||||
and q.is_contiguous()
|
||||
and k.is_contiguous()
|
||||
and q_has_supported_layout
|
||||
and k_has_supported_layout
|
||||
and can_use_fused_inplace_qknorm_rope(head_dim, rope_dim, is_neox, q.dtype)
|
||||
):
|
||||
fused_inplace_qknorm_rope(
|
||||
q=q.reshape(-1, q.shape[-2], head_dim),
|
||||
k=k.reshape(-1, k.shape[-2], head_dim),
|
||||
q=q.view(-1, q.shape[-2], head_dim),
|
||||
k=k.view(-1, k.shape[-2], head_dim),
|
||||
q_weight=q_norm.weight,
|
||||
k_weight=k_norm.weight,
|
||||
cos_sin_cache=cos_sin_cache,
|
||||
@@ -1053,8 +1070,6 @@ def apply_qk_norm_rope(
|
||||
)
|
||||
return q, k
|
||||
|
||||
# TODO: Once CUDA fused_inplace_qknorm_rope supports last-dimension-contiguous q/k,
|
||||
# merge this path with the CUDA fused qknorm+rope branch.
|
||||
if (
|
||||
_is_xpu
|
||||
and allow_inplace
|
||||
|
||||
@@ -14,6 +14,10 @@ import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from sglang.kernels.ops.diffusion.qknorm_rope import (
|
||||
can_use_fused_inplace_qknorm_rope,
|
||||
fused_qknorm_rope_pack_kv,
|
||||
)
|
||||
from sglang.multimodal_gen.configs.models.dits.cosmos3video import Cosmos3VideoConfig
|
||||
from sglang.multimodal_gen.configs.models.fsdp import is_module_list_entry_in
|
||||
from sglang.multimodal_gen.runtime.distributed import (
|
||||
@@ -229,17 +233,60 @@ def _apply_qwen3_qk_norm_rope(
|
||||
rope_cache_positions: torch.Tensor,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
return apply_qk_norm_rope(
|
||||
q=q.contiguous(),
|
||||
k=k.contiguous(),
|
||||
q=q,
|
||||
k=k,
|
||||
q_norm=q_norm,
|
||||
k_norm=k_norm,
|
||||
head_dim=head_dim,
|
||||
cos_sin_cache=cos_sin_cache,
|
||||
is_neox=True,
|
||||
positions=rope_cache_positions,
|
||||
allow_strided_qk=True,
|
||||
)
|
||||
|
||||
|
||||
def _apply_qwen3_qk_norm_rope_pack_kv(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
k_prefix: torch.Tensor,
|
||||
v_prefix: torch.Tensor,
|
||||
q_norm: RMSNorm,
|
||||
k_norm: RMSNorm,
|
||||
head_dim: int,
|
||||
cos_sin_cache: torch.Tensor,
|
||||
rope_cache_positions: torch.Tensor,
|
||||
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
batch_size, suffix_tokens, _, _ = q.shape
|
||||
prefix_tokens = k_prefix.shape[1]
|
||||
packed_kv = torch.empty(
|
||||
2,
|
||||
batch_size,
|
||||
prefix_tokens + suffix_tokens,
|
||||
k.shape[2],
|
||||
head_dim,
|
||||
dtype=q.dtype,
|
||||
device=q.device,
|
||||
)
|
||||
fused_qknorm_rope_pack_kv(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
k_prefix,
|
||||
v_prefix,
|
||||
packed_kv,
|
||||
q_norm.weight,
|
||||
k_norm.weight,
|
||||
cos_sin_cache,
|
||||
rope_cache_positions,
|
||||
is_neox=True,
|
||||
eps=q_norm.variance_epsilon,
|
||||
head_dim=head_dim,
|
||||
rope_dim=cos_sin_cache.shape[-1],
|
||||
)
|
||||
return q, packed_kv[0], packed_kv[1]
|
||||
|
||||
|
||||
def _apply_qwen3_rope_from_cache(
|
||||
q: torch.Tensor, k: torch.Tensor, cos_sin_cache: torch.Tensor
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
@@ -733,7 +780,39 @@ class Cosmos3CrossAttention(nn.Module):
|
||||
:,
|
||||
]
|
||||
|
||||
if use_fused_qk_norm_rope:
|
||||
use_fused_kv_pack = (
|
||||
use_fused_qk_norm_rope
|
||||
and q.device.type == "cuda"
|
||||
and not torch.compiler.is_compiling()
|
||||
and get_sp_world_size() == 1
|
||||
and q.dtype == k.dtype == v.dtype == k_und.dtype == v_und.dtype
|
||||
and self.norm_q.weight.dtype == q.dtype
|
||||
and self.norm_k.weight.dtype == k.dtype
|
||||
and self.norm_q.variance_epsilon == self.norm_k.variance_epsilon
|
||||
and can_use_fused_inplace_qknorm_rope(
|
||||
self.head_dim,
|
||||
cos_sin_cache.shape[-1],
|
||||
True,
|
||||
q.dtype,
|
||||
cos_sin_cache.dtype,
|
||||
pack_kv=True,
|
||||
)
|
||||
)
|
||||
if use_fused_kv_pack:
|
||||
q, packed_k, packed_v = _apply_qwen3_qk_norm_rope_pack_kv(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
k_und,
|
||||
v_und,
|
||||
self.norm_q,
|
||||
self.norm_k,
|
||||
self.head_dim,
|
||||
cos_sin_cache,
|
||||
rope_cache_positions,
|
||||
)
|
||||
out = self.attn.forward(q, packed_k, packed_v)
|
||||
elif use_fused_qk_norm_rope:
|
||||
q, k = _apply_qwen3_qk_norm_rope(
|
||||
q,
|
||||
k,
|
||||
@@ -747,11 +826,10 @@ class Cosmos3CrossAttention(nn.Module):
|
||||
q, k = _apply_qwen3_qk_norm_rope_split(
|
||||
q, k, self.norm_q, self.norm_k, self.head_dim, cos_sin_cache
|
||||
)
|
||||
|
||||
# K/V = [text (replicated on every SP rank) | image (sharded same as Q)].
|
||||
# USPAttention routes through the registered attention backend (FA, sage,
|
||||
# …) and handles the Ulysses all-to-all when SP > 1.
|
||||
out = self.attn.forward_with_replicated_kv_prefix(q, k_und, v_und, k, v)
|
||||
if not use_fused_kv_pack:
|
||||
# K/V = [UND prefix (replicated on SP ranks) | GEN suffix].
|
||||
# USPAttention applies the configured backend and SP collectives.
|
||||
out = self.attn.forward_with_replicated_kv_prefix(q, k_und, v_und, k, v)
|
||||
out = out.reshape(batch_size, seq_len_gen, -1)
|
||||
out, _ = self.to_out(out)
|
||||
return out
|
||||
|
||||
@@ -216,6 +216,164 @@ def test_qknorm_rope_preserves_split_bf16_rounding() -> None:
|
||||
assert torch.equal(k_ref, k_fused)
|
||||
|
||||
|
||||
def test_qknorm_rope_requires_opt_in_for_strided_packed_gqa() -> None:
|
||||
from sglang.kernels.ops.diffusion.qknorm_rope import (
|
||||
fused_inplace_qknorm_rope,
|
||||
)
|
||||
from sglang.multimodal_gen.runtime.layers.layernorm import (
|
||||
RMSNorm,
|
||||
apply_qk_norm_rope,
|
||||
)
|
||||
|
||||
num_tokens, num_q_heads, num_kv_heads, head_dim = 257, 32, 8, 128
|
||||
num_heads = num_q_heads + 2 * num_kv_heads
|
||||
qkv = torch.randn(1, num_tokens, num_heads, head_dim, device=DEVICE, dtype=DTYPE)
|
||||
q_weight = torch.randn(head_dim, device=DEVICE, dtype=DTYPE)
|
||||
k_weight = torch.randn(head_dim, device=DEVICE, dtype=DTYPE)
|
||||
positions = torch.arange(num_tokens, device=DEVICE, dtype=torch.int64)
|
||||
cos_sin_cache = create_cos_sin_cache(head_dim, num_tokens)
|
||||
|
||||
q_ref = qkv[:, :, :num_q_heads].contiguous()
|
||||
k_ref = qkv[:, :, num_q_heads : num_q_heads + num_kv_heads].contiguous()
|
||||
q_norm = RMSNorm(head_dim, eps=1e-6).to(device=DEVICE, dtype=DTYPE)
|
||||
k_norm = RMSNorm(head_dim, eps=1e-6).to(device=DEVICE, dtype=DTYPE)
|
||||
q_norm.weight.data.copy_(q_weight)
|
||||
k_norm.weight.data.copy_(k_weight)
|
||||
qkv_default = qkv.clone()
|
||||
q_default = qkv_default[:, :, :num_q_heads]
|
||||
k_default = qkv_default[:, :, num_q_heads : num_q_heads + num_kv_heads]
|
||||
q_default_out, k_default_out = apply_qk_norm_rope(
|
||||
q=q_default,
|
||||
k=k_default,
|
||||
q_norm=q_norm,
|
||||
k_norm=k_norm,
|
||||
head_dim=head_dim,
|
||||
cos_sin_cache=cos_sin_cache,
|
||||
is_neox=True,
|
||||
positions=positions,
|
||||
)
|
||||
assert q_default_out.data_ptr() != q_default.data_ptr()
|
||||
assert k_default_out.data_ptr() != k_default.data_ptr()
|
||||
|
||||
qkv_fused = qkv.clone()
|
||||
q_fused = qkv_fused[:, :, :num_q_heads]
|
||||
k_fused = qkv_fused[:, :, num_q_heads : num_q_heads + num_kv_heads]
|
||||
v_before = qkv_fused[:, :, num_q_heads + num_kv_heads :].clone()
|
||||
|
||||
fused_inplace_qknorm_rope(
|
||||
q_ref.view(-1, num_q_heads, head_dim),
|
||||
k_ref.view(-1, num_kv_heads, head_dim),
|
||||
q_weight,
|
||||
k_weight,
|
||||
cos_sin_cache,
|
||||
positions,
|
||||
is_neox=True,
|
||||
rope_dim=head_dim,
|
||||
)
|
||||
q_out, k_out = apply_qk_norm_rope(
|
||||
q=q_fused,
|
||||
k=k_fused,
|
||||
q_norm=q_norm,
|
||||
k_norm=k_norm,
|
||||
head_dim=head_dim,
|
||||
cos_sin_cache=cos_sin_cache,
|
||||
is_neox=True,
|
||||
positions=positions,
|
||||
allow_strided_qk=True,
|
||||
)
|
||||
|
||||
assert q_out.data_ptr() == q_fused.data_ptr()
|
||||
assert k_out.data_ptr() == k_fused.data_ptr()
|
||||
assert torch.equal(q_ref, q_out)
|
||||
assert torch.equal(k_ref, k_out)
|
||||
assert torch.equal(v_before, qkv_fused[:, :, num_q_heads + num_kv_heads :])
|
||||
|
||||
|
||||
def test_qknorm_rope_pack_kv_matches_separate_ops() -> None:
|
||||
from sglang.kernels.ops.diffusion.qknorm_rope import (
|
||||
fused_inplace_qknorm_rope,
|
||||
fused_qknorm_rope_pack_kv,
|
||||
)
|
||||
|
||||
batch_size = 2
|
||||
prefix_tokens, suffix_tokens = 17, 257
|
||||
num_q_heads, num_kv_heads, head_dim = 32, 8, 128
|
||||
num_heads = num_q_heads + 2 * num_kv_heads
|
||||
qkv = torch.randn(
|
||||
batch_size,
|
||||
suffix_tokens,
|
||||
num_heads,
|
||||
head_dim,
|
||||
device=DEVICE,
|
||||
dtype=DTYPE,
|
||||
)
|
||||
prefix_qkv = torch.randn(
|
||||
batch_size,
|
||||
prefix_tokens,
|
||||
num_heads,
|
||||
head_dim,
|
||||
device=DEVICE,
|
||||
dtype=DTYPE,
|
||||
)
|
||||
k_prefix = prefix_qkv[:, :, num_q_heads : num_q_heads + num_kv_heads]
|
||||
v_prefix = prefix_qkv[:, :, num_q_heads + num_kv_heads :]
|
||||
q_weight = torch.randn(head_dim, device=DEVICE, dtype=DTYPE)
|
||||
k_weight = torch.randn(head_dim, device=DEVICE, dtype=DTYPE)
|
||||
positions = torch.arange(
|
||||
batch_size * suffix_tokens, device=DEVICE, dtype=torch.int64
|
||||
)
|
||||
cos_sin_cache = create_cos_sin_cache(head_dim, batch_size * suffix_tokens)
|
||||
|
||||
qkv_ref = qkv.clone()
|
||||
q_ref = qkv_ref[:, :, :num_q_heads]
|
||||
k_ref = qkv_ref[:, :, num_q_heads : num_q_heads + num_kv_heads]
|
||||
v_ref = qkv_ref[:, :, num_q_heads + num_kv_heads :]
|
||||
fused_inplace_qknorm_rope(
|
||||
q_ref.view(-1, num_q_heads, head_dim),
|
||||
k_ref.view(-1, num_kv_heads, head_dim),
|
||||
q_weight,
|
||||
k_weight,
|
||||
cos_sin_cache,
|
||||
positions,
|
||||
is_neox=True,
|
||||
rope_dim=head_dim,
|
||||
)
|
||||
packed_k_ref = torch.cat([k_prefix, k_ref], dim=1)
|
||||
packed_v_ref = torch.cat([v_prefix, v_ref], dim=1)
|
||||
|
||||
qkv_fused = qkv.clone()
|
||||
q_fused = qkv_fused[:, :, :num_q_heads]
|
||||
k_fused = qkv_fused[:, :, num_q_heads : num_q_heads + num_kv_heads]
|
||||
v_fused = qkv_fused[:, :, num_q_heads + num_kv_heads :]
|
||||
packed_kv = torch.empty(
|
||||
2,
|
||||
batch_size,
|
||||
prefix_tokens + suffix_tokens,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
device=DEVICE,
|
||||
dtype=DTYPE,
|
||||
)
|
||||
fused_qknorm_rope_pack_kv(
|
||||
q_fused,
|
||||
k_fused,
|
||||
v_fused,
|
||||
k_prefix,
|
||||
v_prefix,
|
||||
packed_kv,
|
||||
q_weight,
|
||||
k_weight,
|
||||
cos_sin_cache,
|
||||
positions,
|
||||
is_neox=True,
|
||||
rope_dim=head_dim,
|
||||
)
|
||||
|
||||
assert torch.equal(q_ref, q_fused)
|
||||
assert torch.equal(packed_k_ref, packed_kv[0])
|
||||
assert torch.equal(packed_v_ref, packed_kv[1])
|
||||
|
||||
|
||||
def test_qknorm_rope_accepts_empty_token_dimension() -> None:
|
||||
from sglang.kernels.ops.diffusion.qknorm_rope import fused_inplace_qknorm_rope
|
||||
|
||||
|
||||
Reference in New Issue
Block a user