Skip to main content
BlogDevOps

llm-d di Kubernetes: Distributed Inference dan Hardening v0.10

llm-d menambah router yang paham LLM di atas vLLM dan Gateway API Inference Extension. Saya membaca catatan v0.10, values file, dan config shutdown-nya.

Matthews Wong

Software Engineer

Oktober 202612 menit baca
llm-d di Kubernetes: Distributed Inference dan Hardening v0.10

Ringkasan Utama

llm-d adalah proyek CNCF Sandbox yang menambahkan router yang paham LLM, prefix-cache routing, prefill/decode disaggregation, dan flow control di atas vLLM dan Kubernetes Gateway API Inference Extension. Rilis v0.10.0 berfokus pada operational hardening: panduan drain, image bertanda tangan, dan pembersihan deprecation, bukan fitur baru.

Service Kubernetes biasa mengirim request ke pod vLLM mana pun yang ia suka, dan itu pilihan yang buruk untuk server LLM. Satu pod mungkin sudah menyimpan prefix prompt percakapan di KV cache-nya, pod lain sedang tertimbun prefill panjang, dan Service tidak bisa membedakannya. llm-d ada untuk memperbaiki keputusan routing itu, dan rilis v0.10.0 pada 29 September adalah yang pertama dengan headline membuat jalur production menjadi membosankan, bukan menambah fitur.

Ini hasil membaca, bukan benchmark. Saya membaca release notes v0.8.0, v0.9.0 dan v0.10.0, halaman proyek CNCF, dokumentasi architecture dan quickstart, serta values file dan komponen kustomize asli di repository. Saya tidak menjalankannya di cluster GPU, jadi setiap angka di bawah dikutip dari file yang saya baca, dan bila dokumentasi diam saya katakan begitu.

Apa yang ditambahkan llm-d di atas vLLM

README proyek menyebut llm-d sebagai distributed inference serving stack untuk Kubernetes, didirikan oleh Red Hat, Google Cloud, IBM Research, CoreWeave dan NVIDIA, berlisensi Apache 2.0. Ia berada di atas model server seperti vLLM dan SGLang, bukan menggantikannya. Cara membacanya yang berguna adalah lewat kepemilikan: siapa memutuskan apa.

LayerMemilikiTidak memiliki
Model server (vLLM, SGLang)Menjalankan model di GPU, TPU atau HPU, KV cache-nya sendiri, API bergaya OpenAIMemutuskan replica mana yang menerima sebuah request
llm-d Router (proxy plus Endpoint Picker)Load balancing yang paham LLM, request queuing, penegakan policy; EPP menilai pod memakai metrics real-time, KV-cache affinity dan policy yang dikonfigurasiMenjalankan inference; ia tidak pernah menyentuh weights
Gateway API Inference ExtensionResource InferencePool dan kontrak ext-proc antara gateway dan Endpoint PickerPicker kelas production; dokumentasinya sendiri menyebut picker bawaan sebagai EPP untuk conformance test dan menunjuk llm-d-router untuk pemakaian nyata
Gateway atau proxy (Envoy Gateway, kgateway, GKE Gateway)Menerima traffic, mencocokkan HTTPRoute, meneruskan detail request ke EPPMengetahui apa pun tentang KV cache atau beban prefill

README mengelompokkan tambahannya ke lima area: intelligent routing, manajemen KV-cache dengan tiered offloading, serving model besar lewat prefill/decode disaggregation dan wide expert parallelism, fitur operasional seperti flow control dan autoscaling berbasis SLO, serta batch processing. Halaman CNCF mencantumkan kemampuan yang sama dan mencatat bahwa llm-d diterima di CNCF pada 12 Maret 2026 dengan tingkat Sandbox, yang penting untuk menakar seberapa besar kita bersandar padanya.

Jalur request: Gateway, EPP dan InferencePool

Dokumentasi Inference Extension menjelaskan alurnya dalam lima langkah, dan halaman architecture llm-d mengonfirmasi bentuk yang sama dengan proxy-nya sendiri yang sesuai dengan extension tersebut:

  1. Gateway memilih InferencePool atau Service memakai API Gateway dan HTTPRoute yang biasa.
  2. Jika targetnya InferencePool, gateway mengirim informasi request ke Endpoint Picker milik pool itu lewat ext-proc.
  3. EPP mengumpulkan metrics dari endpoint di pool, mungkin secara asynchronous.
  4. EPP memberi tahu gateway endpoint mana yang harus menerima request.
  5. Gateway meneruskan request ke endpoint tersebut.

