Repository navigation
feat: Set GPU cap across fleet in Settings + show on dash - #218
Conversation
f45ff64 to
bc71640
Compare
fb8bf32 to
b865351
Compare
|
Pushed a fix for the case the reviewer flagged as the real-world blocker on this fleet: keeping the cap after reboot demanded What changed
Verified
|
|
Re-verified at fd88023. It no longer merges: (Review notes prepared with Claude Code on behalf of the maintainer.) |
Settings → GPU clock cap applies a preset (1800–2400 MHz, every 100 MHz) or
Remove cap to every Spark in turn, reusing the per-unit GpuClockManager and
its GET/PUT/DELETE /api/sparks/:id/gpu-clock semantics, so the sudo and
boot-service behaviour is identical to the Edit unit dialog.
POST /api/sparks/gpu-clock-all takes { maxMHz, persist? } or { reset: true },
charges the destructive limiter once for the batch, looks up a single shared
manager so a fleet apply serialises against an in-flight per-unit change, and
returns one result per unit. Units that cannot take a cap at all (hosts,
Windows/macOS units, no nvidia-smi) come back as skipped with a reason rather
than as errors, and a failure names the unit instead of being counted as a
success; the Settings row reports capped/skipped/failed counts and the reasons.
A status read with no deadline left the row on 'Reading the fleet…' for good
after one transient hang (a restarting dev server, an unreachable Spark), so
nothing was ever marked and the states could not be used.
Each unit read now has its own deadline; the ones that answered mark the
spoken-for state and the rest are counted on the same line ('2 of 3
unreadable'), so a partial fleet is never shown as the whole truth. The
Settings help line no longer mentions the removed 'Remove cap' button.
Installing the boot service needs a root shell, so on a fleet whose passwordless rule covers only `nvidia-smi -lgc`/`-rgc` the whole apply was refused before the cap was touched: nothing was capped and the row showed an error. Where sudo cannot reach a root shell, keep the cap with an `@reboot` line in the SSH user's own crontab that runs the very same `sudo -n nvidia-smi -lgc` the cap itself ran - no more privilege than capping needs. cron also refreshes the marker, so the row still reads the cap after a boot. The unit's status now reports which hook it can take (`persistKind`: unit | cron | none, plus the state of both hooks), Save and the fleet row name the hook that took (`kept after reboot by the boot service` / `by the @reboot cron job`), and both surfaces offer - and default - the tick from that real capability instead of a bare "kept after reboot". A unit with neither hook disables the tick with the reason inline rather than refusing on Apply. A refused cap still leaves an existing service, cron line and marker exactly as they were; reset removes both hooks.
… a time - crontab is read once; only 'no crontab for' counts as empty, any other read failure refuses before anything changes (apply and reset) - a persist apply removes the other hook (cron line under the service, refuses when a service it cannot remove would keep re-applying its cap) - status offers the service only where systemd runs; root without systemd falls back to the @reboot job - fleet reset never reports 'kept after reboot'; fleet success is false when any unit failed; stale fleet reads cannot overwrite a newer one
… and Settings save gate
fd88023 to
71de711
Compare
|
@MiaAI-Lab Hi, merge conflicts have been addressed. Please re-review |
|
Re-reviewed at 71de711. It now merges cleanly with main, and typecheck, the (Review notes prepared with Claude Code on behalf of the maintainer.) |
|
Status at 71de711: no new commits since the 2026-10-16 review, and it still merges cleanly with main. It changes sudo/root and boot-time behaviour on the units, so it is not merged in this pass. Still to fix: (Review notes prepared with Claude Code on behalf of the maintainer.) |
Merge: keep both sides of the CHANGELOG conflict. Review fixes (MiaAI-Lab, 2026-10-16 and 10-18): - Fleet route: success is false when no unit was actually capped or reset (all skipped), answers with an applied count, and has a 3 minute budget; the fleet row reads 'Nothing changed' instead of a green 'Capped'. - @reboot job: three tries, then a failure marker plus a logger line that status reports (cron.failed) and the unit dialog shows. Cleared by the next Apply or Remove. - Values from the unit that go into the crontab line (sudo path, HOME) must be plain absolute paths or the change is refused before the GPU is touched. - The crontab line is matched by the tag at its end, not anywhere. - A cap without keep-after-reboot is recorded before old hooks are removed, and a hook that cannot be removed is named in the error. - README: anchored sudoers rule (regex or exact commands) replaces the 'nvidia-smi -lgc *' wildcard; checked against sudo 1.9.17. The sudo -l probe asks -lgc 0,2400, which both forms allow.
|
@MiaAI-Lab hi, ready for re-review |
|
Re-reviewed at 27ad9ea; it merges cleanly with main, typecheck passes, (Review notes prepared with Claude Code on behalf of the maintainer.) |
|
Status at 27ad9ea: no new commits since the 2026-10-19 re-review. It changes sudo/root and boot-time behaviour on the units, so it is not merged in this pass. One defect remains to fix: in the (Review notes prepared with Claude Code on behalf of the maintainer.) |
danieljohnmorris
left a comment
There was a problem hiding this comment.
Reviewed published head 77dd9ce against base 9437ce8. The requested service-stop marker-loss defect is fixed; the regression models both ExecStop clock reset and ExecStopPost marker deletion and would fail without the new post-lgc marker write. The full PR is not clear of findings.
Confirmed findings
-
P2 — Reset can unlock the GPU but leave status reporting the old cap. commands.js:474–475. Trigger: an existing current-boot root-owned marker, no remaining boot service, and sudo narrowed to the nvidia-smi commands. Reset runs -rgc, then cannot remove the root marker. Reproduced in an isolated Linux non-root sandbox: RESULT=error / CODE=marker-failed; GPU unlocked=true; subsequent status CAP=2100; root marker survives. Correction must prevent stale status while accurately reporting partial completion, or refuse before changing clocks when cleanup is known to be impossible. Suppressing the error alone would leave the status lie unresolved.
-
P2 — Failed boot-hook changes can lose the Save/retry state. FleetGpuClock.tsx:136–138. If applying the live cap succeeds but installing/removing its boot hook fails, the status refresh can match the selected cap. pending then becomes false despite the unsuccessful operation, so Save cannot retry it (or, with other dirty settings, skips retry). Reproduced through the actual component harness: applySucceeded=false, refreshed cap=1800, boot-service failure displayed, pendingForSave=false. Preserve unsuccessful operation intent independently of live-cap equality until retried successfully or cancelled. Existing partial-failure tests leave refreshed status at the old cap and miss this transition. Temporary reproduction file removed.
P3 presentation: FleetGpuClock.tsx:94–108 counts unsupported Darwin/no-NVIDIA units as unreadable. Windows is not a trigger because registry normalization forces kind=host. Unverified risk: duplicate registry IDs targeting the same machine can bypass per-ID operation exclusion.
Reconciliation
Three separate read-only reviewers covered code, failure handling, and test coverage, using the available reviewer agent instead of the reference vendor-specific agents. Duplicate findings merged and P1 ratings reduced to P2 for the bounded triggers above. Rejected the suggestion to let all-skipped batches unblock Save as successful: the maintainer explicitly required zero applied units not to count as success. Also rejected a claimed missing error explanation (the fleet row displays it) and a password-capability finding (the fleet operation has no password input). TypeScript-specific review skills were unavailable; API/runtime types and public JSDoc were covered in the code pass. No separate adversarial pass was requested.
Verification and limits
- Node 22: npm run typecheck; npm run build; npm run test:frontend — pass, frontend 830/830. Build has the existing >500 kB chunk warning; no lint script configured.
- node --test server/gpuclock/tests/*.test.js — 70/70 pass. Separate generated-unit shell smoke executes the actual stop hooks and confirms persistent 1800 -> temporary 2000 with CAP=2000 and no service. Seven generated shell variants pass /bin/sh -n.
- Full Linux non-root server suite: 1117 passed, 1 failed, 1 skipped. The launcher exit-marker assertion also fails on base9437ce8; zsh unavailable. This is not an all-green full suite. Earlier macOS/root-container runs were unsuitable for several integration paths.
- Interactive/visual Settings smoke blocked by browser runtime frame/screenshot failures; only DOM/accessibility rendering, current-fleet state, and initial Save-disabled state observed with isolated API fixtures. Existing frontend tests do not replace visual proof.
- No live GPU/reboot verification, sudo changes, merge, or deployment. GitHub currently reports merge conflicts. CHANGELOG and PR description document the marker fix and verification.
The two broader P2 findings remain unchanged rather than expanding the requested marker-only fix.
|
The new head 77dd9ce repairs the service-stop marker loss ( |
danieljohnmorris
left a comment
There was a problem hiding this comment.
Reconciled published head 689b69a. This supersedes my review at77dd9ce.
All confirmed PR findings are fixed: pre-mutation marker permissions; failed operation retry/persistence intent; unreadable matching selections; per-unit unavailable hook unticking; root/fallback marker migration; Full capacity removing stranded hooks; delayed reboot-job invalidation. Three independent review perspectives rechecked the final changes with no remaining actionable findings. README/CHANGELOG and the PR description now reflect the implementation and evidence.
GitHub reports MERGEABLE / CLEAN after merging upstream00151b5. Typecheck/build pass, frontend 667/667. Generated shell/manual-command runtime smoke and actual Settings browser failure/retry/reset smoke pass with isolated fixtures.
Not an all-green server suite: Linux non-root server run is 1108 passed,1 failed,1 skipped. The sole failure is the launcher output-forgery assertion. Its whole subsystem is byte-identical to latest upstream, but current upstream passes that assertion and its full-suite comparison timed out later; latest-base reproduction is not established. zsh unavailable. No real GPU/reboot verification, merge, or deployment. These verification limits and the maintainer decision on privileged behavior remain; no blanket approval is asserted.
Available reviewer agents replaced vendor-specific review roles; TypeScript skill files unavailable, contracts/types/JSDoc reviewed explicitly. No separate adversarial pass requested.
danieljohnmorris
left a comment
There was a problem hiding this comment.
Final published revision b3fb48b, merged with newly advanced upstream df6a223. The fresh changelog conflict is resolved preserving both sides. GitHub reports MERGEABLE / CLEAN.
All three independent reviewers reaffirmed no remaining actionable findings in the PR integration delta. All confirmed GPU-clock findings are fixed; no GPU-clock runtime/UI/test changes were needed in this final upstream merge. README/CHANGELOG and PR description are current.
Re-run results at b3fb48b: typecheck/build pass; frontend 678/678 in84files. Linux non-root server suite: 1142 passed,2 failed,16 skipped. Failures are the unchanged launcher output assertions at llmlaunch/tests/launcher.test.js:360 and api.test.js:117; skips require zsh. Prior generated-shell and actual Settings browser smokes exercise unchanged GPU-clock code. This is not an all-green server-suite claim; latest-base failure reproduction was not established, and no unrelated launcher fix was added. Full suite qualification, hardware/reboot limits and maintainer approval remain explicit in the description. No merge or deployment.
|
The updated head addresses the previously reported reset/status and retry issues, including marker preflight and keeping persistence changes pending ( |
|
@MiaAI-Lab Ready for your review at b624d89. GitHub reports mergeable:true / clean against main 458cd06; the CHANGELOG conflict is resolved preserving upstream changes. Your marker/reset/status and persistence-retry findings are addressed. Final independent code-correctness, silent-failure and test-coverage reviews are reconciled, including review of the final affected deltas; no confirmed findings remain. Astra-backed read-only workers substituted after Anthropic reviewer/provider authentication failures. TypeScript-specific review/JSDoc skills were unavailable; type/runtime contracts and public JSDoc were reviewed explicitly. No separate adversarial pass was requested. Two further P2 defects found in that final review are fixed:
Verification of the final code in isolated non-root Linux arm64 / Node22.23.3, at default test concurrency: |
|
Re-checked the new head b624d89 after your update: it merges cleanly with current main, typecheck passes, and the full suites pass (server 1152 pass / 0 fail / 16 skipped, frontend 688 pass). The marker, reset/status and persistence-retry fixes from the earlier review are present ( |
|
Now on main as edfce58 (merge of the branch, so every commit and its authorship is preserved; CHANGELOG credits @danieljohnmorris). The merge needed two small hand resolutions, in the CHANGELOG and in the |
The drain change from #218 passes GNU-only --pid to tail, so on macOS the follower exited at once and a Start printed nothing and never emitted the exit marker, undoing the #264 fix. Probe for --pid and otherwise use the earlier timed shutdown. Regression test uses a tail that rejects --pid. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
78 upstream commits: MQTT publishing (MiaAI-Lab#248), harness occupancy helpers (MiaAI-Lab#208), GPU clock cap from Settings (MiaAI-Lab#218), settings i18n foundation (MiaAI-Lab#226), configurable GPU temperature limits (MiaAI-Lab#273), Locals/Splash engines, TensorFold cache-counter fixes (MiaAI-Lab#252/MiaAI-Lab#260/MiaAI-Lab#272) and the zsh-safe remote probes (MiaAI-Lab#255/MiaAI-Lab#257). Conflict resolutions: - server/collectors/LlmProbe.js + its TensorFold test: took main's implementation, which supersedes this fork's /health `live` + /metrics work with the cache-counter precedence, running/waiting aliases, histogram validation and prefill holds. This drops the fork's parallel /metrics fetch (micro-optimisation only). - SystemCollector: main's zsh-safe find probes merged into the fork's build/parse refactor, keeping the fork-only RAPL package-power reads; _getRemoteNetwork now uses the injected executor (the merge had left the fork body calling sshExec, which made main's zsh-glob corpus test run real SSH). - OverviewPage: FleetKpis keeps the fork's placement but takes all sparks, so hidden cluster workers count in the KPI tiles (main's fix). - SparkPage: main's column layout (History in the hardware column, storage/network in the right column) over the fork's full-width History panel. - SettingsDialog: fork VulnHunter/REA opt-in toggles kept, metrics-history toggle switched to main's i18n keys. - index.js: fork vulnHunt/rea spark cleanup kept alongside MQTT removeUnit/applySettings. - CHANGELOG/README/package.json: both sides' entries kept; test globs unioned so the fork-only models/autopower/vulnhunt suites still run.
Adds fleet-wide GPU clock control in Settings, extending the existing GpuClockManager and generated on-unit programs. Ready for maintainer review; not merged or deployed.
Changes
Published head and merge state
Head b624d89 includes current upstream main 458cd06. GitHub REST reports mergeable:true / mergeable_state:clean for those exact SHAs. This supersedes the earlier conflict warning and blocked full-server results in prior comments. No force-push or PR close/reopen was used. The PR remains open; it has not been merged into upstream.
Verification at this head
Isolated non-root Linux arm64 Node22.23.3 container, with zsh installed and init reaping detached children:
npm run typecheck— passed.npm run build— passed; existing >500kB chunk warning remains. No lint script is configured.npm run test:serverandnpm run test:frontend— passed at default concurrency: 1168/1168 server tests, 688/688 frontend tests across 84 files, 0 skipped. No narrowed glob or concurrency override. One stale wording-focused test and one obsolete copy assertion were removed rather than re-pinned; retained tests cover retry and settings-write behavior. React act warnings remain.Review and documentation
All three independent review perspectives returned and reviewed the final affected deltas: code correctness, silent failure, and consumer-visible test coverage. Two confirmed P2 findings from the final review (cron command length and misleading zero-completion summary) are fixed; no confirmed findings remain. Astra-backed read-only workers substituted after reviewer/generic Anthropic agents failed with provider refusal/authentication errors. Results are recorded in the handoff comment. TypeScript-specific review/JSDoc skills were unavailable; type/runtime contracts and public API/JSDoc are reviewed explicitly. No separate adversarial pass was requested.
README, CHANGELOG and CODEBASE document the behavior, prerequisites and verification conventions, including util-linux flock and GNU tail --pid.
Limits and maintainer decision
No physical GPU, real systemd boot or physical reboot verification. This still changes privileged and boot-time behavior, so Mia’s approval is required. Existing low-priority limitations remain: reboot-failure detail is shown in the unit dialog, and custom exact-list sudoers rules must allow the documented 2400MHz status probe. Existing dependency advisories were not changed. No merge or deployment performed.