{"record":{"id":"6cb1723457d10b4a","repo":"vllm-project/vllm","slug":"consumer-tp-size-consumer-tp-size-must-be-a-mult","errorCode":null,"errorMessage":"consumer tp_size {consumer_tp_size} must be a multiple of producer tp_size {producer_tp_size} for heterogeneous-TP P/D","messagePattern":"consumer tp_size (.+?) must be a multiple of producer tp_size (.+?) for heterogeneous-TP P/D","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_connector.py","lineNumber":157,"sourceCode":"        return\n    if local_tp_size <= 0 or remote_tp_size <= 0 or total_num_kv_heads <= 0:\n        raise ValueError(\"TP sizes and total_num_kv_heads must be positive\")\n    if min(local_tp_size, remote_tp_size) >= total_num_kv_heads:\n        return\n    raise NotImplementedError(\n        \"MoRIIO heterogeneous TP requires replicated KV heads on both \"\n        f\"prefill and decode. Got total_num_kv_heads={total_num_kv_heads}, \"\n        f\"local_tp_size={local_tp_size}, remote_tp_size={remote_tp_size}.\"\n    )\n\n\ndef get_moriio_expected_ack_count(producer_tp_size: int, consumer_tp_size: int) -> int:\n    if producer_tp_size <= 0 or consumer_tp_size <= 0:\n        raise ValueError(\"TP sizes must be positive\")\n    if consumer_tp_size <= producer_tp_size:\n        return 1\n    if consumer_tp_size % producer_tp_size != 0:\n        raise ValueError(\n            f\"consumer tp_size {consumer_tp_size} must be a multiple of \"\n            f\"producer tp_size {producer_tp_size} for heterogeneous-TP P/D\"\n        )\n    return consumer_tp_size // producer_tp_size\n\n\ndef resolve_moriio_transfer_ack(\n    ack: MoRIIOTransferAck | TransferId,\n    producer_tp_size: int,\n    live_transfer_ids: Collection[TransferId],\n    notification_counts: dict[TransferId, int],\n    completed_transfer_ids: set[TransferId],\n) -> TransferId | None:\n    if isinstance(ack, str):\n        ack = MoRIIOTransferAck(ack)\n    transfer_id = ack.transfer_id\n    if transfer_id not in live_transfer_ids:\n        return None","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/vllm-project/vllm/blob/c794754062d49a8fdb63ab3c5215b488b865030c/vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_connector.py#L139-L175","documentation":"get_moriio_expected_ack_count requires that when consumer_tp_size > producer_tp_size, the consumer size is an exact multiple of the producer size (the ack count is the ratio consumer/producer). Non-divisible sizes like producer=4 / consumer=6 raise this ValueError.","triggerScenarios":"Disaggregated P/D with decode (consumer) TP larger than prefill (producer) TP and not divisible by it: producer=2/consumer=3, producer=4/consumer=6, etc.","commonSituations":"Scaling out decode replicas with arbitrary GPU counts; combining with the rank-mapping divisibility rules (546/547) but forgetting the ack path has the same constraint.","solutions":["Choose P/D TP sizes where decode TP is a multiple of prefill TP (4->8, 3->6) or both equal","Standardize on power-of-two TP sizes across the cluster","Assert divisibility in the deployment script before bringing up instances"],"exampleFix":"# before: prefill TP=4, decode TP=6 -> raises\n# after: prefill TP=3, decode TP=6 -> ok (ack count 2)","handlingStrategy":"validation","validationCode":"if consumer_tp_size > producer_tp_size:\n    assert consumer_tp_size % producer_tp_size == 0, (\n        f\"consumer TP {consumer_tp_size} must be a multiple of producer TP {producer_tp_size}\"\n    )","typeGuard":"def is_ack_divisible_tp_pair(producer_tp: int, consumer_tp: int) -> bool:\n    return consumer_tp <= producer_tp or consumer_tp % producer_tp == 0","tryCatchPattern":null,"preventionTips":["Validate the P/D TP ratio once at cluster bring-up","Use shared topology config for both roles so they cannot drift"],"tags":["tensor-parallel","kv-transfer","ack","heterogeneous-tp","config"],"backgroundTag":null,"analyzedSha":"c794754062d49a8fdb63ab3c5215b488b865030c","analyzedAt":"2026-08-14T21:17:39.825Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}