InferencePool mengelompokkan pod model server untuk satu base model lewat label selector, dan label itu harus persis sama dengan label di pod. Hanya itu mekanisme discovery-nya: router tidak mendaftarkan backend, ia memantau label. Jadi picker adalah control plane kedua di jalur request, dan semua soal perilaku saat gagal di bagian berikutnya bersumber dari fakta itu.

Menjalankan optimized baseline

Quickstart v0.10 men-deploy Optimized Baseline dalam Standalone Mode, di mana chart router menyertakan proxy Envoy sendiri sehingga tidak perlu gateway terpisah. Prasyaratnya kubectl, helm, node GPU untuk konfigurasi default vLLM dan NVIDIA, serta token HuggingFace. Berikut langkah-langkahnya seperti di halaman itu, diringkas:

# The v0.10 quickstart (Optimized Baseline, Standalone Mode), condensed.
git clone https://github.com/llm-d/llm-d.git && cd llm-d
git checkout main            # I would check out a release tag or branch instead; see the Monday section

export REPO_ROOT=$(realpath $(git rev-parse --show-toplevel))
source ${REPO_ROOT}/guides/env.sh      # defines GAIE_URL, ROUTER_STANDALONE_CHART, ROUTER_CHART_VERSION
export GUIDE_NAME="quickstart"
export NAMESPACE=llm-d-quickstart

# 1. Inference Extension CRDs (InferencePool lives here, not in llm-d)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/${GAIE_URL}/v1-manifests.yaml

