from __future__ import annotations import torch import triton import triton.language as tl from sglang.jit_kernel.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, 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 % 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() # Above this loc count the TMA bulk-store path overtakes the single-CTA-per-loc # Triton kernel. Below it, Triton with BLOCK = next_pow2(total_dim) (one CTA # does the whole row in one tile, no boundary fan-out) is the winning fallback. # Tuned on GB300 with DSv4 row widths. _TMA_BULK_STORE_MIN_LOCS = 768 def set_mla_kv_buffer_triton( kv_buffer: torch.Tensor, loc: torch.Tensor, cache_k_nope: torch.Tensor, cache_k_rope: torch.Tensor, ): """Dispatch MLA paged-KV scatter writes to the fastest available path. Two paths, chosen on ``n_loc``: - ``n_loc >= 768`` (and 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]]``. Wins at large bs because it packs 4-8 items per CTA, drastically reducing the CTA count vs 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. Wins at small bs because there's no per-loc CTA fan-out (5x fewer CTAs than the old BLOCK=128 dispatch) and the row-spanning block makes the boundary branch a one-shot per CTA. This is also 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. """ from sglang.jit_kernel.set_mla_kv_buffer import ( can_use_set_mla_kv_buffer, ) from sglang.jit_kernel.set_mla_kv_buffer import ( set_mla_kv_buffer as jit_set_mla_kv_buffer, ) n_loc = loc.numel() 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 ( n_loc >= _TMA_BULK_STORE_MIN_LOCS and is_arch_support_pdl() and can_use_set_mla_kv_buffer(nope_bytes, rope_bytes) and not get_parallel().dcp_enabled ): jit_set_mla_kv_buffer(kv_buffer, loc, cache_k_nope, cache_k_rope) 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 offsFP8 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) dst_ptr = kv_buffer_fp8_ptr + 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) 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, ): """Fuse BF16/FP16 MLA K quantization with paged KV write.""" 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, 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, 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) dst_ptr = kv_buffer_ptr + 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) def set_mla_kv_scale_buffer_triton( kv_buffer: torch.Tensor, loc: torch.Tensor, cache_k_nope: torch.Tensor, cache_k_rope: torch.Tensor, ): 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, 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, ) def get_mla_kv_buffer_triton( kv_buffer: torch.Tensor, loc: torch.Tensor, cache_k_nope: torch.Tensor, cache_k_rope: torch.Tensor, ): # The source data type will be implicitly converted to the target data type. nope_dim = cache_k_nope.shape[-1] # 512 rope_dim = cache_k_rope.shape[-1] # 64 n_loc = loc.numel() grid = (n_loc,) 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, )