Qwen3.8 Flash-Next · mlx-serve 26.10.1 · Mac Studio M4 Max 128 GB · 2026-10-17 to 2026-10-18

How many agent sessions fit on one local Flash-Next server?

We ran several agent sessions at once against one local server: the Dankpaws Swift 1.5 Qwen3.8 Flash-Next MLX 4.7 bpw model on mlx-serve 26.10.1, on the test machine (Mac Studio M4 Max 128 GB), on 2026-10-17 and 2026-10-18. Two sessions work and stay within the pass rule, but each turn takes about twice as long, so two tasks run together finish no sooner than the same tasks one after the other.

Maximum that passes

2 sessions

Two sessions pass the rule with a warning: worst slowdown 1.76× at 32K and 1.94× at 128K. Three and four sessions fail.

Each session

~2× slower

When two sessions share the server, each turn takes about twice as long as it does alone.

Time saved

0 s saved

Two real Claude Code sessions together finished in 147.6 s on average; the same two tasks one after the other took 137.6 s. Together is 7% later.

How the test works

Timeline of one session alone versus two sessions together: the server first reads each prompt (prefill), then writes tokens one at a time (decode), alternating between the two sessions; a short side request that arrives during a long prefill waits until it ends.One session aloneprefill: the server reads the promptdecode: one tokenreplyprompt cache:already readdecodeTwo sessions together (the server shares generation between them)A prefill: the server reads the promptB prefillA decodeB decodeA decodeB decodeA replyB replyarrives hereshort side requestwaits herereplyprefill: reads the promptdecode: writes one token at a timeprompt cache: skips re-reading

Each session replays a coding agent’s conversation at a fixed context size. It starts cold: the server reads the whole prompt once (prefill, reading the prompt before the first token). Then come warm turns that add a tool result to a prompt the server already holds in its prompt cache and ask for a 512-token reply (decode, generating the reply token by token).

The turn time is the time from sending a tool result to receiving the full reply. The slowdown is a session’s turn time divided by the same session alone: 7.9 s at 32K, 8.6 s at 128K and 11.1 s at 256K.

A test configuration is a number of sessions × a context size × with or without short side requests: one-second permission checks, like an agent asking “may I run this?”, sent while the sessions work. Each configuration ran 3 times unless noted.

Pass rule: worst slowdown ≤ 1.5× passes; 1.5–2.0× passes with a warning; above 2.0× fails. The slowest session decides. A configuration also fails if the server refuses a request or a session misses more recall checks (a fact planted in the prompt that the reply must find) than its rule allows: at least 11 of every 13 answered checks (85%) per session at 256K; none missed at 32K and 128K (a reply cut off at its length limit does not count); none missed with the 8-bit KV cache. Recall counts only answered requests.

Six words used below

Session
One simulated agent working through its conversation.
Turn time
Seconds from sending a tool result to receiving the full reply.
Slowdown
Turn time ÷ the same session’s turn time when it runs alone.
Context size
How long the conversation is: 32K, 128K or 256K tokens.
Prefill
The server reading the prompt before it writes the first token.
Decode
The server writing the reply, one token at a time.

More sessions, slower turns

Dashed lines: at or below 1.5× passes; between 1.5× and 2.0× is a warning (passes); above 2.0× fails.

32K128K
pass ≤ 1.5fail > 2.01 session1 session · 32K: 1.01.01 session · 128K: 1.01.02 sessions2 sessions · 32K: 1.81.82 sessions · 128K: 1.91.93 sessions3 sessions · 32K: 2.42.43 sessions · 128K: 2.42.44 sessions4 sessions · 32K: 3.43.44 sessions · 128K: 3.83.8

Worst slowdown per configuration, with short side requests running. One session at 128K is the session alone. Without side requests: 2 sessions 1.75× at 32K and 1.78× at 128K; 4 sessions 2.84× and 3.66×.

Why two sessions do not add up

Schematic: one bar, the server’s generation budget, split into 1, 2, 3 and 4 equal slices, one slice per session: each session gets less.1 session2 sessions3 sessions4 sessionsSchematic: one generation budget divided among the sessions; measured totals are in the chart below

The server already batches the sessions’ generation: it writes one token for each session in the same step. On this model that step gets slower as sessions are added, so the total grows little. Going from 1 to 4 sessions, the best total grows at most 1.44× the speed of one session alone at 32K and 1.26× at 128K, while each session slows down by more.

32K128K
1 session1 session · 32K: 81.181.11 session · 128K: 77.377.32 sessions2 sessions · 32K: 57.557.52 sessions · 128K: 48.748.73 sessions3 sessions · 32K: 35.835.83 sessions · 128K: 32.032.04 sessions4 sessions · 32K: 29.329.34 sessions · 128K: 19.419.4

