{"record":{"id":"b4d05a3f6d54851d","repo":"sgl-project/sglang","slug":"start-len-must-be-non-negative","errorCode":null,"errorMessage":"{start_len=} must be non-negative","messagePattern":"(.+?) must be non-negative","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/state_capturer/base.py","lineNumber":155,"sourceCode":"\n        Default assumes per-rank-local capture: each rank writes [:local_num_tokens)\n        to its own device_cache. Subclasses with global-tensor capture semantics\n        (e.g. shared cuda graph buffer indexed by dp_rank) should override and\n        consume can_run_graph / cuda_graph_batch.\n        \"\"\"\n        del can_run_graph, cuda_graph_batch  # reserved for subclass override\n        num_tokens = forward_batch.out_cache_loc.shape[0]\n        return self.device_cache.buffer[:num_tokens, :, : self.topk_size]\n\n    def get_topk(\n        self,\n        req_pool_idx: int,\n        seqlen: int,\n        req_to_token_pool: ReqToTokenPool,\n        start_len: int = 0,\n    ) -> torch.Tensor:\n        if start_len < 0:\n            raise ValueError(f\"{start_len=} must be non-negative\")\n        start_len = min(start_len, seqlen - 1)\n        cache_pool_idx = (\n            req_to_token_pool.req_to_token[req_pool_idx][start_len : seqlen - 1]\n            .cpu()\n            .clone()\n        )\n        return self.host_cache.buffer[cache_pool_idx]\n\n    def on_forward_end(\n        self,\n        forward_batch: ForwardBatch,\n        can_run_graph: bool,\n        cuda_graph_batch: Optional[int],\n        no_copy_to_cpu: bool = False,\n    ) -> Optional[TopkCaptureOutput]:\n        \"\"\"If no_copy_to_cpu is True, return a TopkCaptureOutput holding GPU tensors so\n        the overlap thread can do non-blocking D2H + finalize itself. Otherwise sync\n        D2H inline and return None (legacy non-overlap path).","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/state_capturer/base.py#L137-L173","documentation":"StateCapturer.get_topk slices req_to_token from start_len to seqlen-1 to gather routed-expert/indexer top-k state; a negative start_len would slice invalid memory, so it raises ValueError immediately.","triggerScenarios":"Calling get_topk(..., start_len=-1) or with a computed start_len that went negative, e.g. seqlen - context_window underflow for a short sequence, from _maybe_collect_routed_experts / _maybe_collect_indexer_topk.","commonSituations":"Sliding-window or context-window start computation underflowing for sequences shorter than the window; off-by-one in capture-range arithmetic.","solutions":["Clamp: start_len = max(0, min(start_len, seqlen - 1)) before calling","Fix the caller's window arithmetic (use max(0, seqlen - window))","Skip collection for sequences where the computed start is negative"],"exampleFix":"# before\nstate.get_topk(req_pool_idx, seqlen, pool, start_len=seqlen - window)\n# after\nstate.get_topk(req_pool_idx, seqlen, pool, start_len=max(0, seqlen - window))","handlingStrategy":"validation","validationCode":"start_len = max(0, min(start_len, seqlen - 1))\ntopk = capturer.get_topk(req_pool_idx, seqlen, pool, start_len=start_len)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Clamp window-derived start lengths to >= 0","Skip state capture for sequences shorter than the capture window"],"tags":["state-capture","validation","off-by-one"],"backgroundTag":"negative-index-validation","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}