Add the KV-canary plan JIT kernels (#26807)

This commit is contained in:
fzyzcjy
2026-05-31 09:53:56 +08:00
committed by GitHub
parent 70e983a2da
commit 736ad1f32a
18 changed files with 7652 additions and 0 deletions
@@ -0,0 +1,345 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional, Tuple
import torch
import triton
import triton.testing
from sglang.jit_kernel.benchmark.kv_canary.utils import (
POOL_AXIS,
SWA_WINDOW,
BenchCase,
build_fast_matrix_cases,
build_full_matrix_cases,
naive_cumsum_fn,
)
from sglang.jit_kernel.benchmark.utils import (
DEFAULT_DEVICE,
get_benchmark_range,
run_benchmark,
)
from sglang.jit_kernel.kv_canary.plan import launch_canary_plan_kernels
from sglang.jit_kernel.kv_canary.verify import VerifyPlan
from sglang.jit_kernel.kv_canary.write import WritePlan
from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=900, suite="nightly-kernel-1-gpu", nightly=True)
_TOTAL_TOKENS_AXIS: list[int] = [256, 4096, 65536, 262144]
_TOTAL_TOKENS_BS_AXIS: list[int] = [1, 32, 256]
@dataclass(frozen=True, slots=True, kw_only=True)
class _TotalTokensBenchCase:
bs: int
total_tokens: int
pool_kind: str
def _build_total_tokens_cases() -> list[_TotalTokensBenchCase]:
cases: list[_TotalTokensBenchCase] = []
for bs in _TOTAL_TOKENS_BS_AXIS:
for total_tokens in _TOTAL_TOKENS_AXIS:
if total_tokens < bs:
continue
for pool_kind in POOL_AXIS:
cases.append(
_TotalTokensBenchCase(
bs=bs, total_tokens=total_tokens, pool_kind=pool_kind
)
)
return cases
_POOL_CAPACITY_VERIFY_CAP_AXIS: list[int] = [16384, 262144, 1398028]
_POOL_CAPACITY_BS_AXIS: list[int] = [1, 4, 32]
_POOL_CAPACITY_PREFIX_LEN: int = 512
_POOL_CAPACITY_BS_PADDED_AXIS: list[Optional[int]] = [None, 4096]
@dataclass(frozen=True, slots=True, kw_only=True)
class _PoolCapacityBenchCase:
"""One pool-capacity bench point.
Attributes:
bs: Number of active (non-padding) requests in the launch.
bs_padded: Total request-axis size of the input tensors. ``None`` means
no padding (``bs_padded == bs``); a concrete value pads ``req_pool_indices``
with ``REQ_POOL_IDX_PADDING`` sentinels in rows ``[bs, bs_padded)``.
prefix_len: Per-active-request prefix length.
verify_capacity: Plan tensor row capacity.
pool_kind: "full".
"""
bs: int
bs_padded: Optional[int]
prefix_len: int
verify_capacity: int
pool_kind: str
def _build_pool_capacity_cases() -> list[_PoolCapacityBenchCase]:
cases: list[_PoolCapacityBenchCase] = []
for bs in _POOL_CAPACITY_BS_AXIS:
for verify_capacity in _POOL_CAPACITY_VERIFY_CAP_AXIS:
for bs_padded in _POOL_CAPACITY_BS_PADDED_AXIS:
if bs_padded is not None and bs_padded < bs:
continue
cases.append(
_PoolCapacityBenchCase(
bs=bs,
bs_padded=bs_padded,
prefix_len=_POOL_CAPACITY_PREFIX_LEN,
verify_capacity=verify_capacity,
pool_kind="full",
)
)
return cases
_X_NAMES_MATRIX = ["scenario", "bs", "prefix_len", "mode", "extend_len", "pool_kind"]
def _cases_to_matrix_x_vals(
cases: list[BenchCase],
) -> list[tuple[str, int, int, str, int, str]]:
return [
(c.scenario, c.bs, c.prefix_len, c.mode, c.extend_len, c.pool_kind)
for c in cases
]
_X_VALS_MATRIX = _cases_to_matrix_x_vals(
get_benchmark_range(
full_range=build_full_matrix_cases(),
ci_range=build_fast_matrix_cases(),
)
)
_X_NAMES_TT = ["bs", "total_tokens", "pool_kind"]
_X_VALS_TT = [(c.bs, c.total_tokens, c.pool_kind) for c in _build_total_tokens_cases()]
_X_NAMES_PC = ["bs", "bs_padded", "prefix_len", "verify_capacity", "pool_kind"]
_X_VALS_PC = [
(
c.bs,
c.bs_padded if c.bs_padded is not None else c.bs,
c.prefix_len,
c.verify_capacity,
c.pool_kind,
)
for c in _build_pool_capacity_cases()
]
def _build_plan_inputs(
*,
bs: int,
prefix_len: int,
extend_len: int,
pool_kind: str,
device: torch.device,
verify_capacity_override: Optional[int] = None,
bs_padded: Optional[int] = None,
) -> dict:
swa_window_size = SWA_WINDOW if pool_kind == "swa_window_128" else 0
verify_per_req = min(prefix_len, SWA_WINDOW) if swa_window_size > 0 else prefix_len
if verify_capacity_override is not None:
verify_capacity = max(1, verify_capacity_override)
else:
verify_capacity = max(1, bs * verify_per_req)
effective_bs = bs_padded if bs_padded is not None else bs
if effective_bs < bs:
raise ValueError(f"kv-canary bench: bs_padded={bs_padded} must be >= bs={bs}")
write_req_capacity = max(1, effective_bs)
verify_plan = VerifyPlan.allocate(verify_capacity=verify_capacity, device=device)
write_plan = WritePlan.allocate(
write_req_capacity=write_req_capacity, device=device
)
req_pool_indices = torch.zeros(effective_bs, dtype=torch.int64, device=device)
req_pool_indices[:bs] = torch.arange(1, bs + 1, dtype=torch.int64, device=device)
prefix_lens = torch.zeros(effective_bs, dtype=torch.int64, device=device)
prefix_lens[:bs] = prefix_len
extend_seq_lens = torch.zeros(effective_bs, dtype=torch.int64, device=device)
extend_seq_lens[:bs] = extend_len
max_seq_len = max(prefix_len + extend_len, 1)
req_to_token_rows = effective_bs + 1
req_to_token = torch.zeros(
req_to_token_rows,
max_seq_len,
dtype=torch.int32,
device=device,
)
if bs > 0:
row_idx = torch.arange(1, bs + 1, dtype=torch.int32, device=device).unsqueeze(1)
col_idx = torch.arange(max_seq_len, dtype=torch.int32, device=device).unsqueeze(
0
)
req_to_token[1 : bs + 1] = (row_idx - 1) * max_seq_len + col_idx
if swa_window_size > 0:
full_pool_size = effective_bs * max_seq_len + 1
full_to_swa: Optional[torch.Tensor] = torch.arange(
full_pool_size + 1, dtype=torch.int64, device=device
)
full_to_swa[-1] = -1
else:
full_to_swa = None
return dict(
verify_plan_out=verify_plan,
write_plan_out=write_plan,
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
swa_window_size=swa_window_size,
full_to_swa_index_mapping=full_to_swa,
verify_capacity=int(verify_plan.verify_slot_indices.shape[0]),
)
def _make_plan_callable(inputs: dict):
def fn() -> None:
launch_canary_plan_kernels(
verify_plan_out=inputs["verify_plan_out"],
write_plan_out=inputs["write_plan_out"],
req_pool_indices=inputs["req_pool_indices"],
prefix_lens=inputs["prefix_lens"],
extend_seq_lens=inputs["extend_seq_lens"],
req_to_token=inputs["req_to_token"],
swa_window_size=inputs["swa_window_size"],
full_to_swa_index_mapping=inputs["full_to_swa_index_mapping"],
verify_capacity=inputs["verify_capacity"],
req_to_verify_expected_tokens=None,
req_to_verify_expected_tokens_valid_lens=None,
kv_token_id_vs_position_offset=0,
)
return fn
@triton.testing.perf_report(
triton.testing.Benchmark(
x_names=_X_NAMES_MATRIX,
x_vals=_X_VALS_MATRIX,
line_arg="provider",
line_vals=["canary", "naive"],
line_names=["canary_plan_step", "naive torch.cumsum"],
styles=[("blue", "-"), ("red", "--")],
ylabel="us",
plot_name="kv-canary-plan-matrix-perf",
args={},
)
)
def benchmark_matrix(
scenario: str,
bs: int,
prefix_len: int,
mode: str,
extend_len: int,
pool_kind: str,
provider: str,
) -> Tuple[float, float, float]:
del scenario
del mode
device = torch.device(DEFAULT_DEVICE)
if provider == "canary":
inputs = _build_plan_inputs(
bs=bs,
prefix_len=prefix_len,
extend_len=extend_len,
pool_kind=pool_kind,
device=device,
)
fn = _make_plan_callable(inputs)
else:
fn = naive_cumsum_fn(bs=bs, device=device)
return run_benchmark(fn)
@triton.testing.perf_report(
triton.testing.Benchmark(
x_names=_X_NAMES_TT,
x_vals=_X_VALS_TT,
line_arg="provider",
line_vals=["canary", "naive"],
line_names=["canary_plan_step", "naive torch.cumsum"],
styles=[("blue", "-"), ("red", "--")],
ylabel="us",
plot_name="kv-canary-plan-total-tokens-perf",
args={},
)
)
def benchmark_total_tokens(
bs: int,
total_tokens: int,
pool_kind: str,
provider: str,
) -> Tuple[float, float, float]:
device = torch.device(DEFAULT_DEVICE)
per_req_prefix = max(1, total_tokens // max(bs, 1))
if provider == "canary":
inputs = _build_plan_inputs(
bs=bs,
prefix_len=per_req_prefix,
extend_len=1,
pool_kind=pool_kind,
device=device,
)
fn = _make_plan_callable(inputs)
else:
fn = naive_cumsum_fn(bs=bs, device=device)
return run_benchmark(fn)
@triton.testing.perf_report(
triton.testing.Benchmark(
x_names=_X_NAMES_PC,
x_vals=_X_VALS_PC,
line_arg="provider",
line_vals=["canary", "naive"],
line_names=["canary_plan_step", "naive torch.cumsum"],
styles=[("blue", "-"), ("red", "--")],
ylabel="us",
plot_name="kv-canary-plan-pool-capacity-perf",
args={},
)
)
def benchmark_pool_capacity(
bs: int,
bs_padded: int,
prefix_len: int,
verify_capacity: int,
pool_kind: str,
provider: str,
) -> Tuple[float, float, float]:
device = torch.device(DEFAULT_DEVICE)
if provider == "canary":
inputs = _build_plan_inputs(
bs=bs,
prefix_len=prefix_len,
extend_len=1,
pool_kind=pool_kind,
device=device,
verify_capacity_override=verify_capacity,
bs_padded=bs_padded,
)
fn = _make_plan_callable(inputs)
else:
fn = naive_cumsum_fn(bs=bs, device=device)
return run_benchmark(fn)
if __name__ == "__main__":
benchmark_matrix.run(print_data=True)
benchmark_total_tokens.run(print_data=True)
benchmark_pool_capacity.run(print_data=True)
@@ -0,0 +1,308 @@
#pragma once
#include <sgl_kernel/tensor.h> // For TensorMatcher, SymbolicSize, SymbolicDevice
#include <sgl_kernel/utils.h> // For RuntimeCheck
#include <sgl_kernel/runtime.cuh> // For host::runtime::get_sm_count
#include <sgl_kernel/utils.cuh> // For LaunchKernel, SGL_DEVICE
#include <dlpack/dlpack.h>
#include <tvm/ffi/container/tensor.h>
#include <cstdint>
#include <cuda_runtime.h>
namespace {
struct PlanEntriesParams {
// Inputs.
const int64_t* __restrict__ req_pool_indices; // [bs_padded] int64
const int64_t* __restrict__ prefix_lens; // [bs_padded] int64
const int32_t* __restrict__ req_to_token; // [max_reqs, max_seq_len] int32
const int64_t* __restrict__ full_to_swa_lut; // [lut_len] int64, may be nullptr when !HAS_SWA_LUT
const int64_t* __restrict__ verify_offsets_scratch; // [bs_padded + 1] int64 (cumulative prefix sum)
const int32_t* __restrict__ verify_enable; // [1] int32 — 0 ⇒ skip scatter entirely
const int32_t* __restrict__ req_to_verify_expected_tokens; // [max_reqs, max_context_len] int32, may be nullptr
const int64_t* __restrict__ req_to_verify_expected_tokens_valid_lens; // [bs_padded] int64 per-req snapshot length;
// nullptr iff pool is null
// Outputs.
int64_t* __restrict__ out_verify_slot_indices; // [verify_capacity] int64
int64_t* __restrict__ out_verify_expected_tokens; // [verify_capacity] int64
int64_t* __restrict__ out_verify_expected_positions; // [verify_capacity] int64
int64_t* __restrict__ out_verify_prev_slot_indices; // [verify_capacity] int64
// Sizes / strides.
int32_t bs_padded;
int64_t verify_capacity; // out_verify_*[verify_capacity]; scatter is clamped to this length.
int64_t req_to_token_stride0;
int64_t req_to_verify_expected_tokens_stride0;
int32_t kv_token_id_vs_position_offset; // 0 for target pools; +1 for EAGLE draft.
int32_t swa_window_size;
};
// Binary search for the largest req_id such that verify_offsets[req_id] <= tid. Pre-condition: tid is
// strictly less than verify_offsets[bs_padded] = total_verify; bs_padded >= 1; verify_offsets[0] = 0.
SGL_DEVICE int32_t find_req_id(const int64_t* __restrict__ verify_offsets, int32_t bs_padded, int64_t tid) {
int32_t lo = 0;
int32_t hi = bs_padded; // exclusive upper bound; verify_offsets[hi] > tid
while (hi - lo > 1) {
const int32_t mid = (lo + hi) >> 1;
if (verify_offsets[mid] <= tid) {
lo = mid;
} else {
hi = mid;
}
}
return lo;
}
// Translate raw slot value via the SWA LUT. Sentinel passthrough (-1 stays -1). Clamp slot to
// ``lut_len - 1`` defensively; in practice the caller never produces out-of-range slots.
SGL_DEVICE int64_t swa_translate(const int64_t* __restrict__ lut, int64_t lut_len, int64_t raw_slot) {
if (raw_slot < 0) {
return raw_slot;
}
int64_t safe = raw_slot;
if (lut_len > 0 && safe >= lut_len) {
safe = lut_len - 1;
}
return lut[safe];
}
// Persistent grid; one thread = one verify entry (with stride). Template parameter HAS_SWA_LUT switches
// the SWA-translate path off entirely in the FULL pool variant.
// HAS_VERIFY_EXPECTED_TOKEN_POOL toggles the source-of-truth token gather; when off, every active entry
// writes the ``-1`` sentinel so the verify kernel skips the token-mismatch check.
template <bool HAS_SWA_LUT, bool HAS_VERIFY_EXPECTED_TOKEN_POOL>
__global__ void plan_entries_persistent_kernel(
const PlanEntriesParams __grid_constant__ params,
int64_t lut_len // only meaningful when HAS_SWA_LUT
) {
const int64_t total_verify = params.verify_offsets_scratch[params.bs_padded];
if (total_verify <= 0) {
return;
}
if (*params.verify_enable == 0) {
return;
}
// Contract: offsets kernel clears verify_enable iff total_verify > verify_capacity, so reaching
// here implies total_verify <= verify_capacity. Trap on contract break (cassert is NDEBUG-gated).
if (total_verify > params.verify_capacity) {
if (blockIdx.x == 0 && threadIdx.x == 0) {
printf(
"kv-canary plan_entries: total_verify=%lld exceeds verify_capacity=%lld with "
"verify_enable=1 (offsets/entries contract broken)\n",
static_cast<long long>(total_verify),
static_cast<long long>(params.verify_capacity));
}
__trap();
}
const int64_t tid_start = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
const int64_t stride = static_cast<int64_t>(gridDim.x) * blockDim.x;
const int32_t swa_window = params.swa_window_size;
const int64_t req_to_token_stride0 = params.req_to_token_stride0;
for (int64_t tid = tid_start; tid < total_verify; tid += stride) {
// 1) Find the owning req via binary search over verify_offsets.
const int32_t req_id = find_req_id(params.verify_offsets_scratch, params.bs_padded, tid);
const int64_t req_start = params.verify_offsets_scratch[req_id];
const int64_t entry_idx = tid - req_start;
// 2) Load per-req metadata. Padding rows have verify_count=0 in the offsets prefix-sum, so a tid in
// the live range can never land on a padding req; no explicit padding-row check needed.
const int64_t rp = params.req_pool_indices[req_id];
const int64_t prefix_len = params.prefix_lens[req_id];
const int64_t window_start = (swa_window > 0) ? (prefix_len - swa_window > 0 ? prefix_len - swa_window : 0) : 0;
const int64_t out_position = window_start + entry_idx;
// 3) Gather slot + prev_slot via req_to_token.
const int64_t row_base = rp * req_to_token_stride0;
const int32_t slot_raw = params.req_to_token[row_base + out_position];
int64_t out_slot;
if constexpr (HAS_SWA_LUT) {
out_slot = swa_translate(params.full_to_swa_lut, lut_len, static_cast<int64_t>(slot_raw));
} else {
out_slot = static_cast<int64_t>(slot_raw);
}
int64_t out_prev_slot;
if (out_position > 0) {
const int32_t prev_raw = params.req_to_token[row_base + out_position - 1];
if constexpr (HAS_SWA_LUT) {
out_prev_slot = swa_translate(params.full_to_swa_lut, lut_len, static_cast<int64_t>(prev_raw));
} else {
out_prev_slot = static_cast<int64_t>(prev_raw);
}
} else {
out_prev_slot = -1;
}
int64_t out_expected_input_id = -1;
if constexpr (HAS_VERIFY_EXPECTED_TOKEN_POOL) {
const int64_t sot_pos = out_position + static_cast<int64_t>(params.kv_token_id_vs_position_offset);
const int64_t valid_len = params.req_to_verify_expected_tokens_valid_lens[req_id];
if (sot_pos >= 0 && sot_pos < valid_len) {
const int32_t token =
params.req_to_verify_expected_tokens[rp * params.req_to_verify_expected_tokens_stride0 + sot_pos];
out_expected_input_id = static_cast<int64_t>(token);
}
}
// 4) Scatter. out_idx == tid since verify_offsets[req_id] + entry_idx == tid by construction.
params.out_verify_slot_indices[tid] = out_slot;
params.out_verify_expected_tokens[tid] = out_expected_input_id;
params.out_verify_expected_positions[tid] = out_position;
params.out_verify_prev_slot_indices[tid] = out_prev_slot;
}
}
template <bool HAS_SWA_LUT, bool HAS_VERIFY_EXPECTED_TOKEN_POOL>
struct PlanEntriesKernel {
static constexpr int kBlockSize = 128;
static constexpr int kBlocksPerSm = 8;
static auto get_num_sms(DLDevice device) {
static const auto kNumSM = host::runtime::get_sm_count(device.device_id);
return kNumSM;
}
static void
run(const tvm::ffi::TensorView req_pool_indices,
const tvm::ffi::TensorView prefix_lens,
const tvm::ffi::TensorView req_to_token,
const tvm::ffi::Optional<tvm::ffi::TensorView> full_to_swa_index_mapping,
const tvm::ffi::TensorView verify_offsets_scratch,
const tvm::ffi::TensorView verify_enable,
const tvm::ffi::Optional<tvm::ffi::TensorView> req_to_verify_expected_tokens,
const tvm::ffi::Optional<tvm::ffi::TensorView> req_to_verify_expected_tokens_valid_lens,
const tvm::ffi::TensorView out_verify_slot_indices,
const tvm::ffi::TensorView out_verify_expected_tokens,
const tvm::ffi::TensorView out_verify_expected_positions,
const tvm::ffi::TensorView out_verify_prev_slot_indices,
int32_t kv_token_id_vs_position_offset,
int32_t swa_window_size) {
using namespace host;
SymbolicSize Nbs = {"bs_padded"};
SymbolicSize Nscratch = {"verify_offsets_scratch_len"};
SymbolicSize Ncap = {"verify_capacity"};
SymbolicSize Nmax_reqs = {"max_reqs"};
SymbolicSize Nmax_seq_len = {"max_seq_len"};
SymbolicSize Nlut = {"lut_len"};
SymbolicSize Npool_rows = {"req_to_verify_expected_tokens_rows"};
SymbolicSize Npool_cols = {"req_to_verify_expected_tokens_cols"};
SymbolicDevice device_;
device_.set_options<kDLCUDA>();
TensorMatcher({Nbs}) //
.with_dtype<int64_t>()
.with_device<kDLCUDA>(device_)
.verify(req_pool_indices)
.verify(prefix_lens);
TensorMatcher({Nscratch}) //
.with_dtype<int64_t>()
.with_device<kDLCUDA>(device_)
.verify(verify_offsets_scratch);
TensorMatcher({1}) //
.with_dtype<int32_t>()
.with_device<kDLCUDA>(device_)
.verify(verify_enable);
TensorMatcher({Ncap}) //
.with_dtype<int64_t>()
.with_device<kDLCUDA>(device_)
.verify(out_verify_slot_indices)
.verify(out_verify_expected_tokens)
.verify(out_verify_expected_positions)
.verify(out_verify_prev_slot_indices);
TensorMatcher({Nmax_reqs, Nmax_seq_len}) //
.with_dtype<int32_t>()
.with_device<kDLCUDA>(device_)
.verify(req_to_token);
RuntimeCheck(
full_to_swa_index_mapping.has_value() == HAS_SWA_LUT,
"full_to_swa_index_mapping presence does not match HAS_SWA_LUT specialization");
RuntimeCheck(
req_to_verify_expected_tokens.has_value() == HAS_VERIFY_EXPECTED_TOKEN_POOL,
"req_to_verify_expected_tokens presence does not match HAS_VERIFY_EXPECTED_TOKEN_POOL specialization");
if constexpr (HAS_VERIFY_EXPECTED_TOKEN_POOL) {
RuntimeCheck(
req_to_verify_expected_tokens_valid_lens.has_value(),
"req_to_verify_expected_tokens_valid_lens must be set when req_to_verify_expected_tokens is set");
}
if constexpr (HAS_SWA_LUT) {
TensorMatcher({Nlut}) //
.with_dtype<int64_t>()
.with_device<kDLCUDA>(device_)
.verify(full_to_swa_index_mapping.value());
}
if constexpr (HAS_VERIFY_EXPECTED_TOKEN_POOL) {
TensorMatcher({Npool_rows, Npool_cols}) //
.with_dtype<int32_t>()
.with_device<kDLCUDA>(device_)
.verify(req_to_verify_expected_tokens.value());
TensorMatcher({Nbs}) //
.with_dtype<int64_t>()
.with_device<kDLCUDA>(device_)
.verify(req_to_verify_expected_tokens_valid_lens.value());
}
RuntimeCheck(Nscratch.unwrap() >= Nbs.unwrap() + 1, "verify_offsets_scratch length must be >= bs_padded + 1");
const int64_t bs_padded = Nbs.unwrap();
if (bs_padded <= 0) {
return;
}
const int64_t* lut_ptr = nullptr;
int64_t lut_len = 0;
if constexpr (HAS_SWA_LUT) {
lut_ptr = static_cast<const int64_t*>(full_to_swa_index_mapping.value().data_ptr());
lut_len = static_cast<int64_t>(Nlut.unwrap());
}
const int32_t* expected_token_ids_ptr = nullptr;
int64_t expected_token_ids_stride0 = 0;
const int64_t* req_to_verify_expected_tokens_valid_lens_ptr = nullptr;
if constexpr (HAS_VERIFY_EXPECTED_TOKEN_POOL) {
expected_token_ids_ptr = static_cast<const int32_t*>(req_to_verify_expected_tokens.value().data_ptr());
expected_token_ids_stride0 = static_cast<int64_t>(Npool_cols.unwrap());
req_to_verify_expected_tokens_valid_lens_ptr =
static_cast<const int64_t*>(req_to_verify_expected_tokens_valid_lens.value().data_ptr());
}
const PlanEntriesParams params = PlanEntriesParams{
.req_pool_indices = static_cast<const int64_t*>(req_pool_indices.data_ptr()),
.prefix_lens = static_cast<const int64_t*>(prefix_lens.data_ptr()),
.req_to_token = static_cast<const int32_t*>(req_to_token.data_ptr()),
.full_to_swa_lut = lut_ptr,
.verify_offsets_scratch = static_cast<const int64_t*>(verify_offsets_scratch.data_ptr()),
.verify_enable = static_cast<const int32_t*>(verify_enable.data_ptr()),
.req_to_verify_expected_tokens = expected_token_ids_ptr,
.req_to_verify_expected_tokens_valid_lens = req_to_verify_expected_tokens_valid_lens_ptr,
.out_verify_slot_indices = static_cast<int64_t*>(out_verify_slot_indices.data_ptr()),
.out_verify_expected_tokens = static_cast<int64_t*>(out_verify_expected_tokens.data_ptr()),
.out_verify_expected_positions = static_cast<int64_t*>(out_verify_expected_positions.data_ptr()),
.out_verify_prev_slot_indices = static_cast<int64_t*>(out_verify_prev_slot_indices.data_ptr()),
.bs_padded = static_cast<int32_t>(bs_padded),
.verify_capacity = static_cast<int64_t>(Ncap.unwrap()),
.req_to_token_stride0 = static_cast<int64_t>(Nmax_seq_len.unwrap()),
.req_to_verify_expected_tokens_stride0 = expected_token_ids_stride0,
.kv_token_id_vs_position_offset = kv_token_id_vs_position_offset,
.swa_window_size = swa_window_size,
};
const DLDevice device = device_.unwrap();
const int num_sms = get_num_sms(device);
const int num_blocks = num_sms * kBlocksPerSm;
const dim3 grid(num_blocks);
const dim3 block(kBlockSize);
LaunchKernel(grid, block, device)(
plan_entries_persistent_kernel<HAS_SWA_LUT, HAS_VERIFY_EXPECTED_TOKEN_POOL>, params, lut_len);
}
};
} // namespace
@@ -0,0 +1 @@
from sglang.jit_kernel.kv_canary.plan.api import launch_canary_plan_kernels
@@ -0,0 +1,165 @@
from __future__ import annotations
from typing import Optional
import torch
from sglang.jit_kernel.kv_canary.plan.entries_kernel import (
launch_plan_entries_kernel,
)
from sglang.jit_kernel.kv_canary.plan.offsets_kernel import (
_PLAN_BS_BLOCK_SIZE,
launch_plan_offsets_kernel,
)
from sglang.jit_kernel.kv_canary.verify import VerifyPlan
from sglang.jit_kernel.kv_canary.write import WritePlan
def launch_canary_plan_kernels(
*,
verify_plan_out: VerifyPlan,
write_plan_out: WritePlan,
req_pool_indices: torch.Tensor,
prefix_lens: torch.Tensor,
extend_seq_lens: torch.Tensor,
req_to_token: torch.Tensor,
swa_window_size: int,
full_to_swa_index_mapping: Optional[torch.Tensor],
verify_capacity: int,
req_to_verify_expected_tokens: Optional[torch.Tensor],
req_to_verify_expected_tokens_valid_lens: Optional[torch.Tensor],
kv_token_id_vs_position_offset: int,
) -> None:
"""Fill verify_plan_out + write_plan_out from normalized canary plan inputs.
For each req r with req_pool_indices[r] != 0 (0 = padding sentinel):
- **Verify entries**: one per pos in [window_start, prefix_lens[r]), where window_start = max(0,
prefix_lens[r] - swa_window_size) if SWA else 0. slot_idx = req_to_token[req_pool_indices[r], pos]
(SWA-translated via full_to_swa_index_mapping if non-None); prev_slot_idx =
req_to_token[req_pool_indices[r], pos-1] for pos > 0, else -1. (SWA windows do NOT reset the chain —
the writer chains across the entire prefix; verify within an SWA window dereferences the real
predecessor for chain-link reconstruction.) Expected-token gather: when
``req_to_verify_expected_tokens`` is supplied, ``expected_input_id =
req_to_verify_expected_tokens[rp, pos + kv_token_id_vs_position_offset]`` when ``0 <= pos +
kv_token_id_vs_position_offset < req_to_verify_expected_tokens_valid_lens[r]``, else the ``-1``
sentinel (which the verify kernel treats as "skip token-id check").
- **Write metadata** (when extend_seq_lens[r] > 0): contribute extend_seq_lens[r] to the per-req
write count (for write_offsets cumsum). Per-req chain seed = req_to_token[req_pool_indices[r],
prefix_lens[r]-1] (SWA-translated), or -1 if prefix_lens[r] == 0. Per-token write data
(input_ids / positions / out_cache_loc) is NOT materialized here — launch_canary_write_kernel
reads it directly from ForwardBatch via write_offsets.
Args:
verify_plan_out: Pre-allocated VerifyPlan; filled in-place.
write_plan_out: Pre-allocated WritePlan; filled in-place.
req_pool_indices: Per-row ReqToTokenPool row index, shape [bs], int64. 0 is the padding sentinel.
prefix_lens: Per-req prefix length already written before this step, shape [bs], int64.
extend_seq_lens: Per-req tokens being written this step, shape [bs], int64.
req_to_token: ReqToTokenPool.req_to_token; full-pool slot index table, shape [max_reqs, max_seq_len],
int32.
swa_window_size: 0 for the FULL canary group; positive window length for the SWA group.
full_to_swa_index_mapping: SWA LUT, shape [full_pool_size + 1], int64, or None. Required (non-None) iff
swa_window_size > 0. Used to translate verify slot indices and chain-seed slot indices at plan time.
Loaded element-typed via Triton ``tl.load``; intermediate translated slot values are int64 inside the
kernel and stored in the int64 plan schema.
verify_capacity: Length of verify_plan_out.verify_*; on overflow the offsets kernel clears
verify_enable and plan_entries skips the scatter.
req_to_verify_expected_tokens: Optional source-of-truth token pool, shape [max_reqs, max_context_len],
int32. When supplied, the plan kernel gathers expected_input_id for each verify entry from
``[rp, pos + kv_token_id_vs_position_offset]``; when None, every entry gets the ``-1`` sentinel.
req_to_verify_expected_tokens_valid_lens: Per-req snapshot length on ``req_to_verify_expected_tokens``,
shape [bs], int64. Required iff ``req_to_verify_expected_tokens`` is set. Reads past
``valid_lens[r]`` skip the gather (emit ``-1``) — this is what makes the plan kernel correct in the
presence of EAGLE draft / verify positions written past the committed history, and across pool
rows recycled from a longer previous owner whose stale tail still lives at high indices.
kv_token_id_vs_position_offset: Per-buffer-group logical-position offset applied to ``pos`` before
indexing ``req_to_verify_expected_tokens``. 0 for target pools; +1 for EAGLE draft.
Implementation:
- Two sub-kernels launched in sequence:
1. Triton ``_plan_offsets_kernel`` (1-D grid ``(1,)``, single program over all ``bs`` reqs):
reads req_pool_indices[r], prefix_lens[r], extend_seq_lens[r] for each r; computes
verify_count = (prefix_lens - window_start) and write_count = extend_seq_lens (both 0 if rp == 0
padding); gathers seed_slot_full = req_to_token[rp, prefix_lens - 1] (or -1 if prefix_lens == 0),
SWA-translates seed_slot via full_to_swa_index_mapping[seed_slot_full] if non-None; runs
block-level cumsum (``tl.cumsum``) to produce verify_offsets[_PLAN_BS_BLOCK_SIZE + 1] and
write_plan_out.write_offsets[write_req_capacity + 1] in-place; scatters write_seed slots; writes
scalar totals ``verify_plan_out.verify_num_valid`` and ``write_plan_out.write_num_valid_reqs``.
2. CUDA ``plan_entries_persistent_kernel`` (1-D persistent grid sized to ``num_sms *
kBlocksPerSm`` blocks of ``kBlockSize`` threads), wrapped by Python
``launch_plan_entries_kernel``: each thread grid-strides over ``tid ∈ [0, total_verify)``,
locates its owning req via ``find_req_id`` (binary search on verify_offsets), computes
out_position = window_start[req_id] + (tid - verify_offsets[req_id]), gathers slot =
req_to_token[rp, out_position] (SWA-translated when ``HAS_SWA_LUT``), prev_slot =
req_to_token[rp, out_position - 1] when out_position > 0 (also translated) else -1, and
scatters (slot, position, prev_slot) into verify_plan_out at flat index tid.
- All output tensors are addressed at addresses baked into the cuda-graph capture.
Calling contract:
- Pure side-effect; no host work, no D2H.
- Safe in cuda-graph capture; caller refills all input tensors in-place before replay.
- The wrapper launches the plan sub-kernels needed to fill both plans end-to-end.
- Padding rows contribute zero entries.
Pinned by Python reference
:func:`sglang.jit_kernel.kv_canary.plan_ref.launch_canary_plan_kernels_torch_reference`; both the Triton
offsets kernel and the CUDA JIT entries kernel must match byte-for-byte.
"""
bs = int(req_pool_indices.shape[0])
if bs > _PLAN_BS_BLOCK_SIZE:
raise ValueError(
f"kv-canary: launch_canary_plan_kernels supports at most bs={_PLAN_BS_BLOCK_SIZE} reqs per launch, "
f"got bs={bs}. Bump _PLAN_BS_BLOCK_SIZE if real workloads need this."
)
if swa_window_size > 0 and full_to_swa_index_mapping is None:
raise ValueError(
"kv-canary: launch_canary_plan_kernels requires full_to_swa_index_mapping when swa_window_size > 0"
)
device = verify_plan_out.verify_slot_indices.device
verify_offsets_scratch = torch.empty(
_PLAN_BS_BLOCK_SIZE + 1, dtype=torch.int64, device=device
)
plan_verify_capacity = int(verify_plan_out.verify_slot_indices.shape[0])
if verify_capacity != plan_verify_capacity:
raise ValueError(
f"kv-canary: launch_canary_plan_kernels verify_capacity={verify_capacity} does not match "
f"verify_plan_out.verify_slot_indices.shape[0]={plan_verify_capacity}"
)
write_plan_out.write_offsets.zero_()
launch_plan_offsets_kernel(
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
full_to_swa_index_mapping=full_to_swa_index_mapping,
out_verify_offsets_scratch=verify_offsets_scratch,
out_write_offsets=write_plan_out.write_offsets,
out_write_seed_slot_indices=write_plan_out.write_seed_slot_indices,
out_verify_num_valid=verify_plan_out.verify_num_valid,
out_verify_enable=verify_plan_out.enable,
out_write_num_valid_reqs=write_plan_out.write_num_valid_reqs,
swa_window_size=int(swa_window_size),
verify_capacity=verify_capacity,
)
launch_plan_entries_kernel(
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
req_to_token=req_to_token,
full_to_swa_index_mapping=full_to_swa_index_mapping,
verify_offsets_scratch=verify_offsets_scratch,
verify_enable=verify_plan_out.enable,
req_to_verify_expected_tokens=req_to_verify_expected_tokens,
req_to_verify_expected_tokens_valid_lens=req_to_verify_expected_tokens_valid_lens,
out_verify_slot_indices=verify_plan_out.verify_slot_indices,
out_verify_expected_tokens=verify_plan_out.verify_expected_tokens,
out_verify_expected_positions=verify_plan_out.verify_expected_positions,
out_verify_prev_slot_indices=verify_plan_out.verify_prev_slot_indices,
kv_token_id_vs_position_offset=int(kv_token_id_vs_position_offset),
swa_window_size=int(swa_window_size),
)
@@ -0,0 +1,71 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
import torch
from sglang.jit_kernel.utils import cache_once, load_jit, make_cpp_args
if TYPE_CHECKING:
from tvm_ffi.module import Module
@cache_once
def _jit_plan_entries_module(
has_swa_lut: bool, has_verify_expected_token_pool: bool
) -> "Module":
args = make_cpp_args(has_swa_lut, has_verify_expected_token_pool)
return load_jit(
"kv_canary_plan_entries",
*args,
cuda_files=["kv_canary/canary_plan_entries.cuh"],
cuda_wrappers=[
("plan_entries", f"PlanEntriesKernel<{args}>::run"),
],
)
def launch_plan_entries_kernel(
*,
req_pool_indices: torch.Tensor,
prefix_lens: torch.Tensor,
req_to_token: torch.Tensor,
full_to_swa_index_mapping: Optional[torch.Tensor],
verify_offsets_scratch: torch.Tensor,
verify_enable: torch.Tensor,
req_to_verify_expected_tokens: Optional[torch.Tensor],
req_to_verify_expected_tokens_valid_lens: Optional[torch.Tensor],
out_verify_slot_indices: torch.Tensor,
out_verify_expected_tokens: torch.Tensor,
out_verify_expected_positions: torch.Tensor,
out_verify_prev_slot_indices: torch.Tensor,
kv_token_id_vs_position_offset: int,
swa_window_size: int,
) -> None:
has_swa_lut = full_to_swa_index_mapping is not None
has_verify_expected_token_pool = req_to_verify_expected_tokens is not None
if (
has_verify_expected_token_pool
and req_to_verify_expected_tokens_valid_lens is None
):
raise ValueError(
"kv-canary: launch_plan_entries_kernel requires "
"req_to_verify_expected_tokens_valid_lens when req_to_verify_expected_tokens is set"
)
module = _jit_plan_entries_module(has_swa_lut, has_verify_expected_token_pool)
module.plan_entries(
req_pool_indices,
prefix_lens,
req_to_token,
full_to_swa_index_mapping,
verify_offsets_scratch,
verify_enable,
req_to_verify_expected_tokens,
req_to_verify_expected_tokens_valid_lens,
out_verify_slot_indices,
out_verify_expected_tokens,
out_verify_expected_positions,
out_verify_prev_slot_indices,
int(kv_token_id_vs_position_offset),
int(swa_window_size),
)
@@ -0,0 +1,441 @@
from __future__ import annotations
from typing import Optional
import torch
import triton
import triton.language as tl
from sglang.jit_kernel.kv_canary.consts import (
REQ_POOL_IDX_PADDING,
TOKEN_TO_KV_SLOT_PADDING,
)
from sglang.jit_kernel.kv_canary.plan.utils import (
_compute_window_start,
_require_1d,
_require_2d,
_require_dtype,
_require_len,
_require_min_len,
_require_same_device,
_resolve_swa_lut,
_swa_translate_tile,
)
from sglang.jit_kernel.kv_canary.verify import _assert_contiguous
# Upper bound on bs for _plan_offsets_kernel's block-level cumsum. Reqs larger than this exceed Triton's
# single-program tl.cumsum reach. Increase if real workloads ever push past it; the cap is intentionally
# generous so the wrapper never silently truncates.
_PLAN_BS_BLOCK_SIZE: int = 4096
def launch_plan_offsets_kernel(
*,
req_pool_indices: torch.Tensor,
prefix_lens: torch.Tensor,
extend_seq_lens: torch.Tensor,
req_to_token: torch.Tensor,
full_to_swa_index_mapping: Optional[torch.Tensor],
out_verify_offsets_scratch: torch.Tensor,
out_write_offsets: torch.Tensor,
out_write_seed_slot_indices: torch.Tensor,
out_verify_num_valid: torch.Tensor,
out_verify_enable: torch.Tensor,
out_write_num_valid_reqs: torch.Tensor,
swa_window_size: int,
verify_capacity: int,
) -> None:
bs = int(req_pool_indices.shape[0])
lut_tensor, lut_len, has_swa_lut = _resolve_swa_lut(
full_to_swa_index_mapping, out_verify_offsets_scratch.device
)
req_to_token_stride0 = int(req_to_token.stride(0))
write_offsets_len = int(out_write_offsets.shape[0])
write_req_capacity = int(out_write_seed_slot_indices.shape[0])
_validate_offsets_kernel_inputs(
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
lut_tensor=lut_tensor,
out_verify_offsets_scratch=out_verify_offsets_scratch,
out_write_offsets=out_write_offsets,
out_write_seed_slot_indices=out_write_seed_slot_indices,
out_verify_num_valid=out_verify_num_valid,
out_verify_enable=out_verify_enable,
out_write_num_valid_reqs=out_write_num_valid_reqs,
bs=bs,
req_to_token_stride0=req_to_token_stride0,
lut_len=lut_len,
has_swa_lut=has_swa_lut,
write_offsets_len=write_offsets_len,
write_req_capacity=write_req_capacity,
verify_capacity=verify_capacity,
)
_plan_offsets_kernel[(1,)](
req_pool_indices,
prefix_lens,
extend_seq_lens,
req_to_token,
lut_tensor,
out_verify_offsets_scratch,
out_write_offsets,
out_write_seed_slot_indices,
out_verify_num_valid,
out_verify_enable,
out_write_num_valid_reqs,
bs,
req_to_token_stride0,
lut_len,
BS_BLOCK=_PLAN_BS_BLOCK_SIZE,
SWA_WINDOW=int(swa_window_size),
HAS_SWA_LUT=has_swa_lut,
WRITE_OFFSETS_LEN=write_offsets_len,
WRITE_REQ_CAPACITY=write_req_capacity,
VERIFY_CAPACITY=verify_capacity,
REQ_POOL_IDX_PADDING=REQ_POOL_IDX_PADDING,
TOKEN_TO_KV_SLOT_PADDING=TOKEN_TO_KV_SLOT_PADDING,
)
def _validate_offsets_kernel_inputs(
*,
req_pool_indices: torch.Tensor,
prefix_lens: torch.Tensor,
extend_seq_lens: torch.Tensor,
req_to_token: torch.Tensor,
lut_tensor: torch.Tensor,
out_verify_offsets_scratch: torch.Tensor,
out_write_offsets: torch.Tensor,
out_write_seed_slot_indices: torch.Tensor,
out_verify_num_valid: torch.Tensor,
out_verify_enable: torch.Tensor,
out_write_num_valid_reqs: torch.Tensor,
bs: int,
req_to_token_stride0: int,
lut_len: int,
has_swa_lut: bool,
write_offsets_len: int,
write_req_capacity: int,
verify_capacity: int,
) -> None:
_assert_contiguous(req_pool_indices, "req_pool_indices")
_assert_contiguous(prefix_lens, "prefix_lens")
_assert_contiguous(extend_seq_lens, "extend_seq_lens")
_assert_contiguous(req_to_token, "req_to_token")
_assert_contiguous(lut_tensor, "lut_tensor")
_assert_contiguous(out_verify_offsets_scratch, "out_verify_offsets_scratch")
_assert_contiguous(out_write_offsets, "out_write_offsets")
_assert_contiguous(out_write_seed_slot_indices, "out_write_seed_slot_indices")
_assert_contiguous(out_verify_num_valid, "out_verify_num_valid")
_assert_contiguous(out_verify_enable, "out_verify_enable")
_assert_contiguous(out_write_num_valid_reqs, "out_write_num_valid_reqs")
_require_dtype(req_pool_indices, "req_pool_indices", torch.int64)
_require_dtype(prefix_lens, "prefix_lens", torch.int64)
_require_dtype(extend_seq_lens, "extend_seq_lens", torch.int64)
_require_dtype(req_to_token, "req_to_token", torch.int32)
_require_dtype(lut_tensor, "lut_tensor", torch.int64)
_require_dtype(
out_verify_offsets_scratch, "out_verify_offsets_scratch", torch.int64
)
_require_dtype(out_write_offsets, "out_write_offsets", torch.int64)
_require_dtype(
out_write_seed_slot_indices, "out_write_seed_slot_indices", torch.int64
)
_require_dtype(out_verify_num_valid, "out_verify_num_valid", torch.int32)
_require_dtype(out_verify_enable, "out_verify_enable", torch.int32)
_require_dtype(out_write_num_valid_reqs, "out_write_num_valid_reqs", torch.int32)
if bs < 0 or bs > _PLAN_BS_BLOCK_SIZE:
raise ValueError(
f"kv-canary: offsets kernel bs must be in [0, {_PLAN_BS_BLOCK_SIZE}], got {bs}"
)
if write_offsets_len <= 0:
raise ValueError(
f"kv-canary: write_offsets_len must be positive, got {write_offsets_len}"
)
if write_req_capacity < 0:
raise ValueError(
f"kv-canary: write_req_capacity must be non-negative, got {write_req_capacity}"
)
if verify_capacity < 0:
raise ValueError(
f"kv-canary: verify_capacity must be non-negative, got {verify_capacity}"
)
if req_to_token_stride0 <= 0:
raise ValueError(
f"kv-canary: req_to_token_stride0 must be positive, got {req_to_token_stride0}"
)
if lut_len < 0:
raise ValueError(f"kv-canary: lut_len must be non-negative, got {lut_len}")
if not isinstance(has_swa_lut, bool):
raise ValueError(
f"kv-canary: has_swa_lut must be bool, got {type(has_swa_lut).__name__}"
)
if has_swa_lut and lut_len <= 0:
raise ValueError("kv-canary: lut_len must be positive when has_swa_lut is True")
if not has_swa_lut and lut_len != 0:
raise ValueError("kv-canary: lut_len must be 0 when has_swa_lut is False")
_require_len(req_pool_indices, "req_pool_indices", bs)
_require_len(prefix_lens, "prefix_lens", bs)
_require_len(extend_seq_lens, "extend_seq_lens", bs)
_require_2d(req_to_token, "req_to_token")
_require_min_len(lut_tensor, "lut_tensor", max(lut_len, 1))
_require_min_len(
out_verify_offsets_scratch,
"out_verify_offsets_scratch",
_PLAN_BS_BLOCK_SIZE + 1,
)
_require_len(out_write_offsets, "out_write_offsets", write_offsets_len)
_require_len(
out_write_seed_slot_indices,
"out_write_seed_slot_indices",
write_req_capacity,
)
_require_len(out_verify_num_valid, "out_verify_num_valid", 1)
_require_len(out_verify_enable, "out_verify_enable", 1)
_require_len(out_write_num_valid_reqs, "out_write_num_valid_reqs", 1)
_require_1d(lut_tensor, "lut_tensor")
if write_offsets_len != write_req_capacity + 1:
raise ValueError(
f"kv-canary: write_offsets_len must equal write_req_capacity + 1, got "
f"{write_offsets_len} and {write_req_capacity}"
)
if bs > write_req_capacity:
raise ValueError(
f"kv-canary: bs={bs} exceeds write_req_capacity={write_req_capacity}"
)
if req_to_token_stride0 != int(req_to_token.stride(0)):
raise ValueError(
f"kv-canary: req_to_token_stride0={req_to_token_stride0} does not match "
f"req_to_token.stride(0)={int(req_to_token.stride(0))}"
)
_require_same_device(
out_verify_offsets_scratch,
"out_verify_offsets_scratch",
(
(req_pool_indices, "req_pool_indices"),
(prefix_lens, "prefix_lens"),
(extend_seq_lens, "extend_seq_lens"),
(req_to_token, "req_to_token"),
(lut_tensor, "lut_tensor"),
(out_write_offsets, "out_write_offsets"),
(out_write_seed_slot_indices, "out_write_seed_slot_indices"),
(out_verify_num_valid, "out_verify_num_valid"),
(out_verify_enable, "out_verify_enable"),
(out_write_num_valid_reqs, "out_write_num_valid_reqs"),
),
)
@triton.jit
def _plan_offsets_kernel(
# Input pointers.
req_pool_indices_ptr,
prefix_lens_ptr,
extend_seq_lens_ptr,
req_to_token_ptr,
full_to_swa_lut_ptr,
# Output pointers.
out_verify_offsets_ptr,
out_write_offsets_ptr,
out_write_seed_slot_indices_ptr,
out_verify_num_valid_ptr,
out_verify_enable_ptr,
out_write_num_valid_reqs_ptr,
# Runtime sizes.
bs,
req_to_token_stride0,
swa_lut_len,
# Compile-time constants.
BS_BLOCK: tl.constexpr,
SWA_WINDOW: tl.constexpr,
HAS_SWA_LUT: tl.constexpr,
WRITE_OFFSETS_LEN: tl.constexpr,
WRITE_REQ_CAPACITY: tl.constexpr,
VERIFY_CAPACITY: tl.constexpr,
REQ_POOL_IDX_PADDING: tl.constexpr,
TOKEN_TO_KV_SLOT_PADDING: tl.constexpr,
):
bs_offs = tl.arange(0, BS_BLOCK) # [BS_BLOCK]
bs_mask = bs_offs < bs # [BS_BLOCK] bool
# Per-req inputs (int64 for canary-owned metadata; req_to_token keeps its pool dtype).
rpi = tl.load(
req_pool_indices_ptr + bs_offs, mask=bs_mask, other=REQ_POOL_IDX_PADDING
) # [BS_BLOCK]
prefix_lens = tl.load(
prefix_lens_ptr + bs_offs, mask=bs_mask, other=0
) # [BS_BLOCK]
extend_lens = tl.load(
extend_seq_lens_ptr + bs_offs, mask=bs_mask, other=0
) # [BS_BLOCK]
is_active = (rpi != REQ_POOL_IDX_PADDING) & bs_mask # [BS_BLOCK] bool
has_prefix = is_active & (prefix_lens > 0) # [BS_BLOCK] bool
window_starts = _compute_window_start(prefix_lens, SWA_WINDOW) # [BS_BLOCK]
verify_lens = prefix_lens - window_starts # [BS_BLOCK]
verify_lens = tl.where(verify_lens > 0, verify_lens, 0)
verify_lens = tl.where(is_active, verify_lens, 0)
verify_exclusive, total_verify = _exclusive_offsets_and_total(verify_lens)
write_lens = tl.where(extend_lens > 0, extend_lens, 0) # [BS_BLOCK]
write_lens = tl.where(is_active, write_lens, 0)
write_exclusive, total_write = _exclusive_offsets_and_total(write_lens)
_plan_verify_offsets(
verify_exclusive,
total_verify,
bs_offs,
bs_mask,
out_verify_offsets_ptr,
out_verify_num_valid_ptr,
out_verify_enable_ptr,
bs,
VERIFY_CAPACITY,
)
_plan_write_offsets(
rpi,
prefix_lens,
write_lens,
write_exclusive,
total_write,
has_prefix,
bs_offs,
bs_mask,
req_to_token_ptr,
full_to_swa_lut_ptr,
out_write_offsets_ptr,
out_write_seed_slot_indices_ptr,
out_write_num_valid_reqs_ptr,
bs,
req_to_token_stride0,
swa_lut_len,
BS_BLOCK,
HAS_SWA_LUT,
WRITE_OFFSETS_LEN,
WRITE_REQ_CAPACITY,
TOKEN_TO_KV_SLOT_PADDING,
)
@triton.jit
def _exclusive_offsets_and_total(lens):
inclusive = tl.cumsum(lens, axis=0)
return inclusive - lens, tl.sum(lens, axis=0)
@triton.jit
def _plan_verify_offsets(
verify_exclusive,
total_verify,
bs_offs,
bs_mask,
out_verify_offsets_ptr,
out_verify_num_valid_ptr,
out_verify_enable_ptr,
bs,
VERIFY_CAPACITY: tl.constexpr,
):
tl.store(
out_verify_offsets_ptr + bs_offs,
verify_exclusive.to(tl.int64),
mask=bs_mask,
)
tl.store(out_verify_offsets_ptr + bs, total_verify.to(tl.int64))
# Scalar writes: out_verify_num_valid is clamped to the verify_capacity tensor extent so the verify kernel
# never indexes past the buffer; enable carries the overflow bit (0 when total_verify > capacity) so the
# verify kernel skips the whole launch and the host can warn-log this step.
overflow = total_verify > VERIFY_CAPACITY # scalar bool
enable = tl.where(overflow, 0, 1) # scalar
clamped = tl.where(overflow, VERIFY_CAPACITY, total_verify) # scalar
tl.store(out_verify_num_valid_ptr, clamped.to(tl.int32))
tl.store(out_verify_enable_ptr, tl.full((), enable, tl.int32))
@triton.jit
def _plan_write_offsets(
rpi,
prefix_lens,
write_lens,
write_exclusive,
total_write,
has_prefix,
bs_offs,
bs_mask,
req_to_token_ptr,
full_to_swa_lut_ptr,
out_write_offsets_ptr,
out_write_seed_slot_indices_ptr,
out_write_num_valid_reqs_ptr,
bs,
req_to_token_stride0,
swa_lut_len,
BS_BLOCK: tl.constexpr,
HAS_SWA_LUT: tl.constexpr,
WRITE_OFFSETS_LEN: tl.constexpr,
WRITE_REQ_CAPACITY: tl.constexpr,
TOKEN_TO_KV_SLOT_PADDING: tl.constexpr,
):
has_write_contribution = has_prefix & (write_lens > 0) # [BS_BLOCK] bool
# Seed slot per req. prefix_lens == 0 means no prefix → -1 sentinel. Padding row → no write contribution
# → -1 sentinel either way; we also mask write_lens onto seed below to match the ref's "no write → -1".
safe_prefix_pos = tl.where(prefix_lens > 0, prefix_lens - 1, 0) # [BS_BLOCK]
stride_i64 = req_to_token_stride0 # scalar
seed_full = tl.load( # [BS_BLOCK]
req_to_token_ptr + rpi.to(tl.int64) * stride_i64 + safe_prefix_pos.to(tl.int64),
mask=has_prefix,
other=TOKEN_TO_KV_SLOT_PADDING,
)
if HAS_SWA_LUT:
seed_translated = _swa_translate_tile( # [BS_BLOCK]
seed_full,
has_prefix,
full_to_swa_lut_ptr,
swa_lut_len,
)
else:
seed_translated = seed_full
# Reqs with no write contribution should expose seed = -1 (ref's _seed_slot is masked by write_lens > 0).
minus_one = tl.full((BS_BLOCK,), -1, dtype=seed_translated.dtype) # [BS_BLOCK]
seed_slot = tl.where(
has_write_contribution, seed_translated, minus_one
) # [BS_BLOCK]
write_offsets_mask = bs_offs < WRITE_OFFSETS_LEN # [BS_BLOCK] bool
tl.store(
out_write_offsets_ptr + bs_offs,
write_exclusive.to(tl.int64),
mask=write_offsets_mask & bs_mask,
)
# Store the [bs] slot of out_write_offsets (one element past the last per-req entry).
# out_write_offsets has length WRITE_OFFSETS_LEN = write_req_capacity + 1; only store if in range.
write_tail_in_range = bs < WRITE_OFFSETS_LEN # scalar bool
tl.store(
out_write_offsets_ptr + bs,
total_write.to(tl.int64),
mask=write_tail_in_range,
)
# Scatter seed slots (capped to write_req_capacity).
seed_mask = bs_mask & (bs_offs < WRITE_REQ_CAPACITY) # [BS_BLOCK] bool
tl.store(
out_write_seed_slot_indices_ptr + bs_offs,
seed_slot.to(tl.int64),
mask=seed_mask,
)
tl.store(out_write_num_valid_reqs_ptr, tl.full((), bs, tl.int32))
@@ -0,0 +1,97 @@
from __future__ import annotations
from typing import Optional
import torch
import triton
import triton.language as tl
def _resolve_swa_lut(
lut: Optional[torch.Tensor], device: torch.device
) -> tuple[torch.Tensor, int, bool]:
"""Return the (tensor, length, has_lut) triple to launch the plan kernel with.
Triton requires a valid tensor pointer at every kernel-arg slot even when ``HAS_SWA_LUT`` is False, so
when the caller passes ``None`` we substitute a one-element sentinel tensor and set ``lut_len=0``;
the kernel's constexpr branch guarantees no dereference happens. Dtype matches the production LUT
(int64) so Triton ``tl.load`` element typing stays consistent.
"""
if lut is not None:
return lut, int(lut.shape[0]), True
return torch.zeros(1, dtype=torch.int64, device=device), 0, False
def _require_dtype(tensor: torch.Tensor, name: str, dtype: torch.dtype) -> None:
if tensor.dtype != dtype:
raise ValueError(
f"kv-canary: {name} must have dtype {dtype}, got {tensor.dtype}"
)
def _require_1d(tensor: torch.Tensor, name: str) -> None:
if tensor.ndim != 1:
raise ValueError(
f"kv-canary: {name} must be 1-D, got shape {tuple(tensor.shape)}"
)
def _require_2d(tensor: torch.Tensor, name: str) -> None:
if tensor.ndim != 2:
raise ValueError(
f"kv-canary: {name} must be 2-D, got shape {tuple(tensor.shape)}"
)
def _require_len(tensor: torch.Tensor, name: str, expected: int) -> None:
_require_1d(tensor=tensor, name=name)
actual = int(tensor.shape[0])
if actual != expected:
raise ValueError(f"kv-canary: {name} length must be {expected}, got {actual}")
def _require_min_len(tensor: torch.Tensor, name: str, minimum: int) -> None:
_require_1d(tensor=tensor, name=name)
actual = int(tensor.shape[0])
if actual < minimum:
raise ValueError(f"kv-canary: {name} length must be >= {minimum}, got {actual}")
def _require_same_device(
reference: torch.Tensor,
reference_name: str,
tensors: tuple[tuple[torch.Tensor, str], ...],
) -> None:
for tensor, name in tensors:
if tensor.device != reference.device:
raise ValueError(
f"kv-canary: {name} must be on {reference_name}'s device "
f"{reference.device}, got {tensor.device}"
)
@triton.jit
def _compute_window_start(prefix_lens, SWA_WINDOW: tl.constexpr):
"""Per-req window start: max(prefix_lens - SWA_WINDOW, 0) when SWA, else 0.
Works for tile and scalar inputs (broadcasts via prefix_lens shape).
"""
if SWA_WINDOW > 0:
clipped = prefix_lens - SWA_WINDOW
return tl.where(clipped > 0, clipped, 0)
else:
return prefix_lens - prefix_lens
@triton.jit
def _swa_translate_tile(raw, mask, lut_ptr, lut_len):
"""SWA-translate a tile of slot indices. Sentinels (raw < 0) are passed through unchanged.
``lut_len`` is the LUT's length (Python int from the host wrapper); when 0 the LUT is unused (the caller
will only enter this branch when HAS_SWA_LUT is True, so lut_len is always > 0 in practice).
"""
sentinel = raw < 0
safe = tl.where(sentinel, 0, raw)
if lut_len > 0:
safe = tl.where(safe >= lut_len, lut_len - 1, safe)
xlat = tl.load(lut_ptr + safe, mask=mask & (~sentinel), other=0)
return tl.where(sentinel, raw, xlat)
@@ -0,0 +1,317 @@
from __future__ import annotations
from typing import Optional
import torch
from sglang.jit_kernel.kv_canary.consts import REQ_POOL_IDX_PADDING
from sglang.jit_kernel.kv_canary.verify import VerifyPlan
from sglang.jit_kernel.kv_canary.write import WritePlan
def launch_canary_plan_kernels_torch_reference(
*,
verify_plan_out: VerifyPlan,
write_plan_out: WritePlan,
req_pool_indices: torch.Tensor,
prefix_lens: torch.Tensor,
extend_seq_lens: torch.Tensor,
req_to_token: torch.Tensor,
swa_window_size: int,
full_to_swa_index_mapping: Optional[torch.Tensor],
verify_capacity: int,
req_to_verify_expected_tokens: Optional[torch.Tensor],
req_to_verify_expected_tokens_valid_lens: Optional[torch.Tensor],
kv_token_id_vs_position_offset: int,
) -> None:
"""Python reference for :func:`launch_canary_plan_kernels`. Same signature & byte-equal semantics."""
bs = int(req_pool_indices.shape[0])
work_device = torch.device("cpu")
plan_verify_capacity = int(verify_plan_out.verify_slot_indices.shape[0])
if verify_capacity != plan_verify_capacity:
raise ValueError(
f"kv-canary: launch_canary_plan_kernels_torch_reference verify_capacity={verify_capacity} does not "
f"match verify_plan_out.verify_slot_indices.shape[0]={plan_verify_capacity}"
)
write_req_capacity = int(write_plan_out.write_seed_slot_indices.shape[0])
req_pool_indices_host = req_pool_indices.detach().to(
device=work_device, dtype=torch.int64
)
prefix_lens_host = prefix_lens.detach().to(device=work_device, dtype=torch.int64)
extend_seq_lens_host = extend_seq_lens.detach().to(
device=work_device, dtype=torch.int64
)
req_to_token_host = req_to_token.detach().to(device=work_device, dtype=torch.int64)
lut: Optional[torch.Tensor] = None
if full_to_swa_index_mapping is not None:
lut = full_to_swa_index_mapping.detach().to(device=work_device)
expected_token_pool_host: Optional[torch.Tensor] = None
req_to_verify_expected_tokens_valid_lens_host: Optional[torch.Tensor] = None
if req_to_verify_expected_tokens is not None:
expected_token_pool_host = req_to_verify_expected_tokens.detach().to(
device=work_device, dtype=torch.int64
)
if req_to_verify_expected_tokens_valid_lens is None:
raise ValueError(
"kv-canary: launch_canary_plan_kernels_torch_reference requires "
"req_to_verify_expected_tokens_valid_lens when req_to_verify_expected_tokens is set"
)
req_to_verify_expected_tokens_valid_lens_host = (
req_to_verify_expected_tokens_valid_lens.detach().to(
device=work_device, dtype=torch.int64
)
)
total_verify = _materialize_verify_entries(
verify_plan_out=verify_plan_out,
req_pool_indices_host=req_pool_indices_host,
prefix_lens_host=prefix_lens_host,
req_to_token_host=req_to_token_host,
swa_window_size=swa_window_size,
lut=lut,
verify_capacity=verify_capacity,
work_device=work_device,
bs=bs,
expected_token_pool_host=expected_token_pool_host,
req_to_verify_expected_tokens_valid_lens_host=req_to_verify_expected_tokens_valid_lens_host,
kv_token_id_vs_position_offset=int(kv_token_id_vs_position_offset),
)
_materialize_write_metadata(
write_plan_out=write_plan_out,
req_pool_indices_host=req_pool_indices_host,
prefix_lens_host=prefix_lens_host,
extend_seq_lens_host=extend_seq_lens_host,
req_to_token_host=req_to_token_host,
lut=lut,
write_req_capacity=write_req_capacity,
work_device=work_device,
bs=bs,
)
_write_num_valid_and_enable(
verify_plan_out=verify_plan_out,
requested=total_verify,
verify_capacity=verify_capacity,
)
def _write_num_valid_and_enable(
*,
verify_plan_out: VerifyPlan,
requested: int,
verify_capacity: int,
) -> None:
overflow = requested > verify_capacity
clamped = verify_capacity if overflow else requested
enable = 0 if overflow else 1
verify_plan_out.verify_num_valid.fill_(int(clamped))
verify_plan_out.enable.fill_(int(enable))
def _swa_translate_slot(*, slot: int, lut: torch.Tensor) -> int:
if slot < 0:
return slot
lut_len = int(lut.shape[0])
if slot >= lut_len:
raise ValueError(
f"kv-canary: SWA slot {slot} is outside full_to_swa_index_mapping length {lut_len}"
)
return int(lut[slot].item())
def _materialize_verify_entries(
*,
verify_plan_out: VerifyPlan,
req_pool_indices_host: torch.Tensor,
prefix_lens_host: torch.Tensor,
req_to_token_host: torch.Tensor,
swa_window_size: int,
lut: Optional[torch.Tensor],
verify_capacity: int,
work_device: torch.device,
bs: int,
expected_token_pool_host: Optional[torch.Tensor],
req_to_verify_expected_tokens_valid_lens_host: Optional[torch.Tensor],
kv_token_id_vs_position_offset: int,
) -> int:
out_slots: list[int] = []
out_positions: list[int] = []
out_expected_input_ids: list[int] = []
out_prev_slots: list[int] = []
for r in range(bs):
rpi = int(req_pool_indices_host[r].item())
prefix_len = int(prefix_lens_host[r].item())
if rpi == REQ_POOL_IDX_PADDING:
continue
if swa_window_size > 0:
window_start = max(0, prefix_len - swa_window_size)
else:
window_start = 0
verify_len = max(0, prefix_len - window_start)
valid_len_r = (
int(req_to_verify_expected_tokens_valid_lens_host[r].item())
if req_to_verify_expected_tokens_valid_lens_host is not None
else 0
)
for j in range(verify_len):
position = window_start + j
slot_full = int(req_to_token_host[rpi, position].item())
if lut is not None:
slot = _swa_translate_slot(slot=slot_full, lut=lut)
else:
slot = slot_full
prev_position = position - 1
if prev_position < 0:
prev_slot = -1
else:
prev_slot_full = int(req_to_token_host[rpi, prev_position].item())
if lut is not None:
prev_slot = _swa_translate_slot(slot=prev_slot_full, lut=lut)
else:
prev_slot = prev_slot_full
expected_input_id = -1
if expected_token_pool_host is not None:
sot_pos = position + kv_token_id_vs_position_offset
if 0 <= sot_pos < valid_len_r:
expected_input_id = int(
expected_token_pool_host[rpi, sot_pos].item()
)
out_slots.append(slot)
out_positions.append(position)
out_expected_input_ids.append(expected_input_id)
out_prev_slots.append(prev_slot)
total_verify = len(out_slots)
if total_verify == 0:
return 0
# On overflow CUDA plan_entries skips scatter (verify_enable=0); mirror that.
if total_verify > verify_capacity:
return total_verify
slots_t = torch.tensor(out_slots, dtype=torch.int64, device=work_device)
positions_t = torch.tensor(out_positions, dtype=torch.int64, device=work_device)
expected_input_ids_t = torch.tensor(
out_expected_input_ids, dtype=torch.int64, device=work_device
)
prev_slots_t = torch.tensor(out_prev_slots, dtype=torch.int64, device=work_device)
verify_plan_out.verify_slot_indices[:total_verify].copy_(
slots_t.to(verify_plan_out.verify_slot_indices.dtype).to(
verify_plan_out.verify_slot_indices.device
)
)
verify_plan_out.verify_expected_tokens[:total_verify].copy_(
expected_input_ids_t.to(verify_plan_out.verify_expected_tokens.dtype).to(
verify_plan_out.verify_expected_tokens.device
)
)
verify_plan_out.verify_expected_positions[:total_verify].copy_(
positions_t.to(verify_plan_out.verify_expected_positions.dtype).to(
verify_plan_out.verify_expected_positions.device
)
)
verify_plan_out.verify_prev_slot_indices[:total_verify].copy_(
prev_slots_t.to(verify_plan_out.verify_prev_slot_indices.dtype).to(
verify_plan_out.verify_prev_slot_indices.device
)
)
return total_verify
def _materialize_write_metadata(
*,
write_plan_out: WritePlan,
req_pool_indices_host: torch.Tensor,
prefix_lens_host: torch.Tensor,
extend_seq_lens_host: torch.Tensor,
req_to_token_host: torch.Tensor,
lut: Optional[torch.Tensor],
write_req_capacity: int,
work_device: torch.device,
bs: int,
) -> None:
out_write_offsets_len = int(write_plan_out.write_offsets.shape[0])
max_seq_len = int(req_to_token_host.shape[1])
write_offsets_list: list[int] = []
seed_slots_list: list[int] = []
running_offset = 0
for r in range(bs):
write_offsets_list.append(running_offset)
rpi = int(req_pool_indices_host[r].item())
extend_len = int(extend_seq_lens_host[r].item())
if rpi == REQ_POOL_IDX_PADDING or extend_len <= 0:
write_len = 0
else:
write_len = max(0, extend_len)
running_offset += write_len
write_offsets_list.append(running_offset)
copy_len = min(bs + 1, out_write_offsets_len)
write_offsets_t = torch.tensor(
write_offsets_list[:copy_len], dtype=torch.int64, device=work_device
)
write_plan_out.write_offsets[:copy_len].copy_(
write_offsets_t.to(write_plan_out.write_offsets.dtype).to(
write_plan_out.write_offsets.device
)
)
if copy_len < out_write_offsets_len:
write_plan_out.write_offsets[copy_len:].zero_()
capped_reqs = min(bs, write_req_capacity)
for r in range(capped_reqs):
rpi = int(req_pool_indices_host[r].item())
prefix_len = int(prefix_lens_host[r].item())
extend_len = int(extend_seq_lens_host[r].item())
if rpi == REQ_POOL_IDX_PADDING or extend_len <= 0:
seed_slots_list.append(-1)
continue
if prefix_len <= 0:
seed_slots_list.append(-1)
continue
safe_seed_pos = min(prefix_len - 1, max(max_seq_len - 1, 0))
seed_slot_full = int(req_to_token_host[rpi, safe_seed_pos].item())
if lut is not None:
seed_slot = _swa_translate_slot(slot=seed_slot_full, lut=lut)
else:
seed_slot = seed_slot_full
seed_slots_list.append(seed_slot)
if len(seed_slots_list) > 0:
seed_slots_t = torch.tensor(
seed_slots_list, dtype=torch.int64, device=work_device
)
write_plan_out.write_seed_slot_indices[:capped_reqs].copy_(
seed_slots_t.to(write_plan_out.write_seed_slot_indices.dtype).to(
write_plan_out.write_seed_slot_indices.device
)
)
write_plan_out.write_num_valid_reqs.fill_(int(bs))
@@ -0,0 +1,500 @@
from __future__ import annotations
from dataclasses import dataclass, replace
from typing import Any, Callable, Iterator, Optional
import torch
from sglang.jit_kernel.kv_canary.plan import launch_canary_plan_kernels
from sglang.jit_kernel.kv_canary.plan_ref import (
launch_canary_plan_kernels_torch_reference,
)
from sglang.jit_kernel.kv_canary.verify import (
CanaryLaunchTag,
VerifyOrWriteContext,
VerifyPlan,
launch_canary_verify_kernel,
)
from sglang.jit_kernel.kv_canary.verify_ref import (
launch_canary_verify_kernel_torch_reference,
)
from sglang.jit_kernel.kv_canary.write import WritePlan, launch_canary_write_kernel
from sglang.jit_kernel.kv_canary.write_ref import (
launch_canary_write_kernel_torch_reference,
)
from sglang.jit_kernel.tests.kv_canary._canary_helpers import (
FakeViolationLog,
assert_canary_buf_equal,
assert_canary_state_equal,
make_log_pair,
)
_DEVICE = torch.device("cuda")
def _run_both_plan(
*,
triton_verify: VerifyPlan,
triton_write: WritePlan,
ref_verify: VerifyPlan,
ref_write: WritePlan,
req_pool_indices: torch.Tensor,
prefix_lens: torch.Tensor,
extend_seq_lens: torch.Tensor,
req_to_token: torch.Tensor,
extras: tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor],
swa_window_size: int,
full_to_swa_index_mapping: Optional[torch.Tensor],
assert_equal: bool = True,
active_verify_entries: Optional[int] = None,
active_write_reqs: Optional[int] = None,
req_to_verify_expected_tokens: Optional[torch.Tensor] = None,
req_to_verify_expected_tokens_valid_lens: Optional[torch.Tensor] = None,
kv_token_id_vs_position_offset: int = 0,
) -> None:
_ = extras
verify_capacity = int(triton_verify.verify_slot_indices.shape[0])
# Default lens to "no tighter bound than pool width" so existing kernel tests that
# only care about gather wiring keep their old semantics without each call site
# explicitly building a per-req lens tensor.
if (
req_to_verify_expected_tokens is not None
and req_to_verify_expected_tokens_valid_lens is None
):
req_to_verify_expected_tokens_valid_lens = torch.full(
(int(req_pool_indices.shape[0]),),
int(req_to_verify_expected_tokens.shape[1]),
dtype=torch.int64,
device=req_pool_indices.device,
)
launch_canary_plan_kernels(
verify_plan_out=triton_verify,
write_plan_out=triton_write,
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
swa_window_size=swa_window_size,
full_to_swa_index_mapping=full_to_swa_index_mapping,
verify_capacity=verify_capacity,
req_to_verify_expected_tokens=req_to_verify_expected_tokens,
req_to_verify_expected_tokens_valid_lens=req_to_verify_expected_tokens_valid_lens,
kv_token_id_vs_position_offset=kv_token_id_vs_position_offset,
)
launch_canary_plan_kernels_torch_reference(
verify_plan_out=ref_verify,
write_plan_out=ref_write,
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
swa_window_size=swa_window_size,
full_to_swa_index_mapping=full_to_swa_index_mapping,
verify_capacity=int(ref_verify.verify_slot_indices.shape[0]),
req_to_verify_expected_tokens=req_to_verify_expected_tokens,
req_to_verify_expected_tokens_valid_lens=req_to_verify_expected_tokens_valid_lens,
kv_token_id_vs_position_offset=kv_token_id_vs_position_offset,
)
torch.cuda.synchronize()
if assert_equal:
_assert_plans_byte_equal(
triton_verify=triton_verify,
triton_write=triton_write,
ref_verify=ref_verify,
ref_write=ref_write,
active_verify_entries=active_verify_entries,
active_write_reqs=active_write_reqs,
)
def _assert_plans_byte_equal(
*,
triton_verify: VerifyPlan,
triton_write: WritePlan,
ref_verify: VerifyPlan,
ref_write: WritePlan,
active_verify_entries: Optional[int] = None,
active_write_reqs: Optional[int] = None,
) -> None:
"""Byte-equal check on (Triton vs ref) plan outputs.
Optional ``active_verify_entries`` / ``active_write_reqs`` truncate the comparison to the meaningful
prefix; tail entries past the active count are kernel-undefined and need not match byte-equal.
"""
n_verify = (
active_verify_entries
if active_verify_entries is not None
else int(triton_verify.verify_num_valid[0].item())
)
n_verify_ref = int(ref_verify.verify_num_valid[0].item())
assert (
n_verify == n_verify_ref
), f"verify_num_valid diverged: triton={n_verify} ref={n_verify_ref}"
# When total_verify > VERIFY_CAPACITY the offsets kernel clears verify_enable and
# plan_entries skips its scatter — leaving verify_slot_indices/positions/prev_slot_indices
# as whatever the (torch.empty) allocation contained. Skip the byte-equal probe in that
# case; verify_num_valid being clamped + verify_enable=0 is the contract here.
triton_enable = int(triton_verify.enable[0].item())
ref_enable = int(ref_verify.enable[0].item())
assert (
triton_enable == ref_enable
), f"verify_enable diverged: triton={triton_enable} ref={ref_enable}"
if n_verify > 0 and triton_enable != 0:
assert torch.equal(
triton_verify.verify_slot_indices[:n_verify],
ref_verify.verify_slot_indices[:n_verify],
)
assert torch.equal(
triton_verify.verify_expected_tokens[:n_verify],
ref_verify.verify_expected_tokens[:n_verify],
)
assert torch.equal(
triton_verify.verify_expected_positions[:n_verify],
ref_verify.verify_expected_positions[:n_verify],
)
assert torch.equal(
triton_verify.verify_prev_slot_indices[:n_verify],
ref_verify.verify_prev_slot_indices[:n_verify],
)
n_write = (
active_write_reqs
if active_write_reqs is not None
else int(triton_write.write_num_valid_reqs[0].item())
)
n_write_ref = int(ref_write.write_num_valid_reqs[0].item())
assert (
n_write == n_write_ref
), f"write_num_valid_reqs diverged: triton={n_write} ref={n_write_ref}"
assert torch.equal(
triton_write.write_offsets[: n_write + 1],
ref_write.write_offsets[: n_write + 1],
)
if n_write > 0:
assert torch.equal(
triton_write.write_seed_slot_indices[:n_write],
ref_write.write_seed_slot_indices[:n_write],
)
def _run_both_verify(
*,
cuda_canary_buf: torch.Tensor,
ref_canary_buf: torch.Tensor,
plan_cuda,
plan_ref,
cuda_log: FakeViolationLog,
ref_log: FakeViolationLog,
kernel_kind: CanaryLaunchTag = CanaryLaunchTag.HEAD_K_FULL,
assert_equal: bool = True,
check_verify_expected_token: bool = True,
) -> None:
launch_canary_verify_kernel(
context=VerifyOrWriteContext(
canary_buf=cuda_canary_buf,
kernel_kind=kernel_kind,
violation_ring=cuda_log.ring,
violation_write_index=cuda_log.write_index,
slot_run_counter=cuda_log.slot_run_counter,
kernel_run_counter=cuda_log.kernel_run_counter,
enable_chain_position_assert=cuda_log.enable_chain_position_assert,
),
plan=plan_cuda,
check_verify_expected_token=check_verify_expected_token,
)
launch_canary_verify_kernel_torch_reference(
context=VerifyOrWriteContext(
canary_buf=ref_canary_buf,
kernel_kind=kernel_kind,
violation_ring=ref_log.ring,
violation_write_index=ref_log.write_index,
slot_run_counter=ref_log.slot_run_counter,
kernel_run_counter=ref_log.kernel_run_counter,
enable_chain_position_assert=ref_log.enable_chain_position_assert,
),
plan=plan_ref,
check_verify_expected_token=check_verify_expected_token,
)
torch.cuda.synchronize()
if assert_equal:
assert_canary_state_equal(log_a=cuda_log, log_b=ref_log)
def _run_both_write(
*,
cuda_canary_buf: torch.Tensor,
ref_canary_buf: torch.Tensor,
plan_cuda,
plan_ref,
input_ids: torch.Tensor,
positions: torch.Tensor,
out_cache_loc: torch.Tensor,
enable_write_verify_inputs: bool,
expected_input_tokens: torch.Tensor,
expected_input_positions: torch.Tensor,
cuda_log: FakeViolationLog,
ref_log: FakeViolationLog,
kernel_kind: CanaryLaunchTag = CanaryLaunchTag.HEAD_K_FULL,
assert_equal: bool = True,
) -> None:
expected_tokens_for_launch = (
expected_input_tokens if enable_write_verify_inputs else None
)
expected_positions_for_launch = (
expected_input_positions if enable_write_verify_inputs else None
)
launch_canary_write_kernel(
context=VerifyOrWriteContext(
canary_buf=cuda_canary_buf,
kernel_kind=kernel_kind,
violation_ring=cuda_log.ring,
violation_write_index=cuda_log.write_index,
slot_run_counter=cuda_log.slot_run_counter,
kernel_run_counter=cuda_log.kernel_run_counter,
enable_chain_position_assert=cuda_log.enable_chain_position_assert,
),
plan=plan_cuda,
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
enable_write_input_assert=enable_write_verify_inputs,
expected_input_tokens=expected_tokens_for_launch,
expected_input_positions=expected_positions_for_launch,
)
launch_canary_write_kernel_torch_reference(
context=VerifyOrWriteContext(
canary_buf=ref_canary_buf,
kernel_kind=kernel_kind,
violation_ring=ref_log.ring,
violation_write_index=ref_log.write_index,
slot_run_counter=ref_log.slot_run_counter,
kernel_run_counter=ref_log.kernel_run_counter,
enable_chain_position_assert=ref_log.enable_chain_position_assert,
),
plan=plan_ref,
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
enable_write_input_assert=enable_write_verify_inputs,
expected_input_tokens=expected_tokens_for_launch,
expected_input_positions=expected_positions_for_launch,
)
torch.cuda.synchronize()
if assert_equal:
assert_canary_buf_equal(buf_a=cuda_canary_buf, buf_b=ref_canary_buf)
assert_canary_state_equal(log_a=cuda_log, log_b=ref_log)
@dataclass(frozen=True, slots=True, kw_only=True)
class ShrinkResult:
inputs: Any
mutations_applied: list[str]
def shrink_inputs(
inputs: Any,
*,
check_fn: Callable[[Any], bool],
max_iterations: int = 50,
) -> ShrinkResult:
"""Greedy 1-step minify for a fuzz inputs dataclass.
``check_fn(candidate)`` returns True when ``candidate`` still reproduces the failure. Each round
yields candidate-simpler-than-current mutations through ``_yield_simpler``; the first accepted
candidate becomes the new current. Iteration stops when no mutation is accepted or ``max_iterations``
is reached.
"""
current = inputs
applied: list[str] = []
for _ in range(max_iterations):
improved = False
for label, candidate in _yield_simpler(current):
try:
still_fails = check_fn(candidate)
except Exception:
still_fails = False
if still_fails:
current = candidate
applied.append(label)
improved = True
break
if not improved:
break
return ShrinkResult(inputs=current, mutations_applied=applied)
def _yield_simpler(inputs: Any) -> Iterator[tuple[str, Any]]:
"""Yield (label, simpler_candidate) tuples for generic fuzz-input minifiers.
The candidates touch only well-known field names; an inputs dataclass that lacks a field will simply
have that mutation skipped. No kernel-specific knowledge is encoded here so the same shrinker drives
Plan / Verify / Write fuzz failures uniformly.
"""
fields = {
f: getattr(inputs, f) for f in inputs.__dataclass_fields__ # type: ignore[attr-defined]
}
def emit(label: str, **overrides: Any) -> Iterator[tuple[str, Any]]:
candidate = replace(inputs, **overrides)
yield label, candidate
bs_field = (
"req_pool_indices"
if "req_pool_indices" in fields
else ("input_ids" if "input_ids" in fields else None)
)
if bs_field is not None and isinstance(fields[bs_field], torch.Tensor):
tensor = fields[bs_field]
if tensor.numel() > 1:
new_len = tensor.numel() - 1
related_tensors_overrides: dict[str, Any] = {}
for name in (
"req_pool_indices",
"prefix_lens",
"extend_seq_lens",
"input_ids",
"positions",
"out_cache_loc",
"expected_input_tokens",
"expected_input_positions",
):
t = fields.get(name)
if (
isinstance(t, torch.Tensor)
and t.numel() >= new_len
and t.dim() == 1
):
related_tensors_overrides[name] = t[:new_len].contiguous()
if related_tensors_overrides:
yield from emit("drop_last_row", **related_tensors_overrides)
if "swa_window_size" in fields and isinstance(fields["swa_window_size"], int):
if fields["swa_window_size"] != 0:
yield from emit(
"swa_off", swa_window_size=0, full_to_swa_index_mapping=None
)
if "extras_count" in fields and isinstance(fields["extras_count"], int):
if fields["extras_count"] > 0:
yield from emit("extras_zero", extras_count=0)
if "enable_write_verify_inputs" in fields:
cur = fields["enable_write_verify_inputs"]
if hasattr(cur, "value") and int(cur) != 0:
cls = cur.__class__
yield from emit("pseudo_off", enable_write_verify_inputs=cls(0))
for name in ("verify_capacity", "write_req_capacity"):
if name in fields and isinstance(fields[name], int):
current_value = fields[name]
if current_value > 8:
yield from emit(f"shrink_{name}", **{name: max(8, current_value // 2)})
def run_verify_diff(
*,
buf_pair: tuple[torch.Tensor, torch.Tensor],
plan_pair: tuple[VerifyPlan, VerifyPlan],
kernel_kind: CanaryLaunchTag = CanaryLaunchTag.HEAD_K_FULL,
device: torch.device = _DEVICE,
assert_equal: bool = True,
check_verify_expected_token: bool = True,
) -> tuple[FakeViolationLog, FakeViolationLog]:
"""Thin wrapper around ``_run_both_verify`` that creates a fresh log pair and packs (cuda, ref)
buf/plan arguments into 2-tuples to drop ~8 lines of boilerplate per call site.
"""
cuda_log, ref_log = make_log_pair(device=device)
_run_both_verify(
cuda_canary_buf=buf_pair[0],
ref_canary_buf=buf_pair[1],
plan_cuda=plan_pair[0],
plan_ref=plan_pair[1],
cuda_log=cuda_log,
ref_log=ref_log,
kernel_kind=kernel_kind,
assert_equal=assert_equal,
check_verify_expected_token=check_verify_expected_token,
)
return cuda_log, ref_log
def run_write_diff(
*,
buf_pair: tuple[torch.Tensor, torch.Tensor],
plan_pair: tuple[WritePlan, WritePlan],
input_ids: torch.Tensor,
positions: torch.Tensor,
out_cache_loc: torch.Tensor,
expected_input_tokens: torch.Tensor,
expected_input_positions: torch.Tensor,
enable_write_verify_inputs: bool = False,
kernel_kind: CanaryLaunchTag = CanaryLaunchTag.HEAD_K_FULL,
device: torch.device = _DEVICE,
assert_equal: bool = True,
) -> tuple[FakeViolationLog, FakeViolationLog]:
"""Thin wrapper around ``_run_both_write`` that creates a fresh log pair and packs (cuda, ref)
buf/plan arguments into 2-tuples to drop ~10 lines of boilerplate per call site.
"""
cuda_log, ref_log = make_log_pair(device=device)
_run_both_write(
cuda_canary_buf=buf_pair[0],
ref_canary_buf=buf_pair[1],
plan_cuda=plan_pair[0],
plan_ref=plan_pair[1],
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
enable_write_verify_inputs=enable_write_verify_inputs,
expected_input_tokens=expected_input_tokens,
expected_input_positions=expected_input_positions,
cuda_log=cuda_log,
ref_log=ref_log,
kernel_kind=kernel_kind,
assert_equal=assert_equal,
)
return cuda_log, ref_log
def run_plan_diff(
*,
plan_pair: tuple[tuple[VerifyPlan, WritePlan], tuple[VerifyPlan, WritePlan]],
req_pool_indices: torch.Tensor,
prefix_lens: torch.Tensor,
extend_seq_lens: torch.Tensor,
req_to_token: torch.Tensor,
extras: tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor],
swa_window_size: int = 0,
full_to_swa_index_mapping: Optional[torch.Tensor] = None,
assert_equal: bool = True,
active_verify_entries: Optional[int] = None,
active_write_reqs: Optional[int] = None,
req_to_verify_expected_tokens: Optional[torch.Tensor] = None,
req_to_verify_expected_tokens_valid_lens: Optional[torch.Tensor] = None,
kv_token_id_vs_position_offset: int = 0,
) -> None:
"""Thin wrapper around ``_run_both_plan`` that unpacks ``((triton_v, triton_w), (ref_v, ref_w))``
plan pairs to drop the per-call-site ``triton_verify=.../triton_write=.../ref_verify=...`` block.
"""
(triton_verify, triton_write), (ref_verify, ref_write) = plan_pair
_run_both_plan(
triton_verify=triton_verify,
triton_write=triton_write,
ref_verify=ref_verify,
ref_write=ref_write,
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
extras=extras,
swa_window_size=swa_window_size,
full_to_swa_index_mapping=full_to_swa_index_mapping,
assert_equal=assert_equal,
active_verify_entries=active_verify_entries,
active_write_reqs=active_write_reqs,
req_to_verify_expected_tokens=req_to_verify_expected_tokens,
req_to_verify_expected_tokens_valid_lens=req_to_verify_expected_tokens_valid_lens,
kv_token_id_vs_position_offset=kv_token_id_vs_position_offset,
)
@@ -0,0 +1,45 @@
from __future__ import annotations
import random
from typing import Any, Callable
from sglang.jit_kernel.tests.kv_canary._differential import (
ShrinkResult,
shrink_inputs,
)
FUZZ_SEEDS_PR: tuple[int, ...] = (0,)
def check_repro(inputs: Any, *, run_one_fn: Callable[[Any], Any]) -> bool:
try:
run_one_fn(inputs)
except (AssertionError, RuntimeError, ValueError):
return True
return False
def run_fuzz_combo(
seed: int,
*,
draw_fn: Callable[[random.Random], Any],
run_one_fn: Callable[[Any], Any],
summarize_fn: Callable[[Any], str],
n_iter: int,
) -> None:
rng = random.Random(seed)
for iteration in range(n_iter):
inputs = draw_fn(rng)
try:
run_one_fn(inputs)
except AssertionError as exc:
shrunk: ShrinkResult = shrink_inputs(
inputs,
check_fn=lambda i: check_repro(i, run_one_fn=run_one_fn),
)
raise AssertionError(
f"seed={seed} iter={iteration} failure: {exc}\n"
f"original: {summarize_fn(inputs)}\n"
f"shrunk: {summarize_fn(shrunk.inputs)}\n"
f"mutations applied: {shrunk.mutations_applied}"
) from exc
@@ -0,0 +1,425 @@
from __future__ import annotations
import pytest
import torch
from sglang.jit_kernel.kv_canary.verify import (
CanaryLaunchTag,
VerifyOrWriteContext,
VerifyPlan,
launch_canary_verify_kernel,
)
from sglang.jit_kernel.kv_canary.write import WritePlan
from sglang.jit_kernel.tests.kv_canary._canary_helpers import (
FakeViolationLog,
assert_canary_buf_equal,
assert_canary_state_equal,
make_canary_buf,
make_canary_buf_pair,
make_log_pair,
make_verify_plan,
make_verify_plan_pair,
make_write_plan_pair,
stamp_clean_chain,
)
from sglang.jit_kernel.tests.kv_canary._differential import (
_assert_plans_byte_equal,
_run_both_plan,
_run_both_verify,
_run_both_write,
)
from sglang.jit_kernel.tests.kv_canary._fixtures import (
dummy_pseudo_tensors,
empty_extras,
)
from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=60, suite="base-b-kernel-unit-1-gpu-large")
_DEVICE = torch.device("cuda")
def _build_verify_plan_5_entries(
*, device: torch.device
) -> tuple[VerifyPlan, VerifyPlan]:
num_slots = 16
cuda_buf, ref_buf = make_canary_buf_pair(
num_slots=num_slots, slot_stride_bytes=32, device=_DEVICE
)
plan_cuda, plan_ref = make_verify_plan_pair(
slot_indices=[0, 1, 2, 3, 4],
positions=[0, 1, 2, 3, 4],
prev_slot_indices=[-1, 0, 1, 2, 3],
capacity=8,
device=device,
)
return plan_cuda, plan_ref
def _build_write_fixtures(
*, device: torch.device
) -> tuple[WritePlan, WritePlan, torch.Tensor, torch.Tensor, torch.Tensor]:
num_tokens = 5
plan_cuda, plan_ref = make_write_plan_pair(
write_offsets=[0, num_tokens],
seed_slot_indices=[-1],
num_valid_reqs=1,
req_capacity=4,
device=device,
)
input_ids = torch.tensor([10, 20, 30, 40, 50], dtype=torch.int64, device=device)
positions = torch.tensor([0, 1, 2, 3, 4], dtype=torch.int64, device=device)
out_cache_loc = torch.tensor([0, 1, 2, 3, 4], dtype=torch.int64, device=device)
return plan_cuda, plan_ref, input_ids, positions, out_cache_loc
def _build_plan_fixtures(
*, device: torch.device, int64_req_to_token: bool = False
) -> tuple[
torch.Tensor,
torch.Tensor,
torch.Tensor,
torch.Tensor,
]:
bs = 3
max_reqs = 4
max_seq_len = 16
req_pool_indices = torch.tensor([1, 2, 3], dtype=torch.int64, device=device)
prefix_lens = torch.tensor([0, 4, 8], dtype=torch.int64, device=device)
extend_seq_lens = torch.tensor([5, 1, 1], dtype=torch.int64, device=device)
rp_axis = torch.arange(max_reqs, device=device, dtype=torch.int32).unsqueeze(1)
pos_axis = torch.arange(max_seq_len, device=device, dtype=torch.int32).unsqueeze(0)
req_to_token_int32 = (rp_axis * max_seq_len + pos_axis).contiguous()
if int64_req_to_token:
req_to_token = req_to_token_int32.to(torch.int64)
else:
req_to_token = req_to_token_int32
return req_pool_indices, prefix_lens, extend_seq_lens, req_to_token
def test_verify_byte_equal_across_repeated_launches_10x() -> None:
num_launches = 10
plan_cuda, plan_ref = _build_verify_plan_5_entries(device=_DEVICE)
snapshot_rings: list[torch.Tensor] = []
snapshot_write_indices: list[torch.Tensor] = []
snapshot_bufs: list[torch.Tensor] = []
for _ in range(num_launches):
cuda_buf, ref_buf = make_canary_buf_pair(
num_slots=16, slot_stride_bytes=32, device=_DEVICE
)
cuda_log, ref_log = make_log_pair(capacity=64, device=_DEVICE)
_run_both_verify(
cuda_canary_buf=cuda_buf,
ref_canary_buf=ref_buf,
plan_cuda=plan_cuda,
plan_ref=plan_ref,
cuda_log=cuda_log,
ref_log=ref_log,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
)
assert_canary_buf_equal(buf_a=cuda_buf, buf_b=ref_buf)
assert_canary_state_equal(log_a=cuda_log, log_b=ref_log)
snapshot_rings.append(cuda_log.ring.clone())
snapshot_write_indices.append(cuda_log.write_index.clone())
snapshot_bufs.append(cuda_buf.clone())
for i in range(1, num_launches):
assert torch.equal(
snapshot_rings[0], snapshot_rings[i]
), f"violation_ring differs between launch 0 and {i}"
assert torch.equal(
snapshot_write_indices[0], snapshot_write_indices[i]
), f"violation_write_index differs between launch 0 and {i}"
assert torch.equal(
snapshot_bufs[0], snapshot_bufs[i]
), f"canary_buf differs between launch 0 and {i}"
def test_write_byte_equal_across_repeated_launches_10x() -> None:
num_launches = 10
plan_cuda, plan_ref, input_ids, positions, out_cache_loc = _build_write_fixtures(
device=_DEVICE
)
snapshot_bufs: list[torch.Tensor] = []
snapshot_rings: list[torch.Tensor] = []
snapshot_counters: list[torch.Tensor] = []
for _ in range(num_launches):
cuda_buf, ref_buf = make_canary_buf_pair(
num_slots=16, slot_stride_bytes=32, device=_DEVICE
)
cuda_log, ref_log = make_log_pair(capacity=64, device=_DEVICE)
pseudo_tok, pseudo_pos = dummy_pseudo_tensors(input_ids.shape[0])
_run_both_write(
cuda_canary_buf=cuda_buf,
ref_canary_buf=ref_buf,
plan_cuda=plan_cuda,
plan_ref=plan_ref,
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
enable_write_verify_inputs=False,
expected_input_tokens=pseudo_tok,
expected_input_positions=pseudo_pos,
cuda_log=cuda_log,
ref_log=ref_log,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
)
assert_canary_buf_equal(buf_a=cuda_buf, buf_b=ref_buf)
assert_canary_state_equal(log_a=cuda_log, log_b=ref_log)
snapshot_bufs.append(cuda_buf.clone())
snapshot_rings.append(cuda_log.ring.clone())
snapshot_counters.append(cuda_log.slot_run_counter.clone())
for i in range(1, num_launches):
assert torch.equal(
snapshot_bufs[0], snapshot_bufs[i]
), f"canary_buf differs between launch 0 and {i}"
assert torch.equal(
snapshot_rings[0], snapshot_rings[i]
), f"violation_ring differs between launch 0 and {i}"
assert torch.equal(
snapshot_counters[0], snapshot_counters[i]
), f"slot_run_counter differs between launch 0 and {i}"
def test_plan_byte_equal_across_repeated_launches_10x() -> None:
num_launches = 10
req_pool_indices, prefix_lens, extend_seq_lens, req_to_token = _build_plan_fixtures(
device=_DEVICE
)
snapshot_slots: list[torch.Tensor] = []
snapshot_positions: list[torch.Tensor] = []
snapshot_prevs: list[torch.Tensor] = []
snapshot_write_offsets: list[torch.Tensor] = []
for _ in range(num_launches):
triton_v = VerifyPlan.allocate(
verify_capacity=64, device=_DEVICE
).zero_for_testing_()
triton_w = WritePlan.allocate(
write_req_capacity=8, device=_DEVICE
).zero_for_testing_()
ref_v = VerifyPlan.allocate(
verify_capacity=64, device=_DEVICE
).zero_for_testing_()
ref_w = WritePlan.allocate(
write_req_capacity=8, device=_DEVICE
).zero_for_testing_()
_run_both_plan(
triton_verify=triton_v,
triton_write=triton_w,
ref_verify=ref_v,
ref_write=ref_w,
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
extras=empty_extras(),
swa_window_size=0,
full_to_swa_index_mapping=None,
)
_assert_plans_byte_equal(
triton_verify=triton_v,
triton_write=triton_w,
ref_verify=ref_v,
ref_write=ref_w,
)
n_verify = int(triton_v.verify_num_valid[0].item())
snapshot_slots.append(triton_v.verify_slot_indices[:n_verify].clone())
snapshot_positions.append(triton_v.verify_expected_positions[:n_verify].clone())
snapshot_prevs.append(triton_v.verify_prev_slot_indices[:n_verify].clone())
snapshot_write_offsets.append(triton_w.write_offsets.clone())
for i in range(1, num_launches):
assert torch.equal(
snapshot_slots[0], snapshot_slots[i]
), f"verify_slot_indices differs between launch 0 and {i}"
assert torch.equal(
snapshot_positions[0], snapshot_positions[i]
), f"verify_expected_positions differs between launch 0 and {i}"
assert torch.equal(
snapshot_prevs[0], snapshot_prevs[i]
), f"verify_prev_slot_indices differs between launch 0 and {i}"
assert torch.equal(
snapshot_write_offsets[0], snapshot_write_offsets[i]
), f"write_offsets differs between launch 0 and {i}"
def test_verify_multi_launch_100x_counter_linear() -> None:
num_launches = 100
plan_cuda = make_verify_plan(
slot_indices=[0],
positions=[0],
prev_slot_indices=[-1],
capacity=4,
device=_DEVICE,
)
cuda_log = FakeViolationLog.allocate(capacity=64, device=_DEVICE)
for _ in range(num_launches):
cuda_buf = make_canary_buf(num_slots=16, slot_stride_bytes=32, device=_DEVICE)
launch_canary_verify_kernel(
context=VerifyOrWriteContext(
canary_buf=cuda_buf,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
violation_ring=cuda_log.ring,
violation_write_index=cuda_log.write_index,
slot_run_counter=cuda_log.slot_run_counter,
kernel_run_counter=cuda_log.kernel_run_counter,
enable_chain_position_assert=cuda_log.enable_chain_position_assert,
),
plan=plan_cuda,
check_verify_expected_token=True,
)
torch.cuda.synchronize()
assert (
int(cuda_log.kernel_run_counter[0].item()) == num_launches
), f"kernel_run_counter expected {num_launches}, got {cuda_log.kernel_run_counter[0].item()}"
assert int(cuda_log.slot_run_counter[0].item()) == num_launches, (
f"slot_run_counter expected {num_launches} (1 active entry x 100 launches), "
f"got {cuda_log.slot_run_counter[0].item()}"
)
def test_verify_check_disabled_byte_equal() -> None:
"""check_verify_expected_token True vs False produce equivalent violation logs on a clean plan."""
plan_true_cuda, plan_true_ref = _build_verify_plan_5_entries(device=_DEVICE)
plan_false_cuda, plan_false_ref = _build_verify_plan_5_entries(device=_DEVICE)
chain_slot_indices = [0, 1, 2, 3, 4]
chain_tokens = [10, 20, 30, 40, 50]
chain_positions = [0, 1, 2, 3, 4]
cuda_buf_true, ref_buf_true = make_canary_buf_pair(
num_slots=16, slot_stride_bytes=32, device=_DEVICE
)
stamp_clean_chain(
cuda_buf=cuda_buf_true,
ref_buf=ref_buf_true,
slot_indices=chain_slot_indices,
tokens=chain_tokens,
positions=chain_positions,
)
cuda_buf_false, ref_buf_false = make_canary_buf_pair(
num_slots=16, slot_stride_bytes=32, device=_DEVICE
)
stamp_clean_chain(
cuda_buf=cuda_buf_false,
ref_buf=ref_buf_false,
slot_indices=chain_slot_indices,
tokens=chain_tokens,
positions=chain_positions,
)
cuda_log_true, ref_log_true = make_log_pair(capacity=64, device=_DEVICE)
cuda_log_false, ref_log_false = make_log_pair(capacity=64, device=_DEVICE)
_run_both_verify(
cuda_canary_buf=cuda_buf_true,
ref_canary_buf=ref_buf_true,
plan_cuda=plan_true_cuda,
plan_ref=plan_true_ref,
cuda_log=cuda_log_true,
ref_log=ref_log_true,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
check_verify_expected_token=True,
)
_run_both_verify(
cuda_canary_buf=cuda_buf_false,
ref_canary_buf=ref_buf_false,
plan_cuda=plan_false_cuda,
plan_ref=plan_false_ref,
cuda_log=cuda_log_false,
ref_log=ref_log_false,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
check_verify_expected_token=False,
)
assert int(cuda_log_true.write_index[0].item()) == 0
assert int(cuda_log_false.write_index[0].item()) == 0
assert torch.equal(cuda_log_true.ring, cuda_log_false.ring)
assert torch.equal(cuda_log_true.write_index, cuda_log_false.write_index)
assert torch.equal(cuda_log_true.slot_run_counter, cuda_log_false.slot_run_counter)
assert torch.equal(
cuda_log_true.kernel_run_counter, cuda_log_false.kernel_run_counter
)
@pytest.mark.parametrize("per_req_present", [False, True])
def test_plan_per_req_present_or_absent(per_req_present: bool) -> None:
max_reqs = 4
max_seq_len = 16
rp_axis = torch.arange(max_reqs, device=_DEVICE, dtype=torch.int32).unsqueeze(1)
pos_axis = torch.arange(max_seq_len, device=_DEVICE, dtype=torch.int32).unsqueeze(0)
req_to_token = (rp_axis * max_seq_len + pos_axis).contiguous()
if per_req_present:
req_pool_indices = torch.tensor([1, 2], dtype=torch.int64, device=_DEVICE)
prefix_lens = torch.tensor([3, 5], dtype=torch.int64, device=_DEVICE)
extend_seq_lens = torch.tensor([1, 1], dtype=torch.int64, device=_DEVICE)
else:
req_pool_indices = torch.tensor([0], dtype=torch.int64, device=_DEVICE)
prefix_lens = torch.tensor([0], dtype=torch.int64, device=_DEVICE)
extend_seq_lens = torch.tensor([0], dtype=torch.int64, device=_DEVICE)
triton_v = VerifyPlan.allocate(
verify_capacity=64, device=_DEVICE
).zero_for_testing_()
triton_w = WritePlan.allocate(
write_req_capacity=8, device=_DEVICE
).zero_for_testing_()
ref_v = VerifyPlan.allocate(verify_capacity=64, device=_DEVICE).zero_for_testing_()
ref_w = WritePlan.allocate(write_req_capacity=8, device=_DEVICE).zero_for_testing_()
_run_both_plan(
triton_verify=triton_v,
triton_write=triton_w,
ref_verify=ref_v,
ref_write=ref_w,
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
extras=empty_extras(),
swa_window_size=0,
full_to_swa_index_mapping=None,
)
_assert_plans_byte_equal(
triton_verify=triton_v,
triton_write=triton_w,
ref_verify=ref_v,
ref_write=ref_w,
)
if not per_req_present:
assert int(triton_v.verify_num_valid[0].item()) == 0
bs = int(req_pool_indices.shape[0])
assert int(triton_w.write_offsets[bs].item()) == 0
if per_req_present:
assert int(triton_v.verify_num_valid[0].item()) == 8
if __name__ == "__main__":
import sys
sys.exit(pytest.main([__file__, "-v"]))
@@ -0,0 +1,790 @@
from __future__ import annotations
from typing import Any, Optional
import pytest
import torch
from sglang.jit_kernel.kv_canary import consts
from sglang.jit_kernel.kv_canary.plan import launch_canary_plan_kernels
from sglang.jit_kernel.kv_canary.plan_ref import (
launch_canary_plan_kernels_torch_reference,
)
from sglang.jit_kernel.kv_canary.verify import (
CanaryLaunchTag,
VerifyOrWriteContext,
VerifyPlan,
launch_canary_verify_kernel,
)
from sglang.jit_kernel.kv_canary.verify_ref import (
launch_canary_verify_kernel_torch_reference,
)
from sglang.jit_kernel.kv_canary.write import WritePlan, launch_canary_write_kernel
from sglang.jit_kernel.kv_canary.write_ref import (
launch_canary_write_kernel_torch_reference,
)
from sglang.jit_kernel.tests.kv_canary._canary_helpers import (
FakeViolationLog,
assert_canary_buf_equal,
assert_canary_state_equal,
make_canary_buf,
stamp_clean_chain,
write_slot_fields,
)
from sglang.jit_kernel.tests.kv_canary._fixtures import (
empty_extras,
make_req_to_token,
)
from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=30, suite="base-b-kernel-unit-1-gpu-large")
_DEVICE = torch.device("cuda")
def _run_pipeline(
*,
real: bool,
req_pool_indices: torch.Tensor,
prefix_lens: torch.Tensor,
extend_seq_lens: torch.Tensor,
input_ids: torch.Tensor,
positions: torch.Tensor,
out_cache_loc: torch.Tensor,
req_to_token: torch.Tensor,
canary_buf: torch.Tensor,
log: FakeViolationLog,
extras: tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor],
swa_window_size: int,
full_to_swa_index_mapping: Optional[torch.Tensor],
kernel_kind: CanaryLaunchTag,
enable_write_verify_inputs: bool,
expected_input_tokens: torch.Tensor,
expected_input_positions: torch.Tensor,
verify_capacity: int,
write_req_capacity: int,
req_to_verify_expected_tokens: Optional[torch.Tensor] = None,
req_to_verify_expected_tokens_valid_lens: Optional[torch.Tensor] = None,
kv_token_id_vs_position_offset: int = 0,
check_verify_expected_token: bool = True,
) -> tuple[VerifyPlan, WritePlan]:
_ = extras
plan_v = VerifyPlan.allocate(verify_capacity=verify_capacity, device=_DEVICE)
plan_w = WritePlan.allocate(write_req_capacity=write_req_capacity, device=_DEVICE)
# Existing pipeline tests that supply a pool but no per-req lens want "bound by full
# row width" semantics. Synthesise that bound here so callers don't have to.
if (
req_to_verify_expected_tokens is not None
and req_to_verify_expected_tokens_valid_lens is None
):
req_to_verify_expected_tokens_valid_lens = torch.full(
(int(req_pool_indices.shape[0]),),
int(req_to_verify_expected_tokens.shape[1]),
dtype=torch.int64,
device=req_pool_indices.device,
)
plan_fn = (
launch_canary_plan_kernels
if real
else launch_canary_plan_kernels_torch_reference
)
plan_fn(
verify_plan_out=plan_v,
write_plan_out=plan_w,
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
swa_window_size=swa_window_size,
full_to_swa_index_mapping=full_to_swa_index_mapping,
verify_capacity=verify_capacity,
req_to_verify_expected_tokens=req_to_verify_expected_tokens,
req_to_verify_expected_tokens_valid_lens=req_to_verify_expected_tokens_valid_lens,
kv_token_id_vs_position_offset=kv_token_id_vs_position_offset,
)
if real:
context = VerifyOrWriteContext(
canary_buf=canary_buf,
kernel_kind=kernel_kind,
violation_ring=log.ring,
violation_write_index=log.write_index,
slot_run_counter=log.slot_run_counter,
kernel_run_counter=log.kernel_run_counter,
enable_chain_position_assert=log.enable_chain_position_assert,
)
launch_canary_write_kernel(
context=context,
plan=plan_w,
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
enable_write_input_assert=enable_write_verify_inputs,
expected_input_tokens=expected_input_tokens,
expected_input_positions=expected_input_positions,
)
launch_canary_verify_kernel(
context=context,
plan=plan_v,
check_verify_expected_token=check_verify_expected_token,
)
torch.cuda.synchronize()
else:
launch_canary_write_kernel_torch_reference(
context=VerifyOrWriteContext(
canary_buf=canary_buf,
kernel_kind=kernel_kind,
violation_ring=log.ring,
violation_write_index=log.write_index,
slot_run_counter=log.slot_run_counter,
kernel_run_counter=log.kernel_run_counter,
enable_chain_position_assert=log.enable_chain_position_assert,
),
plan=plan_w,
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
enable_write_input_assert=enable_write_verify_inputs,
expected_input_tokens=expected_input_tokens,
expected_input_positions=expected_input_positions,
)
launch_canary_verify_kernel_torch_reference(
context=VerifyOrWriteContext(
canary_buf=canary_buf,
kernel_kind=kernel_kind,
violation_ring=log.ring,
violation_write_index=log.write_index,
slot_run_counter=log.slot_run_counter,
kernel_run_counter=log.kernel_run_counter,
enable_chain_position_assert=log.enable_chain_position_assert,
),
plan=plan_v,
check_verify_expected_token=check_verify_expected_token,
)
return plan_v, plan_w
def _run_both_and_assert_pipeline_equal(
*,
req_pool_indices: torch.Tensor,
prefix_lens: torch.Tensor,
extend_seq_lens: torch.Tensor,
input_ids: torch.Tensor,
positions: torch.Tensor,
out_cache_loc: torch.Tensor,
req_to_token: torch.Tensor,
num_slots: int,
extras: tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor],
swa_window_size: int = 0,
full_to_swa_index_mapping: Optional[torch.Tensor] = None,
kernel_kind: CanaryLaunchTag = CanaryLaunchTag.HEAD_K_FULL,
enable_write_verify_inputs: bool = False,
expected_input_tokens: Optional[torch.Tensor] = None,
expected_input_positions: Optional[torch.Tensor] = None,
ring_capacity: int = 64,
verify_capacity: int = 256,
write_req_capacity: int = 16,
assert_ring_equal: bool = True,
initial_canary_buf: Optional[torch.Tensor] = None,
req_to_verify_expected_tokens: Optional[torch.Tensor] = None,
kv_token_id_vs_position_offset: int = 0,
check_verify_expected_token: bool = True,
) -> tuple[
torch.Tensor,
torch.Tensor,
FakeViolationLog,
FakeViolationLog,
VerifyPlan,
WritePlan,
VerifyPlan,
WritePlan,
]:
# The kernel rejects non-None expected_* tensors when enable_write_verify_inputs=False
# (sanity check to catch caller bugs), so only synthesise zero placeholders in the
# branch that will actually assert against them.
if enable_write_verify_inputs:
total_tokens = int(input_ids.shape[0])
if expected_input_tokens is None:
expected_input_tokens = torch.zeros(
total_tokens, dtype=torch.int64, device=_DEVICE
)
if expected_input_positions is None:
expected_input_positions = torch.zeros(
total_tokens, dtype=torch.int64, device=_DEVICE
)
if initial_canary_buf is None:
buf_real = make_canary_buf(num_slots=num_slots, device=_DEVICE)
else:
buf_real = initial_canary_buf.clone()
buf_ref = buf_real.clone()
log_real = FakeViolationLog.allocate(capacity=ring_capacity, device=_DEVICE)
log_ref = FakeViolationLog.allocate(capacity=ring_capacity, device=_DEVICE)
shared: dict[str, Any] = dict(
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
req_to_token=req_to_token,
extras=extras,
swa_window_size=swa_window_size,
full_to_swa_index_mapping=full_to_swa_index_mapping,
kernel_kind=kernel_kind,
enable_write_verify_inputs=enable_write_verify_inputs,
expected_input_tokens=expected_input_tokens,
expected_input_positions=expected_input_positions,
verify_capacity=verify_capacity,
write_req_capacity=write_req_capacity,
req_to_verify_expected_tokens=req_to_verify_expected_tokens,
kv_token_id_vs_position_offset=kv_token_id_vs_position_offset,
check_verify_expected_token=check_verify_expected_token,
)
plan_v_real, plan_w_real = _run_pipeline(
real=True,
canary_buf=buf_real,
log=log_real,
**shared,
)
plan_v_ref, plan_w_ref = _run_pipeline(
real=False,
canary_buf=buf_ref,
log=log_ref,
**shared,
)
assert_canary_buf_equal(buf_a=buf_real, buf_b=buf_ref)
if assert_ring_equal:
assert_canary_state_equal(log_a=log_real, log_b=log_ref)
else:
assert torch.equal(log_real.write_index, log_ref.write_index)
assert torch.equal(log_real.slot_run_counter, log_ref.slot_run_counter)
assert torch.equal(log_real.kernel_run_counter, log_ref.kernel_run_counter)
return (
buf_real,
buf_ref,
log_real,
log_ref,
plan_v_real,
plan_w_real,
plan_v_ref,
plan_w_ref,
)
def _t(values: list[int]) -> torch.Tensor:
return torch.tensor(values, dtype=torch.int64, device=_DEVICE)
def _linear_r2t(*, max_reqs: int = 4, max_seq_len: int = 16) -> torch.Tensor:
return make_req_to_token(
kind="linear", max_reqs=max_reqs, max_seq_len=max_seq_len, device=_DEVICE
)
def _zero_no_write_inputs() -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""``(input_ids, positions, out_cache_loc)`` zero placeholders for extend_seq_lens=0 tests."""
zeros = torch.zeros(1, dtype=torch.int64, device=_DEVICE)
return zeros.clone(), zeros.clone(), zeros.clone()
def _contiguous_out_cache_loc(
*, req_pool_idx: int, start: int, count: int, max_seq_len: int = 16
) -> torch.Tensor:
return _t([req_pool_idx * max_seq_len + start + i for i in range(count)])
def _stamp_linear_prefix(
*,
initial_buf: torch.Tensor,
initial_ref: torch.Tensor,
req_pool_idx: int,
prefix_len: int,
tokens: list[int],
max_seq_len: int = 16,
) -> None:
"""Stamp clean chain for slots ``[rp*max_seq_len + 0 .. + prefix_len)`` at positions ``0..prefix_len``."""
stamp_clean_chain(
cuda_buf=initial_buf,
ref_buf=initial_ref,
slot_indices=[req_pool_idx * max_seq_len + pos for pos in range(prefix_len)],
tokens=tokens,
positions=list(range(prefix_len)),
)
def test_pipeline_basic_5_step_single_req() -> None:
"""Single req, prefix_len=0, extend_seq_len=5: basic plan→write→verify byte-equal."""
_run_both_and_assert_pipeline_equal(
req_pool_indices=_t([1]),
prefix_lens=_t([0]),
extend_seq_lens=_t([5]),
input_ids=_t([10, 20, 30, 40, 50]),
positions=_t([0, 1, 2, 3, 4]),
out_cache_loc=_contiguous_out_cache_loc(req_pool_idx=1, start=0, count=5),
req_to_token=_linear_r2t(),
num_slots=64,
extras=empty_extras(),
swa_window_size=0,
full_to_swa_index_mapping=None,
)
def test_pipeline_multi_req_mixed_extend_decode() -> None:
"""bs=3: pure extend req, decode req (prefix+1 extend), and padding sentinel row."""
max_seq_len = 16
_run_both_and_assert_pipeline_equal(
req_pool_indices=_t([1, 2, 0]),
prefix_lens=_t([0, 5, 0]),
extend_seq_lens=_t([4, 1, 0]),
input_ids=_t([11, 12, 13, 14, 21]),
positions=_t([0, 1, 2, 3, 5]),
out_cache_loc=_t(
[
1 * max_seq_len + 0,
1 * max_seq_len + 1,
1 * max_seq_len + 2,
1 * max_seq_len + 3,
2 * max_seq_len + 5,
]
),
req_to_token=_linear_r2t(max_reqs=8, max_seq_len=max_seq_len),
num_slots=128,
extras=empty_extras(),
swa_window_size=0,
full_to_swa_index_mapping=None,
write_req_capacity=4,
)
def test_pipeline_swa_window() -> None:
"""SWA window=4, prefix_len=6: verify covers window [2,6), write covers extend tokens."""
max_seq_len = 16
max_reqs = 4
full_to_swa_index_mapping = torch.arange(
max_reqs * max_seq_len + 1, dtype=torch.int64, device=_DEVICE
)
_run_both_and_assert_pipeline_equal(
req_pool_indices=_t([1]),
prefix_lens=_t([6]),
extend_seq_lens=_t([2]),
input_ids=_t([100, 101]),
positions=_t([6, 7]),
out_cache_loc=_t(
[
full_to_swa_index_mapping[1 * max_seq_len + 6].item(),
full_to_swa_index_mapping[1 * max_seq_len + 7].item(),
]
),
req_to_token=_linear_r2t(max_reqs=max_reqs, max_seq_len=max_seq_len),
num_slots=64,
extras=empty_extras(),
swa_window_size=4,
full_to_swa_index_mapping=full_to_swa_index_mapping,
)
def test_pipeline_sweep_no_write() -> None:
"""All extend_seq_lens=0: write_step is no-op, verify sweeps prefix, buf unchanged."""
prefix_len = 4
input_ids, positions, out_cache_loc = _zero_no_write_inputs()
initial_buf = make_canary_buf(num_slots=64, device=_DEVICE)
initial_ref = initial_buf.clone()
_stamp_linear_prefix(
initial_buf=initial_buf,
initial_ref=initial_ref,
req_pool_idx=1,
prefix_len=prefix_len,
tokens=[100 + pos for pos in range(prefix_len)],
)
buf_real, buf_ref, log_real, log_ref, plan_v_real, plan_w_real, _, _ = (
_run_both_and_assert_pipeline_equal(
req_pool_indices=_t([1]),
prefix_lens=_t([prefix_len]),
extend_seq_lens=_t([0]),
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
req_to_token=_linear_r2t(),
num_slots=64,
extras=empty_extras(),
swa_window_size=0,
full_to_swa_index_mapping=None,
initial_canary_buf=initial_buf,
)
)
assert int(plan_v_real.verify_num_valid[0].item()) == prefix_len
assert int(plan_w_real.write_num_valid_reqs[0].item()) == 1
assert int(plan_w_real.write_offsets[1].item()) == 0
assert torch.equal(buf_real, initial_buf)
assert torch.equal(buf_ref, initial_buf)
assert int(log_real.write_index[0].item()) == 0
assert int(log_ref.write_index[0].item()) == 0
assert int(log_real.slot_run_counter[0].item()) == prefix_len
assert int(log_ref.slot_run_counter[0].item()) == prefix_len
def test_pipeline_pseudo_mode_on_match() -> None:
"""enable_write_verify_inputs=ON, expected==actual: zero violations, buf byte-equal."""
input_ids = _t([1, 2, 3, 4])
positions = _t([0, 1, 2, 3])
_, _, log_real, log_ref, _, _, _, _ = _run_both_and_assert_pipeline_equal(
req_pool_indices=_t([1]),
prefix_lens=_t([0]),
extend_seq_lens=_t([4]),
input_ids=input_ids,
positions=positions,
out_cache_loc=_contiguous_out_cache_loc(req_pool_idx=1, start=0, count=4),
req_to_token=_linear_r2t(),
num_slots=64,
extras=empty_extras(),
enable_write_verify_inputs=True,
expected_input_tokens=input_ids.clone(),
expected_input_positions=positions.clone(),
)
assert int(log_real.write_index[0].item()) == 0
assert int(log_ref.write_index[0].item()) == 0
def test_pipeline_pseudo_mode_on_token_mismatch_then_verify_clean() -> None:
"""enable_write_verify_inputs=ON, expected tokens all wrong: write records N violations."""
n_tokens = 3
positions = _t([0, 1, 2])
_, _, log_real, log_ref, _, _, _, _ = _run_both_and_assert_pipeline_equal(
req_pool_indices=_t([1]),
prefix_lens=_t([0]),
extend_seq_lens=_t([3]),
input_ids=_t([10, 20, 30]),
positions=positions,
out_cache_loc=_contiguous_out_cache_loc(req_pool_idx=1, start=0, count=3),
req_to_token=_linear_r2t(),
num_slots=64,
extras=empty_extras(),
enable_write_verify_inputs=True,
expected_input_tokens=_t([99, 99, 99]),
expected_input_positions=positions.clone(),
ring_capacity=64,
)
write_violations = int(log_real.write_index[0].item())
assert (
write_violations == n_tokens
), f"expected {n_tokens} write violations, got {write_violations}"
def test_pipeline_empty_batch() -> None:
"""bs=1 with req_pool_idx=0 (padding): write and verify are no-op, kernel_run_counter == 2 (write+verify)."""
input_ids, positions, out_cache_loc = _zero_no_write_inputs()
_, _, log_real, log_ref, _, _, _, _ = _run_both_and_assert_pipeline_equal(
req_pool_indices=_t([0]),
prefix_lens=_t([0]),
extend_seq_lens=_t([0]),
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
req_to_token=_linear_r2t(),
num_slots=64,
extras=empty_extras(),
)
assert int(log_real.kernel_run_counter[0].item()) == 2
assert int(log_ref.kernel_run_counter[0].item()) == 2
assert int(log_real.write_index[0].item()) == 0
def test_pipeline_negative_slot_swa_out_of_window() -> None:
"""SWA: some out_cache_loc entries map to -1 (out-of-window); write_step skips them, buf unchanged."""
max_seq_len = 16
max_reqs = 4
full_to_swa_index_mapping = torch.arange(
max_reqs * max_seq_len + 1, dtype=torch.int64, device=_DEVICE
)
full_to_swa_index_mapping[1 * max_seq_len + 6] = -1
full_to_swa_index_mapping[1 * max_seq_len + 7] = -1
_run_both_and_assert_pipeline_equal(
req_pool_indices=_t([1]),
prefix_lens=_t([6]),
extend_seq_lens=_t([4]),
input_ids=_t([100, 101, 102, 103]),
positions=_t([6, 7, 8, 9]),
out_cache_loc=_t([-1, -1, 1 * max_seq_len + 8, 1 * max_seq_len + 9]),
req_to_token=_linear_r2t(max_reqs=max_reqs, max_seq_len=max_seq_len),
num_slots=128,
extras=empty_extras(),
swa_window_size=4,
full_to_swa_index_mapping=full_to_swa_index_mapping,
)
def test_pipeline_ring_overflow_via_real_plan() -> None:
"""Verify detects >capacity violations when prev_hash is pre-corrupted; write_index byte-equal, ring relaxed."""
max_seq_len = 16
max_reqs = 4
req_to_token = _linear_r2t(max_reqs=max_reqs, max_seq_len=max_seq_len)
n_slots = 8
req_pool_indices = _t([1])
prefix_lens = _t([n_slots])
extend_seq_lens = _t([0])
input_ids, positions, out_cache_loc = _zero_no_write_inputs()
num_slots = max_reqs * max_seq_len
# Step 1: pre-pollute canary_buf slots [0..n_slots) with wrong prev_hash so verify fires n_slots violations.
buf_real = make_canary_buf(num_slots=num_slots, device=_DEVICE)
buf_ref = make_canary_buf(num_slots=num_slots, device=_DEVICE)
for slot_idx in range(n_slots):
full_slot = 1 * max_seq_len + slot_idx
for buf in (buf_real, buf_ref):
write_slot_fields(
canary_buf=buf,
slot_idx=full_slot,
token=slot_idx + 1,
position=slot_idx,
prev_hash=0x1234_DEAD_BEEF_0000 + slot_idx,
)
# Step 2: run real pipeline (plan + no write + verify); overflow ring capacity=4 with all n_slots violations.
ring_capacity = 4
log_real = FakeViolationLog.allocate(capacity=ring_capacity, device=_DEVICE)
log_ref = FakeViolationLog.allocate(capacity=ring_capacity, device=_DEVICE)
plan_v_real = VerifyPlan.allocate(verify_capacity=256, device=_DEVICE)
plan_w_real = WritePlan.allocate(write_req_capacity=4, device=_DEVICE)
plan_v_ref = VerifyPlan.allocate(verify_capacity=256, device=_DEVICE)
plan_w_ref = WritePlan.allocate(write_req_capacity=4, device=_DEVICE)
launch_canary_plan_kernels(
verify_plan_out=plan_v_real,
write_plan_out=plan_w_real,
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
swa_window_size=0,
full_to_swa_index_mapping=None,
verify_capacity=int(plan_v_real.verify_slot_indices.shape[0]),
req_to_verify_expected_tokens=None,
req_to_verify_expected_tokens_valid_lens=None,
kv_token_id_vs_position_offset=0,
)
launch_canary_plan_kernels_torch_reference(
verify_plan_out=plan_v_ref,
write_plan_out=plan_w_ref,
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=req_to_token,
swa_window_size=0,
full_to_swa_index_mapping=None,
verify_capacity=int(plan_v_ref.verify_slot_indices.shape[0]),
req_to_verify_expected_tokens=None,
req_to_verify_expected_tokens_valid_lens=None,
kv_token_id_vs_position_offset=0,
)
launch_canary_verify_kernel(
context=VerifyOrWriteContext(
canary_buf=buf_real,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
violation_ring=log_real.ring,
violation_write_index=log_real.write_index,
slot_run_counter=log_real.slot_run_counter,
kernel_run_counter=log_real.kernel_run_counter,
enable_chain_position_assert=log_real.enable_chain_position_assert,
),
plan=plan_v_real,
check_verify_expected_token=True,
)
torch.cuda.synchronize()
launch_canary_verify_kernel_torch_reference(
context=VerifyOrWriteContext(
canary_buf=buf_ref,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
violation_ring=log_ref.ring,
violation_write_index=log_ref.write_index,
slot_run_counter=log_ref.slot_run_counter,
kernel_run_counter=log_ref.kernel_run_counter,
enable_chain_position_assert=log_ref.enable_chain_position_assert,
),
plan=plan_v_ref,
check_verify_expected_token=True,
)
# Step 3: write_index byte-equal; ring contents relaxed (atomic order not guaranteed under overflow).
assert torch.equal(log_real.write_index, log_ref.write_index)
assert int(log_real.write_index[0].item()) == n_slots
@pytest.mark.parametrize(
"kernel_kind", [CanaryLaunchTag.HEAD_K_FULL, CanaryLaunchTag.TAIL_V_SWA]
)
def test_pipeline_kernel_kind_propagates(kernel_kind: CanaryLaunchTag) -> None:
"""Different CanaryLaunchTag values: violation ring's kernel_kind field matches on both sides."""
max_seq_len = 16
input_ids, positions, out_cache_loc = _zero_no_write_inputs()
initial_buf = make_canary_buf(num_slots=64, device=_DEVICE)
write_slot_fields(
canary_buf=initial_buf,
slot_idx=1 * max_seq_len,
token=7,
position=99,
prev_hash=0,
)
_, _, log_real, log_ref, _, _, _, _ = _run_both_and_assert_pipeline_equal(
req_pool_indices=_t([1]),
prefix_lens=_t([1]),
extend_seq_lens=_t([0]),
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
req_to_token=_linear_r2t(max_seq_len=max_seq_len),
num_slots=64,
extras=empty_extras(),
kernel_kind=kernel_kind,
initial_canary_buf=initial_buf,
)
assert int(log_real.write_index[0].item()) == 1
assert int(log_ref.write_index[0].item()) == 1
assert int(log_real.ring[0, consts.VIOLATION_FIELD_KERNEL_KIND].item()) == int(
kernel_kind
)
assert int(log_ref.ring[0, consts.VIOLATION_FIELD_KERNEL_KIND].item()) == int(
kernel_kind
)
def test_pipeline_token_mismatch_detected_via_pool() -> None:
"""plan-pool gather + verify-token check: stamped wrong token id raises VERIFY_TOKEN_MISMATCH."""
max_seq_len = 16
max_reqs = 4
prefix_len = 4
input_ids, positions, out_cache_loc = _zero_no_write_inputs()
expected_tokens = [1000 + pos for pos in range(prefix_len)]
pool = torch.full((max_reqs, max_seq_len), -999, dtype=torch.int32, device=_DEVICE)
for pos, token in enumerate(expected_tokens):
pool[1, pos] = token
stored_tokens = [token + 1 for token in expected_tokens]
initial_buf = make_canary_buf(num_slots=64, device=_DEVICE)
initial_ref = initial_buf.clone()
_stamp_linear_prefix(
initial_buf=initial_buf,
initial_ref=initial_ref,
req_pool_idx=1,
prefix_len=prefix_len,
tokens=stored_tokens,
max_seq_len=max_seq_len,
)
_, _, log_real, log_ref, _, _, _, _ = _run_both_and_assert_pipeline_equal(
req_pool_indices=_t([1]),
prefix_lens=_t([prefix_len]),
extend_seq_lens=_t([0]),
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
req_to_token=_linear_r2t(max_reqs=max_reqs, max_seq_len=max_seq_len),
num_slots=64,
extras=empty_extras(),
swa_window_size=0,
full_to_swa_index_mapping=None,
initial_canary_buf=initial_buf,
req_to_verify_expected_tokens=pool,
kv_token_id_vs_position_offset=0,
check_verify_expected_token=True,
)
assert int(log_real.write_index[0].item()) == prefix_len
assert int(log_ref.write_index[0].item()) == prefix_len
# Ring rows may land in any order; collect stored/expected pairs and compare as sets.
observed_pairs: set[tuple[int, int]] = set()
for row_idx in range(prefix_len):
fail_bits = int(
log_real.ring[row_idx, consts.VIOLATION_FIELD_FAIL_REASON_BITS].item()
)
assert fail_bits & int(
consts.FailReason.VERIFY_TOKEN_MISMATCH
), f"row {row_idx}: VERIFY_TOKEN_MISMATCH bit missing in {fail_bits:#b}"
stored = int(log_real.ring[row_idx, consts.VIOLATION_FIELD_STORED_TOKEN].item())
expected = int(
log_real.ring[row_idx, consts.VIOLATION_FIELD_EXPECTED_TOKEN].item()
)
observed_pairs.add((stored, expected))
expected_pairs = {(stored_tokens[i], expected_tokens[i]) for i in range(prefix_len)}
assert observed_pairs == expected_pairs
def test_pipeline_eagle_offset_plus_1_byte_equal() -> None:
"""plan-pool + offset=+1 full pipeline: stamped tokens match pool[rp, pos+1], no violations CUDA vs ref byte-equal."""
max_seq_len = 16
max_reqs = 4
prefix_len = 4
input_ids, positions, out_cache_loc = _zero_no_write_inputs()
stored_tokens = [2000 + pos for pos in range(prefix_len)]
pool = torch.full((max_reqs, max_seq_len), -999, dtype=torch.int32, device=_DEVICE)
for pos in range(prefix_len):
# offset=+1 means kernel gathers from pool[rp, pos + 1], so place stored_tokens[pos] there.
pool[1, pos + 1] = stored_tokens[pos]
initial_buf = make_canary_buf(num_slots=64, device=_DEVICE)
initial_ref = initial_buf.clone()
_stamp_linear_prefix(
initial_buf=initial_buf,
initial_ref=initial_ref,
req_pool_idx=1,
prefix_len=prefix_len,
tokens=stored_tokens,
max_seq_len=max_seq_len,
)
_, _, log_real, log_ref, _, _, _, _ = _run_both_and_assert_pipeline_equal(
req_pool_indices=_t([1]),
prefix_lens=_t([prefix_len]),
extend_seq_lens=_t([0]),
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
req_to_token=_linear_r2t(max_reqs=max_reqs, max_seq_len=max_seq_len),
num_slots=64,
extras=empty_extras(),
swa_window_size=0,
full_to_swa_index_mapping=None,
initial_canary_buf=initial_buf,
req_to_verify_expected_tokens=pool,
kv_token_id_vs_position_offset=1,
check_verify_expected_token=True,
)
assert int(log_real.write_index[0].item()) == 0
assert int(log_ref.write_index[0].item()) == 0
if __name__ == "__main__":
import sys
sys.exit(pytest.main([__file__, "-v"]))
@@ -0,0 +1,222 @@
from __future__ import annotations
import random
from dataclasses import dataclass
from typing import Optional
import pytest
import torch
from sglang.jit_kernel.tests.kv_canary._differential import _run_both_plan
from sglang.jit_kernel.tests.kv_canary._fixtures import (
allocate_plan_pair,
derive_plan_capacity,
make_lut,
make_padding_mask,
make_req_to_token,
)
from sglang.jit_kernel.tests.kv_canary._fuzz_driver import (
FUZZ_SEEDS_PR,
run_fuzz_combo,
)
from sglang.jit_kernel.tests.kv_canary._invariants import PlanInvariants
from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=30, suite="base-b-kernel-unit-1-gpu-large")
_DEVICE = torch.device("cuda")
_FUZZ_ITER_PER_SEED = 50
@dataclass(frozen=True, slots=True, kw_only=True)
class PlanFuzzInputs:
req_pool_indices: torch.Tensor
prefix_lens: torch.Tensor
extend_seq_lens: torch.Tensor
req_to_token: torch.Tensor
swa_window_size: int
full_to_swa_index_mapping: Optional[torch.Tensor]
verify_capacity: int
write_req_capacity: int
req_to_verify_expected_tokens: Optional[torch.Tensor]
kv_token_id_vs_position_offset: int
def _draw_random_plan_inputs(rng: random.Random) -> PlanFuzzInputs:
bs = rng.randint(1, 16)
max_seq_len = rng.choice([8, 16, 64, 128, 256])
swa_enabled = rng.random() < 0.5
swa_window_size = (
rng.choice([4, 16, 64, max_seq_len, max(2, max_seq_len // 3)])
if swa_enabled
else 0
)
swa_window_size = min(swa_window_size, max_seq_len)
lut_kind = (
rng.choice(["identity", "shift", "permutation", "with_oob"])
if swa_enabled
else None
)
rtt_kind = rng.choice(["linear", "sparse_permuted"])
padding_kind = rng.choice(["none", "trailing", "interleaved"])
capacity_kind = rng.choice(["loose", "tight_match", "under_by_one"])
max_reqs = max(bs + 2, 4)
pool_size = max_reqs * max_seq_len
rtt = make_req_to_token(
kind=rtt_kind,
max_reqs=max_reqs,
max_seq_len=max_seq_len,
device=_DEVICE,
rng=rng,
)
padding_mask = make_padding_mask(bs=bs, kind=padding_kind, rng=rng)
req_pool_indices_list: list[int] = []
prefix_lens_list: list[int] = []
extend_seq_lens_list: list[int] = []
for r in range(bs):
if padding_mask[r]:
req_pool_indices_list.append(0)
prefix_lens_list.append(0)
extend_seq_lens_list.append(0)
else:
req_pool_indices_list.append(rng.randint(1, max_reqs - 1))
prefix_lens_list.append(rng.randint(0, max_seq_len - 1))
extend_seq_lens_list.append(rng.randint(1, max(1, max_seq_len // 4)))
req_pool_indices = torch.tensor(
req_pool_indices_list, dtype=torch.int64, device=_DEVICE
)
prefix_lens = torch.tensor(prefix_lens_list, dtype=torch.int64, device=_DEVICE)
extend_seq_lens = torch.tensor(
extend_seq_lens_list, dtype=torch.int64, device=_DEVICE
)
total_verify = 0
for rpi, pfx in zip(req_pool_indices_list, prefix_lens_list):
if rpi == 0:
continue
if swa_window_size > 0:
window_start = max(0, pfx - swa_window_size)
total_verify += max(0, pfx - window_start)
else:
total_verify += pfx
verify_capacity, write_req_capacity = derive_plan_capacity(
kind=capacity_kind,
total_verify=total_verify,
extras_count=0,
bs=bs,
)
full_to_swa: Optional[torch.Tensor]
if swa_window_size > 0 and lut_kind is not None:
full_to_swa = make_lut(
kind=lut_kind, pool_size=pool_size, device=_DEVICE, rng=rng
)
else:
full_to_swa = None
expected_pool_present = rng.random() < 0.5
kv_token_id_vs_position_offset = rng.choice([0, 1])
expected_pool: Optional[torch.Tensor]
if expected_pool_present:
pool_max_context_len = rng.choice(
[
max(1, max_seq_len // 4),
max(1, max_seq_len // 2),
max_seq_len,
]
)
expected_pool = torch.randint(
low=0,
high=50000,
size=(max_reqs, pool_max_context_len),
dtype=torch.int32,
device=_DEVICE,
)
else:
expected_pool = None
return PlanFuzzInputs(
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
extend_seq_lens=extend_seq_lens,
req_to_token=rtt,
swa_window_size=swa_window_size,
full_to_swa_index_mapping=full_to_swa,
verify_capacity=verify_capacity,
write_req_capacity=write_req_capacity,
req_to_verify_expected_tokens=expected_pool,
kv_token_id_vs_position_offset=kv_token_id_vs_position_offset,
)
def _run_one(inputs: PlanFuzzInputs) -> tuple:
triton_v, triton_w, ref_v, ref_w = allocate_plan_pair(
verify_capacity=inputs.verify_capacity,
write_req_capacity=inputs.write_req_capacity,
)
_run_both_plan(
triton_verify=triton_v,
triton_write=triton_w,
ref_verify=ref_v,
ref_write=ref_w,
req_pool_indices=inputs.req_pool_indices,
prefix_lens=inputs.prefix_lens,
extend_seq_lens=inputs.extend_seq_lens,
req_to_token=inputs.req_to_token,
extras=(
torch.empty(0, dtype=torch.int64, device=_DEVICE),
torch.empty(0, dtype=torch.int64, device=_DEVICE),
torch.empty(0, dtype=torch.int64, device=_DEVICE),
torch.zeros(1, dtype=torch.int32, device=_DEVICE),
),
swa_window_size=inputs.swa_window_size,
full_to_swa_index_mapping=inputs.full_to_swa_index_mapping,
req_to_verify_expected_tokens=inputs.req_to_verify_expected_tokens,
kv_token_id_vs_position_offset=inputs.kv_token_id_vs_position_offset,
)
PlanInvariants.assert_all(
verify_plan=triton_v,
write_plan=triton_w,
req_pool_indices=inputs.req_pool_indices,
prefix_lens=inputs.prefix_lens,
extend_seq_lens=inputs.extend_seq_lens,
swa_window_size=inputs.swa_window_size,
extras_slot_indices=torch.empty(0, dtype=torch.int64, device=_DEVICE),
extras_positions=torch.empty(0, dtype=torch.int64, device=_DEVICE),
extras_prev_slot_indices=torch.empty(0, dtype=torch.int64, device=_DEVICE),
extras_count=0,
)
return triton_v, triton_w
def _summarize(inputs: PlanFuzzInputs) -> str:
return (
f"bs={int(inputs.req_pool_indices.shape[0])} "
f"swa={inputs.swa_window_size} "
f"verify_cap={inputs.verify_capacity} write_cap={inputs.write_req_capacity} "
f"has_lut={inputs.full_to_swa_index_mapping is not None} "
f"has_pool={inputs.req_to_verify_expected_tokens is not None} "
f"offset={inputs.kv_token_id_vs_position_offset}"
)
@pytest.mark.parametrize("seed", FUZZ_SEEDS_PR)
def test_plan_fuzz_full_combo(seed: int) -> None:
"""Multi-dim plan fuzzer: random LUT/rtt/padding/capacity/swa × N iters, byte-equal."""
run_fuzz_combo(
seed,
draw_fn=_draw_random_plan_inputs,
run_one_fn=_run_one,
summarize_fn=_summarize,
n_iter=_FUZZ_ITER_PER_SEED,
)
if __name__ == "__main__":
import sys
sys.exit(pytest.main([__file__, "-v"]))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,178 @@
from __future__ import annotations
import random
from dataclasses import dataclass
import pytest
import torch
from sglang.jit_kernel.kv_canary import consts
from sglang.jit_kernel.kv_canary.verify import (
CanaryLaunchTag,
VerifyPlan,
)
from sglang.jit_kernel.tests.kv_canary._canary_helpers import (
FakeViolationLog,
make_canary_buf,
make_log_pair,
make_verify_plan_pair,
stamp_clean_chain,
)
from sglang.jit_kernel.tests.kv_canary._differential import _run_both_verify
from sglang.jit_kernel.tests.kv_canary._fuzz_driver import (
FUZZ_SEEDS_PR,
run_fuzz_combo,
)
from sglang.jit_kernel.tests.kv_canary._invariants import VerifyInvariants
from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=30, suite="base-b-kernel-unit-1-gpu-large")
_DEVICE = torch.device("cuda")
_FUZZ_ITER_PER_SEED = 30
@dataclass(frozen=True, slots=True, kw_only=True)
class VerifyFuzzInputs:
cuda_canary_buf: torch.Tensor
ref_canary_buf: torch.Tensor
plan_cuda: VerifyPlan
plan_ref: VerifyPlan
kernel_kind: CanaryLaunchTag
ring_capacity: int
check_verify_expected_token: bool
def _draw_random_verify_inputs(rng: random.Random) -> VerifyFuzzInputs:
kernel_kind = rng.choice(list(CanaryLaunchTag))
plan_size = rng.randint(0, 32)
num_slots = max(plan_size + 8, 16)
ring_capacity = rng.choice([16, 64, 256])
cuda_buf = make_canary_buf(
num_slots=num_slots, slot_stride_bytes=32, device=_DEVICE
)
ref_buf = cuda_buf.clone()
slot_universe = list(range(1, num_slots))
rng.shuffle(slot_universe)
slot_indices = slot_universe[:plan_size]
tokens = [rng.randint(0, 0xFFFFFFFF) for _ in range(plan_size)]
positions = list(range(plan_size))
prev_slot_indices: list[int] = []
for i in range(plan_size):
if i == 0:
prev_slot_indices.append(-1)
else:
prev_slot_indices.append(slot_indices[i - 1])
if plan_size > 0:
stamp_clean_chain(
cuda_buf=cuda_buf,
ref_buf=ref_buf,
slot_indices=slot_indices,
tokens=tokens,
positions=positions,
)
# Inject prev_slot == TOKEN_TO_KV_SLOT_PADDING into ~15% of entries so the differential
# harness exercises the chain-check-skip branch (added for SWA-evicted ancestor handling).
# Done AFTER stamp_clean_chain so the stored prev_hash on those slots is still chain-clean;
# the kernel must rely on prev_slot==padding (not on stored hash) to decide whether to skip.
for i in range(plan_size):
if rng.random() < 0.15:
prev_slot_indices[i] = consts.TOKEN_TO_KV_SLOT_PADDING
check_verify_expected_token = rng.random() < 0.5
expected_input_ids: list[int] = []
for i in range(plan_size):
# Always pick a value; with check=False the kernel must not deref this column.
if rng.random() < 0.3:
expected_input_ids.append(-1)
elif rng.random() < 0.5:
expected_input_ids.append(int(tokens[i]))
else:
mutated = (int(tokens[i]) ^ 0x1) & 0xFFFFFFFF
expected_input_ids.append(mutated)
plan_cuda, plan_ref = make_verify_plan_pair(
slot_indices=slot_indices,
positions=positions,
prev_slot_indices=prev_slot_indices,
expected_input_ids=expected_input_ids if plan_size > 0 else None,
capacity=max(plan_size, 1),
device=_DEVICE,
)
return VerifyFuzzInputs(
cuda_canary_buf=cuda_buf,
ref_canary_buf=ref_buf,
plan_cuda=plan_cuda,
plan_ref=plan_ref,
kernel_kind=kernel_kind,
ring_capacity=ring_capacity,
check_verify_expected_token=check_verify_expected_token,
)
def _run_one(inputs: VerifyFuzzInputs) -> None:
cuda_buf_before = inputs.cuda_canary_buf.clone()
cuda_log, ref_log = make_log_pair(capacity=inputs.ring_capacity, device=_DEVICE)
log_before = FakeViolationLog.allocate(
capacity=inputs.ring_capacity, device=_DEVICE
)
_run_both_verify(
cuda_canary_buf=inputs.cuda_canary_buf,
ref_canary_buf=inputs.ref_canary_buf,
plan_cuda=inputs.plan_cuda,
plan_ref=inputs.plan_ref,
cuda_log=cuda_log,
ref_log=ref_log,
kernel_kind=inputs.kernel_kind,
assert_equal=False,
check_verify_expected_token=inputs.check_verify_expected_token,
)
assert int(cuda_log.kernel_run_counter[0].item()) == int(
ref_log.kernel_run_counter[0].item()
)
assert int(cuda_log.slot_run_counter[0].item()) == int(
ref_log.slot_run_counter[0].item()
)
assert int(cuda_log.write_index[0].item()) == int(ref_log.write_index[0].item())
VerifyInvariants.assert_all(
canary_buf_before=cuda_buf_before,
canary_buf_after=inputs.cuda_canary_buf,
log_before=log_before,
log_after=cuda_log,
plan=inputs.plan_cuda,
kernel_kind=inputs.kernel_kind,
)
def _summarize(inputs: VerifyFuzzInputs) -> str:
n_active = int(inputs.plan_cuda.verify_num_valid[0].item())
return (
f"plan_size={n_active} kind={inputs.kernel_kind.name} "
f"ring={inputs.ring_capacity} "
f"check_token={inputs.check_verify_expected_token}"
)
@pytest.mark.parametrize("seed", FUZZ_SEEDS_PR)
def test_verify_fuzz_full_combo(seed: int) -> None:
"""Multi-dim verify fuzzer: random kernel kind × plan size × ring capacity × N iters, byte-equal."""
run_fuzz_combo(
seed,
draw_fn=_draw_random_verify_inputs,
run_one_fn=_run_one,
summarize_fn=_summarize,
n_iter=_FUZZ_ITER_PER_SEED,
)
if __name__ == "__main__":
import sys
sys.exit(pytest.main([__file__, "-v"]))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,218 @@
from __future__ import annotations
import random
from dataclasses import dataclass
import pytest
import torch
from sglang.jit_kernel.kv_canary.verify import CanaryLaunchTag
from sglang.jit_kernel.kv_canary.write import WritePlan
from sglang.jit_kernel.tests.kv_canary._canary_helpers import (
FakeViolationLog,
make_canary_buf,
make_log_pair,
make_write_plan_pair,
stamp_pair,
)
from sglang.jit_kernel.tests.kv_canary._differential import _run_both_write
from sglang.jit_kernel.tests.kv_canary._fuzz_driver import (
FUZZ_SEEDS_PR,
run_fuzz_combo,
)
from sglang.jit_kernel.tests.kv_canary._invariants import WriteInvariants
from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=30, suite="base-b-kernel-unit-1-gpu-large")
_DEVICE = torch.device("cuda")
_FUZZ_ITER_PER_SEED = 30
@dataclass(frozen=True, slots=True, kw_only=True)
class WriteFuzzInputs:
cuda_canary_buf: torch.Tensor
ref_canary_buf: torch.Tensor
plan_cuda: WritePlan
plan_ref: WritePlan
input_ids: torch.Tensor
positions: torch.Tensor
out_cache_loc: torch.Tensor
kernel_kind: CanaryLaunchTag
enable_write_verify_inputs: bool
expected_input_tokens: torch.Tensor
expected_input_positions: torch.Tensor
ring_capacity: int
def _draw_random_write_inputs(rng: random.Random) -> WriteFuzzInputs:
enable_write_verify_inputs = rng.choice([False, True])
kernel_kind = rng.choice(list(CanaryLaunchTag))
ring_capacity = rng.choice([16, 64, 256])
n_reqs = rng.randint(1, 4)
per_req_tokens: list[int] = [rng.randint(1, 5) for _ in range(n_reqs)]
total_tokens = sum(per_req_tokens)
num_slots = max(total_tokens + 8, 16)
cuda_buf = make_canary_buf(
num_slots=num_slots, slot_stride_bytes=32, device=_DEVICE
)
ref_buf = cuda_buf.clone()
write_offsets: list[int] = [0]
running = 0
for t in per_req_tokens:
running += t
write_offsets.append(running)
slot_pool = list(range(1, num_slots))
rng.shuffle(slot_pool)
seed_slot_indices: list[int] = []
for _ in range(n_reqs):
if rng.random() < 0.4 or len(slot_pool) <= total_tokens:
seed_slot_indices.append(-1)
else:
seed_slot_indices.append(slot_pool.pop())
out_cache_loc_list: list[int] = []
for _ in range(total_tokens):
if not slot_pool:
out_cache_loc_list.append(-1)
else:
out_cache_loc_list.append(slot_pool.pop())
plan_cuda, plan_ref = make_write_plan_pair(
write_offsets=write_offsets,
seed_slot_indices=seed_slot_indices,
num_valid_reqs=n_reqs,
device=_DEVICE,
)
input_ids = torch.tensor(
[rng.randint(-(1 << 31), (1 << 31) - 1) for _ in range(total_tokens)],
dtype=torch.int64,
device=_DEVICE,
)
# Per-chain sequential positions so the write kernel's chain-step position assert holds.
# For chains with a real seed slot, stamp the seed with (chain_start_position - 1) so the
# first chain entry's position == seed.position + 1.
chain_start_positions: list[int] = [rng.randint(0, 1024) for _ in range(n_reqs)]
positions_list: list[int] = []
for r in range(n_reqs):
start = chain_start_positions[r]
positions_list.extend(start + i for i in range(per_req_tokens[r]))
seed_slot = seed_slot_indices[r]
if seed_slot >= 0:
stamp_pair(
(cuda_buf, ref_buf),
slot_idx=seed_slot,
token=0,
position=start - 1,
prev_hash=0,
)
positions = torch.tensor(positions_list, dtype=torch.int64, device=_DEVICE)
out_cache_loc = torch.tensor(out_cache_loc_list, dtype=torch.int64, device=_DEVICE)
expected_input_tokens = input_ids.clone()
expected_input_positions = positions.clone()
if enable_write_verify_inputs:
candidate_indices = [
idx for idx, slot in enumerate(out_cache_loc_list) if slot >= 0
]
rng.shuffle(candidate_indices)
mismatch_count = rng.randint(0, len(candidate_indices))
for idx in candidate_indices[:mismatch_count]:
if rng.choice([False, True]):
expected_input_tokens[idx] = expected_input_tokens[idx] + 1
else:
expected_input_positions[idx] = expected_input_positions[idx] + 1
return WriteFuzzInputs(
cuda_canary_buf=cuda_buf,
ref_canary_buf=ref_buf,
plan_cuda=plan_cuda,
plan_ref=plan_ref,
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
kernel_kind=kernel_kind,
enable_write_verify_inputs=enable_write_verify_inputs,
expected_input_tokens=expected_input_tokens,
expected_input_positions=expected_input_positions,
ring_capacity=ring_capacity,
)
def _run_one(inputs: WriteFuzzInputs) -> None:
cuda_buf_before = inputs.cuda_canary_buf.clone()
cuda_log, ref_log = make_log_pair(capacity=inputs.ring_capacity, device=_DEVICE)
log_before = FakeViolationLog.allocate(
capacity=inputs.ring_capacity, device=_DEVICE
)
_run_both_write(
cuda_canary_buf=inputs.cuda_canary_buf,
ref_canary_buf=inputs.ref_canary_buf,
plan_cuda=inputs.plan_cuda,
plan_ref=inputs.plan_ref,
input_ids=inputs.input_ids,
positions=inputs.positions,
out_cache_loc=inputs.out_cache_loc,
enable_write_verify_inputs=inputs.enable_write_verify_inputs,
expected_input_tokens=inputs.expected_input_tokens,
expected_input_positions=inputs.expected_input_positions,
cuda_log=cuda_log,
ref_log=ref_log,
kernel_kind=inputs.kernel_kind,
assert_equal=False,
)
assert torch.equal(
inputs.cuda_canary_buf, inputs.ref_canary_buf
), "CUDA vs ref canary_buf diverged"
assert int(cuda_log.write_index[0].item()) == int(ref_log.write_index[0].item())
assert int(cuda_log.slot_run_counter[0].item()) == int(
ref_log.slot_run_counter[0].item()
)
assert int(cuda_log.kernel_run_counter[0].item()) == int(
ref_log.kernel_run_counter[0].item()
)
WriteInvariants.assert_all(
canary_buf_before=cuda_buf_before,
canary_buf_after=inputs.cuda_canary_buf,
plan=inputs.plan_cuda,
input_ids=inputs.input_ids,
positions=inputs.positions,
out_cache_loc=inputs.out_cache_loc,
enable_write_verify_inputs=inputs.enable_write_verify_inputs,
expected_input_tokens=inputs.expected_input_tokens,
expected_input_positions=inputs.expected_input_positions,
log_before=log_before,
log_after=cuda_log,
)
def _summarize(inputs: WriteFuzzInputs) -> str:
n_active = int(inputs.plan_cuda.write_num_valid_reqs[0].item())
total = int(inputs.plan_cuda.write_offsets[n_active].item())
return (
f"n_reqs={n_active} total_tokens={total} kind={inputs.kernel_kind.name} "
f"pseudo={inputs.enable_write_verify_inputs}"
)
@pytest.mark.parametrize("seed", FUZZ_SEEDS_PR)
def test_write_fuzz_full_combo(seed: int) -> None:
"""Multi-dim write fuzzer: random pseudo/kernel × N iters, byte-equal."""
run_fuzz_combo(
seed,
draw_fn=_draw_random_write_inputs,
run_one_fn=_run_one,
summarize_fn=_summarize,
n_iter=_FUZZ_ITER_PER_SEED,
)
if __name__ == "__main__":
import sys
sys.exit(pytest.main([__file__, "-v"]))
@@ -0,0 +1,892 @@
from __future__ import annotations
from dataclasses import dataclass
from unittest.mock import patch
import pytest
import torch
from sglang.jit_kernel.kv_canary import consts
from sglang.jit_kernel.kv_canary import write as write_module
from sglang.jit_kernel.kv_canary.consts import splitmix64, splitmix64_mix3
from sglang.jit_kernel.kv_canary.verify import (
CANARY_SLOT_BYTES,
CanaryLaunchTag,
VerifyOrWriteContext,
launch_canary_verify_kernel,
)
from sglang.jit_kernel.kv_canary.write import (
launch_canary_write_kernel,
)
from sglang.jit_kernel.tests.kv_canary._canary_helpers import (
FakeViolationLog,
assert_canary_state_equal,
assert_only_bits_set,
chain_anchor_signed,
make_canary_buf,
make_canary_buf_pair,
make_log_pair,
make_verify_plan,
make_write_plan,
make_write_plan_pair,
read_slot_fields,
stamp_pair,
to_signed_int64,
)
from sglang.jit_kernel.tests.kv_canary._differential import (
_run_both_write,
run_write_diff,
)
from sglang.jit_kernel.tests.kv_canary._fixtures import (
dummy_pseudo_tensors,
)
from sglang.test.ci.ci_register import register_cuda_ci
register_cuda_ci(est_time=30, suite="base-b-kernel-unit-1-gpu-large")
_DEVICE = torch.device("cuda")
def _int32_tensor(values: list[int]) -> torch.Tensor:
return torch.tensor(values, dtype=torch.int64, device=_DEVICE)
def _make_default_buf_pair(
num_slots: int = 16, slot_stride_bytes: int = 32
) -> tuple[torch.Tensor, torch.Tensor]:
return make_canary_buf_pair(
num_slots=num_slots, slot_stride_bytes=slot_stride_bytes, device=_DEVICE
)
def _run_write(
*,
buf_pair: tuple[torch.Tensor, torch.Tensor],
input_ids: list[int] | torch.Tensor,
positions: list[int] | torch.Tensor,
out_cache_loc: list[int] | torch.Tensor,
write_offsets: list[int] | None = None,
seed_slot_indices: list[int] = (-1,),
num_valid_reqs: int = 1,
req_capacity: int | None = None,
enable_write_verify_inputs: bool = False,
expected_input_tokens: torch.Tensor | None = None,
expected_input_positions: torch.Tensor | None = None,
assert_equal: bool = True,
) -> tuple[FakeViolationLog, FakeViolationLog]:
"""Shared scaffold: build write plan + pseudo tensors and call ``run_write_diff``.
``write_offsets`` defaults to ``[0, len(input_ids)]`` (one req covering all entries).
``expected_input_*`` default to ``dummy_pseudo_tensors(len(input_ids))``.
"""
ids_t = (
input_ids
if isinstance(input_ids, torch.Tensor)
else _int32_tensor(list(input_ids))
)
pos_t = (
positions
if isinstance(positions, torch.Tensor)
else _int32_tensor(list(positions))
)
loc_t = (
out_cache_loc
if isinstance(out_cache_loc, torch.Tensor)
else _int32_tensor(list(out_cache_loc))
)
n_tokens = int(ids_t.shape[0])
if write_offsets is None:
write_offsets = [0, n_tokens]
plan_kwargs = dict(
write_offsets=write_offsets,
seed_slot_indices=list(seed_slot_indices),
num_valid_reqs=num_valid_reqs,
device=_DEVICE,
)
if req_capacity is not None:
plan_kwargs["req_capacity"] = req_capacity
plan_pair = make_write_plan_pair(**plan_kwargs)
if expected_input_tokens is None or expected_input_positions is None:
pseudo_tokens, pseudo_positions = dummy_pseudo_tensors(n_tokens)
if expected_input_tokens is None:
expected_input_tokens = pseudo_tokens
if expected_input_positions is None:
expected_input_positions = pseudo_positions
return run_write_diff(
buf_pair=buf_pair,
plan_pair=plan_pair,
input_ids=ids_t,
positions=pos_t,
out_cache_loc=loc_t,
enable_write_verify_inputs=enable_write_verify_inputs,
expected_input_tokens=expected_input_tokens,
expected_input_positions=expected_input_positions,
assert_equal=assert_equal,
)
@dataclass(frozen=True, slots=True, kw_only=True)
class _WriteSingleSlotInput:
token: int = 42
position: int = 0
enable_write_verify_inputs: bool = False
class _RecordingWriteModule:
def __init__(self) -> None:
self.calls: list[tuple[object, ...]] = []
def canary_write_step_cuda(self, *args: object) -> None:
self.calls.append(args)
def _run_write_single_slot_byte_equal(case: _WriteSingleSlotInput) -> None:
_run_write(
buf_pair=_make_default_buf_pair(),
input_ids=[case.token],
positions=[case.position],
out_cache_loc=[0],
enable_write_verify_inputs=case.enable_write_verify_inputs,
)
class TestSeedSlot:
def setup_method(self) -> None:
self.buf_pair = _make_default_buf_pair()
def test_seed_slot_idx_negative_uses_anchor(self) -> None:
"""``seed_slot_idx == -1`` → initial ``running_prev_hash`` is ``splitmix64(consts.CANARY_CHAIN_ANCHOR)``."""
_run_write(
buf_pair=self.buf_pair,
input_ids=[42],
positions=[0],
out_cache_loc=[3],
)
stored_token, stored_position, stored_prev_hash, _ = read_slot_fields(
canary_buf=self.buf_pair[0], slot_idx=3
)
assert stored_token == 42
assert stored_position == 0
assert stored_prev_hash == chain_anchor_signed()
def test_seed_slot_idx_loads_predecessor(self) -> None:
"""``seed_slot_idx >= 0`` → load 3 fields (token, position, prev_hash) from ``canary_buf[seed]`` and splitmix64_mix3-advance into prev_hash."""
# Step: pre-stamp slot 7 with a known chain link.
seed_token, seed_position = 100, 4
seed_prev_signed = to_signed_int64(splitmix64(consts.CANARY_CHAIN_ANCHOR))
stamp_pair(
self.buf_pair,
slot_idx=7,
token=seed_token,
position=seed_position,
prev_hash=seed_prev_signed,
)
_run_write(
buf_pair=self.buf_pair,
input_ids=[999],
positions=[5],
out_cache_loc=[2],
seed_slot_indices=[7],
)
expected_prev_hash = splitmix64_mix3(
splitmix64(consts.CANARY_CHAIN_ANCHOR), seed_token, seed_position
)
_, _, stored_prev_hash, _ = read_slot_fields(
canary_buf=self.buf_pair[0], slot_idx=2
)
assert stored_prev_hash == to_signed_int64(expected_prev_hash)
def test_seed_slot_chain_link_continuous(self) -> None:
"""After write, ``slot[0].prev_hash`` is consistent with verify's chain reconstruction from seed."""
# Step 1: write a chain from seed slot=7 → newly written slot=2. Then run verify with prev=7 and
# assert no violation — i.e., slot[2].prev_hash is splitmix64_mix3(seed.prev_hash, seed.token, seed.position).
cuda_buf = self.buf_pair[0]
seed_token, seed_position = 11, 0
seed_prev_signed = to_signed_int64(splitmix64(consts.CANARY_CHAIN_ANCHOR))
stamp_pair(
self.buf_pair,
slot_idx=7,
token=seed_token,
position=seed_position,
prev_hash=seed_prev_signed,
)
_run_write(
buf_pair=self.buf_pair,
input_ids=[222],
positions=[1],
out_cache_loc=[2],
seed_slot_indices=[7],
assert_equal=False,
)
# Step 2: verify slot[2] with prev=7 — expects no violation.
verify_plan = make_verify_plan(
slot_indices=[2], positions=[1], prev_slot_indices=[7], device=_DEVICE
)
verify_log = FakeViolationLog.allocate(device=_DEVICE)
launch_canary_verify_kernel(
context=VerifyOrWriteContext(
canary_buf=cuda_buf,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
violation_ring=verify_log.ring,
violation_write_index=verify_log.write_index,
slot_run_counter=verify_log.slot_run_counter,
kernel_run_counter=verify_log.kernel_run_counter,
enable_chain_position_assert=verify_log.enable_chain_position_assert,
),
plan=verify_plan,
check_verify_expected_token=True,
)
torch.cuda.synchronize()
assert int(verify_log.write_index[0].item()) == 0
def test_seed_slot_resume_5_step_hardcoded(self) -> None:
cuda_buf = make_canary_buf(num_slots=50, slot_stride_bytes=32, device=_DEVICE)
ref_buf = cuda_buf.clone()
buf_pair = (cuda_buf, ref_buf)
seed_token = 7
seed_position = 10
seed_prev_hash_signed = to_signed_int64(splitmix64(consts.CANARY_CHAIN_ANCHOR))
stamp_pair(
buf_pair,
slot_idx=42,
token=seed_token,
position=seed_position,
prev_hash=seed_prev_hash_signed,
)
predecessor_advance = splitmix64_mix3(
splitmix64(consts.CANARY_CHAIN_ANCHOR), seed_token, seed_position
)
tokens = [101, 202, 303, 404, 505]
positions = [11, 12, 13, 14, 15]
out_cache_loc = [0, 1, 2, 3, 4]
expected_prev_hashes: list[int] = []
running = predecessor_advance
for t, p in zip(tokens, positions):
expected_prev_hashes.append(running)
running = splitmix64_mix3(running, t, p)
cuda_log, _ = _run_write(
buf_pair=buf_pair,
input_ids=tokens,
positions=positions,
out_cache_loc=out_cache_loc,
write_offsets=[0, 5],
seed_slot_indices=[42],
)
for slot_idx, expected_token, expected_position, expected_prev_u64 in zip(
out_cache_loc, tokens, positions, expected_prev_hashes
):
stored_token, stored_position, stored_prev_hash, stored_real_kv_hash = (
read_slot_fields(canary_buf=cuda_buf, slot_idx=slot_idx)
)
assert stored_token == expected_token
assert stored_position == expected_position
assert stored_prev_hash == to_signed_int64(expected_prev_u64)
assert stored_real_kv_hash == 0
assert int(cuda_log.write_index[0].item()) == 0
def test_seed_continues_existing_chain(self) -> None:
"""Pre-stamp seed slot; subsequent write should continue chain from splitmix64_mix3(seed.*)."""
seed_slot = 3
seed_token = 7
seed_position = 1
expected_seed_prev_hash = splitmix64(consts.CANARY_CHAIN_ANCHOR)
stamp_pair(
self.buf_pair,
slot_idx=seed_slot,
token=seed_token,
position=seed_position,
prev_hash=to_signed_int64(expected_seed_prev_hash),
)
new_slot = 4
new_token = 13
new_position = 2
expected_running = splitmix64_mix3(
expected_seed_prev_hash, seed_token, seed_position
)
_run_write(
buf_pair=self.buf_pair,
input_ids=[new_token],
positions=[new_position],
out_cache_loc=[new_slot],
seed_slot_indices=[seed_slot],
)
new_stored = read_slot_fields(canary_buf=self.buf_pair[0], slot_idx=new_slot)
assert new_stored[0] == new_token
assert new_stored[1] == new_position
assert new_stored[2] == to_signed_int64(
expected_running
), f"new slot prev_hash {new_stored[2]} != expected {to_signed_int64(expected_running)}"
class TestChain:
def setup_method(self) -> None:
self.buf_pair = _make_default_buf_pair()
def test_chain_link_byte_equal_5_step(self) -> None:
"""5-step chain, buf / ring / counters byte-equal against ref."""
_run_write(
buf_pair=self.buf_pair,
input_ids=[10, 20, 30, 40, 50],
positions=[0, 1, 2, 3, 4],
out_cache_loc=[0, 1, 2, 3, 4],
)
def test_chain_link_byte_equal_5_step_hardcoded(self) -> None:
"""5-step write chain with hand-computed splitmix64 expected fields per slot."""
tokens = [101, 202, 303, 404, 505]
positions = [0, 1, 2, 3, 4]
out_cache_loc = [0, 1, 2, 3, 4]
# Step 1: compute the expected stored prev_hash sequence in pure Python via splitmix64.
expected_prev_hashes_u64: list[int] = []
running = splitmix64(consts.CANARY_CHAIN_ANCHOR)
for token, position in zip(tokens, positions):
expected_prev_hashes_u64.append(running)
running = splitmix64_mix3(running, token, position)
expected_prev_hashes_signed = [
to_signed_int64(h) for h in expected_prev_hashes_u64
]
_run_write(
buf_pair=self.buf_pair,
input_ids=tokens,
positions=positions,
out_cache_loc=out_cache_loc,
)
# Step 2: verify every slot's stored 4 fields match the hardcoded expected sequence.
for slot_idx, expected_token, expected_position, expected_prev_signed in zip(
out_cache_loc, tokens, positions, expected_prev_hashes_signed
):
stored_token, stored_position, stored_prev_hash, stored_real_kv_hash = (
read_slot_fields(canary_buf=self.buf_pair[0], slot_idx=slot_idx)
)
assert stored_token == expected_token
assert stored_position == expected_position
assert stored_prev_hash == expected_prev_signed
assert stored_real_kv_hash == 0
class TestMockMode:
def setup_method(self) -> None:
self.buf_pair = _make_default_buf_pair()
def test_mock_mode_off_ignores_expected(self) -> None:
"""``enable_write_verify_inputs = OFF`` → expected tensors are ignored (we pass garbage to prove the kernel skips them)."""
# Garbage expected tensors that, if the kernel mistakenly reads, would generate mismatches.
cuda_log, _ = _run_write(
buf_pair=self.buf_pair,
input_ids=[1, 2, 3],
positions=[0, 1, 2],
out_cache_loc=[0, 1, 2],
expected_input_tokens=_int32_tensor([999, 999, 999]),
expected_input_positions=_int32_tensor([999, 999, 999]),
)
assert int(cuda_log.write_index[0].item()) == 0
def test_mock_mode_on_match_no_violation(self) -> None:
"""``enable_write_verify_inputs = ON`` and expected matches actual → no violation, chain advances."""
input_ids = _int32_tensor([7, 8, 9])
positions = _int32_tensor([0, 1, 2])
cuda_log, _ = _run_write(
buf_pair=self.buf_pair,
input_ids=input_ids,
positions=positions,
out_cache_loc=[0, 1, 2],
enable_write_verify_inputs=True,
expected_input_tokens=input_ids.clone(),
expected_input_positions=positions.clone(),
)
assert int(cuda_log.write_index[0].item()) == 0
def test_mock_mode_on_token_mismatch_records_violation(self) -> None:
"""``enable_write_verify_inputs = ON`` token mismatch → violation recorded; chain advances on ACTUAL token."""
cuda_log, _ = _run_write(
buf_pair=self.buf_pair,
input_ids=[42],
positions=[0],
out_cache_loc=[0],
enable_write_verify_inputs=True,
expected_input_tokens=_int32_tensor([99]),
expected_input_positions=_int32_tensor([0]),
)
fail_bits = int(
cuda_log.ring[0, consts.VIOLATION_FIELD_FAIL_REASON_BITS].item()
)
assert_only_bits_set(fail_bits, consts.FailReason.WRITE_TOKEN_MISMATCH)
# Chain advances on actual (42), not expected (99). Stored token should be 42.
stored_token, _, _, _ = read_slot_fields(
canary_buf=self.buf_pair[0], slot_idx=0
)
assert stored_token == 42
def test_mock_mode_on_position_mismatch_records_violation(self) -> None:
"""``enable_write_verify_inputs = ON`` position mismatch → violation recorded; chain advances on ACTUAL position."""
cuda_log, _ = _run_write(
buf_pair=self.buf_pair,
input_ids=[42],
positions=[7],
out_cache_loc=[0],
enable_write_verify_inputs=True,
expected_input_tokens=_int32_tensor([42]),
expected_input_positions=_int32_tensor([0]),
)
fail_bits = int(
cuda_log.ring[0, consts.VIOLATION_FIELD_FAIL_REASON_BITS].item()
)
assert_only_bits_set(fail_bits, consts.FailReason.WRITE_POSITION_MISMATCH)
_, stored_position, _, _ = read_slot_fields(
canary_buf=self.buf_pair[0], slot_idx=0
)
assert stored_position == 7
def test_mock_mode_chain_advances_on_actual_not_expected(self) -> None:
"""Expected differs from actual on every entry → downstream verify must NOT cascade chain errors."""
cuda_buf = self.buf_pair[0]
# Every actual differs from expected.
cuda_log, _ = _run_write(
buf_pair=self.buf_pair,
input_ids=[10, 20, 30],
positions=[0, 1, 2],
out_cache_loc=[1, 2, 3],
enable_write_verify_inputs=True,
expected_input_tokens=_int32_tensor([999, 999, 999]),
expected_input_positions=_int32_tensor([999, 999, 999]),
)
# All 3 entries should fire a violation row.
assert int(cuda_log.write_index[0].item()) == 3
# Run a downstream verify — it must see no chain mismatch because chain advanced on actuals.
verify_plan = make_verify_plan(
slot_indices=[1, 2, 3],
positions=[0, 1, 2],
prev_slot_indices=[-1, 1, 2],
device=_DEVICE,
)
verify_log = FakeViolationLog.allocate(device=_DEVICE)
launch_canary_verify_kernel(
context=VerifyOrWriteContext(
canary_buf=cuda_buf,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
violation_ring=verify_log.ring,
violation_write_index=verify_log.write_index,
slot_run_counter=verify_log.slot_run_counter,
kernel_run_counter=verify_log.kernel_run_counter,
enable_chain_position_assert=verify_log.enable_chain_position_assert,
),
plan=verify_plan,
check_verify_expected_token=True,
)
torch.cuda.synchronize()
assert int(verify_log.write_index[0].item()) == 0
@pytest.mark.parametrize("bit_to_trigger", ["MOCK_TOKEN", "MOCK_POSITION"])
@pytest.mark.parametrize("injection_position", ["head", "mid", "last"])
def test_mock_violation_bit_injection_position_matrix(
self,
bit_to_trigger: str,
injection_position: str,
) -> None:
"""Sweep injection_position x bit_to_trigger for write-kernel pseudo-mode fail-reason coverage."""
slot_count = 5
tokens = [10, 20, 30, 40, 50]
positions = [0, 1, 2, 3, 4]
out_cache_locs = [0, 1, 2, 3, 4]
corruption_index = {"head": 0, "mid": 2, "last": 4}[injection_position]
corrupt_slot = out_cache_locs[corruption_index]
expected_bit = {
"MOCK_TOKEN": consts.FailReason.WRITE_TOKEN_MISMATCH,
"MOCK_POSITION": consts.FailReason.WRITE_POSITION_MISMATCH,
}[bit_to_trigger]
input_ids = _int32_tensor(tokens)
positions_t = _int32_tensor(positions)
out_cache_loc = _int32_tensor(out_cache_locs)
pseudo_tokens = input_ids.clone()
pseudo_positions = positions_t.clone()
if bit_to_trigger == "MOCK_TOKEN":
pseudo_tokens[corruption_index] = tokens[corruption_index] + 999
else:
pseudo_positions[corruption_index] = positions[corruption_index] + 99
cuda_log, ref_log = _run_write(
buf_pair=self.buf_pair,
input_ids=input_ids,
positions=positions_t,
out_cache_loc=out_cache_loc,
write_offsets=[0, slot_count],
enable_write_verify_inputs=True,
expected_input_tokens=pseudo_tokens,
expected_input_positions=pseudo_positions,
assert_equal=False,
)
found = False
for row_idx in range(int(cuda_log.write_index[0].item())):
fail_bits = int(
cuda_log.ring[row_idx, consts.VIOLATION_FIELD_FAIL_REASON_BITS].item()
)
row_slot = int(cuda_log.ring[row_idx, 1].item())
if (fail_bits & expected_bit) and row_slot == corrupt_slot:
found = True
break
assert found, (
f"expected bit {expected_bit:#x} at slot {corrupt_slot} not found in ring "
f"(bit_to_trigger={bit_to_trigger} injection_position={injection_position})"
)
assert_canary_state_equal(log_a=cuda_log, log_b=ref_log)
class TestSlotHandling:
def setup_method(self) -> None:
self.buf_pair = _make_default_buf_pair()
def test_negative_slot_skips_entry(self) -> None:
"""``out_cache_loc[i] < 0`` → that entry is skipped: no buf write, no violation, no
canary slot mutation, and no write slot_run_counter increment.
Covers both SWA out-of-window (after caller-side LUT gather) and explicit padding intents.
"""
# Two entries: first writes to slot 4 normally; second has slot=-1 and must be skipped.
cuda_log, _ = _run_write(
buf_pair=self.buf_pair,
input_ids=[42, 99],
positions=[0, 1],
out_cache_loc=[4, -1],
)
stored_token, _, _, _ = read_slot_fields(
canary_buf=self.buf_pair[0], slot_idx=4
)
assert stored_token == 42
assert int(cuda_log.slot_run_counter.item()) == 1
def test_pre_translated_slot_writes_normally(self) -> None:
"""``out_cache_loc[i] >= 0`` → the kernel writes to exactly that slot, with no LUT applied. This
confirms the kernel is SWA-agnostic: SWA endpoints feed the same shape of input here after their
host-side gather, so the contract is symmetric across FULL / SWA groups.
"""
# Slot 4 here could equally be a FULL-group raw out_cache_loc value, or the result of an SWA
# endpoint's host gather. The kernel can't tell the difference and that's the point.
_run_write(
buf_pair=self.buf_pair,
input_ids=[55],
positions=[0],
out_cache_loc=[4],
)
stored_token, _, _, _ = read_slot_fields(
canary_buf=self.buf_pair[0], slot_idx=4
)
assert stored_token == 55
def test_padding_block_skipped(self) -> None:
"""``blockIdx.x >= write_num_valid_reqs[0]`` → block early-exits, no write to canary_buf."""
# Allocate plan with req_capacity=4 but only declare 1 active req.
_run_write(
buf_pair=self.buf_pair,
input_ids=[1],
positions=[0],
out_cache_loc=[0],
req_capacity=4,
)
# Only slot 0 should have been written; padding blocks 1..3 must not touch the buffer.
stored_token, _, _, _ = read_slot_fields(
canary_buf=self.buf_pair[0], slot_idx=0
)
assert stored_token == 1
for slot_idx in (1, 2, 3):
stored_token_other, _, _, _ = read_slot_fields(
canary_buf=self.buf_pair[0], slot_idx=slot_idx
)
assert stored_token_other == 0
def test_write_skip_when_out_cache_loc_is_minus_one(self) -> None:
"""out_cache_loc[i] = -1 → that entry's slot is untouched by write kernel."""
cuda_buf = self.buf_pair[0]
cuda_buf_before_slot_view = cuda_buf.view(torch.int64).clone()
_run_write(
buf_pair=self.buf_pair,
input_ids=[100, 200, 300],
positions=[0, 1, 2],
out_cache_loc=[5, -1, 7],
)
after = cuda_buf.view(torch.int64)
for slot in range(cuda_buf.shape[0]):
if slot in (5, 7):
continue
assert torch.equal(
after[slot], cuda_buf_before_slot_view[slot]
), f"slot {slot} should not have been written"
def test_shrink_active_reqs_does_not_write_stale_slots(self) -> None:
"""Run write with bs=3 plan after a bs=8 run on same buffer: stale slots from bs=8 stay intact."""
cuda_buf = make_canary_buf(num_slots=32, slot_stride_bytes=32, device=_DEVICE)
ref_buf = cuda_buf.clone()
buf_pair = (cuda_buf, ref_buf)
big_slots = list(range(1, 9))
_run_write(
buf_pair=buf_pair,
input_ids=list(range(100, 108)),
positions=[0] * 8,
out_cache_loc=big_slots,
write_offsets=[0, 1, 2, 3, 4, 5, 6, 7, 8],
seed_slot_indices=[-1] * 8,
num_valid_reqs=8,
assert_equal=False,
)
untouched_snapshot = cuda_buf.view(torch.int64).clone()
small_slots = [20, 21, 22]
_run_write(
buf_pair=buf_pair,
input_ids=[7, 8, 9],
positions=[0, 0, 0],
out_cache_loc=small_slots,
write_offsets=[0, 1, 2, 3],
seed_slot_indices=[-1, -1, -1],
num_valid_reqs=3,
assert_equal=False,
)
after = cuda_buf.view(torch.int64)
for slot in big_slots:
assert torch.equal(
after[slot], untouched_snapshot[slot]
), f"slot {slot} from earlier bs=8 run was overwritten by bs=3 run"
class TestRunCounter:
def setup_method(self) -> None:
self.buf_pair = _make_default_buf_pair()
def test_kernel_run_counter_per_call(self) -> None:
"""``kernel_run_counter`` increments by 1 per call (even when ``write_num_valid_reqs == 0``)."""
plan_pair = make_write_plan_pair(
write_offsets=[0, 0],
seed_slot_indices=[-1],
num_valid_reqs=0,
device=_DEVICE,
)
input_ids = _int32_tensor([0])
positions = _int32_tensor([0])
out_cache_loc = _int32_tensor([0])
pseudo_tokens, pseudo_positions = dummy_pseudo_tensors(1)
cuda_log, ref_log = make_log_pair(device=_DEVICE)
for _ in range(3):
_run_both_write(
cuda_canary_buf=self.buf_pair[0],
ref_canary_buf=self.buf_pair[1],
plan_cuda=plan_pair[0],
plan_ref=plan_pair[1],
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
enable_write_verify_inputs=False,
expected_input_tokens=pseudo_tokens,
expected_input_positions=pseudo_positions,
cuda_log=cuda_log,
ref_log=ref_log,
assert_equal=False,
)
assert int(cuda_log.kernel_run_counter[0].item()) == 3
assert_canary_state_equal(log_a=cuda_log, log_b=ref_log)
def test_slot_run_counter_sums_entries(self) -> None:
"""``slot_run_counter`` += sum(entry_count) across all active reqs in this call."""
cuda_log, _ = _run_write(
buf_pair=self.buf_pair,
input_ids=[1, 2, 3, 4, 5],
positions=[0, 1, 0, 1, 2],
out_cache_loc=[0, 1, 2, 3, 4],
write_offsets=[0, 2, 5],
seed_slot_indices=[-1, -1],
num_valid_reqs=2,
)
assert int(cuda_log.slot_run_counter[0].item()) == 5
class TestMisc:
def test_empty_plan_no_op(self) -> None:
"""``write_num_valid_reqs = 0`` → no buf write, no slot_run_counter bump, only kernel_run_counter += 1."""
cuda_log, _ = _run_write(
buf_pair=_make_default_buf_pair(),
input_ids=[0],
positions=[0],
out_cache_loc=[0],
write_offsets=[0, 0],
num_valid_reqs=0,
req_capacity=4,
)
assert int(cuda_log.write_index[0].item()) == 0
assert int(cuda_log.slot_run_counter[0].item()) == 0
assert int(cuda_log.kernel_run_counter[0].item()) == 1
def test_disabled_input_verify_does_not_deref_expected_inputs(self) -> None:
"""``enable_write_verify_inputs=False`` must short-circuit before any expected_input_* dereference;
poison those tensors with 0x7F7F7F7F and assert nothing records a violation."""
cuda_buf = make_canary_buf(num_slots=8, slot_stride_bytes=32, device=_DEVICE)
ref_buf = cuda_buf.clone()
buf_pair = (cuda_buf, ref_buf)
garbage_expected_tokens = torch.full(
(1,), 0x7F7F7F7F, dtype=torch.int64, device=_DEVICE
)
garbage_expected_positions = torch.full(
(1,), 0x7F7F7F7F, dtype=torch.int64, device=_DEVICE
)
cuda_log, ref_log = _run_write(
buf_pair=buf_pair,
input_ids=[42],
positions=[0],
out_cache_loc=[0],
expected_input_tokens=garbage_expected_tokens,
expected_input_positions=garbage_expected_positions,
assert_equal=False,
)
assert int(cuda_log.write_index[0].item()) == 0
assert int(ref_log.write_index[0].item()) == 0
def test_disabled_assert_inputs_passes_none_to_cuda(self) -> None:
"""Disabled input assertions pass None instead of dummy tensors."""
canary_buf = torch.zeros(
4, CANARY_SLOT_BYTES, dtype=torch.uint8, device=_DEVICE
)
plan = make_write_plan(
write_offsets=[0, 0],
seed_slot_indices=[-1],
num_valid_reqs=0,
device=_DEVICE,
)
input_ids = torch.zeros(1, dtype=torch.int64, device=_DEVICE)
positions = torch.zeros(1, dtype=torch.int64, device=_DEVICE)
out_cache_loc = torch.zeros(1, dtype=torch.int64, device=_DEVICE)
log = FakeViolationLog.allocate(capacity=2, device=_DEVICE)
context = VerifyOrWriteContext(
canary_buf=canary_buf,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
violation_ring=log.ring,
violation_write_index=log.write_index,
slot_run_counter=log.slot_run_counter,
kernel_run_counter=log.kernel_run_counter,
enable_chain_position_assert=log.enable_chain_position_assert,
)
module = _RecordingWriteModule()
with patch.object(write_module, "_jit_canary_write_module", lambda: module):
launch_canary_write_kernel(
context=context,
plan=plan,
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
enable_write_input_assert=False,
expected_input_tokens=None,
expected_input_positions=None,
)
assert len(module.calls) == 1
call = module.calls[0]
assert call[8] == 0
assert call[9] is None
assert call[10] is None
class TestBoundarySweep:
@pytest.mark.parametrize(
"token_val",
[0, 1, 0xFFFFFFFF, -1, 0x80000000, 0x7FFFFFFF],
)
def test_token_boundary_byte_equal_sweep(self, token_val: int) -> None:
"""Sweep token boundary values; assert CUDA write vs ref buf + state byte-equal."""
_run_write_single_slot_byte_equal(_WriteSingleSlotInput(token=token_val))
@pytest.mark.parametrize(
"position_val",
[0, 1, 127, 128, 129, 0x7FFFFFFF],
)
def test_position_boundary_byte_equal_sweep(self, position_val: int) -> None:
"""Sweep position boundary values; assert CUDA write vs ref buf + state byte-equal."""
_run_write_single_slot_byte_equal(_WriteSingleSlotInput(position=position_val))
class TestPseudoMode:
def setup_method(self) -> None:
self.buf_pair = _make_default_buf_pair()
def test_pseudo_mode_on_catches_token_mismatch(self) -> None:
"""enable_write_verify_inputs=ON + intentional token mismatch → WRITE_TOKEN_MISMATCH bit recorded."""
input_ids = _int32_tensor([10, 20, 30, 40, 50])
positions = _int32_tensor([0, 1, 2, 3, 4])
out_cache_loc = _int32_tensor([1, 2, 3, 4, 5])
pseudo_tokens = _int32_tensor([10, 20, 30, 999, 50])
pseudo_positions = positions.clone()
cuda_log, _ = _run_write(
buf_pair=self.buf_pair,
input_ids=input_ids,
positions=positions,
out_cache_loc=out_cache_loc,
enable_write_verify_inputs=True,
expected_input_tokens=pseudo_tokens,
expected_input_positions=pseudo_positions,
)
assert int(cuda_log.write_index[0].item()) >= 1
bits = int(cuda_log.ring[0, consts.VIOLATION_FIELD_FAIL_REASON_BITS].item())
assert (
bits & consts.FailReason.WRITE_TOKEN_MISMATCH
), f"expected WRITE_TOKEN_MISMATCH bit, got {bits:#b}"
def test_pseudo_mode_off_skips_token_check(self) -> None:
"""enable_write_verify_inputs=False makes the caller pass no expected-input tensors."""
cuda_log, _ = _run_write(
buf_pair=self.buf_pair,
input_ids=[10, 20, 30],
positions=[0, 1, 2],
out_cache_loc=[1, 2, 3],
expected_input_tokens=_int32_tensor([99, 99, 99]),
expected_input_positions=_int32_tensor([99, 99, 99]),
)
assert int(cuda_log.write_index[0].item()) == 0
if __name__ == "__main__":
import sys
sys.exit(pytest.main([__file__, "-v"]))