Repository navigation
forward-serve: a text request to a decision-only pack is a 400 that names the decision route — it was a 500 from inside the forward (Refs #1786) - #2121
Conversation
…ames the decision route (Refs #1786) A pack with non-causal attention has no decode to run. /v1/chat/completions and /v1/completions tried anyway and failed inside the forward with HTTP 500 and its internal wording (measured on FastFlowLM's D3-Nano-NPU2). Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
ready for review at 205b4c7 |
|
A correction about my own PR text: the body says I searched for an existing report before writing this. I had not when I wrote that sentence; I ran the search after opening the PR. Result: nothing open covers it ( |
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT at 205b4c7: READY
A text request to a decision-only pack is refused with a 400 and a JSON body, rather than being served as if the pack could chat:
if (e.fwd && e.fwd->noncausal()) {
res.status = 400;
res.set_content(npu::decision_only_refusal(e.id, e.head != nullptr).dump(), "application/json");
}That is the right pair of decisions: 400 (the request is wrong for this server) not 500 (the server is broken), and a structured body so a caller can branch on it instead of parsing prose. A decision-only pack answering a chat request is the failure mode that produces plausible nonsense rather than an error, so refusing by name is the fix.
Passing e.head != nullptr into the refusal lets the message distinguish "this pack has no chat head at all" from "it has one and still will not chat" — two different operator actions.
Refs #1786 (the
D3-Nano-NPU2row). Backlog, not a release blocker. I searched issues and PRs fornoncausal chat completions 500anddecision model text routefirst; nothing open.The fault, on the NPU
FastFlowLM's
D3-Nano-NPU2(a decision pack: itsdecision_config.jsondeclaresattention_mode: noncausal_full_attention) served by1bit serve --device npu, main before this change:The forward is right to refuse: a non-causal pack encodes a whole prompt in one pass, so there is no token-by-token decode. But the text routes tried anyway, so the client got a server error with the forward's internal wording for a request that can never succeed on that model.
The change
handle_generatechecksfwd->noncausal()before any work and answers 400 withnpu::decision_only_refusal()(app/npu_systemone.h): the model's name, that it does not generate text, and the decision route to use, or, when the serve loaded no decision head, that there is none (so the client is not sent to a route that answers 503).typeisdecision_model_error.After, same pack, binary from this branch (
205b4c7), onenpu-holdticketThe decision route is untouched and returns the same answer as before the change (
fast0.9225, 102 input tokens).Tests
tests/npu_chatml_test.cpp, 4 new checks on the refusal: names the model, says it does not generate text and names/v1/systemone; carries the type; with no head loaded it does not point at the decision route; none of the forward's internal wording is in it.npu_chatml: ok. The handler's branch itself has no device-free harness; the block above is its test.tools/copyright.py --checkrc 0. Docs:docs/npu.md, the Decisions section.Limits
🤖 Generated with Claude Code