Skip to content

[Engine] serve: opt-in STRATA_SLOT_EXPIRE_S keeps a long conversation's batch slot from a short side request (next turn 12.8 -> 0.3 s) - #2589

Open
SuperDistance wants to merge 2 commits into
Niko1221:mainfrom
SuperDistance:pr/slot-keep-value
Open

SuperDistance wants to merge 2 commits into
Niko1221:mainfrom
SuperDistance:pr/slot-keep-value

Conversation

@SuperDistance

@SuperDistance SuperDistance commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Problem and result

I run coding agents on Strata with --batch. Next to their long conversations, clients send short side requests (a title, a summary, a one-line check). When every free slot holds a conversation, pick_slot takes the least recently used one, so a side request can take the slot of a 40K-token conversation that is still going, and that conversation's next turn reads its whole history again: 12.8 s to the first token on 0.2.0-beta (one RTX 5090, below). With the new opt-in STRATA_SLOT_EXPIRE_S=600 the side request takes an expired slot or the one holding the shortest history, and the long conversation's next turn takes 0.34 s. Unset or 0, nothing changes.

Related discussion

#1906 and #1930 (agent sessions reading whole prompts again), #1920, #1938. Next to #2527 / #2528 (andyhrc): their placement policy covers pipelined groups; this is the small counterpart for --batch without pipelined groups (one GPU, or --batch-groups 1). qiuzan2001 noted here that the config's "env" block was ignored: the value is now read from the engine's environment (the server's plus the config's "env"). Rebased onto 0.2.0-beta after the tracker reset (one textual conflict: two functions added at the same place).

Approach

