Skip to content

Commit 04dee66

Browse files
tzulingkclaude
authored andcommitted
[Bugfix][NIXL] Include transfer mode (push/pull) in the compatibility hash (vllm-project#50620)
Signed-off-by: Tzu-Ling <tzulingk@nvidia.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7948db0 commit 04dee66

8 files changed

Lines changed: 76 additions & 3 deletions

File tree

docs/features/nixl_connector_compatibility.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ By default, a **compatibility hash** is checked during handshake. P and D instan
8282
- Attention backend
8383
- KV cache dtype (`cache_dtype`)
8484
- EAGLE/MTP-style speculative method and draft-model configuration
85+
- NIXL transfer mode (push vs pull) — a push (WRITE) connector and a pull (READ)
86+
connector use incompatible transfer protocols and must never be paired
8587

8688
!!! warning
8789
Disable the hash check with `--kv-transfer-config '{"kv_connector_extra_config": {"enforce_handshake_compat": false}}'` at your own risk.

tests/v1/kv_connector/unit/test_nixl_connector.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,9 @@ def test_kv_transfer_handshake(dist_init):
433433
)
434434
)
435435
assert delay
436+
# Pull connector advertises its transfer mode in kv_transfer_params so
437+
# an external router can distinguish it from a push producer.
438+
assert kv_connector_metadata["transfer_mode"] == "pull"
436439

