all_lessons/kubernetes_genai/05lesson 5 / 18

Part I - Inference foundations

Getting Model Weights Into Pods

Lesson 04 gave each model version an immutable identity in a registry — a content-addressed artifact you can name, pin, and roll back. Knowing which bytes you want is only half the job. The runtime cannot serve weights it cannot read, and a 100 GB checkpoint does not arrive the way a 4 KB ConfigMap does. This lesson is about the delivery path: how the bytes physically travel from a registry or object store onto a node and into the pod's filesystem before the server reports ready. That path — not the model code — decides how long a scale-up takes, how much egress every replica burns, and whether a rollback is reproducible.

Source coverage
PDF Chapter 2: accessing model data in Kubernetes; refreshed with current Kubernetes image-volume status (alpha in v1.31, beta in v1.33, stable in recent Kubernetes — v1.35/v1.36 — and enabled by default).
Linear position
Prerequisite: Lesson 04 (Model Artifacts and Registries) — every model version now has an immutable, content-addressed identity you can pin and roll back to.
New capability: Choose the model delivery path — PersistentVolume, init copy, modelcar, or OCI image volume — that minimizes cold start, drift, and operational surprise for a given model size and rollout pattern, and measure model load as its own metric.
The plan
Five moves. (1) Reframe a model as a deployable supply-chain artifact, not configuration, and separate the two questions every path answers — how do the bytes get distributed, and how does the runtime read them. (2) Decompose cold start into five terms and work a real number: an 80-second transfer floor for a 100 GB model at 10 Gbit/s, and how a warm node cache erases it. (3) Walk the four delivery paths — PV, init copy, modelcar, image volume — with the trade each makes. (4) State the operational rule: model load is its own metric, decomposable into node-cache / storage / CUDA / warmup so you can say why a pod was slow. (5) The 2026 status of image volumes, failure modes, a checklist, and the hand-off into GPU nodes.

1 · A model is a supply-chain artifact, not configuration

The instinct from ordinary backend work is to treat "the model" like any other piece of app config: bake it in, mount it, ship it. That instinct breaks at scale. A ConfigMap is kilobytes; a serving checkpoint for a mid-sized open model is tens to hundreds of gigabytes. Llama-3 70B in 16-bit weights is about 70 × 10⁹ params × 2 bytes ≈ 140 GB; even quantized to 8-bit it is ~70 GB. Those bytes do not live in your container image by default, they do not fit in etcd, and they are far too large to re-fetch casually. So the right mental model is the one this lesson keeps returning to:

Mental model
A 100 GB model is not configuration. It is a deployable supply-chain artifact whose delivery path determines startup time, node pressure, rollback speed, and cache hit rate. Designing serving means designing how that artifact is distributed and read — with the same care you would give a binary release, not the casualness you give an environment variable.

The single distinction that organizes every option is this: distributing the artifact (getting the bytes near the pod) is a different problem from reading the artifact (the runtime opening files and loading tensors into GPU memory). A path can be excellent at one and poor at the other. An OCI registry is built to distribute immutable, content-addressed layers and to benefit from node-level layer caching — but how the runtime then reads those bytes depends on whether they are unpacked to disk or mounted. A network filesystem is built to expose one copy of the bytes to many readers — but its read throughput under concurrent load is now on your critical path. Keep the two questions separate and the paths stop looking interchangeable.

PersistentVolume (PV) → one copy of the bytes on shared storage (NFS, a CSI-backed network disk, an object-store-backed FUSE mount), mounted read-only into many pods. Distribution is "write once"; reading is a network/storage concern shared by every replica.
Init container copy → an init container pulls the model from a source (object store, registry) into a pod-local emptyDir ephemeral volume before the main container starts. Each pod gets its own copy; the runtime then reads from fast local disk.
OCI registry distribution + node cache → the model is packaged as an OCI artifact and distributed through the registry. The container runtime caches pulled layers on the node, so the second pod on that node pays nothing to fetch.
Modelcar (sidecar) → the model is packaged in its own container image; a sidecar in the pod makes those bytes available to the model server via a shared volume. A pre-image-volume way to get OCI distribution and node-cache semantics.
OCI image volume → a native Kubernetes feature that mounts an OCI artifact directly into the pod as a read-only volume. Same registry distribution and node cache as a modelcar, but no sidecar to manage — the platform does the mount.

