Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ disable_overlap_scheduler: True
internal_request_auth_key: ${internal_request_auth_key}
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
max_tokens_in_buffer: 2048
EOL

cat >${work_path}/gen_config.yaml << EOL
internal_request_auth_key: ${internal_request_auth_key}
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
Comment thread
nv-xtf marked this conversation as resolved.
max_tokens_in_buffer: 2048
EOL

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
disable_overlap_scheduler: True
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
max_tokens_in_buffer: 2048
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
max_tokens_in_buffer: 2048
2 changes: 2 additions & 0 deletions examples/dwdp/reproduce.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ def build_worker_config(experiment: Dict[str, Any]) -> Dict[str, Any]:
},
"cache_transceiver_config": {
"backend": "UCX",
"transceiver_runtime": "CPP",
"max_tokens_in_buffer": max_tokens_in_buffer,
},
"num_postprocess_workers": 4,
Expand Down Expand Up @@ -256,6 +257,7 @@ def build_worker_config(experiment: Dict[str, Any]) -> Dict[str, Any]:
},
"cache_transceiver_config": {
"backend": "UCX",
"transceiver_runtime": "CPP",
"max_tokens_in_buffer": max_tokens_in_buffer,
},
"moe_config": {
Expand Down
2 changes: 2 additions & 0 deletions examples/wide_ep/slurm_scripts/kimi-k2-thinking.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ worker_config:
layer_updates_per_iter: 1
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
Comment thread
QiJune marked this conversation as resolved.
max_tokens_in_buffer: 8448
stream_interval: 20
num_postprocess_workers: 4
Expand All @@ -94,5 +95,6 @@ worker_config:
dtype: fp8
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
max_tokens_in_buffer: 8448
trust_remote_code: true
21 changes: 14 additions & 7 deletions tensorrt_llm/_torch/models/modeling_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,10 +696,17 @@ def get_preferred_transceiver_runtime(
) -> Optional[Literal["CPP", "PYTHON"]]:
"""Return the model's preferred KV-cache transceiver runtime.

Subclasses can override this to opt into a specific transceiver
implementation ('CPP' or 'PYTHON') that is adopted when the user
leaves ``cache_transceiver_config.transceiver_runtime`` at its
default 'auto'. Return None to defer to the global default (C++).
Subclasses can override this to pin a specific transceiver
implementation ('CPP' or 'PYTHON') that is adopted verbatim when the
user leaves ``cache_transceiver_config.transceiver_runtime`` at its
default 'auto'; unsupported configurations then fail loudly at
transceiver creation rather than being rerouted. Return None to
defer to the global default: the Python transceiver, falling back to
C++ only for conditions decidable from the transceiver config itself
(non-NIXL backend or an infinite ``kv_transfer_timeout_ms``) — other
incompatibilities fail at transceiver creation. The effective
runtime for a no-preference model is therefore
deployment-dependent.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

Args:
pretrained_config: the loaded HF pretrained config (may be None
Expand All @@ -710,9 +717,9 @@ def get_preferred_transceiver_runtime(

This preference is intentionally kept out of the generic
:meth:`get_model_defaults` deep-merge: it must not materialize a
``cache_transceiver_config`` when disaggregated serving is disabled,
and it is only honored when the effective backend supports it (the
Python transceiver requires NIXL).
``cache_transceiver_config`` when disaggregated serving is disabled.
Preferences are adopted only for the 'auto' setting; a 'PYTHON'
preference still requires NIXL when the transceiver is created.
"""
return None

Expand Down
17 changes: 11 additions & 6 deletions tensorrt_llm/llmapi/llm_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -4333,12 +4333,17 @@ class CacheTransceiverConfig(StrictBaseModel, PybindMirror):
default="auto",
description=
"The runtime implementation. 'auto' (default) adopts the model's "
"preferred runtime when the effective backend supports it, and falls "
"back to the C++ transceiver otherwise. 'CPP' selects the C++ "
"transceiver, 'PYTHON' the Python transceiver. None is equivalent to "
"'CPP'. The model preference is only consulted on the PyTorch "
"backend's standard model-loading path; other paths (e.g. AutoDeploy) "
"fall back to the C++ transceiver under 'auto'.")
"preferred runtime when it declares one; otherwise it selects the "
"Python transceiver, falling back to the C++ transceiver only when "
"this config itself rules it out (non-NIXL backend or a null "
"kv_transfer_timeout_ms) — any other incompatibility fails at "
"transceiver creation. The fallback is decided independently on "
"each server and is only logged, not surfaced, so keep context and "
"generation server configurations consistent. 'CPP' selects the C++ "
"transceiver, 'PYTHON' the Python transceiver. None is equivalent "
"to 'CPP'. 'auto' is only resolved on the PyTorch backend's "
"standard model-loading path; other paths (e.g. AutoDeploy) fall "
"back to the C++ transceiver.")

