Skip to content

Remove repeated work from scheduler planning - #188

Merged
lukealonso merged 2 commits into
mainfrom
claude/scheduler-probe-planning
Nov 3, 2026
Merged

lukealonso merged 2 commits into
mainfrom
claude/scheduler-probe-planning

Conversation

@voipmonitor

@voipmonitor voipmonitor commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

At C32 on DeepSeek V4.1 TP4 the serving worker stalled for 10–35 ms inside RequestScheduler::plan every few seconds, and for ~0.7 s after 32 requests arrived. The worker does not poll completions while it plans, so a finished round's successor, or a probe waiting for the queue to drain, started late and the GPU idled.

perf put the time in funded acceptance learning: acceptance_probe compared each probe candidate with the ordinary winner slot by slot, and every comparison of a packed round re-estimated the whole round's prefix lengths and lane values, re-evaluating each lane's acceptance curve for every extra row. The rest of planning also repeated per-lane and per-shape work, so its cost grew roughly with the square of the request count.

Change

The same values are computed fewer times; selection and its arithmetic are unchanged.

  • Acceptance probes estimate each candidate's round widths once; prefix allocation computes each lane's probabilities once.
  • A request's pending advance is tracked where commit and completion already maintain its pending count, instead of scanning all submitted lanes on every use. Debug builds check it against the scan.
  • Packed rounds share one ranking per draft and width bound, evaluate lane curves once per call and priorities once per shape, and sort only the leading cohort.
  • Padding pruning compares only candidates with the same kind, cohort, and prefill row, estimating each candidate's lengths once.
  • Speculative durations read the packed round table directly; it already includes the full B×W graph.

Equivalence

A local differential harness (not in this PR) records every plan, lane value, pruned candidate list, probe choice, and round length over seeded random scenarios: 1–64 requests, host/device/mixed prefix allocation, fixed widths, mixed graphs, grammar and host-stop lanes, queued rounds, retirement, refresh and acceptance probes. Across 1056 scenarios and ~210 000 plans the traces of main and this branch are byte-identical (one seed set also in a debug build).

Results

Median plan() time with one round queued (release test build, frank1):

Requests Allocation main this PR with acceptance probe: main this PR
8 device 140 µs 60 µs 2563 µs 111 µs
16 device 289 µs 96 µs 2153 µs 201 µs
32 device 669 µs 196 µs 5312 µs 413 µs
64 device 1773 µs 436 µs 13380 µs 943 µs
32 host 754 µs 250 µs 1363 µs 497 µs
64 host 1957 µs 615 µs 3546 µs 1215 µs

Service at C32 on switched GPUs 0–3 of frank1 and frank2, #182 applied in both builds, two services × five 30 s cells per row, mirrored order:

Host / package before tok/s (worst cell) after tok/s (worst cell)
frank1 main 2471 (2428) 2524 (2490)
frank1 B 2474 (2432) 2529 (2495)
frank2 main 2478 (2398) 2537 (2503)
frank2 B 2450 (2313) 2540 (2515)
all 40 cells 2468 2532 (+2.6 %)

In a diagnostic run on frank1 the longest plan call fell from 34.4 ms to under 1 ms, no worker iteration exceeded 10 ms, and the GPU busy share of the steady window rose from 98.4–98.9 % to 99.8–99.9 %.

Validation

  • Helios library tests pass in release and debug builds except five template, package-cache, and image-hint tests that fail identically on main (missing local assets).
  • A new test covers the tracked pending ceiling across queued rounds, completion, and retirement.

Limitations

  • While a winning probe waits for the queue to drain, the worker still plans again on every iteration; each plan is now short.

🤖 Generated with Claude Code

Funded acceptance learning compared every probe candidate with the ordinary
winner slot by slot, and each comparison of a packed round re-estimated the
whole round's prefix lengths, including lane values, while the allocation
loop re-evaluated every lane's acceptance curve for each extra row. At
concurrency 32 one plan call took 10-35 ms whenever acceptance credit was
full. The worker does not poll completions while it plans, so a finished
round waited for the plan before its successor or a draining probe could
start: on DeepSeek V4.1 TP4 every few seconds, and for about 0.7 s after 32
requests arrived.

