Skip to content

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) #407

Description

@luopeixiang

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_generatewired_limit, which is why it escapes the bug.

Metadata

Metadata

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions