{"record":{"id":"5c5f5684bf25da07","repo":"sgl-project/sglang","slug":"kv-canary-name-must-be-contiguous","errorCode":null,"errorMessage":"kv-canary: {name} must be contiguous","messagePattern":"kv-canary: (.+?) must be contiguous","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/kernels/ops/kv_canary/verify.py","lineNumber":38,"sourceCode":"    \"\"\"Unique tag per (head | tail | sweep) × (K | V) × (FULL | SWA) launch.\"\"\"\n\n    HEAD_K_FULL = 0\n    HEAD_V_FULL = 1\n    TAIL_K_FULL = 2\n    TAIL_V_FULL = 3\n    SWEEP_K_FULL = 4\n    SWEEP_V_FULL = 5\n    HEAD_K_SWA = 6\n    HEAD_V_SWA = 7\n    TAIL_K_SWA = 8\n    TAIL_V_SWA = 9\n    SWEEP_K_SWA = 10\n    SWEEP_V_SWA = 11\n\n\ndef _assert_contiguous(tensor: torch.Tensor, name: str) -> None:\n    if not tensor.is_contiguous():\n        raise ValueError(f\"kv-canary: {name} must be contiguous\")\n\n\n@dataclass(frozen=True, slots=True, kw_only=True)\nclass RealKvSource:\n    \"\"\"One piece of real KV the canary folds into its fingerprint.\n\n    Slot access invariant (must hold for every source, regardless of underlying layout) — for a given slot_idx,\n    the canary reads exactly these bytes:\n\n        tensor[\n            slot_idx // page_size,\n            (slot_idx % page_size) * num_bytes_per_token\n            : ((slot_idx % page_size) + 1) * num_bytes_per_token\n        ]\n\n    Note that ``tensor`` may have \"holes\" in dim 1 — ``tensor.shape[1]`` can exceed ``page_size *\n    num_bytes_per_token``. Trailing bytes of each row are ignored by the canary; this is exactly how the\n    abstraction accommodates pools whose per-row layout interleaves canary-relevant bytes with other metadata","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/kernels/ops/kv_canary/verify.py#L20-L56","documentation":"Several kv-canary launchers call _assert_contiguous on each tensor argument because the Triton kernels compute flat pointer offsets assuming C-contiguous memory. A non-contiguous tensor (e.g. a transposed or strided view) raises ValueError with the tensor's name.","triggerScenarios":"Passing a transposed view (t.t()), a sliced sub-block (t[:, ::2]), or a tensor from .expand() to launch_canary_verify_kernel, launch_canary_write_kernel, or the offsets-kernel input validator.","commonSituations":"Slicing K/V caches with a stride (e.g. taking every other head); reusing views created for other kernels that tolerate strides.","solutions":["Materialize a contiguous copy: t = t.contiguous() before the call","Fix the producer to allocate the layout the kernel expects rather than re-striding later","Check t.is_contiguous() in debug builds of the caller to catch stray views early"],"exampleFix":"# before\nlaunch_verify(..., k_cache=kv[:, :, ::2, :])\n# after\nk = kv[:, :, ::2, :].contiguous()\nlaunch_verify(..., k_cache=k)","handlingStrategy":"validation","validationCode":"assert t.is_contiguous() for t in inputs  # e.g.\nfor name, t in [('k', k), ('v', v)]:\n    assert t.is_contiguous(), name","typeGuard":"def is_contiguous(t: torch.Tensor) -> bool:\n    return t.is_contiguous()","tryCatchPattern":null,"preventionTips":["Call .contiguous() on any sliced/transposed KV view before kv-canary kernels"],"tags":["kv-cache","contiguity","triton","strides"],"backgroundTag":"non-contiguous-tensor","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}