Skip to content

serve: fix zero-token replies from engine-queue race between requests - #7

Merged
Niko1221 merged 1 commit into
Niko1221:mainfrom
coolio986:fix/engine-queue-race
Sep 26, 2026
Merged

Niko1221 merged 1 commit into
Niko1221:mainfrom
coolio986:fix/engine-queue-race

Conversation

@coolio986

Copy link
Copy Markdown
Contributor

Summary

Fixes blank/empty responses that appeared when a client (e.g. opencode) has two requests in flight at once — a title-generation request alongside the main tool-calling turn. The second request frequently returned zero tokens: the server logged done: 0 tokens in 0 s (length, cancel=False) and the client showed an empty reply.

Root cause

StrataEngine multiplexes every request over one resident strata --serve subprocess and a single shared line queue (self.lines), with no per-request framing. In Service.run(), a stop token (<|im_end|>) breaks the token loop with the engine generator still suspended and its internal done flag False. That generator's cleanup — write STOP, then drain self.lines until DONE — therefore ran only when run()'s frame was torn down later, after the with self.fifo lock had been released. By then the next request held the lock and had issued its own GEN; the prior request's drain raced it on the shared queue and consumed the new request's DONE. The new request saw an immediate end-of-stream: 0 tokens, finish left at its default "length".

Fix

Close the engine generator inside the with self.fifo block via try/finally, so a request's STOP+drain completes while it still holds the lock and cannot leave stray lines on the shared queue for the next request to misread as its own DONE.

Also included:

  • Clamp a non-positive max_new to the default (1024) in both /v1/chat/completions and /v1/messages. Some clients send max_tokens: -1 ("unlimited"); that value passed straight through and GEN -1 made the engine yield 0 tokens.
  • Distinguish a client disconnect: catch GeneratorExit and set finish="disconnect" instead of leaving it as "length".
  • Diagnostics gated on STRATA_DEBUG (off by default): a per-request _debug_req line and a raw: dump of decoded model output at completion.
  • Progress reporting: add tok/s and cancel state to the done: line; emit the progress line every 1 s instead of 15 s.

Test plan

  • python -c "import ast; ast.parse(open('serve/server.py').read())" passes.
  • Mock engine (--engine mock): 2 concurrent streaming + sequential streaming + non-stream OpenAI + Anthropic requests all return content with correct finish reason; no 0 tokens (length) regressions.
  • Real engine (STRATA_DEBUG=1 ./run-iq3_xxs.sh): full opencode agentic loop (title-gen, tool calls, file write/edit, test run, final answer) completes with every turn producing tokens; the previously-blank tools=11 main turn now streams normally.

Symptom: with two requests in flight (opencode fires a title-generation
request alongside the main tool-calling turn), the second request often
returned zero tokens. The server logged `done: 0 tokens in 0 s (length,
cancel=False)` and opencode showed an empty reply.

Cause: StrataEngine multiplexes every request over one resident
`strata --serve` subprocess and a single shared line queue (self.lines),
with no per-request framing. In Service.run(), a stop token (<|im_end|>)
breaks the token loop with the engine generator still suspended and its
internal `done` flag False. That generator's cleanup - write STOP, then
drain self.lines until DONE - therefore ran only when run()'s frame was
torn down later, AFTER the `with self.fifo` lock had been released. By
then the next request held the lock and had issued its own GEN; the prior
request's drain raced it on the shared queue and consumed the NEW
request's DONE. The new request saw an immediate end-of-stream: 0 tokens,
with `finish` left at its default of "length".

Fix: close the engine generator inside the `with self.fifo` block via
try/finally, so a request's STOP+drain completes while it still holds the
lock and cannot leave stray lines on the shared queue for the next
request to misread as its own DONE.

Also in this change:
- Clamp a non-positive max_new to 1024 in both /v1/chat/completions and
  /v1/messages. Some clients send max_tokens: -1 ("unlimited"); that value
  passed straight through and `GEN -1` made the engine yield 0 tokens.
- Distinguish a client disconnect: catch GeneratorExit and set
  finish="disconnect" instead of leaving it as "length".