Generation speed per session, tokens per second (average over the sessions in the run).

Sessions32K per session32K total128K per session128K total
181.181.177.377.3
257.5115.148.797.5
335.8107.532.096.1
429.3117.119.477.6

In the 2-session runs the first session started before the second and ran some turns alone, which lifts the 2-session average. An independent benchmark of the same model agrees from 128K up: about 1.4× total throughput with short prompts, but no gain over one session alone at long contexts.

Short side requests wait for long prompts

A side request takes about a second when the server is free. When it arrives while another session is reading a long prompt, it waits for that read to finish: a cold 128K prompt takes about 166 s to read and a 256K one about 351 s (one session alone, median). At 32K the slowest side requests (95th percentile) took 22.0 s–41.1 s; at 128K and 256K, 168.1 s–358.6 s.

median (p50)95th percentile (p95)
60 s1 session, 32K1 session, 32K · p50: 0.9 s1 session, 32K · p95: 22.0 s2 sessions, 32K2 sessions, 32K · p50: 1.0 s2 sessions, 32K · p95: 33.8 s2 (limit 1), 32K2 (limit 1), 32K · p50: 0.9 s2 (limit 1), 32K · p95: 25.3 s3 sessions, 32K3 sessions, 32K · p50: 1.8 s3 sessions, 32K · p95: 29.2 s4 sessions, 32K4 sessions, 32K · p50: 1.3 s4 sessions, 32K · p95: 41.1 s2 sessions, 128K2 sessions, 128K · p50: 1.0 s2 sessions, 128K · p95: 322.6 s2 (limit 1), 128K2 (limit 1), 128K · p50: 1.0 s2 (limit 1), 128K · p95: 169.7 s3 sessions, 128K3 sessions, 128K · p50: 1.1 s3 sessions, 128K · p95: 168.1 s4 sessions, 128K4 sessions, 128K · p50: 1.4 s4 sessions, 128K · p95: 351.3 s256K + 32K256K + 32K · p50: 0.9 s256K + 32K · p95: 353.6 s2×256K, 4 GB2×256K, 4 GB · p50: 1.5 s2×256K, 4 GB · p95: 355.9 s2×256K, 8-bit2×256K, 8-bit · p50: 1.0 s2×256K, 8-bit · p95: 351.4 s2×256K, 4GB+8bit2×256K, 4GB+8bit · p50: 1.7 s2×256K, 4GB+8bit · p95: 358.6 s

Side-request response time, seconds. The dashed line marks 60 s. “limit 1” runs the two sessions with --max-concurrent 1: the wait is the same, so the flag does not cause it.

Configurationp50p95Over 60 s
1 session, 32K0.9 s22.0 s0
2 sessions, 32K1.0 s33.8 s0
2 (limit 1), 32K0.9 s25.3 s0
3 sessions, 32K1.8 s29.2 s1
4 sessions, 32K1.3 s41.1 s1
2 sessions, 128K1.0 s322.6 s6
2 (limit 1), 128K1.0 s169.7 s8
3 sessions, 128K1.1 s168.1 s9
4 sessions, 128K1.4 s351.3 s9
256K + 32K0.9 s353.6 s4
2×256K, 4 GB1.5 s355.9 s14
2×256K, 8-bit1.0 s351.4 s8
2×256K, 4GB+8bit1.7 s358.6 s11

Upstream, PR #884 lets small requests in between prefill chunks, but it excludes this model’s architecture and is not in a release yet.

Does --max-concurrent matter?

--max-concurrent sets how many requests the server runs at once. On mlx-serve 26.10.1 the default is 1, yet the boot log already says Concurrency: --max-concurrent=1, batched decode on: two sessions share generation either way. The flag is not a queue, and setting it to 2 changes nothing measurable. At 128K the side-request p95 is higher with the flag at 2 because of a single very slow request; the count of side requests over 60 s is close in both runs.

Two sessions + side requests32K, --max-concurrent 132K, --max-concurrent 2128K, --max-concurrent 1128K, --max-concurrent 2
Worst slowdown1.76×1.76×1.83×1.94×
Generation per session, tokens/s61.357.550.848.7
Side-request p9525.3 s33.8 s169.7 s322.6 s
Side requests over 60 s0086
Peak memory held by the server100.1 GB99.9 GB113.5 GB113.5 GB

256K: two long sessions

Two 256K sessions fit only when the prompt cache kept in RAM is cut from 16 GB to 4 GB. That configuration passes with a warning: worst slowdown 1.68× against the 11.1 s reference, or 1.81× against the 10.3 s we measured for one 256K session alone over 3 runs. With the 4 GB cache, warm turns lose about a second restoring the cache from the SSD.

