feat(nvsnap): elect one downloader per cache hash at admission - #2104
Open
balajinvda wants to merge 12 commits into
Open
balajinvda wants to merge 12 commits into
balajinvda wants to merge 12 commits into
Conversation
A Helm chart creates N identical model workers, and today each one downloads and initialises on its own: the pod template cannot name a capture hash, `restore-from: auto` never made a pod a capture source, and the watcher's hash never matched admission's (resolved image digest and injected env on one side, spec tag and original env on the other). Measured on dev1 with a TinyLlama TP=2 Deployment: both replicas were admitted "no capture for hash; admitting pod unchanged", nothing was captured, the scale-up pod cold-started. The webhook now decides per pod, with no template change. A pod is a downloader when it requests a GPU and its main container names a model (`--model`/`--model-path` as one arg or as Dynamo's separate list items, `--model=`, HF_MODEL_ID/MODEL_ID/NIM_MODEL_NAME, or a NIM image); frontends, routers, etcd and nats are left alone. The webhook composes the hash once and stamps it on the pod (annotation full, label short); the watcher and orchestrator capture under the stamped hash instead of recomposing. If a promoted cachedir capture exists the pod restores. Otherwise the webhook creates Lease nvsnap-capture-<hash> with the pod UID as holder: the one admission that succeeds is the leader and gets the capture decoration plus the capture label; every other pod is a follower and gets the restore decoration against the deterministic rox-<hash> claim, a schedulingGate (no node, no GPU held) and a gated label. Any error fails open and admits the pod unchanged. nvsnap-server already receives the promote state from the agent. On `ready` it drops the gate on every gated pod of the hash and deletes the Lease; on `failed`, when the leader pod is gone or terminated, or when the Lease deadline passes, the reconciler evicts the gated followers that have a controller so they are recreated and re-elected. Pod volumes are immutable, so recreation is the only way a follower whose claim will never bind can start. Followers need ReadOnlyMany storage; on per-pod-clone storage the L2 backend cannot name the claim ahead of the promote and followers start cold. Off by default: `agent.election.enabled`, `agent.election.deadline` (60m). Server RBAC gains leases get/list/watch. Tests: model-identity forms and the downloader classifier; Lease election (one leader of four, error surfacing); the webhook matrix (ignored frontend, leader, gated follower with the wait/seed/prewarm inits, cold fallbacks, restore without election, unbound capture left alone, explicit restore-from bypass, metadata maps bootstrapped once); server release, eviction of owned pods only, and leader liveness; the orchestrator and watcher honouring the stamped hash. Mutation-checked: dropping the gate, the leader label, the single bootstrap, the restore-before-elect branch, and the stamped-hash override each turned tests red after compiling. Design: docs/proposals/helm-chart-cache-election.md. Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
The first cluster run of the election admitted both chart pods unchanged with "election: pod has no UID at admission". A mutating webhook sees a CREATE before the API server assigns the UID (and, for generateName pods, the name), so nothing on the pod could serve as the Lease holder. The unit fixture carried a UID and hid this. The elector now mints its own id (UUID, seam for tests), uses it as the Lease holder and returns it; the webhook stamps it on the leader as nvsnap.io/election-id, and the server's leader-liveness check matches that annotation instead of the UID. Fixtures drop the UID so they match what admission actually sees. Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: NVIDIA/nvcf/.coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Dynamo prefill and decode workers run the same image and download the same model, but their args differ by a role flag and their env by the Dynamo, etcd and NATS wiring, so they hashed apart and each role elected its own leader and downloaded once more. The hash now drops the role flags (--is-prefill-worker, --is-decode-worker, --disaggregation-*, --kv-transfer-config) in both the one-token-per-item and the shell-string arg forms, and the DYN_/DYNAMO_ env prefixes plus ETCD_ENDPOINTS, NATS_SERVER and NATS_URL. Flags that change the download (--model, --revision, --tokenizer, quantization) stay in, because the model tree mounts read-only and a pod restoring from a tree missing its files would fail. Compile caches live in the per-pod writable shadow, so a role that needs different kernels recompiles into it. Tests: vLLM, SGLang and shell-string prefill/decode pairs hash equal; different models, revisions and cache env still differ; the stripper's token handling including a dangling value flag. Mutation-checked: hashing the raw args again turns the test red. Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
The first Dynamo operator run on dev1 still elected two leaders: the prefill worker alone carries --kv-events-config, and the operator injects GROVE_* gang-scheduling env whose values name the component and the pod index. Neither changes what a worker downloads. Test built from the live pod specs: prefill equals decode, and two replicas of one component (different GROVE_PCLQ_POD_INDEX) equal each other. Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
NVCF runs each chart in its own namespace, but the promoted rox claim existed only in the capture's namespace, so a restore anywhere else missed and started cold. Promoter gains EnsureClaim(hash, ns), the shape NVCA uses for one model volume across tenant namespaces: shared-volume mints one more secondary static PV per consumer namespace with the CSI handle rewritten for it, pre-bound to a rox claim there (zero copy); snapshot-clone with ReadOnlyMany re-exposes the promote's snapshot handle in the target namespace through a pre-provisioned VolumeSnapshotContent + VolumeSnapshot pair and clones from it; per-pod clone storage reports ErrUnsupported and the pod stays cold. Mount mints the claim on a miss and retries, covering restores admitted after the promote in any namespace. The promote mints the claim in every namespace that already holds pods stamped with the hash before it publishes ready, so gated followers find it bound on release. Everything minted carries nvsnap.io/hash-short and nvsnap.io/namespace and Delete reaps it. Agent RBAC gains volumesnapshotcontents. Tests with fake clients: per-namespace PV shape, handle rewrite, labels, idempotence, a second namespace, not-promoted, Delete reaping across namespaces, Mount minting on a miss, and the snapshot pre-provisioning chain with restore size; per-pod clone unsupported. Mutation-checked: Mount without the mint and a PV without labels each turn tests red. Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
Two pods of one chart admitted in the same second both ran EnsureClaim for nvsnap-xns on dev1; the second's post-create label Update hit "the object has been modified" and the webhook failed open, so that pod started cold. The per-namespace secondary PV is now created with its hash and namespace labels; nothing in EnsureClaim updates anything, so concurrent callers converge through AlreadyExists. Test rejects every PV update and runs EnsureClaim three times. Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
balajinvda
force-pushed
the
nvsnap/helm-election
branch
from
September 25, 2026 19:18
e448708 to
0068faf
Compare
Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
The election was verified against charts that lived only in a scratch directory, so nobody else could rerun the test. deploy/k8s/charts/vllm-workers is a plain vLLM Deployment with N identical GPU workers and no nvsnap labels or annotations (model, tensor parallelism, replicas, image, pull secret and excluded nodes are values). scripts/test-election-e2e.sh installs it, checks one leader and gated followers, waits for the release after the promote, checks the follower came up with no downloads and serves, prints the cold and warm engine phases from the vLLM logs, then uninstalls and reinstalls expecting every pod to restore. Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
balajinvda
force-pushed
the
nvsnap/helm-election
branch
from
September 25, 2026 21:27
3fd97a4 to
297cb5f
Compare
Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
…esystem Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Customers predominantly deploy Helm charts, and a chart creates N identical
model workers. Today each one downloads and initialises on its own: the pod
template cannot name a capture hash,
restore-from: autonever made a pod acapture source, and the watcher's hash never matched admission's. Measured
on dev1 with a TinyLlama TP=2 Deployment, both replicas were admitted "no
capture for hash; admitting pod unchanged" and the scale-up pod cold-started.
The goal, in two sentences: if the hash exists, the pods that would download
the model get the cache volume instead. If it does not, exactly one of them
downloads and the rest get the volume once it exists.
What changed
The webhook decides per pod, with no template change.
container names a model (
--model/--model-pathin one arg or asDynamo's separate list items,
--model=,HF_MODEL_ID/MODEL_ID/NIM_MODEL_NAME, or a NIM image). Frontends, routers, etcd, nats areleft alone. Covers stock vLLM/SGLang and vLLM/SGLang under Dynamo.
(annotation full, label short). The watcher and orchestrator capture
under the stamped hash instead of recomposing from the live pod.
nvsnap-capture-<hash>created with an id thewebhook mints (pods have no UID at CREATE admission). The one admission
that succeeds is the leader and gets the capture decoration plus the
capture label. Every other pod is a follower: restore decoration against
the deterministic
rox-<hash>claim, aschedulingGate(no node, no GPUheld), a gated label.
pvc-state=readyand deletes the Lease. On
failed, a leader pod that is gone orterminated, or a Lease past its deadline, the reconciler evicts gated
followers that have a controller so they are recreated and re-elected.
cold. Any error fails open.
agent.election.enabled,agent.election.deadline(60m). Server RBAC gains leases get/list/watch.
Design:
docs/proposals/helm-chart-cache-election.md.Claims across namespaces
NVCF runs every chart in its own namespace and a PVC is namespaced. The
promoter gains
EnsureClaim(hash, ns): shared-volume mints one moresecondary static PV per consumer namespace with the CSI handle rewritten for
it, pre-bound to a
rox-<hash>claim there (NVCA's pattern, zero copy);snapshot-clone with ReadOnlyMany re-exposes the promote's snapshot handle in
the target namespace through a pre-provisioned VolumeSnapshotContent +
VolumeSnapshot and clones it; per-pod clone reports unsupported and the pod
stays cold.
Mountmints on a miss; the promote mints for every namespacethat already holds stamped pods before publishing ready.
Deletereaps bylabel. Agent RBAC gains volumesnapshotcontents.
Customer Release Notes
Helm chart functions with several identical GPU model workers download and
initialise the model once; the other workers start from that worker's
cache, and a redeploy or scale-up of the same chart starts every worker
from the cache. Requires a ReadOnlyMany storage class for the cache.
Plan Summary
Helm: new
agent.electionvalues (off by default),--electionflags onthe agent DaemonSet, server ClusterRole leases verbs. No new objects.
Usage
Testing
dev1, 2026-09-25, stock TinyLlama TP=2 Deployment with no nvsnap markers,
agent v0.2.75-election2, server v0.0.32-election2, NVMesh:
Cross-namespace, capture only in
nvsnap-system, same stock chart innvsnap-xnswith two replicas admitted in the same second:Dynamo sample (frontend + prefill + decode, Qwen3-0.6B): both workers hash
the same, prefill leader, decode follower, frontend ignored. The gang did
not schedule under kai-scheduler (see Notes).
Unit: model-identity forms and the classifier; Lease election (one leader
of four, uid-less pods, error surfacing); webhook matrix (frontend ignored,
leader, gated follower with wait/seed/prewarm inits, cold fallbacks,
restore without election, unbound capture left alone, explicit
restore-from bypass, metadata maps bootstrapped once); server release,
eviction of owned pods only, leader liveness; orchestrator and watcher
honouring the stamped hash. Mutation-checked: dropping the gate, the
leader label, the single bootstrap, the restore-before-elect branch and
the stamped-hash override each turned tests red after compiling.
Notes
Stacked on #2101 (base is
nvsnap/helm-chart-support); merge that first.Disaggregated workers: Dynamo prefill and decode workers differ by a role
flag and the etcd/NATS/DYN_* wiring. The hash leaves those out
(
stripRoleFlags), so both roles share one leader and one rox; compilecaches recompile into the per-pod writable shadow where they differ. Flags
that change the download (
--model,--revision,--tokenizer) stay in.Gang scheduling: Grove/KAI place a DynamoGraphDeployment as one podgang, so
a follower that is unschedulable until
rox-<hash>exists holds the leadertoo and nothing downloads. Grove also rewrites
schedulingGates. Optionsand a recommendation are on #2099; this PR keeps stock charts working and
does not change behaviour for gang-scheduled workers beyond the shared hash.
Restore-namespace NetworkPolicy: the chart's policy allows egress only to
nvsnap-server and selects every pod, so a namespace without other egress
allows loses DNS. NVCF function namespaces carry NVCA's allows; noted in the
design doc for a follow-up.
References
Relates to #2099
Related Pull Requests
#2101
Dependencies
None