437440
# Decode connector will be able to create handshake with the prefill connector.
438441
decode_connector = NixlConnector(
@@ -2906,6 +2909,42 @@ def test_speculative_attention_backend_not_in_compatibility_hash():
29062909
assert local_hash == remote_hash
29072910

29082911

2912+
@pytest.mark.skip_global_cleanup
2913+
def test_transfer_mode_changes_compatibility_hash():
2914+
# push (WRITE) and pull (READ) connectors use incompatible transfer
2915+
# protocols, so their compatibility hashes must differ; identical modes
2916+
# must match. The default mode is pull.
2917+
config = create_vllm_config()
2918+
2919+
pull_hash = compute_nixl_compatibility_hash(
2920+
config, "FLASH_ATTN", False, transfer_mode="pull"
2921+
)
2922+
push_hash = compute_nixl_compatibility_hash(
2923+
config, "FLASH_ATTN", False, transfer_mode="push"
2924+
)
2925+
2926+
assert pull_hash != push_hash
2927+
assert pull_hash == compute_nixl_compatibility_hash(
2928+
config, "FLASH_ATTN", False, transfer_mode="pull"
2929+
)
2930+
assert compute_nixl_compatibility_hash(config, "FLASH_ATTN", False) == pull_hash
2931+
2932+
2933+
@pytest.mark.skip_global_cleanup
2934+
def test_scheduler_advertises_transfer_mode():
2935+
# Each scheduler advertises its transfer mode in kv_transfer_params so an
2936+
# external router can route pull (READ) vs push (WRITE) producers.
2937+
from vllm.distributed.kv_transfer.kv_connector.v1.nixl.pull_scheduler import (
2938+
NixlPullConnectorScheduler,
2939+
)
2940+
from vllm.distributed.kv_transfer.kv_connector.v1.nixl.push_scheduler import (
2941+
NixlPushConnectorScheduler,
2942+
)
2943+
2944+
assert NixlPullConnectorScheduler._TRANSFER_MODE == "pull"
2945+
assert NixlPushConnectorScheduler._TRANSFER_MODE == "push"
2946+
2947+
29092948
@pytest.mark.parametrize(
29102949
"mismatch_type,config_overrides,version_override,should_fail,enforce_handshake_compat",
29112950
[

vllm/distributed/kv_transfer/kv_connector/v1/nixl/base_scheduler.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@
5151
class NixlBaseConnectorScheduler:
5252
"""Base implementation of Scheduler side methods shared by pull and push."""
5353

54+
# Emitted in kv_transfer_params so an external router can distinguish a
55+
# pull (READ) producer from a push (WRITE) one. Overridden by the push
56+
# scheduler.
57+
_TRANSFER_MODE: str = "pull"
58+
5459
def __init__(
5560
self,
5661
vllm_config: "VllmConfig",

vllm/distributed/kv_transfer/kv_connector/v1/nixl/base_worker.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@
9090
class NixlBaseConnectorWorker:
9191
"""Base implementation of Worker side methods shared by pull and push."""
9292

93+
# Transfer mode included in the NIXL compatibility hash so that a push
94+
# (WRITE) connector and a pull (READ) connector never handshake together.
95+
# Overridden by NixlPushConnectorWorker.
96+
_TRANSFER_MODE: str = "pull"
97+
9398
def _compute_desc_ids(
9499
self,
95100
block_ids: BlockIds,
@@ -976,6 +981,7 @@ def _register_packed_kv_cache(
976981
self.vllm_config,
977982
self.backend_name,
978983
self.transfer_topo.cross_layers_blocks,
984+
transfer_mode=self._TRANSFER_MODE,
979985
)
980986

981987
total_size = storage.nbytes()
@@ -1066,7 +1072,10 @@ def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]):
10661072
is_mamba=self._has_mamba,
10671073
)
10681074
self.compat_hash = compute_nixl_compatibility_hash(
1069-
self.vllm_config, self.backend_name, self.transfer_topo.cross_layers_blocks
1075+
self.vllm_config,
1076+
self.backend_name,
1077+
self.transfer_topo.cross_layers_blocks,
1078+
transfer_mode=self._TRANSFER_MODE,
10701079
)
10711080

10721081
if self.use_host_buffer:

vllm/distributed/kv_transfer/kv_connector/v1/nixl/metadata.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@
4242
# 5: Add remote_blocks_expiry_time to kv_transfer_params + handshake
4343
# clock-sync timestamp
4444
# 6: Validate EAGLE/MTP speculative configuration compatibility
45+
# 7: Include NIXL transfer mode (push vs pull) in the compatibility hash
4546
#
46-
NIXL_CONNECTOR_VERSION: int = 6
47+
NIXL_CONNECTOR_VERSION: int = 7
4748

4849

4950
@dataclass
@@ -123,7 +124,10 @@ def _get_speculative_compatibility_factors(
123124

124125

125126
def compute_nixl_compatibility_hash(
126-
vllm_config: VllmConfig, attn_backend_name: str, cross_layers_blocks: bool
127+
vllm_config: VllmConfig,
128+
attn_backend_name: str,
129+
cross_layers_blocks: bool,
130+
transfer_mode: str = "pull",
127131
) -> str:
128132
"""
129133
Compute compatibility hash for NIXL KV transfer.
@@ -137,6 +141,11 @@ def compute_nixl_compatibility_hash(
137141
- KV cache format (dtype, sliding window)
138142
- Attention backend
139143
- EAGLE/MTP configuration that affects transferred state
144+
- Transfer mode (push vs pull)
145+
146+
The transfer mode is included because the push (WRITE) and pull (READ)
147+
connectors use incompatible transfer protocols; a push connector and a
148+
pull connector must never complete a handshake with each other.
140149
141150
Note: Factors like tensor_parallel_size, block_size, and kv_cache_layout
142151
are validated at runtime in _validate_remote_agent_handshake and are not
@@ -171,6 +180,8 @@ def compute_nixl_compatibility_hash(
171180
"cross_layers_blocks": cross_layers_blocks,
172181
"is_hma_enabled": is_hma_enabled,
173182
"speculative_config": _get_speculative_compatibility_factors(vllm_config),
183+
# push (WRITE) and pull (READ) connectors are protocol-incompatible
184+
"transfer_mode": transfer_mode,
174185
}
175186

176187
compat_hash = hash_factors(factors)

vllm/distributed/kv_transfer/kv_connector/v1/nixl/pull_scheduler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,5 @@ def request_finished(
277277
tp_size=self.vllm_config.parallel_config.tensor_parallel_size,
278278
remote_num_tokens=remote_num_tokens,
279279
remote_blocks_expiry_time=blocks_expiry_time,
280+
transfer_mode=self._TRANSFER_MODE,
280281
)

vllm/distributed/kv_transfer/kv_connector/v1/nixl/push_scheduler.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ class NixlPushConnectorScheduler(NixlBaseConnectorScheduler):
6060
hooks.
6161
"""
6262

63+
_TRANSFER_MODE: str = "push"
64+
6365
def __init__(
6466
self,
6567
vllm_config: VllmConfig,
@@ -290,6 +292,7 @@ def request_finished(
290292
tp_size=self.vllm_config.parallel_config.tensor_parallel_size,
291293
pp_size=self.vllm_config.parallel_config.pipeline_parallel_size,
292294
remote_num_tokens=remote_num_tokens,
295+
transfer_mode=self._TRANSFER_MODE,
293296
)
294297

295298
def build_connector_meta(

vllm/distributed/kv_transfer/kv_connector/v1/nixl/push_worker.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@
7777
class NixlPushConnectorWorker(NixlBaseConnectorWorker):
7878
"""Push-specific (WRITE) worker logic. See module docstring."""
7979

80+
# Distinguishes push from pull in the NIXL compatibility hash.
81+
_TRANSFER_MODE: str = "push"
82+
8083
def __init__(
8184
self,
8285
vllm_config: "VllmConfig",

0 commit comments

Comments
 (0)