2 · The cold-start equation — and why scale-up is a network event

"Cold start" is the time from a pod being scheduled to the model server reporting ready (passing its readiness probe so it can take traffic). It is not one thing. Decompose it:

cold start  =  image pull  +  model fetch  +  model read  +  runtime init  +  warmup
              └──────────── platform concern ───────────┘   └─ model-server concern ─┘

The first three terms are platform concerns and the last two are model-server concerns. The mistake is letting the platform terms hide. Here is why they dominate on a cold node. Suppose you scale from zero and the only path to the bytes is fetching a 100 GB model over a 10 Gbit/s network link:

Worked number — the transfer floor
A 100 GB model is 100 × 8 = 800 gigabits. Over a 10 Gbit/s link, the best case transfer time is 800 / 10 = 80 seconds — and that is before a single tensor is read into HBM, before CUDA initializes, before warmup. Real links rarely sustain line rate, so call it 90-120 s of pure fetch. Now make it concrete at fleet scale: an autoscaler adds 20 replicas in a burst and each independently pulls the 100 GB model from the same object store. That is 20 × 100 GB = 2 TB = 16 Tbit of egress in one scale-up event, and if they share a 10 Gbit/s egress path they serialize16 Tbit / 10 Gbit/s ≈ 1600 s ≈ 27 minutes for the last replica to even start reading. Your "scale-up" just became a 27-minute network event with an object-store bill attached.

The fix is to make model fetch approach zero on the common path, which is exactly what a warm node cache does. If the model bytes are already on the node — because a prior pod pulled the OCI layers, or because a PV already holds them, or because you pre-pulled before scaling — then for the next pod model fetch is a local-disk read at GB/s instead of a network transfer at Gbit/s. The same 100 GB read from a 5 GB/s local NVMe is 100 / 5 = 20 seconds, and four-fifths of cold start vanishes. This is the whole game: cold-start economics are dominated by whether the bytes are already there, and your delivery path is what decides cache-hit behavior. Drag the knobs below to feel the curve.

Cold-start cost — model size vs link bandwidth vs cache-hit rate
A scale-up adds N replicas. Each one is either a cache hit (bytes already on its node — reads from local disk) or a cache miss (must fetch over the network from the source). The bar shows time-to-ready for a single missing replica (fetch + read + init + warmup); the KPIs show the slowest replica in the burst and total egress. Push cache-hit rate up — or bandwidth up, or model size down — and watch both the tail latency and the egress bill collapse.
Fetch (1 miss)
Time-to-ready (1 miss)
Slowest replica (burst)
Total egress this scale-up
Show the core JS
// misses fetch over a shared link and serialize on it; hits read from local disk.
var misses   = Math.round(N * (1 - hitRate));
var bits      = sizeGB * 8;                 // gigabits per model
var fetch1    = bits / bwGbps;              // seconds, one miss alone
var readLocal = sizeGB / LOCAL_GBps;        // local NVMe read (cache hit path)
var ready1    = fetch1 + READ_INIT_WARMUP;  // single missing replica
// the burst of misses shares the egress link -> they serialize:
var tail      = misses * fetch1 + READ_INIT_WARMUP;
var egressGB  = misses * sizeGB;

3 · The four delivery paths and what each trades

The paths are not ranked — they trade who pays for bandwidth, how rollback works, and whether scale-up is predictable. Pick by model size and rollout pattern.

