{"record":{"id":"f901ef75e037ae8c","repo":"sgl-project/sglang","slug":"num-kv-tokens-must-fit-in-the-provided-source-page","errorCode":null,"errorMessage":"num_kv_tokens must fit in the provided source pages, got tokens={num_kv_tokens}, capacity={source_capacity}","messagePattern":"num_kv_tokens must fit in the provided source pages, got tokens=(.+?), capacity=(.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/disaggregation/common/utils.py","lineNumber":168,"sourceCode":"    dcp_rank: int,\n    src_page_offset: int = 0,\n    decode_prefix_len: int = 0,\n    num_kv_tokens: Optional[int] = None,\n) -> DCPTokenTransferPlan:\n    virtual_page_size = physical_page_size * dcp_size\n    if decode_prefix_len % virtual_page_size != 0:\n        raise ValueError(\n            \"PD DCP transfer requires decode_prefix_len to align to the virtual \"\n            f\"DCP page size ({virtual_page_size}), got {decode_prefix_len}\"\n        )\n\n    src_pages = np.asarray(src_page_indices, dtype=np.int64)\n    dst_pages = np.asarray(dst_page_indices, dtype=np.int64)\n    source_capacity = src_pages.size * physical_page_size\n    if num_kv_tokens is None:\n        num_kv_tokens = source_capacity\n    if not 0 <= num_kv_tokens <= source_capacity:\n        raise ValueError(\n            \"num_kv_tokens must fit in the provided source pages, \"\n            f\"got tokens={num_kv_tokens}, capacity={source_capacity}\"\n        )\n    if src_pages.size == 0:\n        empty = np.empty((0,), dtype=np.int64)\n        return DCPTokenTransferPlan(empty, empty.copy())\n\n    chunk_start = decode_prefix_len + src_page_offset * physical_page_size\n    first_owned_offset = (dcp_rank - chunk_start) % dcp_size\n    owned_offsets = np.arange(\n        first_owned_offset, num_kv_tokens, dcp_size, dtype=np.int64\n    )\n    src_token_indices = (\n        src_pages[owned_offsets // physical_page_size] * physical_page_size\n        + owned_offsets % physical_page_size\n    )\n\n    relative_positions = src_page_offset * physical_page_size + owned_offsets","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/disaggregation/common/utils.py#L150-L186","documentation":"build_dcp_token_transfer_plan computes source_capacity = len(src_pages) * physical_page_size and validates 0 <= num_kv_tokens <= source_capacity (num_kv_tokens defaults to full capacity when None). A token count exceeding the source pages means the plan would read past the allocated prefill KV pages.","triggerScenarios":"Calling send_kvcache_dcp with num_kv_tokens larger than the provided src_page_indices can hold — e.g. passing the full sequence length while src pages cover only the chunked-prefill portion, or negative token counts.","commonSituations":"Chunked prefill where num_kv_tokens was computed as total input length but src pages only cover the current chunk; off-by-one in prefix/seq_len arithmetic; passing per-page count instead of token count.","solutions":["Recompute num_kv_tokens from the actual src page list: num_kv_tokens = min(desired, len(src_pages) * physical_page_size)","Check that src_page_indices covers the full range you intend to transfer (include pages from all prefill chunks)","If you want 'everything', pass num_kv_tokens=None to use full capacity"],"exampleFix":"# before\nplan = build_dcp_token_transfer_plan(..., src_page_indices=pages,\n    num_kv_tokens=total_seq_len)  # pages only cover a chunk\n# after\nplan = build_dcp_token_transfer_plan(..., src_page_indices=all_pages,\n    num_kv_tokens=min(total_seq_len, all_pages.size * page_size))","handlingStrategy":"validation","validationCode":"capacity = src_page_indices.size * physical_page_size\nif num_kv_tokens is not None:\n    assert 0 <= num_kv_tokens <= capacity, (num_kv_tokens, capacity)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive num_kv_tokens from the actual page list, or pass None for full capacity","Unit-test chunked-prefill boundary arithmetic"],"tags":["disaggregation","dcp","capacity-check","kv-transfer"],"backgroundTag":"buffer-capacity-exceeded","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}