RuntimeError: There is no Stream(gpu, N) in current thread when calling /v1/messages or /v1/chat/completions on Qwen3.5 MLX models (MLLM path runs in worker thread)
Summary
On Apple Silicon, any model whose name matches the MLLM pattern Qwen3.5- (e.g. mlx-community/Qwen3.5-4B-4bit, mlx-community/Qwen3.5-9B-4bit) fails at inference time for chat-style endpoints with:
RuntimeError: There is no Stream(gpu, 0) in current thread.
(In --continuous-batching mode the error becomes Stream(gpu, 2) at a different site, same root cause.)
/v1/completions (raw prompt) works fine. /v1/chat/completions and /v1/messages both fail. In non-streaming mode /v1/messages currently masks the failure and returns HTTP 200 with empty content and input_tokens/output_tokens = 0, which is a secondary bug.
Environment
- OS: macOS, Apple Silicon (unified memory ~38.7 GB)
- Python: 3.13.12 (cpython-3.13.12-macos-aarch64-none, from uv)
- Installed via:
uv tool install git+https://github.com/waybarrios/vllm-mlx.git (latest)
- Models reproduced on:
mlx-community/Qwen3.5-4B-4bit
mlx-community/Qwen3.5-9B-4bit
- Both are cached locally; running with
--offline.
Reproduction
A. Continuous-batching mode → Stream(gpu, 2)
Start server:
vllm-mlx serve mlx-community/Qwen3.5-9B-4bit \
--port 8000 \
--continuous-batching \
--reasoning-parser qwen3 \
--enable-auto-tool-choice \
--tool-call-parser qwen3_coder \
--mllm-prefill-step-size 8192 \
--offline
Call:
curl -s http://localhost:8000/v1/messages \
-H "Content-Type: application/json" \
-d '{
"model": "mlx-community/Qwen3.5-9B-4bit",
"max_tokens": 256,
"messages": [{"role": "user", "content": "Hello, please introduce yourself."}]
}'
Client sees HTTP 200 with empty content:
{"id":"msg_...","type":"message","role":"assistant",
"model":"mlx-community/Qwen3.5-9B-4bit",
"content":[{"type":"text","text":""}],
"stop_reason":"end_turn",
"usage":{"input_tokens":0,"output_tokens":0}}
Server log shows the actual error being swallowed:
INFO vllm_mlx.mllm_batch_generator: [think_suffix] Detected think tag '<think>' = 2 token(s)
ERROR vllm_mlx.mllm_batch_generator: Failed to process batch of 1 prompts:
RuntimeError: There is no Stream(gpu, 2) in current thread.
Traceback (most recent call last):
File ".../vllm_mlx/mllm_batch_generator.py", line 1559, in _next
new_batch = self._process_prompts(requests)
File ".../vllm_mlx/mllm_batch_generator.py", line 1310, in _process_prompts
mx.eval(sampled, logprobs)
RuntimeError: There is no Stream(gpu, 2) in current thread.
WARNING vllm_mlx.mllm_scheduler: Request ... failed during preprocessing
INFO vllm_mlx.mllm_scheduler: Aborting orphaned MLLM request ...
INFO "POST /v1/messages HTTP/1.1" 200 OK
B. Simple (non-batched) mode → Stream(gpu, 0) + HTTP 500
Start server with the minimum set of flags:
vllm-mlx serve mlx-community/Qwen3.5-9B-4bit --port 8000 --offline
Same curl call → HTTP 500 Internal Server Error. Server log:
INFO vllm_mlx.server: [REQUEST] POST /v1/messages ...
INFO vllm_mlx.models.mllm: MLLM.chat() called with 1 messages
ERROR: Exception in ASGI application
Traceback (most recent call last):
...
File ".../vllm_mlx/server.py", line 3532, in create_anthropic_message
output = await _wait_with_disconnect(...)
File ".../vllm_mlx/server.py", line 2780, in _wait_with_disconnect
return task.result()
File ".../vllm_mlx/engine/simple.py", line 520, in chat
output = await self._run_blocking_serialized(...)
File ".../vllm_mlx/engine/simple.py", line 245, in _run_blocking_serialized
return await asyncio.shield(task)
File ".../asyncio/threads.py", line 25, in to_thread
return await loop.run_in_executor(None, func_call)
File ".../concurrent/futures/thread.py", line 59, in run
result = self.fn(*self.args, **self.kwargs)
File ".../vllm_mlx/models/mllm.py", line 1673, in chat
result = generate(self.model, ...)
File ".../mlx_vlm/generate.py", line 856, in generate
for response in stream_generate(model, processor, prompt, ...):
File ".../mlx_vlm/generate.py", line 727, in stream_generate
with wired_limit(model, [generation_stream]):
File ".../contextlib.py", line 162, in __exit__
self.gen.throw(value)
File ".../mlx_vlm/generate.py", line 327, in wired_limit
mx.synchronize(s)
RuntimeError: There is no Stream(gpu, 0) in current thread.
/v1/chat/completions fails in exactly the same way (also routes through mllm.chat()).
C. /v1/completions (raw prompt) works
Same server, different endpoint:
curl -s http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{"model":"mlx-community/Qwen3.5-9B-4bit","max_tokens":64,"prompt":"hi"}'
Works fine and returns real text. This endpoint does not go through MLLM.chat() → mlx_vlm.generate.stream_generate → wired_limit, which is why it escapes the bug.
RuntimeError: There is no Stream(gpu, N) in current threadwhen calling/v1/messagesor/v1/chat/completionson Qwen3.5 MLX models (MLLM path runs in worker thread)Summary
On Apple Silicon, any model whose name matches the MLLM pattern
Qwen3.5-(e.g.mlx-community/Qwen3.5-4B-4bit,mlx-community/Qwen3.5-9B-4bit) fails at inference time for chat-style endpoints with:(In
--continuous-batchingmode the error becomesStream(gpu, 2)at a different site, same root cause.)/v1/completions(raw prompt) works fine./v1/chat/completionsand/v1/messagesboth fail. In non-streaming mode/v1/messagescurrently masks the failure and returnsHTTP 200with emptycontentandinput_tokens/output_tokens = 0, which is a secondary bug.Environment
uv tool install git+https://github.com/waybarrios/vllm-mlx.git(latest)mlx-community/Qwen3.5-4B-4bitmlx-community/Qwen3.5-9B-4bit--offline.Reproduction
A. Continuous-batching mode →
Stream(gpu, 2)Start server:
Call:
Client sees
HTTP 200with empty content:{"id":"msg_...","type":"message","role":"assistant", "model":"mlx-community/Qwen3.5-9B-4bit", "content":[{"type":"text","text":""}], "stop_reason":"end_turn", "usage":{"input_tokens":0,"output_tokens":0}}Server log shows the actual error being swallowed:
B. Simple (non-batched) mode →
Stream(gpu, 0)+ HTTP 500Start server with the minimum set of flags:
Same curl call →
HTTP 500 Internal Server Error. Server log:/v1/chat/completionsfails in exactly the same way (also routes throughmllm.chat()).C.
/v1/completions(raw prompt) worksSame server, different endpoint:
Works fine and returns real text. This endpoint does not go through
MLLM.chat()→mlx_vlm.generate.stream_generate→wired_limit, which is why it escapes the bug.