Skip to content

serve: --flash, the single-turn NPU profile; --ctx-size reaches the forward route; an over-context request is a 400 (Refs #1785) - #2029

Merged
bong-water-water-bong merged 2 commits into
mainfrom
cl-flm/1785-flash
Nov 10, 2026
Merged

bong-water-water-bong merged 2 commits into
mainfrom
cl-flm/1785-flash

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

Refs #1785 (F5, single-turn flash mode). Third slice after #1941 (the pinned slot, table half) and #1982 (--prefix-pin on the device path). The issue stays open for the bar row, see "What remains".

What changes

  • 1bit serve --flash: one word for the single-turn NPU profile, --ctx-size 1024 plus --prefix-pin. It refuses what contradicts it by name instead of letting one side quietly win: another --ctx-size, --prefix-slots 0, a device other than npu.
  • --ctx-size reaches the forward route. serve --ctx-size N on a model-generic container was accepted and then dropped: serve built the forward's arguments without it and forward-serve parsed none, so the model's own limit applied. This is the correction I posted on forward-serve: --prefix-pin, the pinned shared prefix on the device path, measured (Refs #1785) #1982; the docs sentence is fixed here.
  • An over-context request is a 400, not a 500. forward-serve answers exceed_context_size_error with n_prompt_tokens and n_ctx, before any prefill.
  • docs/npu.md: the flash section and the corrected context sentence.

Proof

Refusals, no model and no device (new tests/serve_flash_test.sh, ctest row serve_flash_refusals, at this head):

$ bash tests/serve_flash_test.sh build/1bit
ok   another context size is refused
ok   and so is one given before --flash
ok   a cache turned off is refused
ok   another device is refused
ok   no device at all is refused too
ok   its own context size, spelled out, is accepted
ok   --prefix-pin next to it is accepted
ok   without --flash none of the rules apply
serve_flash: ok

On main the five refusal arms fail, because --flash is an unknown option there.

On the NPU (npu-hold -l cl-flm-1785 -t 10, pack bonsai2-t2s2-perrole876, 1bit serve --device npu --flash, one 341-token system prompt, three different questions, 8 answer tokens each). The server's own start lines:

1bit forward-serve: context 1024 tokens (prompt plus answer)
1bit forward-serve: prefix pin on: the part a request shares with the previous one keeps a slot
request prompt tokens cached wall
1597-token prompt 1597 n/a HTTP 400 exceed_context_size_error in 0.02 s
R1 (first question) 355 0 56.2 s
R2 (second question; pins 342) 356 0 48.9 s
R3 (third question) 355 341, pinned 13.9 s

That device run was made at 7a2b67e, the same change stacked on #1982 before it merged. app/forward_serve.cpp is byte-identical between 7a2b67e and this head; app/serve.cpp differs only by what main gained meanwhile. I did not repeat the device run after the rebase.

tools/copyright.py --check rc 0; tests_referenced_check.py: all 79 test scripts referenced; npu_sampling: ok.

What remains on #1785

  • The bar row itself on qwen3.5-flash:2b: needs that model's pack and kernels on the box, which are not there. The numbers above are on a different model and say nothing about that bar.

🤖 Generated with Claude Code

bong-water-water-bong and others added 2 commits November 10, 2026 17:15
…orward route; an over-context request is a 400 (Refs #1785, F5)

--flash = --ctx-size 1024 + --prefix-pin, refusing what contradicts it. serve dropped --ctx-size at
the hand-off to forward-serve on a model-generic container; it is carried now and forward-serve
lowers its limit to it. A request that does not fit was a 500; it is a 400 with
type exceed_context_size_error. Measured on the NPU.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…del or device (Refs #1785)

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

ready for review at 171cf6a

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

VERDICT at 171cf6a: READY

Checked at 171cf6a.

  • tests/serve_flash_test.sh against onebit built at this head: all 8 arms ok. That's five refusals by name (another ctx size before or after --flash, --prefix-slots 0, another device, no device), two acceptances, and "without --flash none of the rules apply". The acceptance arms point serve at a missing model under timeout 20, so they stop after argument parsing and no server is started. Nothing was left running.
  • app/forward_serve.cpp compiles at this head. It's NPU-private, so I did a single-file compile with an existing NPU build's command, pointed at this tree.

Read for correctness:

  • The 400. The existing room < 1 guard now throws ContextOverflow, caught as a 400 with exceed_context_size_error, n_prompt_tokens and n_ctx before any prefill. Other errors keep their old path.
  • --ctx-size only lowers the limit. e.max_context = min(model, ctx_size), with a note when asked for more, and a negative value is refused. serve now passes it to the forward route, the dropped-argument bug you corrected on #1982.
  • --flash doesn't quietly override. It sets 1024 + --prefix-pin only after refusing every contradiction by name.

The device table was taken at 7a2b67e before the rebase. You state that, plus forward_serve.cpp being byte-identical since, and that's enough for a flag-and-guard change. The bar row on qwen3.5-flash:2b stays open on #1785, as you say.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT at 171cf6a: READY — and it fixes a second silently-dropped option, which is the same defect as the one that started the chain

The --flash profile itself is a good consolidation: one word for the single-turn NPU profile (--ctx-size 1024 plus --prefix-pin), and — the part that matters — it refuses what contradicts it by name rather than letting one side quietly win: another --ctx-size, --prefix-slots 0, a device other than npu. A profile flag that silently overrides or silently loses is exactly how the flags it bundles end up not applied, which is the failure this chain has already produced twice.

And the second fix in this PR is the same defect as #1991's, which is why it belongs here: serve --ctx-size N on a model-generic container was accepted and then dropped — serve built the forward's arguments without it and forward-serve parsed none, so the model's own limit applied. That is --npu-opt accepted-then-bypassed wearing a different name: an option the user set, silently not in effect, with the output looking like the option did nothing. Fixing it in the PR that introduces a profile depending on --ctx-size is the right place, because otherwise --flash would itself be unreliable.

Three asks, in order:

  1. --flash must be refused, not degraded, when its prerequisites are missing — a container that cannot take a pinned slot (npu: a pinned slot for the prefix single-turn requests share — the table half of the flash profile (Refs #1785) #1941/forward-serve: --prefix-pin, the pinned shared prefix on the device path, measured (Refs #1785) #1982's device-form path), a route that is not the forward. The refusal list for contradicting flags is there; the list for unsupported by this container is what prevents the profile from being accepted on a pack where the pin silently does nothing (the --prefix-pin accounting showed the pin only helps when the pin is actually established).
  2. Test the refusals, not only the profile: tests/serve_flash_test.sh should assert that --flash + --ctx-size 2048 exits non-zero with a message naming both, and that --flash on a non-npu device refuses. A profile flag is mostly a set of constraints, so the constraints are what needs pinning.
  3. One measured row for the profile end-to-end: the honest accounting from forward-serve: --prefix-pin, the pinned shared prefix on the device path, measured (Refs #1785) #1982 was request 3's 2.6× from the pin (23.4 s vs 61.5 s) with requests 1–2 paying ~1–3 s to establish it. --flash should reproduce that shape, and quoting it here ties the profile to a measurement rather than to its parts.

And the docs sentence fix is worth noting: the --ctx-size drop was documented as working, so the doc was consistent with the intent and inconsistent with the behaviour. Fixing the code and the sentence together is the only way that stays true.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY 171cf6a

--flash is a name for two settings, and the implementation treats it as a name rather than as a third setting — which is the distinction that makes it
safe:

if (o.ctx_size != 0 && o.ctx_size != 1024) throw …  "drop --ctx-size N"
if (o.prefix_slots == 0)                   throw …  "drop --prefix-slots 0"
if (o.device != "npu")                     throw …  "--flash is a profile of --device npu"

So a contradictory invocation refuses instead of one flag quietly winning, and each refusal names the flag to drop. That is the same discipline as #1991
one file over: an option that silently loses is how a run reports "no gain" when it actually ran a different profile.

Three things that make it reviewable:

  • the parts stay visible: the comment says "the profile is one word and its parts stay visible", so --flash cannot drift into a bundle nobody can
    audit — a reader still sees 1024 and --prefix-pin;
  • the row needs no model, no device and no port (tests/serve_flash_test.sh, and every case ends in the option check before anything is served) — which is
    why it is a build-job row rather than a GPU-ticket one;
  • ci_targets_check --check → rc 0 at this head.

One thing I would add to docs/npu.md, since this is the profile a single-turn user will reach for: what --flash costs. A 1024-token context and a
pinned prefix are choices with consequences (a longer prompt is truncated or refused; the pin holds memory for the process's life). The doc row likely says
what it sets — the sentence worth adding is when not to use it.

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