{"record":{"id":"e03ca327425ad005","repo":"sgl-project/sglang","slug":"combine-called-before-dispatch","errorCode":null,"errorMessage":"combine() called before dispatch()","messagePattern":"combine\\(\\) called before dispatch\\(\\)","errorType":"error_code","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/layers/moe/token_dispatcher/ascend_tp.py","lineNumber":130,"sourceCode":"            topk_ids,\n            self.num_experts,\n            top_k,\n        )\n\n        self._dispatch_output = AscendTPDispatchOutput(\n            hidden_states=permuted_hidden_states,\n            hidden_states_scale=hidden_states_scale,\n            topk_weights=topk_weights,\n            topk_ids=topk_ids,\n            expanded_row_idx=expanded_row_idx,\n            expert_tokens=expert_tokens,\n            group_list_type=self.group_list_type,\n        )\n        return self._dispatch_output\n\n    def combine(self, combine_input: AscendTPCombineInput) -> torch.Tensor:\n        if self._dispatch_output is None:\n            raise RuntimeError(\"combine() called before dispatch()\")\n\n        dispatch_out = self._dispatch_output\n\n        # The finalizer (possibly wrapped with TP all‑gather) does all the work.\n        final_hidden_states = self.finalize._finalize_routing(\n            combine_input.hidden_states,\n            topk_weights=dispatch_out.topk_weights,\n            expanded_row_idx=dispatch_out.expanded_row_idx,\n            topk_ids=dispatch_out.topk_ids,\n        )\n\n        self._dispatch_output = None\n        return final_hidden_states\n","sourceCodeStart":112,"sourceCodeEnd":144,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/layers/moe/token_dispatcher/ascend_tp.py#L112-L144","documentation":"The Ascend TP token dispatcher caches the routing metadata from dispatch() and requires it in combine(); calling combine() when _dispatch_output is None means the state machine was violated. This is a programming/lifecycle error in the caller (or a scheduler bug), not a config problem.","triggerScenarios":"Constructing AscendTPDispatcher and invoking combine(AscendTPCombineInput) without a prior successful dispatch(hidden_states, topk_output); also if dispatch raised midway and the error was swallowed, leaving state unset.","commonSituations":"Custom inference loops or scripted runtimes that skip/reorder dispatch; recovery code continuing after a swallowed dispatch exception; bugs in new dispatcher integrations.","solutions":["Ensure the call order dispatch() -> (expert compute) -> combine() on the same dispatcher instance","Wrap dispatch in try/except and abort the step (do not proceed to combine) when dispatch fails","If writing a custom integration, mirror the pattern in sglang's token_dispatcher callers"],"exampleFix":"# before\ndisp = AscendTPDispatcher(...)\nout = disp.combine(combine_input)  # RuntimeError\n\n# after\ndispatch_out = disp.dispatch(hidden_states, topk_output)\n...  # expert compute\nout = disp.combine(combine_input)","handlingStrategy":"validation","validationCode":"assert disp._dispatch_output is not None, \"dispatch() must succeed before combine()\"","typeGuard":null,"tryCatchPattern":"try:\n    dispatch_out = disp.dispatch(hidden_states, topk_output)\nexcept Exception:\n    abort_step()  # never fall through to combine()\nelse:\n    out = disp.combine(combine_input)","preventionTips":["Treat dispatcher as a strict dispatch->combine state machine","Abort the batch on dispatch failure rather than continuing the pipeline"],"tags":["ascend","npu","dispatcher","lifecycle","state-machine"],"backgroundTag":"api-call-order-violation","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}