Skip to content

feat: add P/D disaggregation output to llm-d generator - #283

Open
amito wants to merge 1 commit into
llm-d-incubation:mainfrom
amito:feat/pd-disaggregation
Open

feat: add P/D disaggregation output to llm-d generator#283
amito wants to merge 1 commit into
llm-d-incubation:mainfrom
amito:feat/pd-disaggregation

Conversation

@amito

@amito amito commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds prefill/decode (P/D) disaggregation as an output option to the llm-d deployment generator. When P/D is enabled, the generator produces separate Kustomize patches for prefill and decode deployments instead of the single patch-vllm.yaml, allowing users to configure independent replica counts and vLLM arguments for each role.

When pd_enabled=False (the default), output is identical to the current behavior introduced in PR #274. No breaking changes.

What gets generated when P/D is enabled

  • patch-prefill.yaml - Prefill deployment with --kv-role=kv_producer, --enable-chunked-prefill, and configurable prefill_replicas
  • patch-decode.yaml - Decode deployment with --kv-role=kv_consumer and configurable decode_replicas
  • kustomization.yaml - Conditionally references prefill + decode patches instead of the single vLLM patch
  • values.yaml - EPP Helm values (unchanged)

KV transfer uses nixlv2 (NixlConnector) with separate --kv-connector and --kv-role args, matching the llm-d P/D well-lit path.

Template consolidation

All three patch templates (patch-vllm, patch-prefill, patch-decode) are consolidated into a single patch-modelserver.yaml.j2 rendered with different context variables (deployment_name, replica_count, extra_args). This avoids maintaining three near-identical templates that would drift independently. Role-specific differences (e.g., kv-connector args, chunked-prefill) are passed as extra_args from the generator code. If the prefill and decode roles diverge significantly in the future (different sidecars, volumes, probes), splitting back into separate templates is a one-step refactor.

Changes

  • src/planner/configuration/templates/llmd/patch-modelserver.yaml.j2 - Unified template for all model-server patches (replaces patch-vllm, patch-prefill, patch-decode).
  • src/planner/configuration/llmd_generator.py - generate_all() accepts pd_enabled, prefill_replicas, and decode_replicas parameters. Renders the unified template with role-specific context. Validates replica counts >= 1 at the generator level (raises ValueError).
  • src/planner/configuration/templates/llmd/kustomization.yaml.j2 - Conditional patch references based on pd_enabled.
  • src/planner/api/routes/configuration.py - Added pd_enabled, prefill_replicas, and decode_replicas fields to DeploymentRequest with Field(ge=1, le=32) validation on replica counts.
  • ui/components/deployment.py - Added "Enable P/D Disaggregation" checkbox with prefill/decode replica inputs (shown when enabled, llm-d stack only). File display section handles patch_prefill/patch_decode keys.
  • tests/unit/test_llmd_generator.py - Added TestPDDisaggregation (8 tests), TestDeployAPINewParams (4 tests). 31 tests total.

Note on deployment_name: The non-PD patch uses metadata.name: decode (not vllm) because it is a strategic merge patch targeting the upstream llm-d base Deployment/decode. A dedicated test locks this down.

How Has This Been Tested?

12 new unit tests added, full suite passes (31 tests in test_llmd_generator.py):

cd src && uv run pytest ../tests/unit/test_llmd_generator.py -v

Tests cover:

  • test_pd_disabled_produces_single_patch - Default behavior unchanged: patch_vllm present, no patch_prefill/patch_decode.
  • test_pd_disabled_patch_targets_decode_deployment - Non-PD patch uses metadata.name: decode to match the llm-d base Deployment.
  • test_pd_enabled_produces_prefill_and_decode_patches - P/D mode produces patch_prefill and patch_decode, no patch_vllm.
  • test_pd_prefill_patch_has_correct_replicas - Prefill patch uses prefill_replicas value (non-default) and has metadata.name: prefill.
  • test_pd_decode_patch_has_correct_replicas - Decode patch uses decode_replicas value and has metadata.name: decode.
  • test_pd_kustomization_references_both_patches - Kustomization patches list contains patch-prefill.yaml and patch-decode.yaml, not patch-vllm.yaml.
  • test_pd_all_outputs_valid_yaml - All generated contents parse as valid YAML.
  • test_rejects_zero_replicas (parametrized) - Generator raises ValueError when prefill_replicas or decode_replicas < 1.
  • test_deploy_llmd_with_pd_enabled - API integration test: POST with pd_enabled: true returns 200 with patch_prefill and patch_decode.
  • test_deploy_rejects_zero_replicas (parametrized) - API returns 422 when prefill_replicas or decode_replicas is 0.
  • test_deploy_rejects_replicas_above_max - API returns 422 when replica count exceeds 32.

UI manually tested: selecting llm-d stack and enabling the P/D checkbox shows prefill/decode replica inputs; generated files display correctly with the new file labels.

Merge criteria:

  • The commits are squashed in a cohesive manner and have meaningful messages.
  • Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious).
  • The developer has manually tested the changes and verified that the changes work

@amito
amito force-pushed the feat/pd-disaggregation branch 5 times, most recently from 2753ae4 to 73b361f Compare July 19, 2026 11:50
@amito
amito force-pushed the feat/pd-disaggregation branch 4 times, most recently from 4103d42 to cce5a40 Compare July 22, 2026 05:41
@amito
amito marked this pull request as ready for review July 29, 2026 13:02
pd_enabled = st.checkbox(
"Enable P/D Disaggregation",
value=st.session_state.get("pd_enabled", False),
key="pd_enabled",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be a future improvement, but the "Deploy to Kubernetes" path doesn't forward the new params like pd_enabled,prefill_replicas and decode_replicas.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks.

Comment on lines +84 to +86
pd_enabled = st.checkbox(
"Enable P/D Disaggregation",
value=st.session_state.get("pd_enabled", False),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to clear the stale YAMLs after user checks this box?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, fixing this, thanks.

Comment on lines +86 to +91
if prefill_replicas < 1:
msg = f"prefill_replicas must be >= 1, got {prefill_replicas}"
raise ValueError(msg)
if decode_replicas < 1:
msg = f"decode_replicas must be >= 1, got {decode_replicas}"
raise ValueError(msg)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a bug but the generation checks for two different ranges. The Pydantic model in configuration.py checks for Field(1, ge=1, le=32) but here, the generation checks for just <1. Perhaps we should remain consistent?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Fixing this for consistency. Thanks

@amito
amito force-pushed the feat/pd-disaggregation branch from cce5a40 to 45abee6 Compare August 20, 2026 06:34
Add prefill/decode disaggregation as an output option to the llm-d
deployment generator. When pd_enabled=True, the generator produces
separate Kustomize patches for prefill and decode deployments instead
of the single patch-vllm.yaml.

All three patch variants (vllm, prefill, decode) use a single unified
template (patch-modelserver.yaml.j2) rendered with different context
(deployment_name, replica_count, extra_args) to avoid maintaining
near-identical templates that would drift. If the roles diverge
significantly, splitting is a one-step refactor.

Also adds Field(ge=1) validation on prefill_replicas/decode_replicas
in the API schema.

Signed-off-by: Amit Oren <amoren@redhat.com>
@amito
amito force-pushed the feat/pd-disaggregation branch from 45abee6 to dde775f Compare August 20, 2026 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants