Designs by Duhart All work
Rank
4 of 34
Tier
Tier A 90 of 120

23 — Solomon: Inference Serving, Kubernetes & Autoscaling

Solomon 5 of 6 · Solomon/solomon/serving, Solomon/genengine/serving, Solomon/k8s Stack: FastAPI, SSE + WebSocket streaming, continuous batching, KV cache, Prometheus, Docker, k3s, Helm, HPA Status: Live in-cluster. HPA observed scaling to 3 replicas under load.

Where this sits. This is where the ML project becomes an infrastructure project — and the part most directly aligned with a Google Cloud / platform engineering loop.


Where the ML project becomes an infrastructure project
Ingressk3s ingressTLS terminateNetworkPolicydefault denyWorkloadFastAPI podSSE + WebSocketContinuous batchshared forward passHPAscaled to 3, observedObservabilityPrometheusqueue depth, TTFTHelmone chart, values per env

Continuous batching so concurrent requests share a forward pass, SSE and WebSocket streaming so first-token latency is what the user feels, and an HPA observed scaling to three replicas under load, observed, not configured and assumed.

The serving layer

FastAPI exposing:

Why continuous batching is the interesting choice

Static batching makes the first request in a window wait for the window to close. Continuous batching lets a request join a batch already decoding and lets finished sequences leave without stalling the rest. It is the difference between throughput that looks good on a benchmark and latency that is acceptable to a user — and it is why p95 454 ms and 116 tok/s could both be promotion gates rather than a trade-off.

Kubernetes

k3s + Helm. Chart templates cover a multi-phase topology:

k8s/helm/templates/
  inference.yaml              the serving deployment + HPA
  mlflow.yaml                 in-cluster tracking
  phase2-orchestrator.yaml    request orchestration
  phase2-members.yaml         ensemble members
  phase2-retrieval.yaml       the Go retrieval tier
  phase2-search.yaml          search
  phase2-networkpolicy.yaml   east-west traffic restriction
  phase3-gend.yaml            generation daemon
  phase3-host-members.yaml    host-resident members
  secret.yaml

Load behavior

MetricResult
Requests198
Failures0
p95 latency2.7 s
SLO6 s
HPAscaled to 3 replicas under load

The blockers, written down

k8s/INGRESS.md records what could not be done from this host: the Cloudflare Tunnel hostnames are dashboard-managed, so the public hostname → the in-cluster service and the API hostname → the in-cluster service are manual steps, documented as such. BLOCKERS.md records that Phase 2 must restart its general-member to load the promoted weights.

Separating "done" from "blocked on something outside this host" — and naming the exact hostname and port — is the operational writing habit that shows up again in the gap registers.

Known defects, recorded not hidden

From CODE_REVIEW.md:

  1. SSE per-token decode drops inter-word spaces (server and WebSocket path).
  2. The engine swallows generation exceptions → an empty 200 followed by [DONE].
  3. Disconnect-cancellation is dead code → leaked generations when a client hangs up.
  4. make deploy under sudo strips KUBECONFIG.
  5. A prompt-trim edge case at max_tokens ≥ max_len − 1.

Defect 2 is the one to discuss: a streaming endpoint that returns 200 with no content is worse than one that errors, because every client treats it as success. Naming that yourself is the signal.