# 2. Namespace + the weights secret
kubectl create namespace ${NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic llm-d-hf-token \
  --from-literal="HF_TOKEN=${HF_TOKEN}" \
  --namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f -

# 3. The router: an Envoy proxy plus the Endpoint Picker, from an OCI Helm chart
helm install ${GUIDE_NAME} ${ROUTER_STANDALONE_CHART} \
  -f guides/recipes/router/base.values.yaml \
  -f guides/optimized-baseline/router/optimized-baseline.values.yaml \
  -n ${NAMESPACE} --version ${ROUTER_CHART_VERSION}

# 4. The model servers: vLLM pods the router discovers by label
kubectl apply -n ${NAMESPACE} -k guides/optimized-baseline/modelserver/gpu/vllm/base/

# 5. Send a request to the router service, not to a pod
export IP=$(kubectl get service ${GUIDE_NAME}-epp -n ${NAMESPACE} -o jsonpath='{.spec.clusterIP}')
# then, from a pod inside the cluster:
#   curl -X POST http://${IP}/v1/completions -H 'Content-Type: application/json' \
#     -d '{"model": "Qwen/Qwen3-32B", "prompt": "How are you today?"}'

Halaman quickstart, sebagaimana saya baca, menyebut default 8 replica Qwen3-32B, sedangkan halaman guide yang saya baca terpisah menyebut default 2 replica dengan tensor parallelism 2, total empat GPU, dan patch kustomize di repository menetapkan replicas 2 dengan tensor-parallel-size 2. Dokumentasinya tidak sepakat satu sama lain, jadi tentukan ukuran dari patch yang benar-benar Anda apply, dan sesuaikan replicas serta tensor parallelism bila cluster Anda lebih kecil. Akselerator lain punya overlay sendiri di guides/optimized-baseline/modelserver/.

Apa yang sebenarnya diputuskan router baseline

File yang menarik bukan perintah Helm-nya, melainkan EndpointPickerConfig di dalam values. Optimized Baseline menjalankan tepat dua plugin pengambil keputusan, ditambah dua plugin producer yang terdaftar di sampingnya:

# guides/optimized-baseline/router/optimized-baseline.values.yaml (excerpt)
router:
  epp:
    pluginsConfigFile: "optimized-baseline-plugins.yaml"
    pluginsCustomConfig:
      optimized-baseline-plugins.yaml: |
        apiVersion: llm-d.ai/v1
        kind: EndpointPickerConfig
        # peakPrefillThroughput defaults to 15928, calibrated for Qwen3-32B on
        # H100 80 GB at TP=2. Other hardware or models: measure your own.
        plugins:
        - type: approx-prefix-cache-producer
        - type: inflight-load-producer
        - type: prefix-cache-affinity-filter
        - type: token-load-scorer
        schedulingProfiles:
        - name: default
          plugins:
          - pluginRef: prefix-cache-affinity-filter
          - pluginRef: token-load-scorer
  modelServers:
    matchLabels:
      llm-d.ai/guide: "optimized-baseline"   # the InferencePool selector

prefix-cache-affinity-filter mempersempit kandidat ke endpoint yang kemungkinan memakai ulang prefix request, dengan override sadar-saturasi yang menyebar beban ketika endpoint panas, dan token-load-scorer menilai berdasarkan total beban prefill token per model server. Komentar di file itu bagian yang jujur: default peakPrefillThroughput 15928 dikalibrasi untuk Qwen3-32B di H100 80 GB dengan TP=2, dan selain itu sebaiknya ukur nilai sendiri dengan calibration recipe di repository. Menyalin default ke hardware lain menggeser titik di mana router menganggap sebuah endpoint jenuh.

Base values chart router juga menetapkan dasar operasional yang layak ditiru: Envoy dengan log level warn dan --concurrency 8 (komentarnya menyebut trace logging bawaan chart sangat mahal saat beban tinggi dan concurrency yang tidak diatur membuat cgroup oversubscribed), serta tracing disampling 0.1 lewat parentbased_traceidratio.

Tiga rilis, satu arah

Release notes terbaca sebagai sebuah urutan. Tanggal sesuai halaman releases, yang tidak mencantumkan tahun.

RilisBase vLLMYang ditekankan catatannya
v0.8.0 (24 Jun)0.23.0 (dari 0.19.1)Multimodal serving, batch gateway dan flow control naik ke production; cakupan akselerator diperluas; image beralih ke image vLLM upstream; image EPP dan sidecar diganti nama menjadi llm-d-router.
v0.9.0 (17 Aug)0.26.0 (dari 0.23.0)Overlay prefill/decode GB200 untuk GKE A4X; autoscaling pindah dari Prometheus Adapter ke KEDA; setting IPv6; request yang di-drain saat shutdown dipetakan ke 503.
v0.10.0 (29 Sep)0.30.0 (dari 0.26.0)Bertema rollouts, HA dan failure behaviour; image rilis ditandatangani cosign; panduan graceful shutdown; WVA di-deprecate demi KEDA plus EPP; beberapa repository dan image dipindah.

Catatan v0.10.0 menyebut temanya membuat jalur production aman dan membosankan, lalu menambahkan bahwa tujuan di tracking issue tidak sepenuhnya tercapai. Item konkretnya kecil dan operasional: panduan rollouts, panduan high-availability preferred backends untuk GKE, panduan ops graceful shutdown dan request draining, mitigasi startup-time KEDA lewat HPA stabilization window, dan perbaikan CI agar smoke test menolak HTTP error. Baru image inference server yang tercakup penandatanganan cosign; catatannya menyebut image router dan sidecar belum termasuk. Image dari pull request terbuka masuk ke quay.io dan sengaja tidak ditandatangani.

Apa yang terjadi pada request saat sebuah pod mati

Artefak hardening paling berguna yang saya temukan adalah komponen kustomize graceful-shutdown, karena ia menuliskan timeline dan sebuah invariant di komentar. Saat pod dihapus, ia langsung keluar dari InferencePool sementara preStop sleep memberi waktu bagi router dan endpoint Service untuk berhenti mengirim traffic, baru kemudian vLLM mulai melakukan drain:

# recipes/modelserver/components/graceful-shutdown, as documented in the file:
#   t=0s    Pod is Terminating and leaves the InferencePool; preStop sleep starts
#   t=15s   SIGTERM. vLLM with --shutdown-timeout=45 rejects new requests,
#           lets in-flight ones finish
#   t=60s   Drain deadline: vLLM aborts anything still running
#   t=75s   SIGKILL if the process is still alive
#
# The invariant the file states, and the one that bites when you edit one number:
#   terminationGracePeriodSeconds >= preStop sleep (15)
#                                    + --shutdown-timeout (45)
#                                    + teardown buffer (15)
- op: add
  path: /spec/template/spec/terminationGracePeriodSeconds
  value: 75
- op: add
  path: /spec/template/spec/containers/0/lifecycle
  value:
    preStop:
      exec:
        command: ["/bin/sleep", "15"]

Invariant itu jebakan yang menunggu. Komponen ini mengatur preStop sleep dan terminationGracePeriodSeconds tetapi sengaja tidak mengatur --shutdown-timeout, karena patch tiap guide mengganti seluruh daftar args dan patch dijalankan setelah komponen. Jadi flag itu tinggal di args vLLM milik guide, dan siapa pun yang menaikkannya demi melindungi generation yang lebih panjang harus menaikkan grace period dalam perubahan yang sama, atau Kubernetes mengirim SIGKILL di tengah drain.

# recipes/router/base.values.yaml ships:
router:
  inferencePool:
    failureMode: "FailOpen"   # EPP down -> the gateway picks any endpoint

# The Inference Extension API reference says an unset failureMode means
# "FailClose": the parent drops the request when the Endpoint Picker fails.
# Wrong: assuming the router fails the way your last gateway did.
# Right: read the rendered InferencePool and decide which failure you prefer.

Perilaku gagal kedua adalah router itu sendiri. API reference Inference Extension mendokumentasikan failureMode pada referensi endpoint picker dengan dua nilai, dan menyebut nilai yang tidak diset berarti FailClose, di mana gateway membuang request ketika picker gagal. Base values router llm-d menetapkan FailOpen, di mana gateway meneruskan request ke endpoint pilihannya sendiri. Keduanya tidak salah. FailOpen tetap menjawab saat EPP mati tetapi kehilangan routing yang sadar cache dan beban tepat ketika Anda sedang bermasalah; FailClose melindungi backend dari routing buta tetapi mengubah outage EPP menjadi outage penuh.

File base values mengirim EPP dengan replicas 1 dan failureMode FailOpen. Saya tidak menemukan dokumentasi yang menjelaskan apa yang terjadi pada scoring state bila ada replica EPP kedua, jadi saya tidak akan menaikkannya begitu saja. Tentukan failure mode dengan sengaja, lakukan load test dengan mematikan pod EPP, dan lihat kualitas routing yang Anda dapatkan selama ia tiada.

Trade-off yang akan saya catat sebelum mengadopsinya

Tidak ada di sini yang menjadi alasan menghindari llm-d, tetapi beberapa hal di file-file itu menganjurkan kehati-hatian sebelum menaruhnya di depan traffic production:

  • Tingkat Sandbox. Halaman CNCF mencantumkan llm-d di Sandbox, diterima 12 Maret 2026. Itu tanda proyek yang aktif, bukan jaminan stabilitas.
  • Churn di release notes. v0.10.0 men-deprecate guide autoscaler WVA, image llm-d-cuda dan llm-d-aws, serta llmd-fs-connector, dan memindahkan llm-d-kv-cache ke llm-d-router. Mengikuti main berarti menyerap penggantian nama.
  • Default yang tidak di-pin. Di guides/env.sh, rilis Inference Extension default ke latest, versi chart router ke v0 dan tag image endpoint-picker ke main.
  • Tuning spesifik hardware. Konstanta scheduling default dikalibrasi untuk satu setup referensi, dan saya tidak menemukan angka seberapa besar pengaruhnya di tempat lain.

Saya juga tidak menemukan angka throughput atau latency di sumber yang saya baca yang bisa saya ulang dengan bertanggung jawab, jadi saya tidak mengulang apa pun. Dokumentasi menggambarkan well-lit path sebagai recipe yang diuji dan di-benchmark, dan catatan v0.9.0 menyebut benchmark yang diperbarui untuk optimized baseline; bila keuntungan routing menentukan keputusan Anda, ukur di model dan campuran traffic Anda sendiri.

Yang dikerjakan hari Senin

Jika Anda sudah menjalankan vLLM di Kubernetes, eksperimen termurah yang berguna adalah Optimized Baseline di namespace sementara, diperlakukan sebagai latihan rollout, bukan uji performa:

  1. Checkout release tag atau release branch repository, bukan main, dan pin tiga variabel versi di bawah sebelum install.
  2. Install Optimized Baseline, lalu kirim sepuluh request dengan prefix panjang yang sama dan baca counter prefix-cache vllm dari tiap pod, seperti saran langkah verifikasi guide.
  3. Hapus satu pod model server saat ada beban dan perhatikan request yang gagal; bandingkan dengan timeline drain di atas.
  4. Hapus pod EPP saat ada beban dan catat apa yang dilakukan failureMode pilihan Anda terhadap latency dan error.
  5. Catat komponen deprecated mana di cluster Anda, bila ada, yang harus Anda migrasikan.
# guides/env.sh honours these, but defaults every one to a moving target:
#   GAIE_VERSION         defaults to "latest"
#   ROUTER_CHART_VERSION defaults to "v0"
#   ROUTER_EPP_VERSION   defaults to "main"
# Pin them to what you tested, in the shell that runs the install.
export GAIE_VERSION=<the Inference Extension release you tested>
export ROUTER_CHART_VERSION=<the router chart version you tested>
export ROUTER_EPP_VERSION=<the endpoint-picker image tag you tested>
source guides/env.sh
echo "CRDs from: ${GAIE_URL}"        # should no longer say releases/latest

# After install, confirm what the cluster actually holds.
kubectl get inferencepool -n "$NAMESPACE" -o yaml | grep -n failureMode
kubectl get deploy -n "$NAMESPACE" -o jsonpath='{range .items[*]}{.metadata.name}{"  "}{.spec.template.spec.terminationGracePeriodSeconds}{"\n"}{end}'

Perintah di bawah mem-pin versi dan membaca kembali apa yang sebenarnya ada di cluster. Ini pengecekan buatan saya di sekitar variabel repository, bukan bagian dari guide resmi.

llm-d layak dievaluasi bila biaya Anda didominasi prefix prompt yang berulang, karena ia memindahkan keputusan routing ke tempat state cache diketahui. Anggap v0.10 sebagai rilis yang menuliskan failure behaviour, bukan yang membuktikannya: pin versi, pilih FailOpen atau FailClose dengan sengaja, jaga grace period di atas drain timeout, dan ukur di traffic Anda sendiri.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apa itu llm-d dan apa bedanya dengan vLLM?

vLLM adalah model server yang menjalankan model di akselerator. llm-d adalah stack native Kubernetes yang berada di atas model server seperti vLLM dan SGLang dan menambahkan router yang paham LLM, prefix-cache routing, prefill/decode disaggregation, flow control, dan autoscaling. llm-d memutuskan replica mana yang menerima request; inference tetap dikerjakan vLLM.

Apa itu Gateway API Inference Extension?

Itu proyek Kubernetes yang mengubah gateway Gateway API yang mendukung Envoy ext-proc menjadi inference gateway. Ia mendefinisikan resource InferencePool dan kontrak dengan Endpoint Picker yang memilih endpoint terbaik per request memakai metrics model server seperti status prefix cache. llm-d menyediakan Endpoint Picker kelas production yang dibangun di atasnya.

Apa yang berubah di llm-d v0.10.0?

Release notes membingkai v0.10.0 sebagai operational hardening: rollouts, high availability, dan failure behaviour. Rilis ini menambah panduan graceful shutdown, menandatangani image rilis di ghcr.io dengan cosign, menaikkan vLLM ke 0.30.0, dan men-deprecate guide autoscaler WVA demi KEDA plus EPP. Catatannya juga menyebut tidak semua tujuan rilis tercapai.

Apa beda FailOpen dan FailClose pada InferencePool?

failureMode mengatur apa yang dilakukan gateway ketika Endpoint Picker gagal atau tidak merespons. FailOpen meneruskan request ke endpoint yang dipilih gateway sendiri, sedangkan FailClose membuang request. API reference Inference Extension menyebut nilai yang tidak diset berarti FailClose, dan base values router llm-d menetapkan FailOpen, jadi periksa nilai yang dipakai pool Anda.

Apakah llm-d sudah siap production?

llm-d adalah proyek CNCF Sandbox, diterima 12 Maret 2026, dan v0.10 berfokus pada hardening, yang menandakan proyek ini sedang mengerjakannya. Sandbox adalah tingkat kematangan awal, dan release notes menunjukkan deprecation serta penggantian nama yang masih berjalan. Pin versi, uji kegagalan pod dan router di bawah beban, dan ukur keuntungan routing di traffic Anda sendiri sebelum bergantung padanya.