94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
505 lines
18 KiB
Python
505 lines
18 KiB
Python
"""
|
|
Fused FP8 quantization + paged KV cache write kernel for TRTLLM MHA backend.
|
|
|
|
This kernel fuses the following operations:
|
|
1. FP8 quantization of K and V tensors (from BF16/FP16 to FP8)
|
|
2. Per-token or per-page scale computation
|
|
3. Writing quantized K/V to paged KV cache layout
|
|
|
|
Performance benefits:
|
|
- Eliminates intermediate FP8 tensors in memory
|
|
- Reduces kernel launch overhead
|
|
- Better memory bandwidth utilization
|
|
"""
|
|
|
|
import logging
|
|
from typing import Optional
|
|
|
|
import torch
|
|
import triton
|
|
import triton.language as tl
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@triton.jit
|
|
def _process_kv_tensor(
|
|
token_id,
|
|
head_block_id,
|
|
page_id,
|
|
page_offset,
|
|
input_ptr,
|
|
cache_ptr,
|
|
inv_scale,
|
|
use_provided_scale: tl.constexpr,
|
|
num_kv_heads: tl.constexpr,
|
|
head_dim: tl.constexpr,
|
|
input_stride_token: tl.constexpr,
|
|
input_stride_head: tl.constexpr,
|
|
input_stride_dim: tl.constexpr,
|
|
cache_stride_page: tl.constexpr,
|
|
cache_stride_offset: tl.constexpr,
|
|
cache_stride_head: tl.constexpr,
|
|
cache_stride_dim: tl.constexpr,
|
|
BLOCK_HEAD: tl.constexpr,
|
|
BLOCK_DIM: tl.constexpr,
|
|
):
|
|
"""Process a block of heads for a single K or V tensor."""
|
|
head_idx = head_block_id * BLOCK_HEAD
|
|
num_heads_in_block = min(BLOCK_HEAD, num_kv_heads - head_idx)
|
|
|
|
for dim_idx in range(0, head_dim, BLOCK_DIM):
|
|
num_dims_in_block = min(BLOCK_DIM, head_dim - dim_idx)
|
|
|
|
head_offsets = head_idx + tl.arange(0, BLOCK_HEAD)
|
|
dim_offsets = dim_idx + tl.arange(0, BLOCK_DIM)
|
|
|
|
head_mask = head_offsets < (head_idx + num_heads_in_block)
|
|
dim_mask = dim_offsets < (dim_idx + num_dims_in_block)
|
|
|
|
# Load from input using 3D strides
|
|
input_offsets = (
|
|
token_id * input_stride_token
|
|
+ head_offsets[:, None] * input_stride_head
|
|
+ dim_offsets[None, :] * input_stride_dim
|
|
)
|
|
mask = head_mask[:, None] & dim_mask[None, :]
|
|
|
|
block = tl.load(input_ptr + input_offsets, mask=mask, other=0.0)
|
|
|
|
# Quantize to FP8
|
|
if use_provided_scale:
|
|
block_fp8 = (block * inv_scale).to(tl.float8e4nv)
|
|
else:
|
|
block_fp8 = block.to(tl.float8e4nv)
|
|
|
|
# Write to cache at [page_id, page_offset, head, dim]
|
|
cache_offsets = (
|
|
page_id * cache_stride_page
|
|
+ page_offset * cache_stride_offset
|
|
+ head_offsets[:, None] * cache_stride_head
|
|
+ dim_offsets[None, :] * cache_stride_dim
|
|
)
|
|
|
|
tl.store(cache_ptr + cache_offsets, block_fp8, mask=mask)
|
|
|
|
|
|
@triton.jit
|
|
def _fused_fp8_set_kv_buffer_kernel(
|
|
# Input tensors (post-RoPE K and V in FP16/BF16)
|
|
k_ptr, # [num_tokens, num_kv_heads, head_dim]
|
|
v_ptr, # [num_tokens, num_kv_heads, head_dim]
|
|
# Output KV cache buffers (FP8 paged layout)
|
|
k_cache_ptr, # [total_slots, num_kv_heads, head_dim]
|
|
v_cache_ptr, # [total_slots, num_kv_heads, head_dim]
|
|
# Cache location indices
|
|
cache_loc_ptr, # [num_tokens] -> token to cache location mapping
|
|
# Pointers to scalar inverse scales (computed on GPU in wrapper)
|
|
inv_k_scale_ptr, # pointer to 0-D tensor on GPU
|
|
inv_v_scale_ptr, # pointer to 0-D tensor on GPU
|
|
use_provided_scale: tl.constexpr, # whether to use provided scale
|
|
# Tensor dimensions
|
|
num_kv_heads: tl.constexpr,
|
|
head_dim: tl.constexpr,
|
|
page_size: tl.constexpr,
|
|
# Strides for K input [num_tokens, num_kv_heads, head_dim]
|
|
k_stride_token: tl.constexpr,
|
|
k_stride_head: tl.constexpr,
|
|
k_stride_dim: tl.constexpr,
|
|
# Strides for K cache [total_slots, num_kv_heads, head_dim] (logically paged)
|
|
k_cache_stride_page: tl.constexpr,
|
|
k_cache_stride_offset: tl.constexpr,
|
|
k_cache_stride_head: tl.constexpr,
|
|
k_cache_stride_dim: tl.constexpr,
|
|
# Strides for V input [num_tokens, num_kv_heads, head_dim]
|
|
v_stride_token: tl.constexpr,
|
|
v_stride_head: tl.constexpr,
|
|
v_stride_dim: tl.constexpr,
|
|
# Strides for V cache [total_slots, num_kv_heads, head_dim] (logically paged)
|
|
v_cache_stride_page: tl.constexpr,
|
|
v_cache_stride_offset: tl.constexpr,
|
|
v_cache_stride_head: tl.constexpr,
|
|
v_cache_stride_dim: tl.constexpr,
|
|
# Block sizes
|
|
BLOCK_HEAD: tl.constexpr, # Number of heads per block
|
|
BLOCK_DIM: tl.constexpr, # Head dimension block size
|
|
):
|
|
"""
|
|
Fused FP8 quantization + paged KV cache write kernel.
|
|
|
|
Each program processes one token-head_block-kv combination, quantizing and writing
|
|
to the appropriate page in the KV cache.
|
|
|
|
Grid: (num_tokens, num_head_blocks, 2) where dim2: 0=K, 1=V
|
|
"""
|
|
# Get program IDs
|
|
token_id = tl.program_id(0)
|
|
head_block_id = tl.program_id(1)
|
|
kv_idx = tl.program_id(2) # 0 for K, 1 for V
|
|
|
|
# Get cache location for this token
|
|
cache_loc = tl.load(cache_loc_ptr + token_id)
|
|
|
|
# Compute page_id and offset within page
|
|
page_id = cache_loc // page_size
|
|
page_offset = cache_loc % page_size
|
|
|
|
# Select K or V based on kv_idx
|
|
if kv_idx == 0:
|
|
# Process K tensor
|
|
if use_provided_scale:
|
|
inv_scale = tl.load(inv_k_scale_ptr)
|
|
else:
|
|
inv_scale = 1.0
|
|
_process_kv_tensor(
|
|
token_id,
|
|
head_block_id,
|
|
page_id,
|
|
page_offset,
|
|
k_ptr,
|
|
k_cache_ptr,
|
|
inv_scale,
|
|
use_provided_scale,
|
|
num_kv_heads,
|
|
head_dim,
|
|
k_stride_token,
|
|
k_stride_head,
|
|
k_stride_dim,
|
|
k_cache_stride_page,
|
|
k_cache_stride_offset,
|
|
k_cache_stride_head,
|
|
k_cache_stride_dim,
|
|
BLOCK_HEAD,
|
|
BLOCK_DIM,
|
|
)
|
|
else:
|
|
# Process V tensor
|
|
if use_provided_scale:
|
|
inv_scale = tl.load(inv_v_scale_ptr)
|
|
else:
|
|
inv_scale = 1.0
|
|
_process_kv_tensor(
|
|
token_id,
|
|
head_block_id,
|
|
page_id,
|
|
page_offset,
|
|
v_ptr,
|
|
v_cache_ptr,
|
|
inv_scale,
|
|
use_provided_scale,
|
|
num_kv_heads,
|
|
head_dim,
|
|
v_stride_token,
|
|
v_stride_head,
|
|
v_stride_dim,
|
|
v_cache_stride_page,
|
|
v_cache_stride_offset,
|
|
v_cache_stride_head,
|
|
v_cache_stride_dim,
|
|
BLOCK_HEAD,
|
|
BLOCK_DIM,
|
|
)
|
|
|
|
|
|
def fused_fp8_set_kv_buffer(
|
|
k: torch.Tensor, # [num_tokens, num_kv_heads, head_dim] or [num_tokens, num_kv_heads * head_dim]
|
|
v: torch.Tensor, # [num_tokens, num_kv_heads, head_dim] or [num_tokens, num_kv_heads * head_dim]
|
|
k_cache: torch.Tensor, # [total_slots, num_kv_heads, head_dim] or [num_pages, page_size, num_kv_heads, head_dim]
|
|
v_cache: torch.Tensor, # [total_slots, num_kv_heads, head_dim] or [num_pages, page_size, num_kv_heads, head_dim]
|
|
cache_loc: torch.Tensor, # [num_tokens], dtype=int32
|
|
k_scale: Optional[
|
|
float
|
|
] = None, # Scalar scale (matching original set_kv_buffer signature)
|
|
v_scale: Optional[float] = None,
|
|
page_size: int = 16,
|
|
use_triton: bool = True, # Whether to use Triton kernel (set to False to force naive fallback)
|
|
) -> None:
|
|
"""
|
|
Python wrapper for the fused FP8 quantization + paged KV cache write kernel.
|
|
|
|
This function replicates the exact behavior of the original set_kv_buffer but with
|
|
a fused kernel that combines FP8 quantization and cache write.
|
|
|
|
Args:
|
|
k: Key tensor after RoPE, can be 2D or 3D
|
|
v: Value tensor, can be 2D or 3D
|
|
k_cache: Paged K cache buffer in FP8
|
|
v_cache: Paged V cache buffer in FP8
|
|
cache_loc: Cache location for each token, shape [num_tokens]
|
|
k_scale: Optional scalar scale for K (matching original set_kv_buffer)
|
|
v_scale: Optional scalar scale for V (matching original set_kv_buffer)
|
|
page_size: Number of tokens per page
|
|
use_triton: Whether to use optimized Triton kernel
|
|
"""
|
|
num_tokens = k.shape[0]
|
|
|
|
# Step 1: Infer num_kv_heads and head_dim from cache shape
|
|
if k_cache.ndim == 3:
|
|
# 3D cache layout: [total_slots, num_kv_heads, head_dim]
|
|
total_slots, num_kv_heads, head_dim = k_cache.shape
|
|
assert (
|
|
total_slots % page_size == 0
|
|
), f"total_slots ({total_slots}) must be divisible by page_size ({page_size})"
|
|
num_pages = total_slots // page_size
|
|
elif k_cache.ndim == 4:
|
|
# 4D cache layout: [num_pages, page_size, num_kv_heads, head_dim]
|
|
num_pages, ps, num_kv_heads, head_dim = k_cache.shape
|
|
assert (
|
|
ps == page_size
|
|
), f"page_size mismatch: cache has {ps}, expected {page_size}"
|
|
total_slots = num_pages * page_size
|
|
else:
|
|
raise ValueError(f"Unsupported k_cache.ndim={k_cache.ndim}, expected 3 or 4")
|
|
|
|
# Step 2: Validate k, v shapes and normalize
|
|
# Store original 3D shape for Triton path
|
|
k_3d = None
|
|
v_3d = None
|
|
|
|
if k.ndim == 3:
|
|
# Input is [num_tokens, num_kv_heads, head_dim]
|
|
assert (
|
|
k.shape[1] == num_kv_heads
|
|
), f"num_kv_heads mismatch: k.shape[1]={k.shape[1]} vs cache={num_kv_heads}"
|
|
assert (
|
|
k.shape[2] == head_dim
|
|
), f"head_dim mismatch: k.shape[2]={k.shape[2]} vs cache={head_dim}"
|
|
assert v.shape[1] == num_kv_heads and v.shape[2] == head_dim, "v shape mismatch"
|
|
|
|
# Keep 3D for Triton kernel
|
|
k_3d = k
|
|
v_3d = v
|
|
# Create 2D view for naive fallback (will be used only if use_triton=False)
|
|
k_2d = k.reshape(num_tokens, num_kv_heads * head_dim)
|
|
v_2d = v.reshape(num_tokens, num_kv_heads * head_dim)
|
|
elif k.ndim == 2:
|
|
# Input is already [num_tokens, num_kv_heads * head_dim]
|
|
assert (
|
|
k.shape[1] == num_kv_heads * head_dim
|
|
), f"k.shape[1]={k.shape[1]} != {num_kv_heads * head_dim}"
|
|
assert (
|
|
v.shape[1] == num_kv_heads * head_dim
|
|
), f"v.shape[1]={v.shape[1]} != {num_kv_heads * head_dim}"
|
|
|
|
# Create 3D view for Triton kernel
|
|
k_3d = k.view(num_tokens, num_kv_heads, head_dim)
|
|
v_3d = v.view(num_tokens, num_kv_heads, head_dim)
|
|
# Keep 2D for naive
|
|
k_2d = k
|
|
v_2d = v
|
|
else:
|
|
raise ValueError(f"Unsupported k.ndim={k.ndim}, expected 2 or 3")
|
|
|
|
# Step 3: Compute cache strides based on layout
|
|
if k_cache.ndim == 3:
|
|
# 3D cache: [total_slots, num_kv_heads, head_dim]
|
|
stride_slot = k_cache.stride(0)
|
|
stride_head = k_cache.stride(1)
|
|
stride_dim = k_cache.stride(2)
|
|
|
|
k_cache_stride_page = stride_slot * page_size
|
|
k_cache_stride_offset = stride_slot
|
|
k_cache_stride_head = stride_head
|
|
k_cache_stride_dim = stride_dim
|
|
|
|
v_stride_slot = v_cache.stride(0)
|
|
v_stride_head = v_cache.stride(1)
|
|
v_stride_dim = v_cache.stride(2)
|
|
|
|
v_cache_stride_page = v_stride_slot * page_size
|
|
v_cache_stride_offset = v_stride_slot
|
|
v_cache_stride_head = v_stride_head
|
|
v_cache_stride_dim = v_stride_dim
|
|
else:
|
|
# 4D cache: [num_pages, page_size, num_kv_heads, head_dim]
|
|
k_cache_stride_page = k_cache.stride(0)
|
|
k_cache_stride_offset = k_cache.stride(1)
|
|
k_cache_stride_head = k_cache.stride(2)
|
|
k_cache_stride_dim = k_cache.stride(3)
|
|
|
|
v_cache_stride_page = v_cache.stride(0)
|
|
v_cache_stride_offset = v_cache.stride(1)
|
|
v_cache_stride_head = v_cache.stride(2)
|
|
v_cache_stride_dim = v_cache.stride(3)
|
|
|
|
# Decide whether to use provided scale
|
|
use_provided_scale = k_scale is not None and v_scale is not None
|
|
|
|
if use_triton and num_tokens > 0:
|
|
# Use optimized Triton kernel
|
|
# Compute input strides for 3D k, v: [num_tokens, num_kv_heads, head_dim]
|
|
k_stride_token = k_3d.stride(0)
|
|
k_stride_head = k_3d.stride(1)
|
|
k_stride_dim = k_3d.stride(2)
|
|
|
|
v_stride_token = v_3d.stride(0)
|
|
v_stride_head = v_3d.stride(1)
|
|
v_stride_dim = v_3d.stride(2)
|
|
|
|
# Block sizes for tiling (tunable)
|
|
BLOCK_HEAD = min(num_kv_heads, 8) # Process up to 8 heads at once
|
|
BLOCK_DIM = min(head_dim, 128) # Process up to 128 dims at once
|
|
|
|
# Compute number of head blocks
|
|
num_head_blocks = (num_kv_heads + BLOCK_HEAD - 1) // BLOCK_HEAD
|
|
|
|
# Grid: (num_tokens, num_head_blocks, 2)
|
|
# - dim 0: tokens
|
|
# - dim 1: head blocks
|
|
# - dim 2: K/V (0=K, 1=V)
|
|
grid = (num_tokens, num_head_blocks, 2)
|
|
|
|
device = k_3d.device
|
|
|
|
def _to_tensor_scale(scale):
|
|
"""Convert scale to 0-D CUDA tensor (accepts Python float or Tensor)."""
|
|
if isinstance(scale, torch.Tensor):
|
|
return scale.to(device=device, dtype=torch.float32)
|
|
else:
|
|
# Python float / np scalar
|
|
return torch.tensor(float(scale), device=device, dtype=torch.float32)
|
|
|
|
# Compute inverse scales on GPU to avoid GPU→CPU sync in CUDA graph capture.
|
|
# Previously we used float(k_scale) which triggers synchronization and fails
|
|
# during CUDA graph capture with cudaErrorStreamCaptureUnsupported.
|
|
if use_provided_scale:
|
|
k_scale_tensor = _to_tensor_scale(k_scale)
|
|
v_scale_tensor = _to_tensor_scale(v_scale)
|
|
|
|
# Pure GPU scalar operation, safe for CUDA graph
|
|
inv_k_scale = (1.0 / k_scale_tensor).to(device=device, dtype=torch.float32)
|
|
inv_v_scale = (1.0 / v_scale_tensor).to(device=device, dtype=torch.float32)
|
|
|
|
inv_k_scale_ptr = inv_k_scale
|
|
inv_v_scale_ptr = inv_v_scale
|
|
else:
|
|
# When use_provided_scale=False, kernel uses constant 1.0 for inv_scale.
|
|
# Triton will optimize away the tl.load() calls via constant folding.
|
|
# We pass dummy pointers (k_3d) which won't be accessed in the kernel.
|
|
# This avoids creating new GPU tensors during CUDA graph capture.
|
|
inv_k_scale_ptr = k_3d
|
|
inv_v_scale_ptr = k_3d
|
|
|
|
# Launch Triton kernel
|
|
_fused_fp8_set_kv_buffer_kernel[grid](
|
|
k_3d,
|
|
v_3d,
|
|
k_cache,
|
|
v_cache,
|
|
cache_loc,
|
|
inv_k_scale_ptr,
|
|
inv_v_scale_ptr,
|
|
use_provided_scale,
|
|
num_kv_heads,
|
|
head_dim,
|
|
page_size,
|
|
k_stride_token,
|
|
k_stride_head,
|
|
k_stride_dim,
|
|
k_cache_stride_page,
|
|
k_cache_stride_offset,
|
|
k_cache_stride_head,
|
|
k_cache_stride_dim,
|
|
v_stride_token,
|
|
v_stride_head,
|
|
v_stride_dim,
|
|
v_cache_stride_page,
|
|
v_cache_stride_offset,
|
|
v_cache_stride_head,
|
|
v_cache_stride_dim,
|
|
BLOCK_HEAD=BLOCK_HEAD,
|
|
BLOCK_DIM=BLOCK_DIM,
|
|
)
|
|
else:
|
|
# Fallback to naive implementation
|
|
_naive_fp8_set_kv_buffer(
|
|
k_2d, v_2d, k_cache, v_cache, cache_loc, k_scale, v_scale, page_size
|
|
)
|
|
|
|
|
|
def _naive_fp8_set_kv_buffer(
|
|
k: torch.Tensor,
|
|
v: torch.Tensor,
|
|
k_cache: torch.Tensor,
|
|
v_cache: torch.Tensor,
|
|
cache_loc: torch.Tensor,
|
|
k_scale: Optional[float],
|
|
v_scale: Optional[float],
|
|
page_size: int,
|
|
) -> None:
|
|
"""
|
|
Naive fallback implementation that mimics the original set_kv_buffer logic.
|
|
|
|
This directly replicates the behavior of MHATokenToKVPool.set_kv_buffer:
|
|
1. Apply scale (if k.dtype != cache.dtype and scale is provided)
|
|
2. Convert to FP8
|
|
3. Write to cache at cache_loc
|
|
|
|
Args:
|
|
k: [num_tokens, num_kv_heads * head_dim], already reshaped to 2D
|
|
v: [num_tokens, num_kv_heads * head_dim], already reshaped to 2D
|
|
k_cache: [total_slots, num_kv_heads, head_dim] or [num_pages, page_size, num_kv_heads, head_dim]
|
|
v_cache: Same shape as k_cache
|
|
cache_loc: [num_tokens]
|
|
k_scale: Optional scale for K
|
|
v_scale: Optional scale for V
|
|
page_size: Tokens per page
|
|
"""
|
|
num_tokens = k.shape[0]
|
|
|
|
# Infer dimensions from cache
|
|
if k_cache.ndim == 3:
|
|
num_kv_heads = k_cache.shape[1]
|
|
head_dim = k_cache.shape[2]
|
|
elif k_cache.ndim == 4:
|
|
num_kv_heads = k_cache.shape[2]
|
|
head_dim = k_cache.shape[3]
|
|
else:
|
|
raise ValueError(f"Unsupported k_cache.ndim={k_cache.ndim}")
|
|
|
|
# Determine target dtype and storage dtype
|
|
# See: python/sglang/srt/mem_cache/memory_pool.py:445-449
|
|
store_dtype = k_cache.dtype
|
|
if store_dtype == torch.uint8:
|
|
# Cache is stored as uint8 for FP8 (due to index_put limitation)
|
|
dtype = torch.float8_e4m3fn # Logical dtype
|
|
else:
|
|
dtype = store_dtype # Cache dtype is the logical dtype
|
|
|
|
# Replicate the original set_kv_buffer behavior
|
|
# See: python/sglang/srt/mem_cache/memory_pool.py:777-799
|
|
if k.dtype != dtype:
|
|
# Need quantization - clone first to avoid modifying input
|
|
k = k.clone()
|
|
v = v.clone()
|
|
|
|
if k_scale is not None:
|
|
k.div_(k_scale) # In-place division
|
|
if v_scale is not None:
|
|
v.div_(v_scale) # In-place division
|
|
|
|
k = k.to(dtype)
|
|
v = v.to(dtype)
|
|
|
|
# View FP8 as uint8 if needed (for index_put compatibility)
|
|
if store_dtype == torch.uint8 and dtype in (torch.float8_e5m2, torch.float8_e4m3fn):
|
|
k = k.view(torch.uint8)
|
|
v = v.view(torch.uint8)
|
|
|
|
# Reshape from [T, H*D] to [T, H, D]
|
|
k = k.view(num_tokens, num_kv_heads, head_dim)
|
|
v = v.view(num_tokens, num_kv_heads, head_dim)
|
|
|
|
# Write to cache using advanced indexing (same as original)
|
|
if k_cache.ndim == 3:
|
|
# 3D cache: [total_slots, H, D]
|
|
k_cache[cache_loc] = k
|
|
v_cache[cache_loc] = v
|
|
else:
|
|
# 4D cache: [num_pages, page_size, H, D]
|
|
# Decompose loc into page_id and page_offset (vectorized)
|
|
page_ids = cache_loc // page_size
|
|
page_offsets = cache_loc % page_size
|
|
k_cache[page_ids, page_offsets] = k
|
|
v_cache[page_ids, page_offsets] = v
|