Repository navigation
Conversation
…them transformers' from_pretrained memory-maps safetensors and faults them in a page at a time. On NFS that is one ~260 KiB read in flight at ~28 ms each: Qwen3-8B took 903 s to load in the trainer on Filestore Basic HDD, while vLLM 0.30 loaded the same files from the same instance in 102 s because it prefetches them into the page cache with eight threads and 16 MiB reads (runs/2026-10-15_cold_start). training.prefetch does the same before every trainer load; OPEN_RL_PREFETCH=auto|always|off and the thread and block sizes are environment knobs. The ready keys now carry a phase record (design 016, A6): process start, prefetch result, load start and end for trainers; engine build start and end for samplers. The model view shows it as ready_at and phases.
…er prefetch Measurement tooling: scripts/cold_start_client.py stamps the demo's client phases; cold_start_timeline.py records Workloads, pods, events, worker logs, the Redis ready keys and the API server log and joins them into a timeline; cold_start_case.sh runs one case; cold_start_fio.sh, cold_start_monitoring.py and cold_start_resident.py check the storage from the node, Cloud Monitoring and the page cache. runs/2026-10-15_cold_start holds the records and REPORT.md. Headline: the 8B trainer load took 903 s on a cold Filestore because from_pretrained reads 260 KiB at a time with two in flight; vLLM read the same files in 102 s. Latency-bound, not bandwidth-bound. With the prefetch the trainer loads in 12-13.5 s on a warm server cache (39.9 s before) and reads cold disk at 115 MiB/s (10.8-17 before).
…device memory The cluster deployments never set OPEN_RL_TRAIN_TOKEN_BUDGET, so every forward_backward ran one sequence per pass: 64 passes for a 64-rollout GRPO step, 20 s of training per step for a 0.6B model on an H100. One fleet-wide number does not work either: 65536 tokens of gemma-4-e2b logits (262k vocabulary) is 22 GiB and OOMs an H100. The trainer now computes the budget from the model's vocab_size and the free memory on its device, and caps an explicit setting the same way. Measured: 0.6B training 20 s -> 4 s, gemma 31-35 s -> 8-10 s per step on H100 (runs/2026-10-17_demo_baselines).
…cache on the node A worker pull is 92 s on an H100 node and 169 s on an L4 node, paid twice by a cold job (runs/2026-10-15_cold_start). The open-rl-worker-prepull DaemonSet in the base bundle pulls the image at deploy time on every openrl.io/enabled node, and workers now default to imagePullPolicy IfNotPresent so the pulled image is what runs. vLLM compiles the model on every sampler start, 28-37 s on an H100 and 122 s on an L4 for gemma-4-e2b, and kept the result in the container. Samplers now mount a hostPath (/var/lib/openrl/vllm-cache, OPEN_RL_VLLM_CACHE_HOSTPATH) as VLLM_CACHE_ROOT, so the second sampler of a model on a node skips it.
…L_VLLM_CACHE_DIR)
Measured on open-rl-dra: a sampler of a model already compiled on any node loads the AOT artifact in 1.1 s instead of compiling for 56-67 s, and its engine initializes in 20 s instead of 88-106 s. The cache is 61 MB per model and GPU type. The per-node hostPath remains for installs without a shared volume (unset OPEN_RL_VLLM_CACHE_DIR).
vLLM's default list captured dozens of sizes on every sampler start, 12-40 s of it (runs/2026-10-17_demo_baselines). GRPO sends a few shapes that pad to the next power of two; VLLM_CUDAGRAPH_CAPTURE_SIZES overrides the list or keeps vLLM's with "default". The deployment can now also forward VLLM_ENABLE_V1_MULTIPROCESSING to try the in-process engine.
An FFT sampler applies each sparse delta through vLLM's checkpoint patch loader, which stages a dense copy of every patched checkpoint tensor on the device. gemma-4-e2b's per-layer input embedding is 4.38 GiB of that, and vLLM's default share leaves 2.2 GiB free on an L4: the sampler died at its first update (runs/2026-10-17_demo_baselines, solo gemma probe). The sampler now reads the largest tensor from the checkpoint's safetensors headers and takes that much, plus a margin, off vLLM's share of the device, never below a floor; OPEN_RL_WEIGHT_UPDATE_HEADROOM_GIB overrides it. The estimator adds the same room to the FFT sampler's placement claim from a per-model table, so the claim and the runtime agree. LoRA samplers never update base weights and are unchanged.
Twelve clients taking filelock locks in one cache directory on NFS lost a client to a stale file handle (drill 2). An init container now seeds an emptyDir per client with the dataset and the model's tokenizer and config files, skipping weight files; 38 MB for gemma-4-e2b. Also clears two lint findings in demo_report.py.
…te's room at its peak vLLM's patch loader stages each patched checkpoint tensor densely, then its NaN-masked copy_ builds a bool mask and the merged result: 2.5x the tensor, all live at once. For gemma-4-e2b's per-layer embedding that is 11 GiB. Measured on an L4 (runs/2026-10-17_demo_baselines): 1x headroom still died at the 2.19 GiB mask; the update went through with 10.7 GiB free. The sampler now wakes only the weights when it takes the GPU and wakes the KV cache after ensure_weights, vLLM's documented order for RL updates, so the parked KV cache is not in the way. The runtime headroom is 2.5x the largest tensor plus a margin, and vLLM's share never drops below what the resident weights need. The estimator's claim uses the same factor: a gemma FFT sampler no longer fits a 24 GB device and goes to the 80 GB tier by its own size.
Host memory bounds how many workers park on a node, but says nothing about time: six FFT samplers on one H100 ran each step six times slower while other GPUs sat idle (runs/2026-10-17_demo_baselines, drill 3). --max-seats-per-claim (OPEN_RL_MAX_SEATS_PER_CLAIM, default 0 for no limit) is a hard policy limit. A ledger at the limit is not a selection candidate, and the ledger CAS loop refuses a join past it so two joiners on one snapshot cannot both take the last seat. A worker that finds every eligible ledger full cuts its own claim; with no free device it stays Pending, the reason names the limit, and it waits until a node arrives or a seat frees. Under binpack this fills one claim to the limit and moves to the next free device.
--lora-tags and --fft-tags set TINKER_TAGS on the client, e.g. openrl.sampler_replicas=2, so a drill can give the shared LoRA runtimes a second sampler without touching the recipe.
A waiting worker's reason, for example the seat limit, was only visible in describe. It is the column to watch during a drill.
cell_up.sh brings up an OpenRL cell (cluster, Spot GPU pool, drivers, bundle) in a chosen region, reports the pool's quota or stockout errors instead of hanging, and leaves the caller's kubectl context alone; cell_seed_hf.sh copies the model cache in through a bucket; cell_down.sh removes the cell. demo_rehearsal.sh checks the fleet is idle, trims the shared volume, optionally warms the LoRA runtimes, kicks off the job set and watches.
… notes The cold-start archive kept 243 raw files (pod logs, event dumps, fio output) next to its report. The reports and timelines stay; the raw files go, they are reproducible with the scripts. Adds the fleet demo notes (drills 1 to 7 with the scorecard and what each change did) and the fleet request with the quota findings.
This was referenced Oct 22, 2026
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.
Faster cold starts, safe weight updates on small GPUs, and a seat limit for shared claims
This branch comes out of two weeks of measuring OpenRL under load on GKE: first one
job at a time, then 12 and 24 jobs at once on a mixed H100 and L4 fleet. Each change
below fixes one thing the measurements pointed at. The write-up with every drill is in
runs/2026-10-17_demo_baselines/notes.md; the cold-start study is inruns/2026-10-15_cold_start/REPORT.md.What a cold job paid before, and what it pays now
One job alone, from the client's start to the end of its first step. H100 unless noted.
from_pretrainedWhat 12 jobs at once got, same fleet, same jobs
Jobs that finished 3 steps within 180 s of kickoff. Six H100s; the L4s hurt every mix and
are out of these rows.
The changes
Trainer and sampler start
training/prefetch.py: reads a checkpoint with several threads and large blocksbefore
from_pretrained. The mmap path read 260 KiB at a time with two reads inflight and was latency bound on NFS.
OPEN_RL_PREFETCH=auto|always|off.memory. The cluster never set
OPEN_RL_TRAIN_TOKEN_BUDGET, so it ran one sequenceper pass. A fixed 65536 ran gemma out of memory: 262k vocabulary, 22 GiB of logits.
timestamps, and
openrl_statusshows them.Sampler start on every node
k8s/deploy/base/worker-prepull.yaml: a DaemonSet that pulls the worker image onevery enabled node at deploy time. Workers default to
imagePullPolicy: IfNotPresent.OPEN_RL_VLLM_CACHE_DIR, bundledefault) or on a hostPath (
OPEN_RL_VLLM_CACHE_HOSTPATH). The first sampler of amodel on a GPU type compiles; every later one loads the artifact from any node. The
cache key covers model, GPU type, and engine config, so LoRA and FFT samplers warm
separately.
max_num_seqs(
VLLM_CUDAGRAPH_CAPTURE_SIZES,defaultrestores vLLM's list).Weight updates on small devices
loader stages each patched tensor densely, then builds a mask and a merged copy: 2.5x
the tensor, 11 GiB for gemma's per-layer embedding, while vLLM's share left 2 GiB.
wakes the KV cache (vLLM's documented order for RL updates). The headroom it keeps is
2.5x the largest checkpoint tensor plus a margin, read from the safetensors headers,
never below what the resident weights need (
OPEN_RL_WEIGHT_UPDATE_HEADROOM_GIBoverrides).
claim grew from 11.7 to 23.6 GiB, so it no longer fits a 24 GB device and lands on the
80 GB tier by its own size.
Scheduler
--max-seats-per-claim(OPEN_RL_MAX_SEATS_PER_CLAIM, default 0 for no limit). Ashared claim at the limit is not a candidate; the ledger's CAS re-checks it; a worker
that finds every claim full cuts its own and waits Pending until a GPU is added or a
seat frees. Before this, six FFT samplers time-sliced one H100 while other GPUs idled.
kubectl get workloadsshows the status reason, which names the seat limit while aworker waits.
Demo and drill tooling (
scripts/)demo_jobs.pyrenders N in-cluster client Jobs fromkind:model:countspecs, with aper-client Hugging Face cache seeded from the shared one (twelve clients locking one
NFS directory lost a client to a stale file handle) and optional session tags.
demo_report.py,demo_watch.py,demo_rehearsal.sh: per-job and per-step timingsfrom
metrics.jsonl, a one-screen live view, and a one-command rehearsal with anidle check, volume trim, and an optional warm-up of the LoRA runtimes.
cold_start_*.py|sh: the instrumented single-job measurement used for the report.cell_up.sh,cell_seed_hf.sh,cell_down.sh: an OpenRL cell in any GCP region.What the drills still say
jobs waiting. Started warm, the same three jobs finish three steps in about 90 s.
The FFT half queues. More large GPUs, not more policy, is what moves it.
L4 goes there and runs three times slower. This branch does not change that.
Tests
make test(260 unit tests),make -C scheduler/controller fmt-check vet manifests-check test, and lint on every tracked file. New tests cover the prefetch,the token budget, the ready records, the capture sizes, the headroom and its floor, the
wake order, the estimator's update room, the seat limit in selection and in the ledger
CAS, and the demo tooling's defaults.