94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
262 lines
13 KiB
Python
262 lines
13 KiB
Python
from __future__ import annotations
|
||
|
||
from dataclasses import dataclass
|
||
from typing import TYPE_CHECKING
|
||
|
||
import torch
|
||
|
||
from sglang.jit_kernel.kv_canary import consts
|
||
from sglang.jit_kernel.kv_canary.verify import (
|
||
VerifyOrWriteContext,
|
||
_assert_contiguous,
|
||
_build_real_kv_source_abi,
|
||
)
|
||
from sglang.jit_kernel.utils import cache_once, load_jit
|
||
|
||
if TYPE_CHECKING:
|
||
from tvm_ffi.module import Module
|
||
|
||
|
||
@dataclass(frozen=True, slots=True, kw_only=True)
|
||
class WritePlan:
|
||
"""Write plan consumed by launch_canary_write_kernel: per-token slot indices + per-req metadata.
|
||
|
||
Fully per-req — no per-token tile. launch_canary_write_kernel uses write_offsets to map each thread's
|
||
(req, j) into a flat index i, then reads token-level data from input_ids / positions /
|
||
out_cache_loc[i] directly.
|
||
SWA translation of per-token slots is done **host-side by the caller** (typically the endpoint) before
|
||
invoking launch_canary_write_kernel — the kernel is SWA-agnostic and only understands "slot ≥ 0 ⇒ write;
|
||
slot < 0 ⇒ skip this entry". Only the chain-seed slot (a per-req gather from req_to_token at plan time)
|
||
is SWA-translated by the plan kernel and lives in write_seed_slot_indices.
|
||
|
||
Req r's write entries occupy flat indices [write_offsets[r], write_offsets[r+1]). seed_slot_idx == -1 means
|
||
K_req_old == 0 (anchor on CANARY_CHAIN_ANCHOR).
|
||
|
||
Fields:
|
||
write_offsets: Exclusive prefix-sum offsets indexing into ForwardBatch's input_ids / positions /
|
||
out_cache_loc, shape [write_req_capacity + 1], int64. write_offsets[0] == 0;
|
||
write_offsets[write_num_valid_reqs[0]] == total_write_entries.
|
||
write_seed_slot_indices: Chain-seed slot per write req, shape [write_req_capacity], int64. Already
|
||
SWA-translated. -1 = no prefix (chain anchors on CANARY_CHAIN_ANCHOR).
|
||
write_num_valid_reqs: Active write-req count, shape [1], int32. launch_canary_write_kernel skips blocks
|
||
with block_id >= write_num_valid_reqs[0].
|
||
"""
|
||
|
||
write_offsets: torch.Tensor
|
||
write_seed_slot_indices: torch.Tensor
|
||
write_num_valid_reqs: torch.Tensor
|
||
|
||
@classmethod
|
||
def allocate(
|
||
cls,
|
||
*,
|
||
write_req_capacity: int,
|
||
device: torch.device,
|
||
) -> WritePlan:
|
||
if write_req_capacity <= 0:
|
||
raise ValueError(
|
||
f"kv-canary: WritePlan write_req_capacity must be positive, got {write_req_capacity}"
|
||
)
|
||
return cls(
|
||
write_offsets=torch.empty(
|
||
write_req_capacity + 1, dtype=torch.int64, device=device
|
||
),
|
||
write_seed_slot_indices=torch.empty(
|
||
write_req_capacity, dtype=torch.int64, device=device
|
||
),
|
||
write_num_valid_reqs=torch.empty(1, dtype=torch.int32, device=device),
|
||
)
|
||
|
||
def zero_for_testing_(self) -> WritePlan:
|
||
"""WARN: ONLY use it when testing plan kernel. Do not use it when testing verify or
|
||
write kernel to avoid hiding bugs."""
|
||
self.write_offsets.zero_()
|
||
self.write_seed_slot_indices.zero_()
|
||
self.write_num_valid_reqs.zero_()
|
||
return self
|
||
|
||
|
||
def launch_canary_write_kernel(
|
||
*,
|
||
context: VerifyOrWriteContext,
|
||
plan: WritePlan,
|
||
input_ids: torch.Tensor,
|
||
positions: torch.Tensor,
|
||
out_cache_loc: torch.Tensor,
|
||
enable_write_input_assert: bool,
|
||
expected_input_tokens: torch.Tensor | None,
|
||
expected_input_positions: torch.Tensor | None,
|
||
) -> None:
|
||
"""Write canary fingerprints into one canary buffer per a WritePlan.
|
||
|
||
Grid: one CUDA block per active write req, single thread per block (chain is intrinsically serial).
|
||
Block r walks entries ``[plan.write_offsets[r], plan.write_offsets[r+1])``. Per chain step ``i``:
|
||
|
||
- ``slot`` = ``out_cache_loc[i]`` (caller-pre-translated for SWA groups; entries set to -1 are skipped).
|
||
- ``token / position`` = ``input_ids[i] / positions[i]``.
|
||
- ``real_kv_hash`` = ``real_kv_fold_sources(real_kv_sources, slot)`` if ``real_kv_hash_mode != NONE`` else 0.
|
||
- Store 4 int64s ``(token, position, running_prev_hash, real_kv_hash)`` into ``canary_buf[slot]``.
|
||
- Advance ``running_prev_hash = splitmix64_mix3(prev, token, position)``, where
|
||
splitmix64_mix3 folds each input via ``acc = splitmix64(acc ^ next)`` starting from ``splitmix64(prev)``.
|
||
``real_kv_hash`` is intentionally not folded into the chain — see ``compute_slot_hash`` in
|
||
``csrc/kv_canary/canary_common.cuh`` for the radix-folding rationale.
|
||
|
||
Initial ``running_prev_hash`` when ``seed_slot_idx >= 0``: load (token, position, prev_hash) from
|
||
``canary_buf[plan.write_seed_slot_indices[r]]`` and set
|
||
``running_prev_hash = splitmix64_mix3(seed.prev_hash, seed.token, seed.position)``
|
||
(i.e. apply the same advance step that produced ``seed``'s successor — this keeps slot[0]'s stored
|
||
``prev_hash`` consistent with the chain link). Else
|
||
``running_prev_hash = splitmix64(CANARY_CHAIN_ANCHOR)``. ``write_seed_slot_indices`` is already
|
||
SWA-translated by the plan kernel; ``CANARY_CHAIN_ANCHOR`` is hardcoded module-level (no runtime seed).
|
||
|
||
Write-time input verification (caller-driven, kernel is oracle-agnostic): when
|
||
``enable_write_input_assert`` is True the kernel additionally compares ``input_ids[i]`` against
|
||
``expected_input_tokens[i]`` and ``positions[i]`` against ``expected_input_positions[i]``; mismatch
|
||
on either field records a violation. The chain still advances on the actual values (not the expected
|
||
ones) so a downstream verify won't cascade. Whoever produced the expected tensors is responsible for
|
||
filling them; the kernel runs no oracle internally.
|
||
|
||
Write only writes canary_buf (reads only at seed slots). Block uses no shared memory.
|
||
|
||
The ForwardBatch-derived arguments are passed through unchanged from the source ForwardBatch — canary does not transform
|
||
them.
|
||
|
||
Args:
|
||
context: Shared verify/write launch context, including canary buffer, launch tag, violation sink,
|
||
health counters, and real KV fingerprint sources.
|
||
plan: Pre-allocated WritePlan.
|
||
input_ids: ForwardBatch.input_ids; token ids being written, shape [num_tokens_padded], int64.
|
||
Flattened across reqs in plan.write_offsets order; tail beyond
|
||
plan.write_offsets[plan.write_num_valid_reqs[0]] is cuda-graph padding.
|
||
positions: ForwardBatch.positions; sequence positions of input_ids, shape [num_tokens_padded], int64.
|
||
out_cache_loc: Per-token canary slot index, shape [num_tokens_padded], int64. The caller is
|
||
responsible for translating ForwardBatch.out_cache_loc into the canary's index space for SWA
|
||
groups (typically a host-side LUT gather in the endpoint); FULL groups pass it through
|
||
unchanged. A -1 entry signals skip-this-token (used for SWA out-of-window slots or padding).
|
||
The kernel does not consult any LUT.
|
||
enable_write_input_assert: bool toggle. False = expected_input_* tensors must be None. True = compare
|
||
each chain step's actual (token, position) against the caller-supplied expected tensors below.
|
||
expected_input_tokens: Expected token id per write entry, shape [num_tokens_padded], int64. Only read
|
||
when enable_write_input_assert is True; must be None when enable_write_input_assert is False.
|
||
Layout mirrors input_ids (flattened across reqs in plan.write_offsets order); padding tail
|
||
is ignored. Filled by the caller from whichever oracle produces expected inputs — the kernel
|
||
knows no oracle.
|
||
expected_input_positions: Expected position per write entry, shape [num_tokens_padded], int64, or None.
|
||
Same shape/layout/lifetime rules as expected_input_tokens.
|
||
|
||
Implementation:
|
||
- CUDA __global__ `canary_write_kernel`: 1-D grid `(write_req_capacity, 1, 1)` blocks × `(1, 1, 1)` thread
|
||
per block. block_id r = blockIdx.x = one write req; chains are intrinsically serial so a single thread
|
||
per block is optimal (warp-level parallelism would idle 31 lanes).
|
||
- Per block, early-exit on r >= plan.write_num_valid_reqs[0]. Else load entry_start = plan.write_offsets[r],
|
||
entry_count = plan.write_offsets[r+1] - entry_start, seed_slot_idx = plan.write_seed_slot_indices[r] into
|
||
registers.
|
||
- Initialize running_prev_hash: if seed_slot_idx >= 0, load (token, position, prev_hash) from
|
||
canary_buf[seed_slot_idx] and set running_prev_hash = splitmix64_mix3(prev_hash, token, position);
|
||
else running_prev_hash = splitmix64(kCanaryChainAnchor).
|
||
- Serial chain loop `for j in range(entry_count)`:
|
||
i = entry_start + j;
|
||
slot = out_cache_loc[i]; // caller-pre-translated; the kernel never consults a LUT
|
||
if (slot < 0) continue; // -1 sentinel = skip (SWA out-of-window or padding)
|
||
token = input_ids[i]; position = positions[i];
|
||
real_kv_hash = (real_kv_hash_mode == NONE) ? 0 : real_kv_fold_sources(real_kv_sources, slot);
|
||
// applies RealKvSource access invariant
|
||
if enable_write_input_assert:
|
||
if token != expected_input_tokens[i] or position != expected_input_positions[i]:
|
||
record_violation(); // chain still advances on the ACTUAL (token, position) below
|
||
store (token, position, running_prev_hash, real_kv_hash) to canary_buf[slot] as 4 int64 fields;
|
||
running_prev_hash = splitmix64_mix3(running_prev_hash, token, position);
|
||
- All chain state lives in the block's single thread's registers. No shared memory, no cross-block
|
||
coordination.
|
||
- record_violation() identical to verify (atomicAdd + atomic-write).
|
||
- Counters: thread of block 0 does atomicAdd(kernel_run_counter, 1); each block accumulates its
|
||
entry_count and atomicAdds to slot_run_counter once at exit.
|
||
|
||
Calling contract:
|
||
- Pure side-effect; never raises.
|
||
- Input-verification mismatch records violations but does NOT abort the chain.
|
||
- kernel_run_counter is bumped every call.
|
||
- Safe in cuda-graph capture; caller refills input_ids / positions / out_cache_loc / plan
|
||
in-place before replay.
|
||
|
||
Pinned by torch reference
|
||
:func:`sglang.jit_kernel.kv_canary.write_ref.launch_canary_write_kernel_torch_reference`; CUDA must match
|
||
byte-for-byte.
|
||
"""
|
||
canary_buf = context.canary_buf
|
||
real_kv_sources = context.real_kv_sources
|
||
if len(real_kv_sources) > consts.MAX_REAL_KV_SOURCES:
|
||
raise ValueError(
|
||
f"kv-canary: at most {consts.MAX_REAL_KV_SOURCES} RealKvSource entries supported by the CUDA ABI, "
|
||
f"got {len(real_kv_sources)}"
|
||
)
|
||
|
||
_assert_contiguous(canary_buf, "canary_buf")
|
||
_assert_contiguous(plan.write_offsets, "plan.write_offsets")
|
||
_assert_contiguous(plan.write_seed_slot_indices, "plan.write_seed_slot_indices")
|
||
_assert_contiguous(plan.write_num_valid_reqs, "plan.write_num_valid_reqs")
|
||
_assert_contiguous(input_ids, "input_ids")
|
||
_assert_contiguous(positions, "positions")
|
||
_assert_contiguous(out_cache_loc, "out_cache_loc")
|
||
if enable_write_input_assert:
|
||
if expected_input_tokens is None or expected_input_positions is None:
|
||
raise ValueError(
|
||
"kv-canary: expected input tensors are required when enable_write_input_assert=True"
|
||
)
|
||
_assert_contiguous(expected_input_tokens, "expected_input_tokens")
|
||
_assert_contiguous(expected_input_positions, "expected_input_positions")
|
||
else:
|
||
if expected_input_tokens is not None or expected_input_positions is not None:
|
||
raise ValueError(
|
||
"kv-canary: expected input tensors must be None when enable_write_input_assert=False"
|
||
)
|
||
_assert_contiguous(context.violation_ring, "violation_ring")
|
||
_assert_contiguous(context.violation_write_index, "violation_write_index")
|
||
_assert_contiguous(context.slot_run_counter, "slot_run_counter")
|
||
_assert_contiguous(context.kernel_run_counter, "kernel_run_counter")
|
||
_assert_contiguous(
|
||
context.enable_chain_position_assert, "enable_chain_position_assert"
|
||
)
|
||
|
||
padded_bufs, source_params = _build_real_kv_source_abi(
|
||
real_kv_sources=real_kv_sources, device=canary_buf.device
|
||
)
|
||
|
||
module = _jit_canary_write_module()
|
||
module.canary_write_step_cuda(
|
||
canary_buf,
|
||
plan.write_offsets,
|
||
plan.write_seed_slot_indices,
|
||
plan.write_num_valid_reqs,
|
||
input_ids,
|
||
positions,
|
||
out_cache_loc,
|
||
int(context.kernel_kind),
|
||
int(enable_write_input_assert),
|
||
expected_input_tokens,
|
||
expected_input_positions,
|
||
context.violation_ring,
|
||
context.violation_write_index,
|
||
context.slot_run_counter,
|
||
context.kernel_run_counter,
|
||
context.enable_chain_position_assert,
|
||
padded_bufs[0],
|
||
padded_bufs[1],
|
||
padded_bufs[2],
|
||
padded_bufs[3],
|
||
source_params,
|
||
len(real_kv_sources),
|
||
int(context.real_kv_hash_mode),
|
||
)
|
||
|
||
|
||
@cache_once
|
||
def _jit_canary_write_module() -> Module:
|
||
return load_jit(
|
||
"kv_canary_write",
|
||
cuda_files=["kv_canary/canary_write.cuh"],
|
||
cuda_wrappers=[
|
||
("canary_write_step_cuda", "canary::canary_write_step_cuda"),
|
||
],
|
||
)
|