Skip to content

perf(engine): Macs with a hardware GPU render with more capture workers - #6636

Merged
miguel-heygen merged 3 commits into
mainfrom
perf/apple-gpu-worker-sizing
Oct 28, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
perf/apple-gpu-worker-sizing

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

What

On a Mac with a hardware GPU, auto worker sizing for screenshot capture to disk uses budgets measured for that path. A 14-core, 24 GB Mac now captures with 8 workers instead of 4: a 5 minute film renders 43% faster.

Why

Auto sizing on these Macs was capped by budgets set for other capture paths:

  • Parent V8 heap, 640 MB per worker. That figure came from a heap OOM where frames passed through this process. Screenshot workers on a Mac write their frames to disk. Measured parent heap peak (--debug perf summary): 270 MB at 4 workers and 329 MB at 8 on a 42 s 1080x1920 film; 61 MB and 163 MB on a 5 minute 1080p film. That is about 15-25 MB per extra worker, against Node's ~4.1 GB default heap. The cap held a 14-core Mac at 4 workers.
  • Contention cap, 2.5 cores per worker. This was written for SwiftShader, where every Chrome rasterises on the CPU. With Metal drawing, the same Mac kept getting faster up to 8 workers.
  • Auto ceiling of 6 workers on any machine under 48 cores.

The memory rule (half of RAM at 1.5 GB per worker) is unchanged and is what now binds: 8 workers on 24 GB, 5 on 16 GB. The low-memory profile still pins 8 GB machines to one worker.

Why under 100 lines

One lever: Mac hardware-GPU disk capture gets its own heap and core budget inside the existing sizing function. The memory rule, every other platform and every other capture path are untouched.

Related work

Refs #5945.

How

  • computeWorkerSizing (engine) takes appleGpuDiskCapture. When set, it uses 128 MB of heap per worker (about 5x the measured peak), 1.75 cores per worker, and an auto ceiling of 8. An explicit PRODUCER_CORES_PER_WORKER still wins over the 1.75 default.
  • The orchestrator sets the flag only when all four hold (isAppleGpuDiskCapture): macOS; the resolved browser GPU mode is hardware; the render does not take the layered composite route (HDR, or shader transitions on a format page-side compositing does not cover), whose workers feed frames through this process; and the parallel-stream router is off. Together these mean frames go to disk. It uses the existing shouldUseLayeredComposite, resolveParallelCaptureMode and isCaptureParallelStreamRouterEnabled owners.
  • Linux, Windows, software GL, streamed capture and cloud chunks size exactly as before.

Test plan

  • Unit test added: the same 14-core / 24 GB / 4.1 GB-heap case that sizes 4 workers by default sizes 8 (memory-bound) with the flag, and an explicit coresPerWorker still applies.
  • Fails without the fix: parallelCoordinator.test.ts > computeWorkerSizing > sizes Apple-GPU disk capture by its own heap and core budget (expected 4 to be 8 with only the test applied).
  • Unit test added: isAppleGpuDiskCapture holds only for hardware-GPU Mac renders that write to disk; layered, streamed, software-GL and non-Mac renders keep the old sizing. Dropping the layered condition fails it (expected true to be false).
  • tsc --noEmit (engine, producer) exit 0; engine parallelCoordinator.test.ts 55 passed; producer renderOrchestrator.test.ts 290 passed; comment ratchet passes; oxfmt and oxlint clean.
  • Manual testing performed: before/after on the real CLI (below).
  • Documentation updated (not applicable)
  • Comments follow CONTRIBUTING.md "Comments"

Before / after

Published [email protected] vs the same bundle with only this change, both at auto worker count. Apple M4 Pro (14 cores, 24 GB, hardware GL), hyperframes render on repo/synthetic fixtures that take the screenshot + disk path, interleaved, medians. wall - compile drops compile, which is network-bound for the first two fixtures. Peak RSS is the whole process tree (CLI + Chrome + ffmpeg), sampled every 250 ms.

fixture workers wall - compile peak RSS output
style-11-prod, 1080x1920, 14 s (3 runs) 4 -> 8 10.16 -> 8.22 s (-19%) 2.9 -> 4.8 GB PSNR ~55 dB vs 4 workers (Chrome rasterises a few pixels differently once 6+ GPU browsers run; the same at every run)
overlay-montage-prod + mix-blend layer, 42 s (3 runs) 4 -> 8 23.50 -> 16.83 s (-28%) 4.1 -> 5.8 GB within this fixture's own run-to-run variation
1080p 5 minute text film with music (2 runs) 4 -> 8 123.98 -> 71.19 s (-43%) 2.9 -> 4.8 GB byte-identical

Explicit worker sweep on the same Mac (5 minute film): 4 workers 121.6 s, 6 workers 82.9 s, 8 workers 64.1 s, all byte-identical.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES · 1 blocker · 1 important · 0 nits · head 6e234229

The sizing change is sound for the screenshot→disk route, and the fails without the fix: line names a real test. The blocker is the red Comments run.

Scope

Read all 5 files. Traced the flag through resolveRenderWorkerCount (spread reaches both computeWorkerSizing and the baseline calculateOptimalWorkers), the DE router (pins 3 workers, unaffected) and the capture-plan routes. CI, Windows render verification, regression and CodeQL were still running at review time. No prior reviews.

— Genesis reviewer

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 105 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

Unstable (2)

  • seqplainkeys-none-center-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true
  • seqplainkeys-none-pct-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE — 2 resolved, 0 open, 0 new · 6e234229 → 193d321c. CI, Windows render verification, regression and Comments are green.

— Genesis reviewer

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE — 0 open, 0 new · 193d321c → fe217dfa (rebase only). The PR patch matches 193d321 line for line apart from hunk offsets. The one main change in renderOrchestrator.ts it now sits on (calibration measures screenshot mode) does not touch the sizing flag. CI, Windows render verification, regression and Comments are green.

— Genesis reviewer

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit e6476c2 Oct 28, 2026
78 checks passed
@miguel-heygen
miguel-heygen deleted the perf/apple-gpu-worker-sizing branch October 28, 2026 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant