Skip to content

perf(producer): chunks capture frames round-robin so the encoder runs during capture - #6364

Merged
miguel-heygen merged 3 commits into
mainfrom
perf/cloudcut
Oct 26, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
perf/cloudcut

Conversation

@miguel-heygen

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

Copy link
Copy Markdown
Collaborator

Problem

The encoder that runs during capture consumes frames in order (0, 1, 2, ...), but capture workers took contiguous ranges. It could only use worker 0's frames while capture ran, then encoded everything else after capture ended. On a distributed chunk that tail was about a quarter of the chunk's billed time.

Change

When the encoder overlaps capture (distributed chunks and in-process disk capture), workers take frames round-robin, using the existing distributeFramesInterleaved and stride merge.

  • A worker that dies mid-capture leaves every Nth frame missing. The missing-frame retry now rebuilds one stride task per dead worker in a single batch, instead of one one-frame batch per frame.
  • Screenshot-mode reuse of unchanged frames required the previous frame to be exactly one back, which never holds at stride above 1. It now reuses when every frame in the gap is unchanged, through one helper shared with the worker path.
  • Everything else keeps contiguous ranges.

Evidence

  • Tests: round-robin vs contiguous task hand-out, the stride retry (a dead worker's frames come back as one task, with and without a chunk offset), and the stride-gap reuse (reuse when all gap frames are static, none when one is not). Each fails with its fix removed.
  • One 68-frame 4K chunk, 3 workers, same plan, screenshot mode on both sides: output file identical (same sha256) in all four runs. Frames fed to the encoder during capture: 68 of 68 vs 52 and 30. Post-capture encode stage 3.6 and 3.8 s vs 7.2 and 8.9 s.
  • Lambda, 5 interleaved cold before/after pairs on a 4K 60 s project, same hour: cost lower in 5 of 5 (mean -4.2%), wall lower in 5 of 5 (mean -2.5%), output frames identical (SSIM 1.0 over 1500 frames). Both intervals touch zero, so this is a consistent small gain.

… during capture

The overlapped encoder takes frames in order, but workers captured contiguous ranges, so it sat idle until worker 0 finished and then encoded the other workers' frames after capture. With the encoder on, workers now take frames round-robin and it keeps pace.

@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 18ecd937

Interleaved capture keeps output correct for seek-pure compositions. Ordering, frameRangeStart, stride-aware expected counts, merge and encoder backpressure all check out. Two other paths assume each worker captures a contiguous range.

Blocker: after interleaved capture, the missing-frame retry splits into one-frame batches. On the disk path, transient_browser and protocol_timeout don't abort the other workers (isFatalCaptureFailure, captureFailure.ts:212). So when one worker dies, the frames still missing are its stride w, w+N, w+2N, …. findMissingFrameRanges (renderOrchestrator.ts:937) turns each of those into its own range. buildMissingFrameRetryBatches (:968) then gives each range one worker and runs the batches one after another. Example: 1800 frames, 4 workers, worker 2 dies (Target closed) after 10 frames. Before, that left 1 range and 1 batch. Now it leaves ~440 ranges in ~110 sequential executeParallelCapture batches, each opening fresh sessions to capture one frame. A recovery that took seconds now takes minutes, and on a chunk it can run into the timeout. Fix: when the first attempt was interleaved, group the missing frames into one task per worker, e.g. reuse the dead worker's stride or pack the missing frames into contiguous runs.

Important: interleaving turns off static-frame dedup in screenshot mode. captureFrameCore reuses a frame only when lastFrameAbsoluteIndex === absFrameIndex - 1 (frameCapture.ts:4655-4657). That is never true at stride > 1. Every predicted-static frame gets re-seeked and re-screenshotted, while armStaticDedup still pays for its verification seeks. A title-card or slideshow composition could lose more capture time than the overlap saves, and the evidence doesn't include a static-heavy composition. Fix: use the same gap check as the worker path (:4829-4833: reuse when every frame in (last, i] is static), or keep contiguous ranges when dedup is armed.

Scope

I read all 4 changed files plus their consumers: parallelCoordinator distribution/merge/capture loop, static dedup, video injector, BeginFrame ticks, motion blur, drawElement verify (unreachable under overlap), and when the overlap flag is evaluated. The second commit only removes a comment, to fix the Comments ratchet that failed on 34ce93f9. CI at 18ecd937 was still running when I posted.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 271 (base branch 271), smooth 140 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-px-r0-nested-z100: tracking 0.07, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.07, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.07, 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

…e task and keeps static-frame reuse

A worker that dies during round-robin capture leaves every Nth frame missing; the missing-frame retry turned each into its own one-frame batch. It now retries one stride task per dead worker. Screenshot-mode static reuse also required the previous frame to be exactly one back, which never holds at stride above 1; it now reuses when every frame in the gap is static, like the worker path.

@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 blocker · 0 important · 1 nit — 18ecd937 → 4a722a6e: 2 resolved, 0 open

  • ✅ Retry: buildMissingFrameRetryBatches now groups the missing frames into one stride task per residue class, so there are at most initialWorkerCount tasks instead of one batch per frame. When I put the old stride of 1 back at the call site, the new retry test fails.
  • ✅ Dedup: captureFrameCore reuses a frame across the stride gap through the shared framesAreStatic check. When I put the old === abs - 1 back, the new stride-gap test fails.
  • Nit: framesAreStatic(set, last + 1, abs) returns true for an empty range, so if a session ever captured a frame at or below its last one, it would reuse the later buffer. I found no current caller that does this, but an explicit last < abs guard would keep the check as strict as the old one.

All CI on this head is green, including required Test and Windows. Locally: producer renderOrchestrator.test.ts 289/289, engine frameCapture-staticDedupIndex.test.ts 8/8.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 924da50 Oct 26, 2026
122 checks passed
@miguel-heygen
miguel-heygen deleted the perf/cloudcut branch October 26, 2026 02: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