Skip to main content
Three serving layers run the same vLLM engine on this platform: raw vLLM (serving/raw-vllm), KServe InferenceService (serving/kserve), and llm-d LLMInferenceService (serving/llm-d). They are not stacked: a model is served on one of them. This runbook is how an operator picks the layer for a model and moves a model between them. The architecture-level comparison is Raw vLLM vs KServe; the per-layer operational gotchas live in vllm-serving.md, kserve.md, and kserve-modelcar.md.

Pick the layer

Decision shortcut:
  • Single benchmarked endpoint, minimal moving parts: raw vLLM.
  • A fleet of models with a uniform CR, stable per-model URLs, declarative canary, native scale-to-zero: KServe.
  • A model too big for one GPU, or one that needs KV-aware/disaggregated serving: llm-d (validated on the multi-GPU substrate; on a single GPU the manifest demonstrates the mechanism, with the throughput win observable once a second GPU is present).

What changes between layers (same model, same engine)

Feature gates and bring-up

Each layer’s Argo Applications exist only when its feature group is enabled in environments/ai-dev/config.yaml, then materialized with make resolve-groups && make root (see staged-bring-up.md). The GPU-bearing apps are manual-sync (the cost gate):

Move a model from raw vLLM to KServe

The same Qwen2.5-0.5B-Instruct is already served on both layers, so this is the worked path. Bring up the KServe variant; the raw-vLLM Deployment is independent and stays at replicas:0.
  1. Confirm the gate + controller. kserve: true in config.yaml; the kserve group synced (cert-manager + KServe controller Healthy). The Gateway (serving/kserve/gateway.yaml, gatewayClassName: agentgateway) must exist for the ISVC URL to resolve.
  2. Use a custom-container predictor. serving/kserve/inferenceservice.yaml runs vLLM verbatim (container named kserve-container) instead of model+runtime. The model+runtime binding force-injects --model=/mnt/models and requires a storageUri + storage-initializer, which the HF-429 egress breaks (kserve.md §4-5). Keep --model=/models/qwen against the pre-staged kserve-model-cache PVC, or switch to the digest-pinned oci:// modelcar for big models (kserve-modelcar.md).
  3. Set an explicit limits.cpu. KServe defaults a cpu limit of 1 when unset; any requests.cpu>1 then makes the Deployment invalid and it never updates (kserve.md §3).
  4. Sync and validate through the gateway:
  5. Re-point the tenant alias (optional). To route the LiteLLM alias to the KServe path instead of raw vLLM, change api_base in the model’s model_list entry (platform/litellm/values.yaml) to the predictor Service (http://qwen-cpu-predictor.kserve:80) or the gateway, then argocd app sync litellm. The tenant-facing alias and virtual keys are unchanged.
KServe gives native minReplicas:0 scale-to-zero; idle-pause a running predictor with the serving.kserve.io/stop annotation (kserve.md §8), not make vllm-down.

Move a model to llm-d

llm-d is a contained, advanced scale-out path, isolated in the llm-d namespace with its own gateway/GIE, not a replacement for the reference agentgateway+GIE path. serving/llm-d/llminferenceservice.yaml serves the same Qwen2.5-0.5B-Instruct with prefill/decode disaggregation and KV-aware routing.
  1. Enable + materialize: llm-d: true -> make resolve-groups && make root PROFILE=full so the llm-d Application exists (manual-sync, ServerSideApply=true for the large CRD).
  2. Confirm GPU capacity. Disaggregation = 1 prefill GPU + 1 decode GPU. On a single GPU (GPUS_ALL_REGIONS=1) only one pool schedules, so the disaggregation/KV-routing throughput win is not yet observable; the manifest demonstrates the mechanism. The throughput benchmark runs on the 2-GPU substrate (see serving/llm-d/README.md).
  3. Sync the path:
Weights come from the hf://Qwen/Qwen2.5-0.5B-Instruct URI (KServe’s storage layer stages them), not a hand-wired PVC like raw vLLM. The router (scheduler: {} + route: {}) creates the bundled InferencePool + EPP and the HTTPRoute on the isolated llm-d-gateway.

Switching back / tearing down a layer

Layers are independent, so “switching off” a layer is scaling its workload to $0 and (optionally) re-pointing the LiteLLM alias back:
To remove a whole layer’s Applications, disable its feature flag (make resolve-groups) or delete its catalog group; full ordered teardown (Gateways before the cluster to avoid orphaned LBs) is in teardown.md.