- Diagnostics gated on STRATA_DEBUG (off by default): a per-request
  `_debug_req` line (api, msg/tool counts, raw max_tokens, resolved
  max_new, thinking flag, prompt tokens) and a `raw:` dump of the decoded
  model output at completion.
- Progress reporting: add tok/s and cancel state to the `done:` line, and
  emit the progress line every 1 s instead of every 15 s for live
  throughput.
@Niko1221
Niko1221 merged commit 6d2b450 into Niko1221:main Sep 26, 2026
@Niko1221

Copy link
Copy Markdown
Owner

Merged, thanks! Ran 10 rounds of overlapping requests on the real engine and got 0 empty replies, and max_tokens: -1 works now too. In v0.1.3.

MistVVK referenced this pull request in MistVVK/XeStrata Oct 3, 2026
MistVVK referenced this pull request in MistVVK/XeStrata Oct 10, 2026
…(7900 XT, R9700, Strix Halo) (#7)

* Add experimental Linux gfx1100 setup and GLM HIP compatibility

Keep CUDA WMMA guarded and use an RDNA-sized FP32 attention tile. Validate fused HC and prompt attention against CPU references; full-model inference and CUDA compilation remain unverified.

* Port Strata multi-architecture HIP checks and RDNA4 intrinsics

* Check HIP architecture list normalization and rejection cases

* Document multi-architecture R9700 validation and compile-only gfx1151

* Let HIP choose the GLM prefill chunk

* HIP prompt speed: tuned hipBLASLt GLM projections and a runtime sub-batch

- The GLM prompt path's FP16 projections (hgemm_q) go through the existing
  strata::prefill::Gemm when STRATA_HIPBLASLT_TUNING names a table; without
  one nothing changes. Shapes without a row keep plain hipBLAS.
- STRATA_GLM_PREFILL_SUB replaces the compile-time 256-token sub-batch of the
  mixers and the dense FFN (default still 256).
- tune_hipblaslt: the absolute-error gate scales with sqrt(K / 4096), so the
  K=16384 projection is no longer rejected (relative L2 stays at 1e-4).
- Tables for gfx1100, gfx1151 and gfx1201 (hipBLASLt 100202, ROCm 7.2).
- maya.py: HIP configs on 20 GB+ cards use sub-batches of 1024 with a 4 GB
  prompt budget, and point at the card's table when one is shipped.
- glm_model_test links strata_prefill (the prompt path now uses Gemm).

Prompt speed, GLM-5.3-Flash IQ2_XXS: RX 7900 XT ~198 -> ~413 tok/s,
Ryzen AI Max+ 395 ~116 -> ~217, R9700 ~490-560.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v2d9KENHa9tcUo4yLWTYK

* Keep the hipBLASLt prompt path HIP-only and link the GLM binaries in order

PrefillState's Gemm member, its include and the lazy setup now build only
with STRATA_USE_HIP, so CUDA's prompt path is as before. On HIP the GLM
binaries (glm_model_test, glm_pack_test, glm_model_big_test) link
strata_engine then strata_prefill, as strata does; GNU ld resolves the
archives in that order.

Docs: Maya versions as v1.0.x, the F32 prompt attention's 32-row FP16 tile
(v1.0.7) replaces the HIP-only 16-row tile, #2 is merged, and the full-model
results on the RX 7900 XT and R9700.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016v2d9KENHa9tcUo4yLWTYK

* Update AMD audit docs for GLM hipBLASLt

---------

Co-authored-by: boxwrench <203946641+boxwrench@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
MistVVK referenced this pull request in MistVVK/XeStrata Oct 10, 2026
…ux through ROCm 7, one GPU, text only (#7, @boxwrench); NVIDIA unchanged (same tokens as v1.0.10 on 2x V100)
DarthCY-K pushed a commit to DarthCY-K/Strata that referenced this pull request Oct 20, 2026
… down instead of ending the start, and its message names the error (Niko1221#7)

The layer split's two page-locked hand-off buffers (chunk x 40 KiB each, cudaHostAllocPortable) failed with the bare
"prefill: the layer split's hand-off buffers", which the chunk step-down (kStepChunks in generate.cpp, which matches
"do not fit") did not take, so the server exited.  Under WSL2 the driver page-locks only about 1 GiB: a 15,616-token
chunk's two buffers are 1.22 GiB (issue Niko1221#7).  Now the message reads "... (2 x N MiB of page-locked RAM for a chunk of
C tokens) do not fit: <cudaGetErrorString>", the failed pointer is cleared, and the start tries the next smaller
chunk, down to 512 tokens.  One GPU allocates no hand-off buffers: unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
DarthCY-K pushed a commit to DarthCY-K/Strata that referenced this pull request Oct 20, 2026
…locked is refused at start when the drafter runs on another card, instead of an illegal memory access later (Niko1221#7)

When cudaHostAlloc refuses the native token embedding (WSL2 page-locks only about 1 GiB; BF16 is 1,212 MiB),
NativeEmbed keeps it in the VRAM of the card that loaded it (CUDA0).  On a layer split the MTP drafter - and E-9's
batch with STRATA_SPLIT_MTP_BATCH=1 - runs on the last stage's card and gathers from that pointer: "mtp prefill: an
illegal memory access", sticky (issue Niko1221#7).  NativeEmbed::vram_device() now names that card (-1 when mapped), and the
start stops before the drafter loads, saying why and what helps (run natively, or on one GPU; a smaller --prefill does
not).  No device copy is made.  One GPU, or a mapped table: unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
DarthCY-K pushed a commit to DarthCY-K/Strata that referenced this pull request Oct 20, 2026
…robe that only warns, the Windows watchdog and prompt-loan fixes, BENCH.bat), Niko1221#1852's prompt encoder, issue Niko1221#7's WSL2 warning and layer-split fixes - README, docs/NVFP4.md and the bundle version

README is origin/main's (2392100: "Not WSL2" in Requirements and Limits) with the version, a What's new block and the
"!" section (upstream's guard comes first; the audit needs STRATA_NAN_GUARD=0).  README.upstream.md = v0.1.43.1's
README.  docs/NVFP4.md "Upstream 0.1.43.1": the port, the merges by hand, the PLE probe, Niko1221#1852, Niko1221#7 and every check
(release build 925c7326: 2K / 32K logits and tokens = 0.1.42-nvfp4.2's engine ddd78216; serve KL texts byte-identical;
defaults off = upstream 0.1.43.1 on IQ2_XS; 116/119 ctest, serve 751, tools 821; decode 179.2 vs 178.1 tokens/s).
VERSION 0.1.43.1-nvfp4.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
DarthCY-K pushed a commit to DarthCY-K/Strata that referenced this pull request Oct 20, 2026
… base like CUDA0's - 0.25 for NVFP4, not 0.55

CUDA0 takes 0.25 for an NVFP4 pack (its 2.76 MB experts cross the link twice as long as the i-quants' the 0.55 was
measured on), but every later stage of a layer split scaled its probed link from 0.55: issue Niko1221#7's CUDA1 on 6.8 GB/s
got 0.19 instead of 0.085. One-GPU runs are unchanged (the stage loop does not run).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
DarthCY-K pushed a commit to DarthCY-K/Strata that referenced this pull request Oct 20, 2026
…orks and upstream PRs, the BF16 NaN fix, the layer split's PCIe base, the per-GPU Monitor, the first Linux build

What's new and "Upstream 0.1.43.1" name G1's six commits with their authors (jarydkrish, LeGeRyChEeSe Niko1221#1881, Mert
Özkan Niko1221#1840, ANBAL534, sebastianrcnt, shoftee), the later stage's PCIe base 0.25 for NVFP4 (Niko1221#7), the Monitor's GPU
and Disk cards with psutil in the Windows serve env, and the Linux build (Ubuntu 24.04, gcc 13/15, CUDA 13.3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
RandoTeam added a commit to RandoTeam/4gbVramStrata that referenced this pull request Nov 1, 2026
[Other] the release zip carries the engine's sources
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