max_tokens_in_buffer: Optional[int] = Field(
default=None,
Expand Down
70 changes: 54 additions & 16 deletions tensorrt_llm/llmapi/llm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,19 +675,54 @@ def _resolve_kv_cache_manager_v2_auto(llm_args: 'TorchLlmArgs',
return use_v2


def _transceiver_python_fallback_reason(
llm_args: 'TorchLlmArgs') -> Optional[str]:
Comment thread
Shixiaowei02 marked this conversation as resolved.
"""Why 'auto' should not default to the Python transceiver, or None.

Checks only the constraints that are decidable from
``cache_transceiver_config`` itself (backend and timeout). Any other
incompatibility (e.g. non-helix context parallelism) is NOT resolved
here and fails loudly at transceiver creation instead. Only consulted
when the model expressed no runtime preference: a model preferring
'PYTHON' may genuinely require it (e.g. recurrent-state transfer), so it
is never silently rerouted — transceiver creation raises with an
actionable message instead.
"""
cfg = llm_args.cache_transceiver_config
effective_backend, _ = cfg._resolve_default_backend()
if effective_backend != "NIXL":
return (f"backend {effective_backend!r} (the Python transceiver "
"requires NIXL)")
if cfg.kv_transfer_timeout_ms is None:
return ("kv_transfer_timeout_ms=None (the Python transceiver "
"requires a finite timeout)")
# Deliberately reads only cache_transceiver_config: external callers
# (e.g. the perf-sanity cache-transceiver precheck) invoke the resolver
# with a lightweight stand-in object, and per-server fields beyond this
# config could resolve differently on ctx and gen servers (e.g. context
# parallelism, where only the gen side runs helix). Conditions the
# Python transceiver cannot serve (such as non-helix CP) fail loudly at
# transceiver creation instead.
return None


def _resolve_transceiver_runtime_auto(llm_args: 'TorchLlmArgs',
model_cls: Optional[type] = None,
pretrained_config: Any = None) -> None:
"""Resolve the 'auto' sentinel in cache_transceiver_config.transceiver_runtime.

Semantics:
- Disagg disabled (config is None or backend is None): no-op. The model
preference must never materialize or alter a transceiver config that the
user did not enable.
- Disagg disabled (config is None or backend is None): no-op. Resolution
must never materialize or alter a transceiver config that the user did
not enable.
- Explicit user value ('CPP'/'PYTHON'/None): left untouched.
- 'auto': adopt ``model_cls.get_preferred_transceiver_runtime()`` when the
effective backend supports it (the Python transceiver requires NIXL);
otherwise fall back to None (C++ transceiver).
- 'auto': a model preference from
``model_cls.get_preferred_transceiver_runtime()`` ('CPP' or 'PYTHON')
is adopted verbatim — never rerouted, so unsupported configurations
surface as transceiver-creation errors. Without a preference, default
to the Python (V2) transceiver, falling back to None (C++ transceiver)
for configurations it does not support (non-NIXL backend or an
infinite kv_transfer_timeout_ms).

``pretrained_config`` is forwarded to the hook so implementation classes
shared by several architectures can differentiate per checkpoint.
Expand All @@ -709,15 +744,18 @@ def _resolve_transceiver_runtime_auto(llm_args: 'TorchLlmArgs',
f"{model_cls.__name__}.get_preferred_transceiver_runtime() must "
f"return 'CPP', 'PYTHON', or None, got {preferred!r}.")

effective_backend, _ = cfg._resolve_default_backend()
if preferred == "PYTHON" and effective_backend != "NIXL":
logger.info(
f"Model prefers the Python transceiver, but backend "
f"{effective_backend} does not support it; falling back to the "
f"C++ transceiver.")
preferred = None

cfg.transceiver_runtime = preferred
resolved = preferred if preferred is not None else "PYTHON"
Comment thread
nv-xtf marked this conversation as resolved.

# Fallbacks apply only to the no-preference default: an explicit model
# preference is adopted verbatim (see _transceiver_python_fallback_reason).
if preferred is None:
fallback_reason = _transceiver_python_fallback_reason(llm_args)
if fallback_reason is not None:
logger.info(
f"Falling back to the C++ transceiver: {fallback_reason}.")
resolved = None

cfg.transceiver_runtime = resolved
logger.info(
f"Resolved transceiver_runtime='auto' to {preferred!r} for "
f"Resolved transceiver_runtime='auto' to {resolved!r} for "
f"{model_cls.__name__ if model_cls is not None else 'unknown model'}.")
13 changes: 12 additions & 1 deletion tests/integration/defs/accuracy/test_disaggregated_serving.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,9 +1202,13 @@ def test_auto_dtype_with_helix(self, comms_medium, cuda_graph_config,
# DEFAULT drops the per-test UCX pinning but still runs UCX, since
# launch_disaggregated_llm sets TRTLLM_USE_UCX_KVCACHE=1 for every
# backend but NIXL. Transport coverage is unchanged by this move.
# CPP is explicit: this test runs on UCX (see the DEFAULT note
# above), and DeepSeek's Python preference would otherwise be
# adopted verbatim and fail at creation on a non-NIXL backend.
"cache_transceiver_config": {
"backend": "DEFAULT",
"max_tokens_in_buffer": 8192,
"transceiver_runtime": "CPP",
},
}
gen_server_config = {
Expand All @@ -1225,6 +1229,7 @@ def test_auto_dtype_with_helix(self, comms_medium, cuda_graph_config,
"cache_transceiver_config": {
"backend": "DEFAULT",
"max_tokens_in_buffer": 8192,
"transceiver_runtime": "CPP",
},
"enable_attention_dp": enable_attention_dp,
}
Expand Down Expand Up @@ -1714,6 +1719,8 @@ class TestQwen3_8B(LlmapiAccuracyTestHarness):

@pytest.mark.skip_less_device(2)
def test_nixl_backend(self):
# transceiver_runtime is left at 'auto', which resolves to the Python
Comment thread
Shixiaowei02 marked this conversation as resolved.
# transceiver (the global default) on the NIXL backend.
ctx_server_config = {
"disable_overlap_scheduler": True,
"cache_transceiver_config": {
Expand Down Expand Up @@ -2040,6 +2047,9 @@ class TestQwen3_30B_A3B(LlmapiAccuracyTestHarness):
def test_mixed_ctx_gen_model(self, ctx_pp, gen_tp):
ctx_model = self.FP4_MODEL
gen_model = self.FP8_MODEL
# Explicit NIXL so the launcher does not force the UCX env fallback;
# with the NIXL backend, transceiver_runtime='auto' resolves to the
# Python transceiver (the global default).
return run_parallel_test("Qwen3/Qwen3-30B-A3B",
ctx_model,
ctx_pp=ctx_pp,
Expand All @@ -2050,7 +2060,8 @@ def test_mixed_ctx_gen_model(self, ctx_pp, gen_tp):
ctx_model=ctx_model,
gen_model=gen_model,
ctx_instances=1,
gen_instances=1)
gen_instances=1,
cache_transceiver_backend="NIXL")
Comment thread
Shixiaowei02 marked this conversation as resolved.


@pytest.mark.timeout(10800)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ def test_dwdp_accuracy(self):
},
"cache_transceiver_config": {
"backend": "UCX",
"transceiver_runtime": "CPP",
Comment thread
nv-xtf marked this conversation as resolved.
"max_tokens_in_buffer": 8192,
},
"moe_config": {
Expand Down Expand Up @@ -261,6 +262,7 @@ def test_dwdp_accuracy(self):
},
"cache_transceiver_config": {
"backend": "UCX",
"transceiver_runtime": "CPP",
"max_tokens_in_buffer": 8192,
},
"moe_config": {
Expand Down Expand Up @@ -344,6 +346,7 @@ def test_dwdp_accuracy_contention_opt(self):
},
"cache_transceiver_config": {
"backend": "UCX",
"transceiver_runtime": "CPP",
"max_tokens_in_buffer": 8192,
},
"moe_config": {
Expand Down Expand Up @@ -377,6 +380,7 @@ def test_dwdp_accuracy_contention_opt(self):
},
"cache_transceiver_config": {
"backend": "UCX",
"transceiver_runtime": "CPP",
"max_tokens_in_buffer": 8192,
},
"moe_config": {
Expand Down Expand Up @@ -479,6 +483,7 @@ def test_dwdp_accuracy_mode_b_overlap(self):
},
"cache_transceiver_config": {
"backend": "UCX",
"transceiver_runtime": "CPP",
"max_tokens_in_buffer": 8192,
},
"moe_config": {
Expand Down Expand Up @@ -515,6 +520,7 @@ def test_dwdp_accuracy_mode_b_overlap(self):
},
"cache_transceiver_config": {
"backend": "UCX",
"transceiver_runtime": "CPP",
"max_tokens_in_buffer": 8192,
},
"moe_config": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ context_servers:
pipeline_parallel_size: 1
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
generation_servers:
num_instances: 1
tensor_parallel_size: 1
pipeline_parallel_size: 1
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ context_servers:
pipeline_parallel_size: 1
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
generation_servers:
num_instances: 1
tensor_parallel_size: 1
Expand All @@ -29,3 +30,4 @@ generation_servers:
tokens_per_block: 32
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ context_servers:
pipeline_parallel_size: 1
cache_transceiver_config:
backend: MPI
transceiver_runtime: CPP
generation_servers:
num_instances: 1
tensor_parallel_size: 2
pipeline_parallel_size: 1
cache_transceiver_config:
backend: MPI
transceiver_runtime: CPP
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ context_servers:
perf_metrics_max_requests: 1000
cache_transceiver_config:
backend: DEFAULT
transceiver_runtime: CPP
generation_servers:
num_instances: 1
tensor_parallel_size: 1
Expand All @@ -22,3 +23,4 @@ generation_servers:
perf_metrics_max_requests: 1000
cache_transceiver_config:
backend: DEFAULT
transceiver_runtime: CPP
8 changes: 4 additions & 4 deletions tests/integration/defs/disaggregated/test_disaggregated.py
Original file line number Diff line number Diff line change
Expand Up @@ -1687,8 +1687,8 @@ def extra_endpoints_test(_server_url: str):
# Use helper function to validate all timing metrics comprehensively
validate_timing_metrics(item, "perf_metrics test")

# This test validates the C++ transceiver's timing-metric semantics. Force
# DEFAULT to UCX so Llama's Python preference falls back to C++.
# This test validates the C++ transceiver's timing-metric semantics: the
# config pins transceiver_runtime=CPP, and DEFAULT is forced to UCX.
env = llm_venv._new_env | {
"TRTLLM_USE_NIXL_KVCACHE": "0",
"TRTLLM_USE_UCX_KVCACHE": "1",
Expand Down Expand Up @@ -1731,8 +1731,8 @@ def test_disaggregated_kv_cache_time_output(disaggregated_test_root, llm_venv,

output_path = os.path.join(llm_venv.get_working_directory(), "cache_time")
env = llm_venv._new_env.copy()
# This test validates the C++ transceiver's CSV format. Selecting UCX for
# the DEFAULT backend also resolves the automatic runtime to C++.
# This test validates the C++ transceiver's CSV format: the config pins
# transceiver_runtime=CPP, and DEFAULT is forced to UCX.
env["TRTLLM_USE_NIXL_KVCACHE"] = "0"
env["TRTLLM_USE_UCX_KVCACHE"] = "1"
env["UCX_TLS"] = get_ucx_tls()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ server_configs:
tokens_per_block: 64
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
max_tokens_in_buffer: 120000
client_configs:
- name: "con4_iter10_8k1k"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ server_configs:
free_gpu_memory_fraction: 0.75
cache_transceiver_config:
backend: UCX
transceiver_runtime: CPP
max_tokens_in_buffer: 8448
client_configs:
- name: "con128_iter5_2k1k"
Expand Down
Loading
Loading