Delivery pathGood fitTrade-off
PersistentVolumeMany pods need shared read access to the same bytes; storage backend has predictable, high concurrent-read throughput; model rarely changes.Storage throughput and mount semantics are now on the critical path — every replica's read contends on the same volume, and a slow CSI driver throttles the whole fleet.
Init copySimple workflows; you want the main container reading from fast pod-local disk; per-pod isolation is fine.Every pod restart re-copies the full model — repeated fetch cost, and a cold-node scale-up pays the network transfer per replica.
Modelcar (sidecar)You want OCI distribution, immutability, and node-cache semantics on a cluster whose runtime predates native image volumes.A sidecar lifecycle to manage, shared-volume plumbing, and multi-arch image complexity — operational surface that the native feature removes.
OCI image volumeCluster on a recent Kubernetes (the feature is stable and default-on in v1.35/v1.36); you want immutable, content-addressed model bytes mounted read-only with node-cache reuse and no sidecar.Container runtime, registry policy, and artifact format must all support it — the API being stable does not mean your runtime/registry do.

Notice how each path answers the two questions from §1 differently. The PV is shared distribution with shared reading — one copy, many concurrent readers, so it shines when reads parallelize and dies when they contend. The init copy is per-pod distribution with fast local reading — great read path, but it re-pays fetch on every restart. The modelcar and image volume both get distribution and node caching from the OCI registry; the image volume is simply the modelcar idea promoted into the platform so you no longer babysit a sidecar.

Why immutability matters for rollback
An OCI image volume or modelcar references the artifact by digest (or a tag you pin to a digest). Rolling back is then exactly redeploying the previous digest — reproducible, content-addressed, the property lesson 04 bought you. A PV or an init copy that fetches "latest" from a mutable location has no such guarantee: the bytes under that name may have changed, so "roll back the Deployment" does not roll back the model.

4 · Measure model load as its own metric

The operational rule that ties this together: model load is its own metric, never hidden inside pod startup. If all you have is "pod took 6 minutes to become ready," you cannot act. A platform that has done this right can decompose that number and tell you exactly which term in the cold-start equation was slow:

Node lacked the layer
Cache miss — the node had to fetch the model over the network. Signal: high model fetch time correlated with first-pod-on-node; near-zero on subsequent pods. Fix: pre-pull / better cache-hit rate.
Storage was slow
PV / network filesystem read throughput collapsed under concurrent readers. Signal: model read time scales with replica count. Fix: faster CSI backend, fewer concurrent readers, or switch to per-pod local reads.
CUDA init was slow
Context creation, allocator, kernel compilation. Signal: runtime init time independent of model size, sensitive to driver/runtime version. Fix: pinned base image, cached compiled kernels.
Runtime was warming
The server is compiling/warming kernels and priming the KV cache. Signal: warmup time, first requests slow then fast. Fix: warmup requests gated before readiness, or accept a warm-up window.

Emit each of these as a separate timing (a span or a labeled histogram), distinct from the HTTP process-start time. The payoff is that an incident becomes a lookup instead of a guess: you read the slow term and go straight to its fix, rather than re-running the deploy and hoping.

5 · Where image volumes stand in 2026

The book's chapter was written when OCI image volumes were an early, experimental idea, so its caveats need refreshing. As of the current Kubernetes task documentation, image volumes are stable in recent Kubernetes (alpha in v1.31, beta in v1.33, stable and enabled by default by v1.35/v1.36). That changes the design conversation. The question is no longer "is this experimental, do I dare?" — it is the more concrete supply-chain question: does my cluster version, my container runtime, my registry policy, and my artifact format all support the path I want? A stable API does not mean your container runtime implements it, that your registry serves the artifact format, or that your image-pull policy allows it. Keep the external reference because runtime and registry support can still lag the API feature state, and the docs track the runtime support matrix.

Verify the path, not just the version
Read it directly: Kubernetes image volumes. Before committing a delivery path, confirm the four links in the chain — API version (a Kubernetes recent enough that the feature is stable, v1.35/v1.36), container runtime support, registry artifact format, and your image-pull / admission policy — not just that the feature is "stable."