Recall rule at 256K: a session must get at least 11 of every 13 answered checks (85%) right, so it may miss up to 2 of 13, the same as one 256K session alone. With the 8-bit KV cache it must miss none. The table shows each session’s count.

worst slowdown
pass ≤ 1.5fail > 2.016 GB cache †‡16 GB cache †‡ · worst slowdown: 2.02.04 GB cache4 GB cache · worst slowdown: 1.71.78-bit KV †8-bit KV † · worst slowdown: 1.61.64 GB + 8-bit KV ‡4 GB + 8-bit KV ‡ · worst slowdown: 1.51.5

Dashed lines: at or below 1.5× passes; between 1.5× and 2.0× is a warning (passes); above 2.0× fails. † the server refused a request; ‡ a session missed more recall checks than the rule allows. In the 256K + 32K configuration the slowest session is the 32K one.

SessionsPrompt cache in RAMKV cachePeak memoryRefused / HTTP errors (client)Recall per sessionWorst slowdownResult
one 256K + one 32K16 GBfull115.0 GB1session 1: 10/12 (miss)
session 2: 13/13
1.96×fail
two 256K4 GBfull113.3 GB0session 1: 11/13
session 2: 13/13
1.68×pass with a warning
two 256K16 GB8-bit113.7 GB1session 1: 13/13
session 2: 12/12
1.56×fail
two 256K4 GB8-bit107.7 GB0session 1: 12/13 (miss)
session 2: 13/13
1.50×fail

Refused / HTTP errors (client): requests the client saw fail. The server log can count more refusals than the client sees, so the two numbers can differ.

Two real Claude Code sessions

Two real Claude Code 2.1.292 sessions (claude -p) shared the everyday server setup (128K context, default --max-concurrent 1), each on a fixed coding task with the same model as both main and fast model. Every task was done, with no errors and no refusals. Each session took 1.84×–2.36× its time alone, above the 1.5× rule.

alone (mean)together (mean)
Task A: add a CLI flagTask A: add a CLI flag · alone: 75.475.4Task A: add a CLI flag · together: 144.9144.9Task B: fix two bash bugsTask B: fix two bash bugs · alone: 62.262.2Task B: fix two bash bugs · together: 137.8137.8

Wall time per task, seconds. The “alone” control has 2 repetitions per task and “together” has 3.

Functional pass, time fail. The pair finished in 147.6 s on average; the two tasks one after the other take 137.6 s. Running them together finishes 7% later.

--prefill-decode-share 0.3 and 0.5

--prefill-decode-share makes generation keep a share of the time while another request reads its prompt. We tried it at 128K to clear the side-request wait. It did not: as many side requests waited over 60 s as without it, and the cold first token took about twice as long. Not adopted.

Setting (two sessions + side requests, 128K)Worst slowdownSide requests over 60 sSide-request p95Cold first token (median)
off (default), --max-concurrent 21.94×6322.6 s168 s
off (default), --max-concurrent 11.83×8169.7 s169 s
--prefill-decode-share 0.32.02×8176.4 s319 s
--prefill-decode-share 0.51.92×8175.4 s327 s

What we learned

  1. mlx-serve 26.10.1 already batches generation for this model at the default --max-concurrent 1; the flag is not a queue.
  2. Two sessions is the most that passes: 1.76× at 32K and 1.94× at 128K. Three fail (2.37× / 2.36×), four fail (3.37× / 3.85× with side requests, 2.84× / 3.66× without).
  3. Total generation speed grows little with more sessions, so two sessions together are not faster: two real Claude Code tasks took 147.6 s together against 137.6 s one after the other.
  4. A short request that arrives while another session reads a long prompt waits for it (p95 168.1 s–358.6 s at 128K and 256K); neither --max-concurrent nor --prefill-decode-share removes the wait.
  5. Two 256K sessions fit only with a 4 GB prompt cache in RAM; with 16 GB or an 8-bit KV cache the server refused requests or the model missed a recall check.
  6. The everyday server setup stays as it was. The launcher gained a 256k profile for two long sessions.

Related upstream work

LinkTypeStateHow it relates
PR #568PRmerged, in 26.10.1Adds --prefill-decode-share: generation keeps a share of the time while another request reads its prompt; the waiting request gets its first token later by the same amount. Tested here at 0.3 and 0.5.
PR #884PRmerged 2026-10-17, not in a releaseLets small requests in at prefill chunk boundaries, which is the fix for side requests stuck behind a long prompt. It excludes the qwen4_exp architecture (Flash-Next).
issue #528issueclosed by PR #621Requests refused after the prompt cache was restored from the SSD: the restored part was billed again at admission. The closest match to the refusals seen here.
PR #621PRmerged, in 26.10.1The fix for #528: a restore from the SSD is credited, so a warm turn is not billed as a cold one. It is in the version tested here, so the refusals below are a different or remaining case.
issue #567issueclosedBatched generation is not batch-invariant: a request’s output can change with the request it shares a batch with.
independent benchmarkexternal benchmarkexternalSame model on mlx-serve: about 1.4× total throughput with short prompts, and less than one session alone from 32K up.

