Uh oh!
There was an error while loading. Please reload this page.
feat(installer): CPU is a share weight, so limits carry memory only (backend#2418) - #820
Conversation
…backend#2418) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…bats assertions (backend#2418) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…table-producer # Conflicts: # scripts/manifest.sha256
Uh oh!
There was an error while loading. Please reload this page.
…mory-only limits (backend#2418) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
shujaatTracebloc
commented
Aug 25, 2026
bugbot run |
saadqbal
left a comment
There was a problem hiding this comment.
The policy reasoning is right and the _training_limits tests are the good kind — the ephemeral-storage case in particular, since a hardcoded "memory only" filter would silently drop a disk limit. But Bugbot's High is correct, and I'd add that its second half is the worse of the two.
I traced it. _existing_training_values reads RESOURCE_LIMITS (:101) and that value becomes _TB_TRAINING_SIZE (:514), which line 1871 writes back as RESOURCE_REQUESTS. So a reinstall after this writer carries memory=29Gi into the requests half and the cpu request is gone — not just the limit. The pod drops out of any cpu share weight at all, silently.
The second half is nastier. The historic-default gate is "$prev" != "$_TRAINING_DEFAULT" against the literal cpu=2,memory=8Gi. Post-filter a default install writes memory=8Gi, which no longer equals the literal, so the gate reads it as a deliberate choice, keeps it, and machine sizing never runs again. That is precisely the "unschedulable 8Gi on exactly the machines this sizing exists to fix" the comment at :511 warns about, reintroduced by the filter that was added above it.
The single-lookup design at :504 is right — it's reading the wrong field now. Carry and the literal comparison both need the full envelope, so RESOURCE_REQUESTS is the field to read.
What I'd take from this: no test writes values, reads them back, and carries. _training_limits is well covered in isolation and the round-trip is what broke — worth a test that installs, re-reads, and asserts requests still has cpu, because that's the shape of the bug and it would have caught both halves.
Two smaller things while you're in here:
The ordering constraint on client-runtime#388 is documented in four places and enforced in none — every 388 in the diff is a comment. Merging to develop is fine, but nothing stops a promotion from shipping this ahead of the runtime, and the failure mode you describe (2-core limit under a 7-core request, pod never schedules) is fleet-wide and silent until someone trains. Worth deciding whether that's an installer preflight on the jobs-manager version or an explicit hold on the promotion — prose in a comment won't survive the release train.
Minor twin divergence: bash matches case "$pair" in cpu=*), which is case-sensitive, while PowerShell's -like 'cpu=*' is case-insensitive. CPU=7,memory=29Gi keeps the cpu limit on Linux/macOS and drops it on Windows. Same class as the trim divergence the fixture docstring records catching, and every fixture case is lowercase so nothing pins it.
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 14272c8. Configure here.
…ad round trip (backend#2418) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
shujaatTracebloc
commented
Aug 25, 2026
All three points taken — one was already fixed when you reviewed, two were not, and both of those are now in 1. The High — already fixed, in |
shujaatTracebloc
commented
Aug 25, 2026
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 43c01e9. Configure here.
saadqbal
left a comment
There was a problem hiding this comment.
All three addressed, and the carry fix is better than what I asked for.
Reading RESOURCE_REQUESTS with LIMITS as fallback is the right shape — a bare switch would have dropped the carry for a chart-direct install that set only the one key, and you kept that path working and said why. It also fixes the second half for free: prev is the full envelope again, so the historic-literal gate matches cpu=2,memory=8Gi and the machine gets re-sized instead of inheriting a post-filter memory=8Gi as a deliberate choice. The three new _existing_training_values tests cover exactly that, and naming the mutation in the first one ("read RESOURCE_LIMITS here and this reddens with 'memory=29Gi'") is what makes it worth having.
[Cc][Pp][Uu]=* over ${pair,,} for bash 3.2 is the detail I'd have missed — and pinning it from the PowerShell side with "the bash twin now agrees" is the right place for it, since that's the assertion that fails if either half drifts.
I read the cpu-only fallback again and I'm not going to push on it. Carrying the operator's stated value beats discarding it, both directions lose something, and you documented which one you picked.
Not blocking on this, but the client-runtime#388 ordering constraint is still prose-only here and in #821 — two PRs now, both correct, both safe to merge to develop and neither safe to promote until the runtime ships. Wherever that gets enforced, I don't think it should be a comment.
Holding my change request on one thing only: Prereqs — almalinux:8 is still running. Nothing left on my side — I'll clear it as soon as that lands.
shujaatTracebloc
commented
Aug 25, 2026
On the ordering constraint you raised: filed as backend#2473 rather than left in a thread, since you were right that four prose comments are invisible to the release train. It records the two mechanisms that would actually enforce it — pin I recommended the promotion hold and said why, but the decision isn't mine to land unilaterally, so the ticket holds it as pending rather than losing it when these merge. It also notes this is a general shape, not a one-off: any future |
saadqbal
left a comment
There was a problem hiding this comment.
Prereqs green across all six distros — clearing my change request. Approving.
Everything I raised is in and verified: the carry reads RESOURCE_REQUESTS with a documented LIMITS fallback, which fixes the cpu-drop and restores the historic-literal match in one move; the round-trip is tested with the mutation named; and [Cc][Pp][Uu]=* closes the twin divergence without reaching for bash 4 syntax.
One thing that isn't mine to hold and I want on the record rather than in a comment thread: this and #821 are both correct and neither is safe to promote until client-runtime#388 ships. Two PRs now depend on a constraint that exists only in prose. Merging to develop is fine — I'd just want the promotion side to have something mechanical before this reaches an edge, because the failure mode is fleet-wide and silent until someone tries to train.
Uh oh!
There was an error while loading. Please reload this page.
…ent#836) (#850) When TRACEBLOC_TRAINING_RESOURCES is unset, the VM-ceiling sizing (backend#2221 / #804) derives e.g. cpu=9,memory=12Gi, and the L0.2 limits half (backend#2418 / #820) drops cpu so RESOURCE_LIMITS ships memory-only (memory=12Gi). A pre-backend#2223 chart schema pinned RESOURCE_LIMITS to `^(cpu=\S+,memory=\S+)?$`, which rejects that value, aborting `helm install`. backend#2223 (#812) already loosened the schema to admit any subset, so the current chart accepts memory-only limits — the two changes are a coordinated pair. Rather than revert the memory-only design, this adds regression tests that keep the derivation and the schema pinned together: - install-client-helm.bats: drives the real derivation (_resolve_training_size then _training_limits) for the VM-ceiling repro and asserts the derived RESOURCE_LIMITS matches the pattern READ FROM client/values.schema.json — so a re-tightening back to the strict pattern reddens here, not at a customer's helm step. - chart-env-vocabulary.sh: renders the same memory=12Gi through the REAL chart schema via `helm template`, the authoritative validator. No production code changes. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
What
Both installers now write
RESOURCE_LIMITSwith memory only — the two halves of the envelope are no longer the same string:This is the producer half of backend#2418 (L0.2) — the half that reaches the fleet. #2418 delivered the same policy for the derive path (client-runtime#378), which is flagged off and runs nowhere; the installer writes
RESOURCE_*unconditionally on every install, so this is the path that actually decides what a customer's training pod gets.Why the two dimensions differ
They are not the same kind of resource:
requestswith nolimitsbecomes a cgroupcpu.weight— a share under contention, and the whole machine when nobody else wants it. Withrequests == limitsit becomes acpu.maxquota that throttles at its ceiling even on a completely idle box. On an 8-core machine, a run sized to 7 cores was capped at 7 while the 8th sat unused, benefiting nobody.requests == limitsstays, and it is the load-bearing safety property of the whole ladder.Guaranteed QoS is given up deliberately — a pod is Guaranteed only when every container has limits for both dimensions. That is the trade: the memory guarantee is what mattered, and CPU burstability is what lets a second job exist at all.
This requires a jobs-manager that treats
RESOURCE_LIMITSas the complete limits envelope: client-runtime#388, merged todevelopbut not yet released.An older image merges the parsed pairs onto its built-in
cpu=2,memory=8Giliteral, so an omittedcpucomes back as a 2-core limit under a 7-core request — which Kubernetes rejects outright, and the pod never schedules. Measured, not inferred:Same shape as the SafeTensors reader-before-writer ordering (avg#183 / DownloadView#1111): the runtime ships first, the fleet picks it up, then this. Holding this as a draft for that reason as much as for review.
A real twin divergence, found and fixed in development
The two installers are twins, and the bash side had a bug the PowerShell side did not:
So an operator writing
TRACEBLOC_TRAINING_RESOURCES="cpu=7, memory=29Gi"would have kept the CPU limit on Linux/macOS while.Trim()dropped it on Windows — silently, and in the dangerous direction. Fixed by trimming each pair before the match; both twins now agree on all nine inputs I checked, including whitespace, empty pairs and a cpu-only envelope.That is the same "shared contract, two control flows" class backend#2220 found five of, which is why I did not stop at per-twin unit tests:
installer_parity.jsongains alimitsverdict field (schema_version 1 → 2), so both parity suites assert it per row against the same fixture. Leaving a new shared contract outside that fixture is exactly the pattern that produced those five bugs. The generator emits the new field and the bash table is regenerated, not hand-edited.Decisions worth arguing with
Every non-cpu dimension survives, not "memory only". backend#2223 added
ephemeral-storage; a hardcoded memory filter would silently drop a disk limit and let a pod fill the node's disk.cpu=7,memory=29Gi,ephemeral-storage=26Gi→memory=29Gi,ephemeral-storage=26Gi.A cpu-only envelope returns the input unchanged, never empty. An empty
RESOURCE_LIMITSreads to jobs-manager as unset, which since #388 mirrors the requests side back — resurrecting the very CPU limit this function exists to drop.$sizeis never empty on a reachable path (_training_resources' four-way fallback always yields one), and an empty input returns empty, which is the honest answer about an envelope that does not exist.cpu=is matched as a prefix, so a futurecpuset=0-3survives — pinned by a test, because prefix matching is the kind of thing that quietly eats a neighbour.Tests
+7bats and+7Pester on the helpers themselves — the same seven cases on both sides, including the whitespace case that was the divergence — plus thelimitsassertion inside both parity suites, plus two existing write-site assertions re-pointed (the installer no longer writes the same string twice, and one now also asserts nocpuappears in the limits value at all).Mutations proven:
cpuin the bash twinNo
Chart.yamlbump: nothing underclient/templates|values|schemachanged, and the guard agrees. Bumping anyway would publish a chart version with no chart diff.Not in this PR
client/templates/jobs-manager-deployment.yamlcurrently emits both env vars whenever either is set, defaulting the missing one to"cpu=2,memory=8Gi"— the chart-level twin of the bug #388 just fixed in the runtime, and it makes #388's mirroring unreachable for chart-direct installs. That is a separate change with the same ordering constraint, and it is aclient/templatesedit so it will need the Chart.yaml bump. Filed as the next PR rather than folded in here.Refs: backend#2418, backend#664, client-runtime#388, backend#1236, backend#2220, backend#2223
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
🤖 Generated with Claude Code
Note
High Risk
Changes what every install writes for training pod resources and reinstall carry logic; shipping before client-runtime#388 can yield invalid Kubernetes requests/limits and pods that never schedule.
Overview
Implements Utilization Ladder L0.2 in both installers:
RESOURCE_REQUESTSstill carries the fullcpu,memoryenvelope, whileRESOURCE_LIMITSis derived by dropping everycpu=*pair (memory and other non-cpu dimensions stay). Bash_training_limitsand PowerShellGet-TrainingLimitsare the twins, with trim and case-insensitivecpu=matching so Linux/macOS and Windows agree.Reinstall / carry-forward no longer reads memory-only limits as the carried size. Both
install-client-helm.shandinstall-k8s.ps1preferRESOURCE_REQUESTS, fall back toRESOURCE_LIMITSonly when requests is missing, so reinstall keeps CPU requests and the historiccpu=2,memory=8Gigate still works.Parity contract bumps
installer_parity.jsonto schema_version 2 with a per-rowlimitsverdict; the generator andinstaller_parity.bashemit/assert it in both parity suites. Tests add helper coverage, carry-path cases, and install round-trip scenarios; manifest hashes update for the touched scripts.Reviewed by Cursor Bugbot for commit 43c01e9. Bugbot is set up for automated code reviews on this repo. Configure here.