STRATA_SLOT_EXPIRE_S (seconds; off unless set). Set, a new conversation that finds no empty slot takes an expired one first (idle that long or longer: its client has likely moved on), the least recently used among them; else the one holding the shortest history. Unset or 0: the least recently used one, as before (a test checks it picks main's slot on 3,000 random slot tables). A request still goes to the slot holding its own prefix first, and pipelined groups return before this rule. Changes: pick_slot's last min() key, slot_expire_s(env), serve/test_slot_alloc.py, and the sentence on slot choice in docs/BATCHING.md.

Which batch slot a new conversation takes: its own history; else an empty slot; else (new) the longest idle of the slots idle STRATA_SLOT_EXPIRE_S or more; else (new) the one holding the shortest history

Validation

Passed:

  • 0.2.0-beta (main e88ee39) + this branch (ca7d91a), Windows 11, Python 3.12: python -m unittest serve.test_slot_alloc 25 OK; the whole serve suite (python -m unittest discover -s serve -t . -p "test_*.py") 899 OK, 21 skipped, against 881 OK, 21 skipped on main.
  • One RTX 5090 (Windows 11), engine built from 0.2.0-beta (e88ee39), Swift 1.5 IQ3_S, --batch 3, no pipelined groups, no conversation cache, a fresh engine per arm, STRATA_SLOT_EXPIRE_S=600 in the config's "env" block (main ignores it). A third stream decodes all along, so every request goes through a slot. Conversation A (a 40,250-token prompt) and B (about 1,770 tokens) finish and stay in their slots; then a one-line side request; then A's and B's next turns:
main this, STRATA_SLOT_EXPIRE_S=600 this, unset
slot the side request took A's (40,250 tokens held) B's (1,781 held) A's (40,250 held)
A's next turn: first token 12.78 s 0.34 s 12.70 s
B's next turn: first token 1.20 s 1.18 s 1.17 s
  • Before the rebase, on 0.1.45 (477ac13), same bench: 14.79 / 14.84 s on main against 0.32 / 0.32 s with 600, and 14.72 s unset. A 21-hour replay of my own agent traffic through the slots (ids, times and token counts only): 17.8 / 20.6 / 21.1% fewer prompt tokens read at 4 / 6 / 8 slots with 600.

Failed: none observed.

Not run: a split with --batch-groups 1 on GPUs; the RAM conversation cache on (replay only: smaller gain); AMD, Linux, Mac (Python-only change, nothing to build).

Limits

Opt-in, because it changes whose conversation loses its slot and is measured on one user's traffic. A long conversation whose client has gone keeps its slot until it expires; a client whose prompts never match its slot leaves histories that stay until they expire.

@SuperDistance
SuperDistance marked this pull request as ready for review October 15, 2026 06:31
@qiuzan2001

Copy link
Copy Markdown

serve.test_slot_alloc passes here (Linux). One setup trap: slot_expire_s() reads os.environ of the server process, once, when StrataEngine is defined (SLOT_EXPIRE_S = slot_expire_s()), while the run config's "env" block, where the other STRATA_* switches usually go, is only applied to the engine's environment (child_env, serve/server.py:2822). So "env": {"STRATA_SLOT_EXPIRE_S": "600"} in strata-<model>.json is accepted but changes nothing; only an exported shell variable before START-HERE works. BATCHING.md does say "in the server's environment", but reading it from the config (or from child_env(cfg), as #2528's policy tunables do) would match how users set these. Also, the branch conflicts textually with #2527 (3 files), which reworks slot placement for pipelined groups.

@SuperDistance SuperDistance changed the title serve: opt-in STRATA_SLOT_EXPIRE_S keeps a long conversation's batch slot from a short side request (next turn 12.7 -> 0.4 s) serve: opt-in STRATA_SLOT_EXPIRE_S keeps a long conversation's batch slot from a short side request (next turn 14.8 -> 0.3 s) Oct 18, 2026
@Niko1221

Copy link
Copy Markdown
Owner

Thanks for the pull request.

We're resetting the tracker for 0.2.0-beta. There are more than 1,300 open issues and pull requests, too many for us to review properly, so we're closing everything that isn't being worked on right now. Closed here does not mean rejected.

To reopen it takes about two minutes:

  1. Rebase on main if it no longer applies, and re-run your checks on 0.2.0-beta.
  2. Start the title with its area: [Engine], [GUI], [TUI], [Benchmark], [Docs] or [Other].
  3. Fill in the pull request template: the problem and result, and the validation (what you ran, on which revision, what you saw).
  4. Comment /reopen. A check runs and either reopens the pull request or lists what is still missing.

The rules are in CONTRIBUTING.md.

@Niko1221 Niko1221 closed this Oct 19, 2026
…onversation cheapest to lose

With no empty slot, pick_slot takes the least recently used one. A short side request (a title, a one-line question)
then takes the slot of a long conversation that is still going, when that conversation's last turn ended before the
other slot's, and its next turn reads the whole history again.

New, off unless set: STRATA_SLOT_EXPIRE_S (seconds, in the server's environment). Set, the order is an empty slot, then an expired one (idle that
long or longer: its client has likely moved on), then the one holding the shortest history, the least recently used
among equals. Unset or 0, every held slot counts as expired and the least recently used goes, as before. A request
still goes to the slot holding its own prefix first, and the pipelined groups' order (Niko1221#793) is unchanged.
serve/test_slot_alloc: one test per rule, the measured case below, random slot tables checked against the rule and
(off) against the old order, and the variable's values. docs/BATCHING.md says the order and the variable.

Measured on one RTX 5090 (engine from main 296b59d, --batch 3, no conversation cache, a third stream decoding all
along, the rule at 600 s): with a 40,248-token and a 1,777-token conversation held, a one-line side request took the
long one's slot on main, and its next turn read all 40,273 tokens again (12.72 and 12.70 s to the first token); with
the rule it takes the short one's slot and the long one's next turn reuses 40,251 tokens (0.41 and 0.34 s).

Replayed on a 21-hour timeline of my own agent traffic (9,771 requests, 4,503 conversations; ids, times and token
counts only) through 4 / 6 / 8 slots, STRATA_SLOT_EXPIRE_S=600 reads 17.8 / 20.6 / 21.1% fewer prompt tokens than the
least-recently-used order; the best possible order, computed knowing the whole timeline, reads 18.5 / 22.6 / 22.4%
fewer. Thinned to 5-50% of its conversations for smaller setups, 2 to 4 slots read 0.3-22.5% less on average; 1 slot
has nothing to choose. Where it loses: a long conversation whose client has gone keeps its slot until it expires
while busy short chats share the other slots.
The engine reads it from its own environment (child_env: the server's, plus the config's "env" block), so
"env": {"STRATA_SLOT_EXPIRE_S": "600"} in strata-<model>.json works like the other STRATA_* switches; the server's
environment still works, and the config wins when both are set. Found by qiuzan2001 in review.
@SuperDistance SuperDistance changed the title serve: opt-in STRATA_SLOT_EXPIRE_S keeps a long conversation's batch slot from a short side request (next turn 14.8 -> 0.3 s) [Engine] serve: opt-in STRATA_SLOT_EXPIRE_S keeps a long conversation's batch slot from a short side request (next turn 12.8 -> 0.3 s) Oct 20, 2026
@SuperDistance

Copy link
Copy Markdown
Contributor Author

/reopen

@github-actions github-actions Bot reopened this Oct 20, 2026
@github-actions

Copy link
Copy Markdown

Reopened, thank you. A maintainer will pick it up from here. If it conflicts with main, rebase it first; that speeds up review.

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.

3 participants