Planning also repeated linear work per lane and per shape:
- Each speculative-token bound scanned every lane of the submitted steps to
  find the request's pending advance. The ceiling is now tracked on the
  request where commit and completion already track its pending count.
- Packed rounds rebuilt rankings, gains, and per-lane curves for every shape
  and priorities for every ordering. Shapes sharing draft and width bounds now
  share one ranking, lane curves are evaluated once per call, priorities once
  per shape, and only the leading cohort of each ordering is sorted.
- Padding pruning compared all candidate pairs and re-estimated round lengths
  for each pair. Only candidates with the same kind, cohort, and prefill row
  are compared, and each candidate's lengths are estimated once.
- Service rates and next-progress values evaluated the forecast curve for
  every batch and width, and every speculative duration repeated a hashed
  lookup of the full graph, which the packed round table already includes.

Selection, round lengths, and their arithmetic are unchanged. Seeded
scheduler traces over 1056 scenarios, with 1-64 requests, host and device
prefix allocation, fixed widths, mixed graphs, grammar and host-stop lanes,
queued rounds, retirements, and timing and acceptance probes, record every
plan, lane value, pruned candidate list, probe choice, and round length:
about 210 000 plans compare byte for byte with main; the first quarter also
in a debug build.

Median plan time with one round queued (release test build):

  requests  allocation  main      after     acceptance probe: main  after
        8   device      140 us     60 us                  2563 us  111 us
       16   device      289 us     96 us                  2153 us  201 us
       32   device      669 us    196 us                  5312 us  413 us
       64   device     1773 us    436 us                 13380 us  943 us
       32   host        754 us    250 us                  1363 us  497 us
       64   host       1957 us    615 us                  3546 us 1215 us

DeepSeek V4.1 Flash TP4 on switched GPUs 0-3 of frank1 and frank2,
concurrency 32, seven drafts, verification budget at most 128, alpha 0.6,
Engram tables in RAM, with the exposed-handoff cost change in both builds
(tokens/s, mean of ten 30 s cells, worst cell in parentheses):

  host/package   before          after
  frank1 main    2471 (2428)     2524 (2490)
  frank1 B       2474 (2432)     2529 (2495)
  frank2 main    2478 (2398)     2537 (2503)
  frank2 B       2450 (2313)     2540 (2515)
  all 40 cells   2468            2532 (+2.6%)

In a diagnostic run of the same service, the GPU busy share of the steady
window rose from 98.4-98.9% to 99.8-99.9%, and no worker iteration exceeded
10 ms.

Validation: Helios library tests (runtime-package and server features) pass
in release and debug builds except five frontend-template, package-cache,
and image-hint tests that fail identically on main for missing local assets.
Debug builds check the tracked pending ceiling against the submitted lanes on
every use. A new test covers queued rounds, completion, and retirement.

Limitations: while a winning probe waits for the queue to drain, the worker
still plans again on every iteration; each plan is now short.

Signed-off-by: Martin Vit <martin@voipmonitor.org>
Design-Signed-Off-By: Claude (Opus 5.5); design-review=1c01
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Derive pending advance from the scheduled and consumed cursors, retaining
submission scans for completion reconciliation. Stream acceptance candidates
into winner selection without storing all candidate length arrays.

Rank monotone prefix survival once with depth and slot tie-breaking. Share
the ranking with host cohort allocation and preserve request limits, fixed
widths, and GPU-owned allocation ceilings.

Validation: 346 scheduler and runtime phase tests pass in both debug and
release builds; two tests are ignored by default. Differential tests cover
host/GPU allocation, MTP/copy proposals, ties, tails, and cohort growth. A
release CPU benchmark measures planning with one queued round and funded
or unfunded acceptance probes. GPU serving measurements were not run.

Design-Signed-Off-By: Codex (GPT-6); design-review=1c01
Signed-off-by: Luke Alonso <lalonso@gmail.com>
@lukealonso
lukealonso merged commit 44ec239 into main Nov 3, 2026
2 of 3 checks passed
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.

2 participants