Files
sglang/python/sglang/kernels/ops/kvcache/mla_buffer.py
T
2026-09-18 22:40:54 +08:00

541 lines
15 KiB
Python

from __future__ import annotations
from typing import Optional
import torch
import triton
import triton.language as tl
from sglang.kernels.jit.utils import is_arch_support_pdl
from sglang.srt.runtime_context import get_parallel
@triton.jit
def set_mla_kv_buffer_kernel(
kv_buffer_ptr,
cache_k_nope_ptr,
cache_k_rope_ptr,
loc_ptr,
reserved_skip_index,
buffer_stride: tl.constexpr,
nope_stride: tl.constexpr,
rope_stride: tl.constexpr,
nope_dim: tl.constexpr,
rope_dim: tl.constexpr,
BLOCK: tl.constexpr,
DCP_RANK: tl.constexpr,
DCP_WORLD_SIZE: tl.constexpr,
USE_GDC: tl.constexpr = False,
):
pid_loc = tl.program_id(0)
pid_blk = tl.program_id(1)
base = pid_blk * BLOCK
offs = base + tl.arange(0, BLOCK)
total_dim = nope_dim + rope_dim
mask = offs < total_dim
if USE_GDC:
tl.extra.cuda.gdc_wait()
loc = tl.load(loc_ptr + pid_loc).to(tl.int64)
is_valid = (loc != reserved_skip_index) & (loc % DCP_WORLD_SIZE == DCP_RANK)
safe_loc = tl.where(is_valid, loc, 0)
safe_loc = safe_loc // DCP_WORLD_SIZE
dst_ptr = kv_buffer_ptr + safe_loc * buffer_stride + offs
# Three-way branch to handle boundary correctly while preserving fast path
if base + BLOCK <= nope_dim:
# Fast path: entire block is in nope region
src = tl.load(
cache_k_nope_ptr + pid_loc * nope_stride + offs,
mask=mask,
)
elif base >= nope_dim:
# Fast path: entire block is in rope region
offs_rope = offs - nope_dim
src = tl.load(
cache_k_rope_ptr + pid_loc * rope_stride + offs_rope,
mask=mask,
)
else:
# Boundary case: block spans nope/rope boundary (e.g., FP8 with nope_dim=528)
# Handle each offset individually to avoid negative indexing
is_nope = offs < nope_dim
is_rope = (offs >= nope_dim) & (offs < (nope_dim + rope_dim))
src_nope = tl.load(
cache_k_nope_ptr + pid_loc * nope_stride + offs,
mask=mask & is_nope,
other=0,
)
src_rope = tl.load(
cache_k_rope_ptr + pid_loc * rope_stride + (offs - nope_dim),
mask=mask & is_rope,
other=0,
)
src = tl.where(is_nope, src_nope, src_rope)
tl.store(dst_ptr, src, mask=mask & is_valid)
if USE_GDC:
tl.extra.cuda.gdc_launch_dependents()
@triton.jit
def set_mla_kv_buffer_kernel_norope(
kv_buffer_ptr,
cache_k_nope_ptr,
loc_ptr,
buffer_stride: tl.constexpr,
nope_stride: tl.constexpr,
nope_dim: tl.constexpr,
BLOCK: tl.constexpr,
USE_GDC: tl.constexpr = False,
):
pid_loc = tl.program_id(0)
pid_blk = tl.program_id(1)
base = pid_blk * BLOCK
offs = base + tl.arange(0, BLOCK)
mask = offs < nope_dim
if USE_GDC:
tl.extra.cuda.gdc_wait()
loc = tl.load(loc_ptr + pid_loc).to(tl.int64)
dst_ptr = kv_buffer_ptr + loc * buffer_stride + offs
src = tl.load(
cache_k_nope_ptr + pid_loc * nope_stride + offs,
mask=mask,
)
tl.store(dst_ptr, src, mask=mask)
if USE_GDC:
tl.extra.cuda.gdc_launch_dependents()
def _set_mla_kv_buffer_impl(
kv_buffer: torch.Tensor,
loc: torch.Tensor,
cache_k_nope: torch.Tensor,
cache_k_rope: Optional[torch.Tensor] = None,
*,
reserved_skip_index: int,
dcp_world_size: int,
dcp_rank: int,
):
"""Dispatch MLA paged-KV scatter writes to the fastest available path.
Two paths:
- SM90+ with TMA-compatible row widths: JIT CUDA kernel where each warp
loads one (nope, rope) row into shared memory and issues a single
``cp.async.bulk.global.shared::cta`` store to scatter the row at
``kv_buffer[loc[item]]``. It packs 4-8 items per CTA, so the CTA count
falls well below single-CTA-per-loc.
- Otherwise: Triton kernel with ``BLOCK = next_pow2(nope_dim + rope_dim)``,
i.e. one CTA per loc covering the entire row in one tile. This is the
path for SM<90 and for shapes that violate the TMA 16-byte alignment.
Speedup vs the legacy BLOCK=128 Triton kernel on GB300 (BF16, nope=512,
rope=64): ~1.05x at bs=8, ~1.5x at bs=128, 3.5x at bs=512, **11.7x at
bs=16384**.
Name retained for caller compatibility; the implementation is no longer
Triton-only.
Writes targeting ``reserved_skip_index`` are skipped. Slot 0 is reserved
for CUDA-graph padding by default; pass -1 to disable skipping.
Shared body of the two entry points below; the owner rule reaches it as
``1, 0`` (nothing to select) or as the live topology.
"""
has_rope = cache_k_rope is not None and cache_k_rope.numel() > 0
n_loc = loc.numel()
nope_dim = cache_k_nope.shape[-1]
if not has_rope:
BLOCK = triton.next_power_of_2(nope_dim)
grid = (n_loc, 1)
pdl_kwargs = (
{"USE_GDC": True, "launch_pdl": True} if is_arch_support_pdl() else {}
)
set_mla_kv_buffer_kernel_norope[grid](
kv_buffer,
cache_k_nope,
loc,
kv_buffer.stride(0),
cache_k_nope.stride(0),
nope_dim,
BLOCK=BLOCK,
**pdl_kwargs,
)
return
from sglang.kernels.ops.kvcache.set_mla_kv_buffer import (
can_use_set_mla_kv_buffer,
)
from sglang.kernels.ops.kvcache.set_mla_kv_buffer import (
set_mla_kv_buffer as jit_set_mla_kv_buffer,
)
nope_bytes = cache_k_nope.shape[-1] * cache_k_nope.element_size()
rope_bytes = cache_k_rope.shape[-1] * cache_k_rope.element_size()
if (
is_arch_support_pdl()
and can_use_set_mla_kv_buffer(nope_bytes, rope_bytes)
and dcp_world_size == 1
):
jit_set_mla_kv_buffer(
kv_buffer,
loc,
cache_k_nope,
cache_k_rope,
reserved_skip_index=reserved_skip_index,
)
return
# Fallback: Triton with BLOCK = next_pow2(total_dim). One CTA per loc; the
# whole row in one tile (the existing 3-way nope/rope/boundary branch in
# ``set_mla_kv_buffer_kernel`` handles the over-allocation past total_dim
# via the offs<total_dim mask). Beats BLOCK=128 by 60-2700 ns across the
# 2 <= bs <= 512 range on GB300.
rope_dim = cache_k_rope.shape[-1]
total_dim = nope_dim + rope_dim
BLOCK = triton.next_power_of_2(total_dim)
grid = (n_loc, 1)
pdl_kwargs = {"USE_GDC": True, "launch_pdl": True} if is_arch_support_pdl() else {}
set_mla_kv_buffer_kernel[grid](
kv_buffer,
cache_k_nope,
cache_k_rope,
loc,
reserved_skip_index,
kv_buffer.stride(0),
cache_k_nope.stride(0),
cache_k_rope.stride(0),
nope_dim,
rope_dim,
BLOCK=BLOCK,
DCP_RANK=dcp_rank,
DCP_WORLD_SIZE=dcp_world_size,
**pdl_kwargs,
)
def set_mla_kv_buffer_triton(
kv_buffer: torch.Tensor,
loc: torch.Tensor,
cache_k_nope: torch.Tensor,
cache_k_rope: torch.Tensor,
*,
reserved_skip_index: int = 0,
):
"""Scatter at locs already addressing this rank's rows (widened ->
`set_mla_kv_buffer_dcp_sharded_triton`)."""
_set_mla_kv_buffer_impl(
kv_buffer,
loc,
cache_k_nope,
cache_k_rope,
reserved_skip_index=reserved_skip_index,
dcp_world_size=1,
dcp_rank=0,
)
def set_mla_kv_buffer_dcp_sharded_triton(
kv_buffer: torch.Tensor,
loc: torch.Tensor,
cache_k_nope: torch.Tensor,
cache_k_rope: torch.Tensor,
*,
reserved_skip_index: int = 0,
):
"""Scatter at DCP-WIDENED locs: select this rank's ids and collapse them."""
parallel = get_parallel()
_set_mla_kv_buffer_impl(
kv_buffer,
loc,
cache_k_nope,
cache_k_rope,
reserved_skip_index=reserved_skip_index,
dcp_world_size=parallel.attn_dcp_size,
dcp_rank=parallel.attn_dcp_rank,
)
@triton.jit
def set_mla_kv_buffer_fp8_quant_kernel(
kv_buffer_fp8_ptr,
cache_k_nope_ptr,
cache_k_rope_ptr,
loc_ptr,
reserved_skip_index,
buffer_stride: tl.constexpr,
nope_stride: tl.constexpr,
rope_stride: tl.constexpr,
nope_dim: tl.constexpr,
rope_dim: tl.constexpr,
BLOCK: tl.constexpr,
USE_GDC: tl.constexpr = False,
):
"""Fuse BF16/FP16->FP8 cast with paged KV write."""
pid_loc = tl.program_id(0)
pid_blk = tl.program_id(1)
base = pid_blk * BLOCK
offs = base + tl.arange(0, BLOCK)
total_dim = nope_dim + rope_dim
mask = offs < total_dim
if USE_GDC:
tl.extra.cuda.gdc_wait()
loc = tl.load(loc_ptr + pid_loc).to(tl.int64)
is_valid = loc != reserved_skip_index
safe_loc = tl.where(is_valid, loc, 0)
dst_ptr = kv_buffer_fp8_ptr + safe_loc * buffer_stride + offs
if base + BLOCK <= nope_dim:
src = tl.load(
cache_k_nope_ptr + pid_loc * nope_stride + offs,
mask=mask,
other=0.0,
)
elif base >= nope_dim:
offs_rope = offs - nope_dim
src = tl.load(
cache_k_rope_ptr + pid_loc * rope_stride + offs_rope,
mask=mask,
other=0.0,
)
else:
is_nope = offs < nope_dim
src_nope = tl.load(
cache_k_nope_ptr + pid_loc * nope_stride + offs,
mask=mask & is_nope,
other=0.0,
)
src_rope = tl.load(
cache_k_rope_ptr + pid_loc * rope_stride + (offs - nope_dim),
mask=mask & ~is_nope,
other=0.0,
)
src = tl.where(is_nope, src_nope, src_rope)
# Destination pointer is FP8-typed view; tl.store performs downcast.
tl.store(dst_ptr, src, mask=mask & is_valid)
if USE_GDC:
tl.extra.cuda.gdc_launch_dependents()
def set_mla_kv_buffer_triton_fp8_quant(
kv_buffer: torch.Tensor,
loc: torch.Tensor,
cache_k_nope: torch.Tensor,
cache_k_rope: torch.Tensor,
fp8_dtype: torch.dtype,
*,
reserved_skip_index: int = 0,
):
"""Fuse BF16/FP16 MLA K quantization with paged KV write.
Writes targeting ``reserved_skip_index`` are skipped. Pass -1 to disable.
"""
kv_buffer_fp8 = kv_buffer.view(fp8_dtype)
nope_dim = cache_k_nope.shape[-1]
rope_dim = cache_k_rope.shape[-1]
total_dim = nope_dim + rope_dim
BLOCK = 128
n_loc = loc.numel()
grid = (n_loc, triton.cdiv(total_dim, BLOCK))
pdl_kwargs = {"USE_GDC": True, "launch_pdl": True} if is_arch_support_pdl() else {}
set_mla_kv_buffer_fp8_quant_kernel[grid](
kv_buffer_fp8,
cache_k_nope,
cache_k_rope,
loc,
reserved_skip_index,
kv_buffer_fp8.stride(0),
cache_k_nope.stride(0),
cache_k_rope.stride(0),
nope_dim,
rope_dim,
BLOCK=BLOCK,
**pdl_kwargs,
)
@triton.jit
def set_mla_kv_scale_buffer_kernel(
kv_buffer_ptr,
cache_k_nope_ptr,
cache_k_rope_ptr,
loc_ptr,
reserved_skip_index,
buffer_stride: tl.constexpr,
nope_stride: tl.constexpr,
rope_stride: tl.constexpr,
nope_dim: tl.constexpr,
rope_dim: tl.constexpr,
BLOCK: tl.constexpr,
):
pid_loc = tl.program_id(0)
pid_blk = tl.program_id(1)
base = pid_blk * BLOCK
offs = base + tl.arange(0, BLOCK)
total_dim = nope_dim + rope_dim
mask = offs < total_dim # Make sure don't cross the boundary
loc = tl.load(loc_ptr + pid_loc)
is_valid = loc != reserved_skip_index
safe_loc = tl.where(is_valid, loc, 0)
dst_ptr = kv_buffer_ptr + safe_loc * buffer_stride + offs
# Check each offs should read 'nope' or 'rope'
is_nope = offs < nope_dim
src_nope = tl.load(
cache_k_nope_ptr + pid_loc * nope_stride + offs, mask=mask & is_nope, other=0.0
)
src_rope = tl.load(
cache_k_rope_ptr + pid_loc * rope_stride + (offs - nope_dim),
mask=mask & ~is_nope,
other=0.0,
)
# Combine nope + rope
src = src_nope + src_rope
tl.store(dst_ptr, src, mask=mask & is_valid)
def set_mla_kv_scale_buffer_triton(
kv_buffer: torch.Tensor,
loc: torch.Tensor,
cache_k_nope: torch.Tensor,
cache_k_rope: torch.Tensor,
*,
reserved_skip_index: int = 0,
):
"""Write MLA scale rows while preserving the reserved padding slot."""
nope_dim = cache_k_nope.shape[-1]
rope_dim = cache_k_rope.shape[-1]
total_dim = nope_dim + rope_dim
BLOCK = 128 # Keep origin, works for smaller total_dim as well.
n_loc = loc.numel()
grid = (n_loc, triton.cdiv(total_dim, BLOCK))
set_mla_kv_scale_buffer_kernel[grid](
kv_buffer,
cache_k_nope,
cache_k_rope,
loc,
reserved_skip_index,
kv_buffer.stride(0),
cache_k_nope.stride(0),
cache_k_rope.stride(0),
nope_dim,
rope_dim,
BLOCK=BLOCK,
)
@triton.jit
def get_mla_kv_buffer_kernel(
kv_buffer_ptr,
cache_k_nope_ptr,
cache_k_rope_ptr,
loc_ptr,
buffer_stride: tl.constexpr,
nope_stride: tl.constexpr,
rope_stride: tl.constexpr,
nope_dim: tl.constexpr,
rope_dim: tl.constexpr,
):
pid_loc = tl.program_id(0)
loc = tl.load(loc_ptr + pid_loc).to(tl.int64)
loc_src_ptr = kv_buffer_ptr + loc * buffer_stride
nope_offs = tl.arange(0, nope_dim)
nope_src_ptr = loc_src_ptr + nope_offs
nope_src = tl.load(nope_src_ptr)
tl.store(
cache_k_nope_ptr + pid_loc * nope_stride + nope_offs,
nope_src,
)
rope_offs = tl.arange(0, rope_dim)
rope_src_ptr = loc_src_ptr + nope_dim + rope_offs
rope_src = tl.load(rope_src_ptr)
tl.store(
cache_k_rope_ptr + pid_loc * rope_stride + rope_offs,
rope_src,
)
@triton.jit
def get_mla_kv_buffer_kernel_norope(
kv_buffer_ptr,
cache_k_nope_ptr,
loc_ptr,
buffer_stride: tl.constexpr,
nope_stride: tl.constexpr,
nope_dim: tl.constexpr,
):
pid_loc = tl.program_id(0)
loc = tl.load(loc_ptr + pid_loc).to(tl.int64)
loc_src_ptr = kv_buffer_ptr + loc * buffer_stride
nope_offs = tl.arange(0, nope_dim)
nope_src = tl.load(loc_src_ptr + nope_offs)
tl.store(
cache_k_nope_ptr + pid_loc * nope_stride + nope_offs,
nope_src,
)
def get_mla_kv_buffer_triton(
kv_buffer: torch.Tensor,
loc: torch.Tensor,
cache_k_nope: torch.Tensor,
cache_k_rope: Optional[torch.Tensor] = None,
):
# The source data type will be implicitly converted to the target data type.
nope_dim = cache_k_nope.shape[-1] # 512
n_loc = loc.numel()
grid = (n_loc,)
has_rope = cache_k_rope is not None and cache_k_rope.numel() > 0
if not has_rope:
get_mla_kv_buffer_kernel_norope[grid](
kv_buffer,
cache_k_nope,
loc,
kv_buffer.stride(0),
cache_k_nope.stride(0),
nope_dim,
)
return
rope_dim = cache_k_rope.shape[-1] # 64
get_mla_kv_buffer_kernel[grid](
kv_buffer,
cache_k_nope,
cache_k_rope,
loc,
kv_buffer.stride(0),
cache_k_nope.stride(0),
cache_k_rope.stride(0),
nope_dim,
rope_dim,
)