Designs by DuhartAll work
Rank
4 of 28
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.


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.

Interview surface this opens