6 · Failure modes and a checklist

Failure modes

  • Scale-from-zero stalls on the fetch. The Deployment scales from zero but spends minutes pulling weights before readiness, so autoscaling reacts far too late to the load that triggered it. Signal: model fetch dominates cold start and time-to-ready tracks model size, not request volume. The autoscaler is now staging huge artifacts under time pressure, not just scheduling pods.
  • The thundering-herd egress event. Every replica independently downloads the same model from a remote source, saturating egress or the object store and serializing on the shared link (see §2's 27-minute tail). Signal: egress bytes spike to N × model_size on every scale-up; object-store throttling errors. Fix: node cache, PV, or pre-pull so only the first pod on a node fetches.
  • Mutable tags make rollback non-reproducible. The delivery path fetches "latest" or a tag that was re-pushed, so redeploying the previous Deployment spec does not bring back the previous bytes. Signal: two pods on the same spec serve different model behavior; a rollback "succeeds" but the regression persists. Fix: reference by digest, the immutability lesson 04 bought.

Implementation checklist

  • Use a PV when many pods need shared read access and the storage backend has predictable, high concurrent-read throughput — and you have load-tested reads at the replica count you will actually run.
  • Use a modelcar or OCI image volume when registry distribution, immutability, and node-cache semantics are valuable — i.e. when reproducible rollback and "second pod on the node is free" matter more than a single shared copy.
  • Pre-pull or prefetch large models onto nodes before scale-up if cold-start SLOs matter, so the autoscaler's pods hit a warm cache instead of a cold network.
  • Measure node-local cache hit rate and model load time separately from HTTP process startup, and break model load into fetch / read / init / warmup so the slow term is a lookup, not a guess.
  • Reference model bytes by digest (or a tag pinned to one) on every path, so a Deployment rollback is also a model rollback.
  • Confirm the path's full support chain — cluster version, runtime, registry format, pull policy — before depending on it (§5).

Checkpoint exercise

Try it
You serve a 100 GB model behind an HPA that can burst from 2 to 30 replicas, with a cold-start SLO of 60 seconds and nodes that hold ~4 model-sized layers in local cache. Pick a delivery path and defend it: estimate the fetch term for a cache hit vs miss at your node bandwidth, decide whether to pre-pull and to how many nodes, and name the one metric you would alert on to catch the thundering-herd egress failure before it shows up on the bill.

Where this points next

You can now get the right bytes onto a node and into a pod, and you can say why a cold start was slow. But every term in the cold-start equation assumed a node was there and capable — that it had the GPU, the HBM, the driver, and the room to mount the volume. Nodes with accelerators are not interchangeable commodity compute; they are a scarce, typed, expensive resource with their own scheduling and lifecycle rules. The next lesson — 06 - GPU Nodes as a Platform Layer — treats the GPU node as a platform abstraction: how Kubernetes discovers, labels, taints, and schedules accelerators, and why "just add a node" is its own multi-minute event that compounds the cold start you just learned to measure.

Takeaway
A 100 GB model is a deployable supply-chain artifact, not configuration: its delivery path determines startup time, node pressure, rollback speed, and cache-hit rate. Separate distributing the bytes from reading them, then choose among PersistentVolume (one shared copy, many readers — fast when reads parallelize), init copy (per-pod local read, re-fetched every restart), modelcar (OCI distribution + node cache via a sidecar), and OCI image volume (the native, sidecar-free version, stable in recent Kubernetes — v1.35/v1.36). Cold start is image pull + model fetch + model read + runtime init + warmup; only the last two are the server's, and the platform terms dominate — a 100 GB model is an 80 s transfer floor at 10 Gbit/s and a 20-replica burst is a 2 TB egress event unless a warm node cache makes fetch ~free. So pin bytes by digest for reproducible rollback, pre-pull before scale-up when SLOs bite, and measure model load as its own decomposed metric — node-cache / storage / CUDA / warmup — so a slow start is a lookup, not a guess.

Interview prompts