Add the KV-canary core: data layer, MHA KV-pool patcher, and per-forward runner (#26808)

This commit is contained in:
fzyzcjy
2026-05-31 09:54:24 +08:00
committed by GitHub
parent 736ad1f32a
commit 11391b2a1c
46 changed files with 3756 additions and 0 deletions
View File
@@ -0,0 +1,31 @@
import unittest
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.kv_canary.e2e_base import (
_LONG_PROMPT_BODY,
_UNIQUE_PROMPT_FIRST_CHARS,
_make_unique_prompts,
)
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=1, suite="base-b-test-cpu")
class TestCanaryE2EBase(CustomTestCase):
def test_make_unique_prompts_have_distinct_first_characters(self) -> None:
"""Verify generated prompts use distinct first characters and all end with the shared long body."""
prompts = _make_unique_prompts(8)
self.assertEqual(len({prompt[0] for prompt in prompts}), len(prompts))
self.assertTrue(all(prompt.endswith(_LONG_PROMPT_BODY) for prompt in prompts))
def test_make_unique_prompts_rejects_more_prompts_than_distinct_first_characters(
self,
) -> None:
"""Verify prompt generation rejects requests beyond the unique prefix budget."""
with self.assertRaisesRegex(ValueError, "unique prompt count"):
_make_unique_prompts(len(_UNIQUE_PROMPT_FIRST_CHARS) + 1)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,27 @@
from __future__ import annotations
import unittest
import torch
from sglang.jit_kernel.kv_canary.verify import CANARY_SLOT_BYTES
from sglang.srt.kv_canary.pool_patcher.buffer_alloc import alloc_canary_buf
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.kv_canary.fixtures import DEFAULT_DEVICE
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=10, stage="extra-a", runner_config="1-gpu-small")
class TestAllocCanaryBuf(CustomTestCase):
def test_alloc_canary_buf_shape_and_dtype(self) -> None:
"""Verify alloc_canary_buf returns a zeroed uint8 buffer of [num_slots, CANARY_SLOT_BYTES]."""
buf = alloc_canary_buf(num_slots=8, device=DEFAULT_DEVICE)
self.assertEqual(buf.shape, (8, CANARY_SLOT_BYTES))
self.assertEqual(buf.dtype, torch.uint8)
self.assertEqual(buf.device.type, DEFAULT_DEVICE.type)
self.assertTrue(torch.equal(buf, torch.zeros_like(buf)))
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,89 @@
from __future__ import annotations
import unittest
from types import SimpleNamespace
from sglang.srt.kv_canary.capacities import CanaryLaunchCapacities
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=45, stage="extra-a", runner_config="1-gpu-small")
class TestComputeLaunchCapacities(CustomTestCase):
@staticmethod
def _make_server_args(*, max_bs: int) -> SimpleNamespace:
return SimpleNamespace(
cuda_graph_max_bs=max_bs,
speculative_num_draft_tokens=0,
chunked_prefill_size=None,
max_prefill_tokens=128,
)
@staticmethod
def _from_args(
*,
max_bs: int,
max_seq_len: int,
max_total_num_tokens: int | None = None,
) -> CanaryLaunchCapacities:
if max_total_num_tokens is None:
max_total_num_tokens = max_bs * max_seq_len
return CanaryLaunchCapacities.from_args(
server_args=TestComputeLaunchCapacities._make_server_args(max_bs=max_bs),
req_to_token_pool_size=max_bs,
max_seq_len_per_req=max_seq_len,
pool_slot_count=max_total_num_tokens,
)
def test_per_forward_verify_capacity_covers_multi_req_prefix_sum(self) -> None:
"""Verify per-forward verify capacity equals max_total_num_tokens * 3."""
max_bs = 8
max_seq_len = 64
max_total_num_tokens = 1024
capacities = self._from_args(
max_bs=max_bs,
max_seq_len=max_seq_len,
max_total_num_tokens=max_total_num_tokens,
)
self.assertEqual(
capacities.per_forward_verify_capacity,
int(max_total_num_tokens * 3),
)
def test_from_args_treats_missing_speculative_draft_tokens_as_zero(self) -> None:
"""per_forward_write_entry_capacity is floored by max_prefill_tokens when batch * tokens_per_bs is smaller."""
server_args = self._make_server_args(max_bs=2)
server_args.speculative_num_draft_tokens = None
capacities = CanaryLaunchCapacities.from_args(
server_args=server_args,
req_to_token_pool_size=2,
max_seq_len_per_req=32,
pool_slot_count=64,
)
self.assertEqual(capacities.per_forward_write_entry_capacity, 128)
def test_manual_capacities_reject_non_positive_fields(self) -> None:
"""Verify manual launch capacities fail instead of being clamped."""
with self.assertRaisesRegex(ValueError, "per_forward_verify_capacity"):
CanaryLaunchCapacities(
per_forward_verify_capacity=0,
per_forward_write_req_capacity=1,
per_forward_write_entry_capacity=1,
)
def test_from_args_rejects_empty_pool_capacity(self) -> None:
"""Verify derived launch capacities reject invalid pool sizing."""
with self.assertRaisesRegex(ValueError, "pool_slot_count"):
CanaryLaunchCapacities.from_args(
server_args=self._make_server_args(max_bs=1),
req_to_token_pool_size=1,
max_seq_len_per_req=1,
pool_slot_count=0,
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,241 @@
from __future__ import annotations
import unittest
from types import SimpleNamespace
from unittest.mock import patch
import torch
from sglang.jit_kernel.kv_canary.verify import (
CANARY_SLOT_BYTES,
CanaryLaunchTag,
VerifyPlan,
)
from sglang.jit_kernel.kv_canary.write import WritePlan
from sglang.srt.kv_canary import endpoint as endpoint_module
from sglang.srt.kv_canary.endpoint import (
CanaryEndpoint,
)
from sglang.srt.kv_canary.expected_inputs import ExpectedInputs
from sglang.srt.kv_canary.state import (
ViolationLog,
)
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.kv_canary.fixtures import DEFAULT_DEVICE
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=20, stage="extra-a", runner_config="1-gpu-small")
def _make_endpoint(*, device, kernel_kind=CanaryLaunchTag.HEAD_K_FULL, swa_lut=None):
canary_buf = torch.zeros(4, CANARY_SLOT_BYTES, dtype=torch.uint8, device=device)
slot_view = torch.zeros(1, dtype=torch.int64, device=device)
kernel_view = torch.zeros(1, dtype=torch.int64, device=device)
enable_chain_position_assert = torch.ones(1, dtype=torch.int32, device=device)
return CanaryEndpoint(
kernel_kind=kernel_kind,
canary_buf=canary_buf,
full_to_swa_index_mapping=swa_lut,
slot_run_counter_view=slot_view,
kernel_run_counter_view=kernel_view,
enable_chain_position_assert=enable_chain_position_assert,
)
def _make_kernel_args(device):
verify_plan = VerifyPlan.allocate(verify_capacity=1, device=device)
write_plan = WritePlan.allocate(write_req_capacity=1, device=device)
log = ViolationLog.allocate(ring_capacity=2, device=device)
return SimpleNamespace(
verify_plan=verify_plan,
write_plan=write_plan,
violation_log=log,
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),
enable_write_input_assert=False,
enable_verify_token_assert=False,
expected_inputs=ExpectedInputs.allocate(capacity=1, device=device),
)
class TestSelfUnitEndpoint(CustomTestCase):
def setUp(self):
self.device = DEFAULT_DEVICE
def test_launch_per_forward_passes_kernel_kind(self):
"""Verify per-forward launch passes the endpoint kernel kind."""
captured: list[tuple[str, CanaryLaunchTag]] = []
with patch.object(
endpoint_module,
"launch_canary_verify_kernel",
lambda **kwargs: captured.append(("verify", kwargs["context"].kernel_kind)),
), patch.object(
endpoint_module,
"launch_canary_write_kernel",
lambda **kwargs: captured.append(("write", kwargs["context"].kernel_kind)),
):
ep = _make_endpoint(
device=self.device, kernel_kind=CanaryLaunchTag.TAIL_V_SWA
)
args = _make_kernel_args(self.device)
ep.launch_per_forward(
verify_plan=args.verify_plan,
write_plan=args.write_plan,
input_ids=args.input_ids,
positions=args.positions,
out_cache_loc=args.out_cache_loc,
enable_write_input_assert=args.enable_write_input_assert,
enable_verify_token_assert=args.enable_verify_token_assert,
expected_inputs=args.expected_inputs,
violation_log=args.violation_log,
)
self.assertIn(("verify", CanaryLaunchTag.TAIL_V_SWA), captured)
self.assertIn(("write", CanaryLaunchTag.TAIL_V_SWA), captured)
def test_endpoint_shares_violation_log_across_launches(self):
"""Verify endpoints can reuse the same violation log."""
captured_rings: list[int] = []
with patch.object(
endpoint_module,
"launch_canary_verify_kernel",
lambda **kwargs: captured_rings.append(
kwargs["context"].violation_ring.data_ptr()
),
), patch.object(
endpoint_module,
"launch_canary_write_kernel",
lambda **kwargs: None,
):
shared_log = ViolationLog.allocate(ring_capacity=2, device=self.device)
ep_a = _make_endpoint(
device=self.device, kernel_kind=CanaryLaunchTag.HEAD_K_FULL
)
ep_b = _make_endpoint(
device=self.device, kernel_kind=CanaryLaunchTag.HEAD_V_FULL
)
args = _make_kernel_args(self.device)
ep_a.launch_per_forward(
verify_plan=args.verify_plan,
write_plan=args.write_plan,
input_ids=args.input_ids,
positions=args.positions,
out_cache_loc=args.out_cache_loc,
enable_write_input_assert=args.enable_write_input_assert,
enable_verify_token_assert=args.enable_verify_token_assert,
expected_inputs=args.expected_inputs,
violation_log=shared_log,
)
ep_b.launch_per_forward(
verify_plan=args.verify_plan,
write_plan=args.write_plan,
input_ids=args.input_ids,
positions=args.positions,
out_cache_loc=args.out_cache_loc,
enable_write_input_assert=args.enable_write_input_assert,
enable_verify_token_assert=args.enable_verify_token_assert,
expected_inputs=args.expected_inputs,
violation_log=shared_log,
)
self.assertEqual(captured_rings[0], captured_rings[1])
self.assertEqual(captured_rings[0], shared_log.violation_ring.data_ptr())
def test_swa_endpoint_pre_translates_out_cache_loc(self):
"""Verify SWA endpoints translate cache locations before write launch."""
captured: list[torch.Tensor] = []
with patch.object(
endpoint_module, "launch_canary_verify_kernel", lambda **kwargs: None
), patch.object(
endpoint_module,
"launch_canary_write_kernel",
lambda **kwargs: captured.append(kwargs["out_cache_loc"]),
):
# LUT maps full slot i → swa slot (i + 100) so we can verify the gather happened.
lut = torch.arange(8, dtype=torch.int64, device=self.device) + 100
swa_ep = _make_endpoint(
device=self.device,
kernel_kind=CanaryLaunchTag.HEAD_K_SWA,
swa_lut=lut,
)
full_ep = _make_endpoint(
device=self.device,
kernel_kind=CanaryLaunchTag.HEAD_K_FULL,
swa_lut=None,
)
args = _make_kernel_args(self.device)
swa_ep.launch_per_forward(
verify_plan=args.verify_plan,
write_plan=args.write_plan,
input_ids=args.input_ids,
positions=args.positions,
out_cache_loc=args.out_cache_loc,
enable_write_input_assert=args.enable_write_input_assert,
enable_verify_token_assert=args.enable_verify_token_assert,
expected_inputs=args.expected_inputs,
violation_log=args.violation_log,
)
full_ep.launch_per_forward(
verify_plan=args.verify_plan,
write_plan=args.write_plan,
input_ids=args.input_ids,
positions=args.positions,
out_cache_loc=args.out_cache_loc,
enable_write_input_assert=args.enable_write_input_assert,
enable_verify_token_assert=args.enable_verify_token_assert,
expected_inputs=args.expected_inputs,
violation_log=args.violation_log,
)
# SWA call: out_cache_loc was rewritten via lut gather (so identity-shifted by +100 here).
expected_swa = lut[args.out_cache_loc]
self.assertTrue(torch.equal(captured[0], expected_swa))
# FULL call: out_cache_loc keeps the same values and dtype.
self.assertIs(captured[1], args.out_cache_loc)
def test_swa_endpoint_trailing_sentinel_row_yields_skip(self):
"""Verify SWA sentinel cache rows become write-skip markers."""
captured: list[torch.Tensor] = []
with patch.object(
endpoint_module, "launch_canary_verify_kernel", lambda **kwargs: None
), patch.object(
endpoint_module,
"launch_canary_write_kernel",
lambda **kwargs: captured.append(kwargs["out_cache_loc"]),
):
# 8 in-window rows + 1 trailing sentinel row at index 8.
lut = torch.arange(8, dtype=torch.int64, device=self.device)
lut = torch.cat(
[lut, torch.tensor([-1], dtype=torch.int64, device=self.device)]
)
swa_ep = _make_endpoint(
device=self.device, kernel_kind=CanaryLaunchTag.HEAD_K_SWA, swa_lut=lut
)
args = _make_kernel_args(self.device)
# Point out_cache_loc at the trailing-sentinel-row index — this is how sglang signals
# "this token is out-of-window for the SWA group" pre-cleanup, and the new host gather must
# produce -1 here.
args.out_cache_loc.fill_(8)
swa_ep.launch_per_forward(
verify_plan=args.verify_plan,
write_plan=args.write_plan,
input_ids=args.input_ids,
positions=args.positions,
out_cache_loc=args.out_cache_loc,
enable_write_input_assert=args.enable_write_input_assert,
enable_verify_token_assert=args.enable_verify_token_assert,
expected_inputs=args.expected_inputs,
violation_log=args.violation_log,
)
self.assertTrue(
torch.equal(
captured[0],
torch.tensor([-1], dtype=torch.int64, device=self.device),
)
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,180 @@
from __future__ import annotations
import unittest
from typing import cast
import torch
from sglang.srt.kv_canary.runner.future_tensor import FutureTensors
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=20, stage="extra-a", runner_config="1-gpu-small")
class _FakeEvent:
def __init__(self) -> None:
self.synchronize_count = 0
def synchronize(self) -> None:
self.synchronize_count += 1
class TestFutureTensors(CustomTestCase):
def test_cuda_stage_then_wait_returns_host_copy(self) -> None:
"""Verify staged CUDA tensors are copied back on wait."""
device = torch.device("cuda")
alt_stream = torch.cuda.Stream(device=device)
default_stream = torch.cuda.current_stream(device)
self.assertNotEqual(alt_stream.stream_id, default_stream.stream_id)
src_first = torch.tensor([41], dtype=torch.int32, device=device)
future_first = FutureTensors.device_to_host(
xs_device=src_first, d2h_stream=alt_stream
)
result_first = future_first.wait()
self.assertEqual(int(result_first.item()), 41)
src_second = torch.tensor([97], dtype=torch.int32, device=device)
future_second = FutureTensors.device_to_host(
xs_device=src_second, d2h_stream=alt_stream
)
result_second = future_second.wait()
self.assertEqual(int(result_second.item()), 97)
def test_cuda_pinned_when_stream_is_provided(self) -> None:
"""Verify CUDA staging uses pinned host memory with a stream."""
device = torch.device("cuda")
alt_stream = torch.cuda.Stream(device=device)
src = torch.tensor([5], dtype=torch.int32, device=device)
future = FutureTensors.device_to_host(xs_device=src, d2h_stream=alt_stream)
staged_tensors = [
v for v in future._data.values() if isinstance(v, torch.Tensor)
]
self.assertTrue(staged_tensors)
self.assertTrue(all(t.is_pinned() for t in staged_tensors))
self.assertEqual(int(future.wait().item()), 5)
def test_cuda_each_call_allocates_fresh_host(self) -> None:
"""Verify each CUDA staging call owns a fresh host buffer."""
device = torch.device("cuda")
alt_stream = torch.cuda.Stream(device=device)
src_a = torch.tensor([13], dtype=torch.int32, device=device)
src_b = torch.tensor([29], dtype=torch.int32, device=device)
future_a = FutureTensors.device_to_host(xs_device=src_a, d2h_stream=alt_stream)
future_b = FutureTensors.device_to_host(xs_device=src_b, d2h_stream=alt_stream)
ptrs_a = {
v.data_ptr() for v in future_a._data.values() if isinstance(v, torch.Tensor)
}
ptrs_b = {
v.data_ptr() for v in future_b._data.values() if isinstance(v, torch.Tensor)
}
self.assertTrue(ptrs_a and ptrs_b)
self.assertFalse(ptrs_a & ptrs_b)
self.assertEqual(int(future_a.wait().item()), 13)
self.assertEqual(int(future_b.wait().item()), 29)
def test_dict_of_all_tensors_roundtrip(self) -> None:
"""Verify a dict of multiple tensors round-trips entry-by-entry."""
device = torch.device("cuda")
stream = torch.cuda.Stream(device=device)
src = {
"x": torch.tensor([11, 22], dtype=torch.int64, device=device),
"y": torch.tensor([99], dtype=torch.int32, device=device),
}
future = FutureTensors.device_to_host(xs_device=src, d2h_stream=stream)
out = future.wait()
self.assertIsInstance(out, dict)
self.assertEqual(out["x"].tolist(), [11, 22])
self.assertEqual(int(out["y"].item()), 99)
self.assertTrue(out["x"].is_pinned())
self.assertTrue(out["y"].is_pinned())
def test_dict_mixes_tensor_and_passthrough(self) -> None:
"""Verify non-tensor dict entries ride through verbatim alongside staging."""
device = torch.device("cuda")
stream = torch.cuda.Stream(device=device)
sentinel_obj = {"nested": [1, 2, 3]}
src = {
"step": 42,
"label": "decode",
"extra": sentinel_obj,
"counter": torch.tensor([7], dtype=torch.int32, device=device),
}
future = FutureTensors.device_to_host(xs_device=src, d2h_stream=stream)
out = future.wait()
self.assertEqual(out["step"], 42)
self.assertEqual(out["label"], "decode")
# Identity (not deep-copy) — callers can rely on shared mutable references.
self.assertIs(out["extra"], sentinel_obj)
self.assertEqual(int(out["counter"].item()), 7)
self.assertTrue(out["counter"].is_pinned())
def test_dict_passthrough_preserves_tensor_value(self) -> None:
"""Verify tensors share device memory but non-tensor types are not staged."""
device = torch.device("cuda")
stream = torch.cuda.Stream(device=device)
src_tensor = torch.tensor([3], dtype=torch.int32, device=device)
src = {"step": 100, "buf": src_tensor}
future = FutureTensors.device_to_host(xs_device=src, d2h_stream=stream)
out = future.wait()
# Tensor is staged to a fresh pinned-host buffer (different storage from src).
self.assertNotEqual(out["buf"].data_ptr(), src_tensor.data_ptr())
self.assertTrue(out["buf"].is_pinned())
# Non-tensor passes through with no copy.
self.assertEqual(out["step"], 100)
self.assertIsInstance(out["step"], int)
def test_dict_without_tensor_raises(self) -> None:
"""Verify a tensor-less dict raises (no device to anchor the d2h sync)."""
device = torch.device("cuda")
stream = torch.cuda.Stream(device=device)
with self.assertRaises(ValueError):
FutureTensors.device_to_host(
xs_device={"step": 0, "label": "decode"}, d2h_stream=stream
)
def test_wait_called_twice_raises(self) -> None:
"""Verify wait() after the first drain raises (state cleared)."""
device = torch.device("cuda")
stream = torch.cuda.Stream(device=device)
src = torch.tensor([3], dtype=torch.int32, device=device)
future = FutureTensors.device_to_host(xs_device=src, d2h_stream=stream)
self.assertEqual(int(future.wait().item()), 3)
with self.assertRaises(RuntimeError):
future.wait()
def test_wait_clears_fields_and_rejects_second_wait(self) -> None:
"""Verify wait() syncs the event exactly once and clears internal state."""
tensor = torch.tensor([1, 2, 3])
event = _FakeEvent()
future = FutureTensors(
_data={"x": tensor}, _event=cast(torch.cuda.Event, event)
)
result = future.wait()
self.assertIs(result["x"], tensor)
self.assertEqual(event.synchronize_count, 1)
self.assertIsNone(future._data)
self.assertIsNone(future._event)
with self.assertRaisesRegex(RuntimeError, "called more than once"):
future.wait()
# Failed wait must not re-trigger event.synchronize.
self.assertEqual(event.synchronize_count, 1)
def test_dict_anchor_picked_from_first_tensor(self) -> None:
"""Verify staging works when the first key is a non-tensor (anchor must scan)."""
device = torch.device("cuda")
stream = torch.cuda.Stream(device=device)
src = {
"step": 5,
"buf": torch.tensor([17], dtype=torch.int32, device=device),
}
out = FutureTensors.device_to_host(xs_device=src, d2h_stream=stream).wait()
self.assertEqual(out["step"], 5)
self.assertEqual(int(out["buf"].item()), 17)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,128 @@
from __future__ import annotations
import unittest
from types import SimpleNamespace
import torch
from sglang.srt.kv_canary.plan_input import PlanInput
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.kv_canary.fixtures import (
DEFAULT_DEVICE,
make_forward_batch,
)
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=30, stage="extra-a", runner_config="1-gpu-small")
def _make_static_plan_input(*, bs_capacity: int, device) -> PlanInput:
return PlanInput(
req_pool_indices=torch.zeros(bs_capacity, dtype=torch.int64, device=device),
prefix_lens=torch.zeros(bs_capacity, dtype=torch.int64, device=device),
extend_seq_lens=torch.zeros(bs_capacity, dtype=torch.int64, device=device),
req_to_verify_expected_tokens_valid_lens=torch.zeros(
bs_capacity, dtype=torch.int64, device=device
),
)
class TestSelfUnitPlanInput(CustomTestCase):
def setUp(self):
self.device = DEFAULT_DEVICE
def test_plan_input_fill_from_forward_batch_extend(self):
"""Verify extend batches populate per-forward plan inputs."""
fb = make_forward_batch(
self.device,
req_pool_indices=torch.tensor(
[1, 2], dtype=torch.int64, device=self.device
),
seq_lens=torch.tensor([10, 12], dtype=torch.int32, device=self.device),
extend_prefix_lens=torch.tensor(
[3, 5], dtype=torch.int32, device=self.device
),
extend_seq_lens=torch.tensor([7, 7], dtype=torch.int32, device=self.device),
is_extend=True,
)
plan = _make_static_plan_input(bs_capacity=4, device=self.device)
plan.fill_from_forward_batch(forward_batch=fb)
self.assertEqual(plan.req_pool_indices[:2].tolist(), [1, 2])
self.assertEqual(plan.req_pool_indices[2:].tolist(), [0, 0])
self.assertEqual(plan.prefix_lens[:2].tolist(), [3, 5])
self.assertEqual(plan.extend_seq_lens[:2].tolist(), [7, 7])
self.assertEqual(plan.prefix_lens.dtype, torch.int64)
self.assertEqual(plan.extend_seq_lens.dtype, torch.int64)
def test_plan_input_fill_from_forward_batch_target_verify(self):
"""Verify target-verify batches derive draft verification spans."""
# TARGET_VERIFY writes spec_info.draft_token_num positions per req starting at the
# current (un-bumped) seq_lens; extend_prefix_lens / extend_seq_lens are deliberately
# NOT supplied because init_new does not populate them for this mode.
spec_info = SimpleNamespace(draft_token_num=4)
fb = make_forward_batch(
self.device,
req_pool_indices=torch.tensor(
[6, 7], dtype=torch.int64, device=self.device
),
seq_lens=torch.tensor([10, 14], dtype=torch.int32, device=self.device),
is_target_verify=True,
spec_info=spec_info,
)
plan = _make_static_plan_input(bs_capacity=4, device=self.device)
plan.fill_from_forward_batch(forward_batch=fb)
self.assertEqual(plan.prefix_lens[:2].tolist(), [10, 14])
self.assertEqual(plan.extend_seq_lens[:2].tolist(), [4, 4])
def test_plan_input_fill_from_forward_batch_draft_extend_v2(self):
"""Verify draft-extend-v2 batches derive prefix lengths from sequence lengths."""
# DRAFT_EXTEND_V2 has seq_lens already bumped by the per-req draft refill length;
# extend_prefix_lens is intentionally absent (cuda-graph replay does not set it). The
# builder must derive prefix as seq_lens - extend_seq_lens.
fb = make_forward_batch(
self.device,
req_pool_indices=torch.tensor(
[4, 5], dtype=torch.int64, device=self.device
),
seq_lens=torch.tensor([14, 18], dtype=torch.int32, device=self.device),
extend_seq_lens=torch.tensor([4, 4], dtype=torch.int32, device=self.device),
is_draft_extend_v2=True,
)
plan = _make_static_plan_input(bs_capacity=4, device=self.device)
plan.fill_from_forward_batch(forward_batch=fb)
self.assertEqual(plan.prefix_lens[:2].tolist(), [10, 14])
self.assertEqual(plan.extend_seq_lens[:2].tolist(), [4, 4])
def test_plan_input_fill_from_forward_batch_decode(self):
"""Verify decode batches populate one-token verification spans."""
fb = make_forward_batch(
self.device,
req_pool_indices=torch.tensor(
[1, 2, 3], dtype=torch.int64, device=self.device
),
seq_lens=torch.tensor([4, 7, 1], dtype=torch.int32, device=self.device),
is_extend=False,
)
plan = _make_static_plan_input(bs_capacity=4, device=self.device)
plan.fill_from_forward_batch(forward_batch=fb)
self.assertEqual(plan.prefix_lens[:3].tolist(), [3, 6, 0])
self.assertEqual(plan.extend_seq_lens[:3].tolist(), [1, 1, 1])
def test_plan_input_padding_dummy_sentinel(self):
"""Verify padding sentinel rows remain valid plan input entries."""
fb = make_forward_batch(
self.device,
req_pool_indices=torch.tensor(
[0, 5, 0], dtype=torch.int64, device=self.device
),
seq_lens=torch.tensor([0, 3, 0], dtype=torch.int32, device=self.device),
is_extend=False,
)
plan = _make_static_plan_input(bs_capacity=4, device=self.device)
plan.fill_from_forward_batch(forward_batch=fb)
self.assertEqual(plan.req_pool_indices[:3].tolist(), [0, 5, 0])
self.assertEqual(plan.req_pool_indices.dtype, torch.int64)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,116 @@
from __future__ import annotations
import unittest
from sglang.jit_kernel.kv_canary.verify import CANARY_SLOT_BYTES
from sglang.srt.kv_canary.buffer_group import PoolKind
from sglang.srt.kv_canary.pool_patcher.api import attach_canary_buffers
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.kv_canary.fixtures import (
DEFAULT_DEVICE,
make_base_config,
make_mha_pool,
)
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=45, stage="extra-a", runner_config="1-gpu-small")
class PoolPatcherHelper:
def setUp(self):
self.device = DEFAULT_DEVICE
self.config = make_base_config()
class TestAttachCanaryBuffers(PoolPatcherHelper, CustomTestCase):
def test_canary_buffer_group_allocate_full_only(self):
"""Verify MHA pools allocate only full canary buffers."""
pool = make_mha_pool(self.device, num_slots=16, dim=8, layer_num=2)
groups_tuple = attach_canary_buffers(
pool=pool,
config=self.config,
device=self.device,
kv_token_id_vs_position_offset=0,
)
groups = {g.kind: g for g in groups_tuple}
self.assertEqual(set(groups.keys()), {PoolKind.FULL})
group = groups[PoolKind.FULL]
self.assertEqual(group.k_head.shape, (16, CANARY_SLOT_BYTES))
self.assertEqual(group.k_tail.shape, (16, CANARY_SLOT_BYTES))
self.assertIsNotNone(group.v_head)
self.assertIsNotNone(group.v_tail)
self.assertEqual(group.v_head.shape, (16, CANARY_SLOT_BYTES))
class TestPoolPatcherBufferInfos(PoolPatcherHelper, CustomTestCase):
def test_get_contiguous_buf_infos_inserts_canary_entries(self):
"""Verify contiguous buffer metadata includes canary entries after patching."""
for patched in (False, True):
with self.subTest(patched=patched):
pool = make_mha_pool(self.device, num_slots=16, dim=8, layer_num=2)
ptrs_before, _, _ = pool.get_contiguous_buf_infos()
n_before = len(ptrs_before)
if patched:
attach_canary_buffers(
pool=pool,
config=self.config,
device=self.device,
kv_token_id_vs_position_offset=0,
)
ptrs_after, _, _ = pool.get_contiguous_buf_infos()
self.assertEqual(len(ptrs_after), n_before + 4)
else:
ptrs_after, _, _ = pool.get_contiguous_buf_infos()
self.assertEqual(ptrs_after, ptrs_before)
def test_pd_layout_canary_inserted_correctly(self):
"""Verify PD (prefill-decode disaggregation) canary buffers are inserted in layout order."""
pool = make_mha_pool(self.device, num_slots=16, dim=8, layer_num=2)
k_ptrs_orig = [b.data_ptr() for b in pool.k_buffer]
v_ptrs_orig = [b.data_ptr() for b in pool.v_buffer]
groups_tuple = attach_canary_buffers(
pool=pool,
config=self.config,
device=self.device,
kv_token_id_vs_position_offset=0,
)
group = {g.kind: g for g in groups_tuple}[PoolKind.FULL]
ptrs_after, _, _ = pool.get_contiguous_buf_infos()
canary_k_ptrs = [group.k_head.data_ptr(), group.k_tail.data_ptr()]
canary_v_ptrs = [group.v_head.data_ptr(), group.v_tail.data_ptr()]
self.assertEqual(ptrs_after[0], canary_k_ptrs[0])
self.assertEqual(ptrs_after[1 : 1 + len(k_ptrs_orig)], k_ptrs_orig)
k_tail_idx = 1 + len(k_ptrs_orig)
self.assertEqual(ptrs_after[k_tail_idx], canary_k_ptrs[1])
self.assertEqual(ptrs_after[k_tail_idx + 1], canary_v_ptrs[0])
v_start = k_tail_idx + 2
self.assertEqual(ptrs_after[v_start : v_start + len(v_ptrs_orig)], v_ptrs_orig)
self.assertEqual(ptrs_after[-1], canary_v_ptrs[1])
class TestCanaryBufferBudget(PoolPatcherHelper, CustomTestCase):
def test_canary_buf_per_token_bytes_within_budget(self):
"""Verify canary per-token storage stays below the real KV budget."""
pool = make_mha_pool(self.device, num_slots=16, dim=64, layer_num=2)
groups_tuple = attach_canary_buffers(
pool=pool,
config=self.config,
device=self.device,
kv_token_id_vs_position_offset=0,
)
group = {g.kind: g for g in groups_tuple}[PoolKind.FULL]
slot_stride_bytes = group.k_head.stride(0) * group.k_head.element_size()
self.assertLessEqual(slot_stride_bytes, CANARY_SLOT_BYTES)
real_kv_per_token_bytes = (
pool.k_buffer[0].stride(0) * pool.k_buffer[0].element_size()
)
self.assertLess(slot_stride_bytes, real_kv_per_token_bytes)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,52 @@
from __future__ import annotations
import unittest
from sglang.srt.kv_canary.pool_patcher.utils import wrap_method
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=10, stage="extra-a", runner_config="1-gpu-small")
class _FakeObj:
def greet(self, name: str) -> str:
return f"hello {name}"
class TestPoolPatcherUtils(CustomTestCase):
def test_wrap_method_delegates_to_wrapper(self) -> None:
"""Verify wrapped methods delegate through the wrapper."""
obj = _FakeObj()
def _with_validation(original, name: str) -> str:
return original(name) + "!"
wrap_method(obj, "greet", wrapper=_with_validation)
self.assertEqual(obj.greet("world"), "hello world!")
def test_wrap_method_missing_method_raises_attribute_error(self) -> None:
"""Verify wrapping a missing method raises AttributeError."""
obj = _FakeObj()
with self.assertRaisesRegex(AttributeError, "missing required method"):
wrap_method(
obj, "nonexistent", wrapper=lambda orig, *a, **kw: orig(*a, **kw)
)
def test_wrap_method_double_wrap_raises_runtime_error(self) -> None:
"""Verify wrapping the same method twice raises RuntimeError."""
obj = _FakeObj()
wrap_method(obj, "greet", wrapper=lambda orig, *a, **kw: orig(*a, **kw))
with self.assertRaisesRegex(RuntimeError, "already wrapped by kv-canary"):
wrap_method(obj, "greet", wrapper=lambda orig, *a, **kw: orig(*a, **kw))
def test_wrap_method_preserves_functools_wraps_metadata(self) -> None:
"""Verify wrapping preserves method metadata."""
obj = _FakeObj()
original_name = obj.greet.__name__
wrap_method(obj, "greet", wrapper=lambda orig, *a, **kw: orig(*a, **kw))
self.assertEqual(obj.greet.__name__, original_name)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,333 @@
from __future__ import annotations
import unittest
from types import SimpleNamespace
from unittest.mock import patch
import torch
from sglang.jit_kernel.kv_canary import consts
from sglang.jit_kernel.kv_canary.consts import FailReason
from sglang.jit_kernel.kv_canary.verify import CanaryLaunchTag
from sglang.srt.kv_canary.config import CanaryMode
from sglang.srt.kv_canary.runner import violation_reporter as violation_reporter_module
from sglang.srt.kv_canary.runner.violation_reporter import (
ViolationReporter,
_format_violation,
)
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=5, stage="extra-a", runner_config="1-gpu-small")
def _make_row(
*,
kernel_kind: CanaryLaunchTag = CanaryLaunchTag.HEAD_K_FULL,
slot_idx: int = 17,
position: int = 42,
stored_token: int = 111,
expected_token: int = 0,
stored_chain_hash: int = 0,
expected_aux: int = 0,
fail_reason_bits: int = 0,
) -> list[int]:
row = [0] * consts.VIOLATION_FIELDS
row[consts.VIOLATION_FIELD_KERNEL_KIND] = int(kernel_kind)
row[consts.VIOLATION_FIELD_SLOT_IDX] = slot_idx
row[consts.VIOLATION_FIELD_POSITION] = position
row[consts.VIOLATION_FIELD_STORED_TOKEN] = stored_token
row[consts.VIOLATION_FIELD_EXPECTED_TOKEN] = expected_token
row[consts.VIOLATION_FIELD_STORED_CHAIN_HASH] = stored_chain_hash
row[consts.VIOLATION_FIELD_EXPECTED_AUX] = expected_aux
row[consts.VIOLATION_FIELD_FAIL_REASON_BITS] = fail_reason_bits
return row
class TestViolationReporter(CustomTestCase):
def test_format_violation_verify_path_labels_each_bit(self) -> None:
"""Verify verify-path violations render each fail-reason bit."""
row = _make_row(
stored_chain_hash=0x1111111111111111,
expected_aux=0x2222222222222222,
fail_reason_bits=int(
FailReason.VERIFY_CHAIN_HASH_MISMATCH
| FailReason.VERIFY_POSITION_MISMATCH
),
)
out = _format_violation(
row=row, total=1, ring_overflow=False, step_when_pumped=7
)
self.assertEqual(
out,
"kv_canary violation: launch_tag=HEAD_K_FULL fail_reason=verify_chain_hash+verify_position "
"slot_idx=17 position=42 stored_token=111 expected_token=0 stored_chain_hash=0x1111111111111111 "
"expected_aux=0x2222222222222222\n"
"KV cache canary violation detected (kernel_kind=HEAD_K_FULL, slot_idx=17, position=42)\n"
"canary_kind: per_forward_head_k_full\n"
" fail_reasons: verify_chain_hash verify_position\n"
" stored: token_id=111 position=42 prev_hash=0x1111111111111111\n"
" expected: prev_hash=0x2222222222222222\n"
" total_violations=1 ring_overflow=False step_when_pumped=7",
)
def test_format_violation_write_token_mismatch_labels_and_position(self) -> None:
"""Verify write-token violations render token and position details."""
row = _make_row(
position=42,
stored_token=999,
expected_token=888,
stored_chain_hash=0xDEADBEEFCAFEBABE,
expected_aux=43,
fail_reason_bits=int(FailReason.WRITE_TOKEN_MISMATCH),
)
out = _format_violation(
row=row, total=1, ring_overflow=False, step_when_pumped=0
)
self.assertEqual(
out,
"kv_canary violation: launch_tag=HEAD_K_FULL fail_reason=write_token slot_idx=17 position=42 "
"stored_token=999 expected_token=888 stored_chain_hash=0xdeadbeefcafebabe "
"expected_aux=0x000000000000002b\n"
"KV cache canary violation detected (kernel_kind=HEAD_K_FULL, slot_idx=17, position=42)\n"
"canary_kind: per_forward_head_k_full\n"
" fail_reasons: write_token\n"
" actual: token_id=999 position=42 prev_hash=0xdeadbeefcafebabe\n"
" expected: token_id=888 position=43\n"
" total_violations=1 ring_overflow=False step_when_pumped=0",
)
def test_format_violation_write_position_mismatch_uses_expected_aux_as_position(
self,
) -> None:
"""Verify write-position violations render expected_aux as a position."""
row = _make_row(
position=42,
stored_token=111,
expected_token=111,
expected_aux=99,
fail_reason_bits=int(FailReason.WRITE_POSITION_MISMATCH),
)
out = _format_violation(
row=row, total=1, ring_overflow=False, step_when_pumped=0
)
self.assertEqual(
out,
"kv_canary violation: launch_tag=HEAD_K_FULL fail_reason=write_position slot_idx=17 position=42 "
"stored_token=111 expected_token=111 stored_chain_hash=0x0000000000000000 "
"expected_aux=0x0000000000000063\n"
"KV cache canary violation detected (kernel_kind=HEAD_K_FULL, slot_idx=17, position=42)\n"
"canary_kind: per_forward_head_k_full\n"
" fail_reasons: write_position\n"
" actual: token_id=111 position=42 prev_hash=0x0000000000000000\n"
" expected: token_id=111 position=99\n"
" total_violations=1 ring_overflow=False step_when_pumped=0",
)
def test_format_violation_combined_write_bits_render_both_labels(self) -> None:
"""Verify combined write violation bits render both labels."""
row = _make_row(
fail_reason_bits=int(
FailReason.WRITE_TOKEN_MISMATCH | FailReason.WRITE_POSITION_MISMATCH
),
)
out = _format_violation(
row=row, total=1, ring_overflow=False, step_when_pumped=0
)
self.assertEqual(
out,
"kv_canary violation: launch_tag=HEAD_K_FULL fail_reason=write_token+write_position "
"slot_idx=17 position=42 stored_token=111 expected_token=0 stored_chain_hash=0x0000000000000000 "
"expected_aux=0x0000000000000000\n"
"KV cache canary violation detected (kernel_kind=HEAD_K_FULL, slot_idx=17, position=42)\n"
"canary_kind: per_forward_head_k_full\n"
" fail_reasons: write_token write_position\n"
" actual: token_id=111 position=42 prev_hash=0x0000000000000000\n"
" expected: token_id=0 position=0\n"
" total_violations=1 ring_overflow=False step_when_pumped=0",
)
def test_format_violation_unknown_kernel_kind_renders_unknown_label(self) -> None:
"""Verify unknown kernel kinds render an unknown label."""
row = _make_row(fail_reason_bits=int(FailReason.VERIFY_CHAIN_HASH_MISMATCH))
row[consts.VIOLATION_FIELD_KERNEL_KIND] = 9999
out = _format_violation(
row=row, total=1, ring_overflow=False, step_when_pumped=0
)
self.assertEqual(
out,
"kv_canary violation: launch_tag=unknown(9999) fail_reason=verify_chain_hash slot_idx=17 position=42 "
"stored_token=111 expected_token=0 stored_chain_hash=0x0000000000000000 "
"expected_aux=0x0000000000000000\n"
"KV cache canary violation detected (kernel_kind=unknown(9999), slot_idx=17, position=42)\n"
"canary_kind: unknown(9999)\n"
" fail_reasons: verify_chain_hash\n"
" stored: token_id=111 position=42 prev_hash=0x0000000000000000\n"
" expected: prev_hash=0x0000000000000000\n"
" total_violations=1 ring_overflow=False step_when_pumped=0",
)
def _make_reporter(
*,
rows: list[list[int]],
write_index: int,
ring_capacity: int,
mode: CanaryMode = CanaryMode.LOG,
) -> ViolationReporter:
ring = torch.zeros(ring_capacity, consts.VIOLATION_FIELDS, dtype=torch.int64)
for i, row in enumerate(rows):
ring[i] = torch.tensor(row, dtype=torch.int64)
violation_log = SimpleNamespace(
violation_ring=ring,
violation_write_index=torch.tensor([write_index], dtype=torch.int32),
)
device_state = SimpleNamespace(violation_log=violation_log)
config = SimpleNamespace(mode=mode)
return ViolationReporter(config=config, device_state=device_state)
class TestLogOrRaiseViolation(CustomTestCase):
def test_log_or_raise_violation_empty_ring_is_noop(self) -> None:
"""Empty ring (write_index=0) emits no warning and leaves reporter non-raised."""
reporter = _make_reporter(
rows=[], write_index=0, ring_capacity=4, mode=CanaryMode.LOG
)
with patch.object(violation_reporter_module.logger, "warning") as mock_warning:
reporter.log_or_raise_violation(outer_step_counter=0)
mock_warning.assert_not_called()
self.assertFalse(reporter.is_raised)
def test_log_mode_emits_one_warning_per_violation(self) -> None:
"""Log mode with 2 valid rows emits 2 warnings, each a full _format_violation snapshot for that row."""
rows = [
_make_row(
slot_idx=11,
position=101,
fail_reason_bits=int(FailReason.VERIFY_CHAIN_HASH_MISMATCH),
),
_make_row(
slot_idx=22,
position=202,
fail_reason_bits=int(FailReason.VERIFY_POSITION_MISMATCH),
),
]
reporter = _make_reporter(
rows=rows, write_index=2, ring_capacity=4, mode=CanaryMode.LOG
)
with patch.object(violation_reporter_module.logger, "warning") as mock_warning:
reporter.log_or_raise_violation(outer_step_counter=7)
self.assertEqual(mock_warning.call_count, 2)
messages: list[str] = [call.args[0] for call in mock_warning.call_args_list]
self.assertEqual(
messages[0],
"kv_canary violation: launch_tag=HEAD_K_FULL fail_reason=verify_chain_hash slot_idx=11 position=101 "
"stored_token=111 expected_token=0 stored_chain_hash=0x0000000000000000 "
"expected_aux=0x0000000000000000\n"
"KV cache canary violation detected (kernel_kind=HEAD_K_FULL, slot_idx=11, position=101)\n"
"canary_kind: per_forward_head_k_full\n"
" fail_reasons: verify_chain_hash\n"
" stored: token_id=111 position=101 prev_hash=0x0000000000000000\n"
" expected: prev_hash=0x0000000000000000\n"
" total_violations=2 ring_overflow=False step_when_pumped=7",
)
self.assertEqual(
messages[1],
"kv_canary violation: launch_tag=HEAD_K_FULL fail_reason=verify_position slot_idx=22 position=202 "
"stored_token=111 expected_token=0 stored_chain_hash=0x0000000000000000 "
"expected_aux=0x0000000000000000\n"
"KV cache canary violation detected (kernel_kind=HEAD_K_FULL, slot_idx=22, position=202)\n"
"canary_kind: per_forward_head_k_full\n"
" fail_reasons: verify_position\n"
" stored: token_id=111 position=202 prev_hash=0x0000000000000000\n"
" expected: prev_hash=0x0000000000000000\n"
" total_violations=2 ring_overflow=False step_when_pumped=7",
)
self.assertFalse(reporter.is_raised)
def test_raise_mode_raises_one_error_containing_all_violations(self) -> None:
"""Raise mode raises a single RuntimeError whose text is the 2 formatted rows joined with single newlines."""
rows = [
_make_row(
slot_idx=11,
position=101,
fail_reason_bits=int(FailReason.VERIFY_CHAIN_HASH_MISMATCH),
),
_make_row(
slot_idx=22,
position=202,
fail_reason_bits=int(FailReason.VERIFY_POSITION_MISMATCH),
),
]
reporter = _make_reporter(
rows=rows, write_index=2, ring_capacity=4, mode=CanaryMode.RAISE
)
with self.assertRaises(RuntimeError) as ctx:
reporter.log_or_raise_violation(outer_step_counter=5)
self.assertEqual(
str(ctx.exception),
"kv_canary violation: launch_tag=HEAD_K_FULL fail_reason=verify_chain_hash slot_idx=11 position=101 "
"stored_token=111 expected_token=0 stored_chain_hash=0x0000000000000000 "
"expected_aux=0x0000000000000000\n"
"KV cache canary violation detected (kernel_kind=HEAD_K_FULL, slot_idx=11, position=101)\n"
"canary_kind: per_forward_head_k_full\n"
" fail_reasons: verify_chain_hash\n"
" stored: token_id=111 position=101 prev_hash=0x0000000000000000\n"
" expected: prev_hash=0x0000000000000000\n"
" total_violations=2 ring_overflow=False step_when_pumped=5\n"
"kv_canary violation: launch_tag=HEAD_K_FULL fail_reason=verify_position slot_idx=22 position=202 "
"stored_token=111 expected_token=0 stored_chain_hash=0x0000000000000000 "
"expected_aux=0x0000000000000000\n"
"KV cache canary violation detected (kernel_kind=HEAD_K_FULL, slot_idx=22, position=202)\n"
"canary_kind: per_forward_head_k_full\n"
" fail_reasons: verify_position\n"
" stored: token_id=111 position=202 prev_hash=0x0000000000000000\n"
" expected: prev_hash=0x0000000000000000\n"
" total_violations=2 ring_overflow=False step_when_pumped=5",
)
self.assertTrue(reporter.is_raised)
def test_log_mode_ring_overflow_marks_overflow_in_each_row(self) -> None:
"""Log mode with write_index=5 but ring_capacity=2 emits 2 warnings, each a full snapshot with overflow footer."""
rows = [
_make_row(slot_idx=11, position=101),
_make_row(slot_idx=22, position=202),
]
reporter = _make_reporter(
rows=rows, write_index=5, ring_capacity=2, mode=CanaryMode.LOG
)
with patch.object(violation_reporter_module.logger, "warning") as mock_warning:
reporter.log_or_raise_violation(outer_step_counter=0)
self.assertEqual(mock_warning.call_count, 2)
messages: list[str] = [call.args[0] for call in mock_warning.call_args_list]
self.assertEqual(
messages[0],
"kv_canary violation: launch_tag=HEAD_K_FULL fail_reason=none slot_idx=11 position=101 "
"stored_token=111 expected_token=0 stored_chain_hash=0x0000000000000000 "
"expected_aux=0x0000000000000000\n"
"KV cache canary violation detected (kernel_kind=HEAD_K_FULL, slot_idx=11, position=101)\n"
"canary_kind: per_forward_head_k_full\n"
" fail_reasons: none\n"
" stored: token_id=111 position=101 prev_hash=0x0000000000000000\n"
" expected: prev_hash=0x0000000000000000\n"
" total_violations=5 ring_overflow=True step_when_pumped=0",
)
self.assertEqual(
messages[1],
"kv_canary violation: launch_tag=HEAD_K_FULL fail_reason=none slot_idx=22 position=202 "
"stored_token=111 expected_token=0 stored_chain_hash=0x0000000000000000 "
"expected_aux=0x0000000000000000\n"
"KV cache canary violation detected (kernel_kind=HEAD_K_FULL, slot_idx=22, position=202)\n"
"canary_kind: per_forward_head_k_full\n"
" fail_reasons: none\n"
" stored: token_id=111 position=202 prev_hash=0x0000000000000000\n"
" expected: prev_hash=0x0000000000000000\n"
" total_violations=5 ring_overflow=True step_when_pumped=0",
)
if __name__ == "__main__":
unittest.main()