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
13 changes: 2 additions & 11 deletions tests/distributed/test_dcp_a2a.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,20 +105,11 @@ class TestDCPCommBackendConfig:
"""Test --dcp-comm-backend config validation."""

def test_default_is_ag_rs(self):
"""Default comm backend is ag_rs."""
"""Comm backend resolves to ag_rs unless the model asks otherwise."""
config = ParallelConfig()
config.set_dcp_defaults()
assert config.dcp_comm_backend == "ag_rs"

def test_a2a_requires_dcp_greater_than_1(self):
"""A2A backend requires decode_context_parallel_size > 1."""
with pytest.raises(
ValueError, match="requires decode_context_parallel_size > 1"
):
ParallelConfig(
dcp_comm_backend="a2a",
decode_context_parallel_size=1,
)

def test_a2a_with_dcp_valid(self):
"""A2A backend is valid when DCP > 1."""
config = ParallelConfig(
Expand Down
38 changes: 31 additions & 7 deletions vllm/config/parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,12 +351,26 @@ class is dynamically inherited by the worker class. This is used to inject
and will be deprecated when PCP is fully supported.

"""
dcp_comm_backend: DCPCommBackend = "ag_rs"
dcp_comm_backend: DCPCommBackend | None = None
"""Communication backend for Decode Context Parallel (DCP).
- "ag_rs": AllGather + ReduceScatter (default, existing behavior)
- "ag_rs": AllGather + ReduceScatter (existing behavior)
- "a2a": All-to-All exchange of partial outputs + LSE, then
combine with Triton kernel. Reduces NCCL calls from 3 to 2
per layer for MLA models.

`None` selects the model default, which is "ag_rs" unless the model
overrides it via [`set_dcp_defaults`][vllm.config.ParallelConfig.set_dcp_defaults].
"""

dcp_q_replicate: bool | None = None
"""Replicate the MLA query projection within each DCP group so decode can skip the
query all-gather.

With DCP the KV cache is sharded across the group, so the standard MLA decode path
all-gathers the query every step. Replicating the (small) query projection at load
time lets each rank materialize the full group-local head set and skip that
collective, at the cost of computing the projection redundantly on every rank
in the group.
"""

cp_kv_cache_interleave_size: int = 1
Expand Down Expand Up @@ -538,13 +552,23 @@ def _validate_parallel_config(self) -> Self:
f"{sorted({1, pcp, tp * pcp})}."
)

if self.dcp_comm_backend == "a2a" and self.decode_context_parallel_size <= 1:
raise ValueError(
"dcp_comm_backend='a2a' requires decode_context_parallel_size > 1."
)

return self

def set_dcp_defaults(
self,
comm_backend: DCPCommBackend = "ag_rs",
q_replicate: bool = False,
) -> None:
"""Fill in the DCP options the user left unset.

Models can set their preferred DCP settings by calling this from their
`verify_and_update_config` hook.
"""
if self.dcp_comm_backend is None:
self.dcp_comm_backend = comm_backend
if self.dcp_q_replicate is None:
self.dcp_q_replicate = q_replicate

@property
def world_size_across_dp(self) -> int:
"""Process world size across TP, PCP, PP, and DP."""
Expand Down
4 changes: 4 additions & 0 deletions vllm/config/vllm.py
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,10 @@ def __post_init__(self):

self.try_verify_and_update_config()

# Models may have supplied their own DCP defaults above; anything still
# unset falls back to the stock ones.
self.parallel_config.set_dcp_defaults()

if self.model_config is not None:
self.model_config.verify_with_parallel_config(self.parallel_config)
self.model_config.verify_dual_chunk_attention_config(self.load_config)
Expand Down
8 changes: 7 additions & 1 deletion vllm/engine/arg_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,8 @@ class EngineArgs:
tensor_parallel_size: int = ParallelConfig.tensor_parallel_size
prefill_context_parallel_size: int = ParallelConfig.prefill_context_parallel_size
decode_context_parallel_size: int = ParallelConfig.decode_context_parallel_size
dcp_comm_backend: DCPCommBackend = ParallelConfig.dcp_comm_backend
dcp_comm_backend: DCPCommBackend | None = ParallelConfig.dcp_comm_backend
dcp_q_replicate: bool | None = ParallelConfig.dcp_q_replicate
dcp_kv_cache_interleave_size: int = ParallelConfig.dcp_kv_cache_interleave_size
cp_kv_cache_interleave_size: int = ParallelConfig.cp_kv_cache_interleave_size
data_parallel_size: int = ParallelConfig.data_parallel_size
Expand Down Expand Up @@ -1070,6 +1071,10 @@ def add_cli_args(parser: FlexibleArgumentParser) -> FlexibleArgumentParser:
"--dcp-comm-backend",
**parallel_kwargs["dcp_comm_backend"],
)
parallel_group.add_argument(
"--dcp-q-replicate",
**parallel_kwargs["dcp_q_replicate"],
)
parallel_group.add_argument(
"--dcp-kv-cache-interleave-size",
**parallel_kwargs["dcp_kv_cache_interleave_size"],
Expand Down Expand Up @@ -2295,6 +2300,7 @@ def create_engine_config(
worker_extension_cls=self.worker_extension_cls,
decode_context_parallel_size=self.decode_context_parallel_size,
dcp_comm_backend=self.dcp_comm_backend,
dcp_q_replicate=self.dcp_q_replicate,
dcp_kv_cache_interleave_size=self.dcp_kv_cache_interleave_size,
cp_kv_cache_interleave_size=self.cp_kv_cache_interleave_size,
_api_process_count=self._api_process_count,
Expand Down
11 changes: 11 additions & 0 deletions vllm/model_executor/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ def verify_and_update_config(cls, vllm_config: "VllmConfig") -> None:
logger.info("Using bfloat16 kv-cache for DeepSeekV3.2")


class GlmMoeDsaForCausalLM(VerifyAndUpdateConfig):
@staticmethod
def verify_and_update_config(vllm_config: "VllmConfig") -> None:
# For Glm-Moe-DSA, qrep + a2a is better than the default all-gather + ag-rs
# in most cases.
vllm_config.parallel_config.set_dcp_defaults(
comm_backend="a2a", q_replicate=True
)


class Ernie4_5_VLMoeForConditionalGenerationConfig(VerifyAndUpdateConfig):
@staticmethod
def verify_and_update_config(vllm_config: "VllmConfig") -> None:
Expand Down Expand Up @@ -913,6 +923,7 @@ def verify_and_update_config(vllm_config: "VllmConfig") -> None:
"Gemma4ForCausalLM": Gemma4Config,
"Gemma4ForConditionalGeneration": Gemma4Config,
"Gemma4UnifiedForConditionalGeneration": Gemma4Config,
"GlmMoeDsaForCausalLM": GlmMoeDsaForCausalLM,
"GptOssForCausalLM": GptOssForCausalLMConfig,
"LongcatFlashNgramForCausalLM": LongcatFlashNgramForCausalLMConfig,
"GteModel": SnowflakeGteNewModelConfig,
Expand Down
8 changes: 7 additions & 1 deletion vllm/model_executor/models/deepseek_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,8 +1041,14 @@ def __init__(
prefix=f"{prefix}.kv_a_proj_with_mqa",
)

qrep_enabled = (
# The env var predates the config field and still wins if set explicitly.
qrep_requested = (
envs.VLLM_DCP_Q_REPLICATE
if envs.is_set("VLLM_DCP_Q_REPLICATE")
else bool(vllm_config.parallel_config.dcp_q_replicate)
)
qrep_enabled = (
qrep_requested
and vllm_config.parallel_config.decode_context_parallel_size > 1
and vllm_config.parallel_config.prefill_context_parallel_size <= 1
)
Expand Down
Loading