We have not opened an issue about the admission refusals below, and their cause is not confirmed.

Admission refusals

Some requests were refused before the server started reading them: 4 sessions + side requests, 128K; 256K + 32K sessions, 16 GB prompt cache; 2 × 256K, 8-bit KV cache. One of the server’s messages, verbatim:

prompt 125549 tokens needs ~6266MB at prefill chunk 512 (the narrowest width tried), ~5968MB available + ~13863MB the hot cache can give back (~17420MB resident, ~3556MB of it pinned by this request's own prefix) — refused before prefill

Read literally, the message lists more memory available plus memory the prompt cache can give back than the request needs, even after leaving out the part pinned by the request’s own prefix. It looks like an admission accounting problem in this server version, similar to issue #528, whose fix is already in this version. Cause not confirmed.

Not measured

Reproduce and data

Server commands, from the repository root ($MODEL is the model directory):

tools/scripts/serve-flashnext-daily-driver.sh        # everyday setup: 128K, 16 GB prompt cache
tools/scripts/serve-flashnext-daily-driver.sh 256k   # two long sessions: 256K, 4 GB prompt cache

# the 256k profile runs:
mlx-serve --model "$MODEL" --serve --host 0.0.0.0 --port 11234 --metrics --mtp \
  --ssm-checkpoint-max 16 --prefix-cache-mem 4GB --prefix-cache-disk 100GB \
  --prefix-cache-entries 64 --ctx-size 262144 --max-concurrent 2

Runbook: plan.md. Raw results and stage reports: results/. The data behind this page is embedded in it as JSON (#data).

All test configurations

Result applies the pass rule to the worst slowdown (session, ÷ the session alone), then fails any configuration with a refused request or a recall miss. Recall counts only answered requests. Refused / HTTP errors (client) counts what the client saw; the server log can count more refusals. 256K slowdowns use the 11.1 s reference.

CodeConfigurationSessionsContextSide requestsWorst slowdownRefused / HTTP errors (client)RecallPeak memoryResult
w1c11 session, 32K132Kno1.00×013/1396.0 GBpass
w1c1a1 session + side requests, 32K132Kyes1.04×013/1396.9 GBpass
w1c22 sessions, 32K232Kno1.75×026/26100.7 GBpass with a warning
w1c2a2 sessions + side requests, 32K232Kyes1.76×025/2699.9 GBpass with a warning
w1c1x22 sessions, --max-concurrent 1, 32K232Kyes1.76×026/26100.1 GBpass with a warning
w1c3a3 sessions + side requests, 32K332Kyes2.37×039/39101.4 GBfail
w1c44 sessions, 32K432Kno2.84×052/52103.6 GBfail
w1c4a4 sessions + side requests, 32K432Kyes3.37×052/52104.3 GBfail
w1c11 session, 128K1128Kno1.00×012/13110.5 GBpass
w1c22 sessions, 128K2128Kno1.78×026/26112.6 GBpass with a warning
w1c2a2 sessions + side requests, 128K2128Kyes1.94×026/26113.5 GBpass with a warning
w1c1x22 sessions, --max-concurrent 1, 128K2128Kyes1.83×026/26113.5 GBpass with a warning
w1c3a3 sessions + side requests, 128K3128Kyes2.36×039/39114.7 GBfail
w1c44 sessions, 128K4128Kno3.66×051/52 (miss)116.0 GBfail
w1c4a4 sessions + side requests, 128K4128Kyes3.85×149/51 (miss)115.9 GBfail
w1c2ap32 sessions + side requests, prefill-decode-share 0.3, 128K2128Kyes2.02×026/26n/afail
w1c2ap52 sessions + side requests, prefill-decode-share 0.5, 128K2128Kyes1.92×026/26n/apass with a warning
w1c11 session, 256K1256Kno0.93×012/13113.9 GBpass
w1c2L256K + 32K sessions, 16 GB prompt cache2256K + 32Kyes1.96×123/25 (miss)115.0 GBfail
w1c2m42 × 256K, 4 GB prompt cache2256Kyes1.68×024/26113.3 GBpass with a warning
w1c2q82 × 256K, 8-bit KV cache2256Kyes1.56×125/25113.7 GBfail
w1c2m4q82 × 256K, 4 GB cache + 8-bit KV2256Kyes1.50×025/26 (miss)107.7 GBfail