This directory contains Helm charts for deploying the llm-d Router components: the Endpoint Picker (EPP) and the InferencePool resource.
We provide two charts depending on your deployment mode, both leveraging a shared core library chart (routerlib):
llm-d-router-gateway: Used for Gateway Mode. It deploys EPP and creates anInferencePoolresource. It integrates with the Kubernetes Gateway API (typically viaHTTPRoutepointing to theInferencePool) for multi-pool, dynamic routing.llm-d-router-standalone: Used for Standalone Mode (Service-backed or direct pod routing). EPP can be deployed without creating anInferencePoolresource (by settingrouter.inferencePool.create=false). It supports running a proxy (Envoy or Agentgateway) to intercept and route traffic, either as a sidecar in the EPP pod or as a separate horizontally scalable service.routerlib(Library Chart): Encapsulates the core templates and default configurations for EPP andInferencePool. It is not deployable on its own.
Before installing the charts, ensure that the Gateway API Inference Extension CRDs are installed in your cluster. Refer to the getting started guide for installation instructions.
Standalone mode is useful when you want to run EPP as a local router/proxy for a specific model service, without integrating with a cluster-wide Gateway.
Deploys EPP with an Envoy sidecar proxy that intercepts incoming HTTP/gRPC traffic and routes it using EPP:
helm install my-standalone-router ./config/charts/llm-d-router-standalone \
--set router.modelServers.matchLabels.app=my-vllm-serviceDeploys EPP with an Agentgateway proxy. This mode requires disabling the InferencePool resource creation (create=false) and routes traffic directly to model servers:
helm install my-standalone-router ./config/charts/llm-d-router-standalone \
--set router.inferencePool.create=false \
--set router.proxy.proxyType=agentgateway \
--set router.modelServers.matchLabels.app=my-model-serviceDeploys the proxy as its own horizontally scalable Deployment and Service instead of as a sidecar in the EPP pod. The proxy reaches EPP over the EPP Service and fails open if EPP is unreachable, so the data path stays available across EPP active/passive failover:
helm install my-standalone-router ./config/charts/llm-d-router-standalone \
--set router.modelServers.matchLabels.app=my-vllm-service \
--set router.inferencePool.create=false \
--set router.proxy.mode=service \
--set router.proxy.replicas=3To deploy an InferencePool named vllm-qwen3-32b that selects model servers with the label app=vllm-qwen3-32b and routes to port 8000:
helm install vllm-qwen3-32b ./config/charts/llm-d-router-gateway \
--set router.modelServers.matchLabels.app=vllm-qwen3-32bTo deploy provider-specific resources (like health check policies or destination rules), specify the provider name:
helm install vllm-qwen3-32b ./config/charts/llm-d-router-gateway \
--set router.modelServers.matchLabels.app=vllm-qwen3-32b \
--set provider.name=gke # Options: [none, gke, istio]If you previously used the gateway-api-inference-extension Helm charts (either inferencepool or standalone), please note that the llm-d-router charts have been restructured.
To prevent accidental misconfiguration, the new charts include strict validation checks that will fail the installation if they detect legacy top-level keys in your values file or command-line flags.
You must migrate your values according to the following mapping:
| Legacy Key (Top-level) | New Key (Nested/Renamed) | Description |
|---|---|---|
inferenceExtension.* |
router.epp.* router.monitoring.* router.tracing.* router.latencyPredictor.* |
EPP configuration has been restructured and nested under the router block. |
inferencePool.* |
router.modelServers.* router.inferencePool.* |
Model server configuration and pool control flags have moved under router. |
inferenceObjectives |
router.inferenceObjectives |
Objectives list has moved under router. |
experimentalHttpRoute |
httpRoute |
Gateway HTTPRoute configuration has been renamed (Gateway chart only). |
For a detailed list of all new configuration options, refer to the Configuration & Customization section below.
Since both charts use routerlib under the hood, all configurations and customizations are shared under the router values block. EPP and the llm-d Router can be customized by grouping configuration blocks in your values.yaml file. Below is the complete documentation and reference for each component.
Core settings for the Endpoint Picker Proxy (EPP) container and pod, including scaling, images, command-line flags, custom environment variables, resources, and custom plugins configuration (pluginsCustomConfig).
Note
High Availability (HA) Modes:
When EPP is scaled (router.epp.replicas > 1):
- Active-Passive Mode (Default): The chart automatically enables the
--ha-enable-leader-electionflag. Only one leader replica active-routes traffic, coordinates lease status, and maintains absolute routing state, while other replicas act as warm standbys. - Active-Active Mode: You can explicitly disable leader-election by passing
ha-enable-leader-election: falseunderrouter.epp.flags. In this mode, all replicas process traffic concurrently.- Warning: In active-active mode, you must only use active-active compatible plugins—specifically plugins that pull real-time metrics/state dynamically from the backend model servers (such as the precise prefix cache, queue, and KV-cache utilization scorers). Avoid plugins that rely on local in-memory routing state, as this state is not synchronized across replicas.
| Parameter Name | Description | Default |
|---|---|---|
router.epp.parsers |
List of request parser types for EPP. Options: [openai-parser, anthropic-parser, vllmgrpc-parser, vllmhttp-parser, passthrough-parser]. Empty for auto-selection. |
[] |
router.epp.replicas |
Number of EPP replicas. Set > 1 to enable multi-replica EPP. | 1 |
router.epp.extProcPort |
Port EPP uses for external processing gRPC communication. | 9002 |
router.epp.image.registry |
EPP container image registry. | ghcr.io/llm-d |
router.epp.image.repository |
EPP container image repository. | llm-d-router-endpoint-picker-dev |
router.epp.image.tag |
EPP container image tag. | main |
router.epp.image.pullPolicy |
EPP container image pull policy. | Always |
router.epp.env |
Extra environment variables for EPP container. | [] |
router.epp.extraContainerPorts |
Extra ports to expose on the EPP container. | [] |
router.extraServicePorts |
Extra ports to expose on the EPP Service. | [] |
router.clusterDomain |
Kubernetes cluster DNS domain used to build in-cluster Service FQDNs. | cluster.local |
router.epp.flags |
Map of command-line flags passed directly to the EPP binary. | {} |
router.epp.affinity |
Affinity rules for EPP pods. | {} |
router.epp.tolerations |
Tolerations for EPP pods. | [] |
router.epp.resources |
EPP container resource requests and limits. | requests.cpu: "8", requests.memory: 8Gi, limits.memory: 16Gi |
router.epp.pluginsConfigFile |
EPP plugins configuration file name. | default-plugins.yaml |
router.epp.pluginsCustomConfig |
Inline custom YAML configuration for EPP plugins. | {} |
router.epp.volumes |
Extra volumes for EPP pod. | [] |
router.epp.volumeMounts |
Extra volume mounts for EPP container. | [] |
To fully customize the EPP core container and pod (e.g., HA scaling, custom image, debugging flags, custom environment variables, custom plugin scheduling, and resource allocations), define the router block in your values.yaml as follows:
router:
epp:
# Run EPP in multi-replica mode
replicas: 3
image:
registry: my-registry.io
repository: my-epp-dev
tag: v1.2.3
pullPolicy: IfNotPresent
extProcPort: 9002
parser: vllmgrpc-parser
flags:
# Enable debug logging (verbosity 3)
v: 3
tracing: true
# Enable active-passive mode (one active leader, the other two are standby).
ha-enable-leader-election: true
env:
- name: FEATURE_FLAG_ENABLED
value: "true"
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
resources:
requests:
cpu: "8"
memory: 16Gi
limits:
memory: 32Gi
pluginsConfigFile: "custom-plugins.yaml"
pluginsCustomConfig:
custom-plugins.yaml: |
apiVersion: inference.networking.x-k8s.io/v1alpha1
kind: EndpointPickerConfig
plugins:
- type: queue-scorer
- type: custom-scorer
parameters:
threshold: 64
schedulingProfiles:
- name: default
plugins:
- pluginRef: queue-scorer
- pluginRef: custom-scorer
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-central1-a
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
volumeMounts:
- mountPath: /models
name: model-volume
volumes:
- name: model-volume
emptyDir: {}Configuration for the backend model servers that EPP routes traffic to. These settings are used by EPP to parse requests and route traffic correctly.
| Parameter Name | Description | Default |
|---|---|---|
router.modelServers.matchLabels |
REQUIRED (when create=true). Label selector to match model server pods. |
{} |
router.modelServers.type |
Type of model servers in the pool. Options: [vllm, sglang, triton-tensorrt-llm, trtllm-serve, triton]. |
vllm |
router.modelServers.protocol |
Protocol used by model servers. Options: [http, grpc]. |
http |
router.modelServers.targetPorts |
Port(s) EPP routes traffic to on the model servers. | [{number: 8000}] |
router.modelServers.targetPortNumber |
Legacy fallback port number for GKE health check policies. | 8000 |
router:
modelServers:
# Label selector to match your model server pods
matchLabels:
app: my-sglang-deployment
type: sglang
protocol: grpc
targetPorts:
- number: 50051Settings for managing the InferencePool Kubernetes resource and optional InferenceObjective priority routing rules.
Important
inferenceObjectives can only be configured when inferencePool.create is true. Creating InferenceObjectives without creating an InferencePool is not supported and will trigger a chart validation error.
| Parameter Name | Description | Default |
|---|---|---|
router.inferencePool.create |
Whether to create the InferencePool resource. Set to false in standalone mode for Service-backed routing. |
true |
router.inferencePool.failureMode |
EPP failure mode when external processing fails (configured on the pool). Options: [FailOpen, FailClosed]. |
FailOpen |
router.inferenceObjectives |
List of names and priorities to create optional InferenceObjective resources. |
[] |
router:
inferencePool:
# Enable or disable InferencePool resource creation (false in standalone service-backed mode)
create: true
# If EPP fails to process the request, route anyway (FailOpen) or fail the request (FailClosed)
failureMode: FailClosed
# Optional: Define InferenceObjective(s) for this InferencePool
inferenceObjectives:
- name: high-priority
priority: 100
- name: background-batch
priority: 10Configures metrics scraping via Prometheus (compatible with Google Managed Prometheus or standard Prometheus Operator) and OpenTelemetry distributed tracing endpoints.
| Parameter Name | Description | Default |
|---|---|---|
router.monitoring.provider.name |
Metrics provider. Options: [gmp, prometheusoperator]. |
prometheusoperator |
router.monitoring.provider.gmp.autopilot |
Set to true if deploying GMP on GKE Autopilot. |
false |
router.tracing.enabled |
Enable OpenTelemetry tracing for EPP. | false |
router.tracing.otelExporterEndpoint |
OTLP gRPC collector endpoint. | http://localhost:4317 |
router.tracing.sampling.sampler |
Trace sampler type. | parentbased_traceidratio |
router.tracing.sampling.samplerArg |
Sampler argument (e.g., sampling ratio "0.1"). |
"0.1" |
router:
monitoring:
interval: "10s"
provider:
name: "gmp" # Use Google Managed Prometheus (GMP)
gmp:
autopilot: true # Enable if running on GKE Autopilot
prometheus:
enabled: true
auth:
enabled: true # Requires token authorization to scrape metrics on port 9090
tracing:
enabled: true
otelExporterEndpoint: "http://otel-collector.monitoring.svc.cluster.local:4317"
sampling:
sampler: "parentbased_traceidratio"
samplerArg: "0.1" # Sample 10% of requestsRuns a tokenizer sidecar that EPP queries to tokenize incoming requests, enabling precise, token-count-aware routing policies (e.g., precise prefix-cache matching).
The sidecar runs vLLM's vllm launch render <modelName> and exposes /v1/completions/render and /v1/chat/completions/render over loopback HTTP. Wire EPP to it via router.epp.pluginsCustomConfig with type: token-producer and vllm:.
| Parameter Name | Description | Default |
|---|---|---|
router.tokenizer.enabled |
Enable the vLLM /render tokenizer sidecar in the EPP deployment. |
false |
router.tokenizer.modelName |
REQUIRED when enabled. Model name passed as the first positional arg to the sidecar's vllm launch render command. |
"" |
router.tokenizer.image.registry |
Tokenizer container image registry. | docker.io |
router.tokenizer.image.repository |
Tokenizer container image repository. | vllm/vllm-openai-cpu |
router.tokenizer.image.tag |
Tokenizer container image tag. | v0.19.1 |
router.tokenizer.image.pullPolicy |
Tokenizer container image pull policy. | IfNotPresent |
router.tokenizer.port |
Container port the sidecar listens on. | 8000 |
router.tokenizer.command |
Override container command. Empty renders ["vllm", "launch", "render"]. |
[] |
router.tokenizer.args |
Override container args. Empty renders ["<modelName>", "--port=<port>"]. |
[] |
router.tokenizer.env |
Extra environment variables (e.g., HuggingFace token). | [HF_TOKEN] |
router.tokenizer.resources |
Tokenizer container resource requests and limits. | requests.cpu: "4", requests.memory: 8Gi |
router.tokenizer.volumeMounts |
Extra volume mounts for the tokenizer container. | [] |
router.tokenizer.readinessProbe |
Readiness probe spec. | httpGet /health on the render-http named port |
router:
epp:
volumes:
- name: model-cache-volume
persistentVolumeClaim:
claimName: pvc-model-cache
tokenizer:
enabled: true
modelName: "Qwen/Qwen3-32B"
image:
registry: docker.io
repository: vllm/vllm-openai-cpu
tag: v0.19.1
env:
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: my-hf-token-secret
key: token
- name: HF_HOME
value: /models
resources:
requests:
cpu: "4"
memory: 8Gi
limits:
memory: 16Gi
volumeMounts:
# If pre-loading models from an external volume:
- mountPath: /models
name: model-cache-volumeThe llm-d-uds-tokenizer sidecar (gRPC over a Unix Domain Socket) is no longer
templated by this chart; the render backend above supersedes it. If you still
need it during migration, set it up in two steps.
Step 1 — inject the sidecar into the EPP deployment. The chart does not render it, so patch it in yourself (e.g. via kustomize). The EPP container must mount the shared socket volume so it can reach the tokenizer:
spec:
template:
spec:
containers:
# Existing EPP container — add the shared socket mount.
- name: epp
volumeMounts:
- name: tokenizer-uds
mountPath: /tmp/tokenizer
# The deprecated sidecar.
- name: tokenizer-uds
image: ghcr.io/llm-d/llm-d-uds-tokenizer:vllm-v0.19.1
env:
- name: TOKENIZERS_DIR
value: /tokenizers
- name: HF_HOME
value: /tokenizers
# Required for gated/private tokenizers on HuggingFace.
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: llm-d-hf-token
key: HF_TOKEN
volumeMounts:
- name: tokenizers
mountPath: /tokenizers
- name: tokenizer-uds
mountPath: /tmp/tokenizer
volumes:
- name: tokenizers
emptyDir: {}
- name: tokenizer-uds
emptyDir: {}Step 2 — point EPP at the socket. Configure the tokenizer plugin via
router.epp.pluginsCustomConfig (the chart writes this into the EPP config
mounted at /config). Select the deprecated UDS backend with
udsTokenizerConfig:
router:
epp:
pluginsCustomConfig:
custom-plugins.yaml: |
plugins:
- type: token-producer
parameters:
modelName: "Qwen/Qwen3-32B"
udsTokenizerConfig:
socketFile: /tmp/tokenizer/tokenizer-uds.socketEnables latency predictor containers inside the EPP deployment to feed metrics to a latency scorer plugin, allowing EPP to route traffic based on real-time predicted latencies.
| Parameter Name | Description | Default |
|---|---|---|
router.latencyPredictor.enabled |
Enable latency-based routing (requires extra Borg/training setup). | false |
router.latencyPredictor.trainingServer.image |
Latency training server image configuration. | |
router.latencyPredictor.predictionServers.image |
Latency prediction server image configuration. | |
router.latencyPredictor.eppEnv |
EPP tuning variables for Latency Predictor. |
router:
latencyPredictor:
enabled: true
trainingServer:
image:
registry: my-company-docker.pkg.dev/k8s-staging-images
repository: latency-training-server
tag: latest
predictionServers:
image:
registry: my-company-docker.pkg.dev/k8s-staging-images
repository: latency-prediction-server
tag: latest
eppEnv:
LATENCY_MAX_SAMPLE_SIZE: "20000"
LATENCY_MAX_CONCURRENT_DISPATCHES: "48"
LATENCY_COALESCE_WINDOW_MS: "2"Depending on your target deployment architecture (Gateway Mode vs Standalone Mode), utilize the following specific configurations.
Configures routing policies, target Gateway interfaces, and priority objectives exclusive to the Gateway implementation chart (llm-d-router-gateway).
| Parameter Name | Description | Default |
|---|---|---|
provider.name |
Name of Gateway implementation. Options: [none, gke, istio]. |
none |
provider.istio.destinationRule.host |
Custom host value for Istio DestinationRule. | "" |
provider.istio.destinationRule.trafficPolicy.connectionPool |
Connection pool settings for Istio DestinationRule. | {} |
provider.gke.preferredBackends.enabled |
Enable Preferred Backends high availability routing for GKE. | false |
provider.gke.preferredBackends.preferredReplicas |
Replica count for primary active pod ordinals pinned to PREFERRED tier. | 1 |
provider.gke.preferredBackends.defaultReplicas |
Replica count for standby backup pod ordinals pinned to DEFAULT tier. | 1 |
provider.gke.preferredBackends.balancingMode |
Load balancing calculation mode (RATE, UTILIZATION, CONNECTION). |
RATE |
provider.gke.preferredBackends.maxRatePerEndpoint |
Maximum requests per second per pod instance before spilling over. | 100 |
provider.gke.preferredBackends.capacityScalerPercent |
Effective capacity ceiling percentage (0 to 100). |
100 |
httpRoute.create |
Deploy an HTTPRoute resource as part of the gateway chart. |
false |
httpRoute.inferenceGatewayName |
Target Gateway name for the HTTPRoute. |
inference-gateway |
httpRoute.inferenceGatewayNamespace |
Target Gateway namespace for the HTTPRoute. |
"" |
httpRoute.requestTimeout |
Request timeout for the HTTPRoute (Istio/non-GKE only). |
300s |
provider:
name: gke # Use GKE gateway implementation
gke:
preferredBackends:
enabled: true
preferredReplicas: 1
defaultReplicas: 1
balancingMode: RATE
maxRatePerEndpoint: 100
capacityScalerPercent: 100
httpRoute:
create: true
inferenceGatewayName: "my-company-gateway"
inferenceGatewayNamespace: "gateway-infra"
requestTimeout: "120s"Configures EPP to run with a proxy (Envoy proxy or Agentgateway proxy) that intercepts and routes client traffic directly to model servers (exclusive to llm-d-router-standalone). The proxy runs as a sidecar in the EPP pod by default, or as a separate horizontally scalable service when router.proxy.mode=service (Envoy only).
| Parameter Name | Description | Default |
|---|---|---|
router.proxy.enabled |
Enable the proxy (Envoy or Agentgateway) in front of EPP. | false |
router.proxy.proxyType |
Type of proxy. Options: [envoy, agentgateway]. |
envoy |
router.proxy.mode |
Proxy deployment mode. sidecar runs the proxy in the EPP pod; service runs it as its own horizontally scalable Deployment and Service reaching EPP over the EPP Service (Envoy only). |
sidecar |
router.proxy.replicas |
Replica count for the proxy Deployment when mode=service. |
2 |
router.proxy.failOpen |
Whether the proxy passes traffic through (fail-open) when EPP is unreachable. | true |
router.proxy.name |
Name of the sidecar container. | "" |
router.proxy.image |
Sidecar container image. | "" |
router.proxy.imagePullPolicy |
Sidecar container image pull policy. | IfNotPresent |
router.proxy.command |
Sidecar container command. | "" |
router.proxy.args |
Sidecar container arguments. | [] |
router.proxy.env |
Sidecar container environment variables. | [] |
router.proxy.ports |
Sidecar container ports. | [] |
router.proxy.livenessProbe |
Sidecar container liveness probe. | {} |
router.proxy.readinessProbe |
Sidecar container readiness probe. | {} |
router.proxy.resources |
Sidecar container resource requests and limits. | {} |
router.proxy.volumeMounts |
Sidecar container volume mounts. | [] |
router.proxy.volumes |
Sidecar container volumes. | [] |
router.proxy.configMapData |
Key-value pairs to include in a ConfigMap created for the sidecar. | {} |
To deploy EPP in standalone mode with an Agentgateway sidecar routing traffic directly to model servers matching the label app=my-model-service (bypassing InferencePool creation):
router:
inferencePool:
create: false # Disable InferencePool creation
modelServers:
matchLabels:
app: "my-model-service"
targetPorts:
- number: 8000
proxy:
enabled: true
proxyType: agentgateway
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
memory: 8Gi