Bug
GRPO 训练作业(POST /admin/api/fine-tune/grpo/jobs,#363/PR#366)启动后立即失败:
status: failed
error: "There is no Stream(gpu, 6) in current thread."
复现
服务在跑(已加载 VL-7B 等模型),创建一个最小 GRPO 作业:
curl -X POST "http://localhost:11434/admin/api/fine-tune/grpo/jobs" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{
"model_id": "Qwen3-0.6B-4bit",
"prompts": ["What is 2+2?", "Say hello."],
"adapter_name": "grpo_smoke",
"config": {"group_size":2,"iters":2,"batch_size":1,"learning_rate":1e-5,
"lora_layers":4,"lora_rank":4,"lora_alpha":8.0,
"max_completion_len":16,"temperature":0.8}
}'
→ status=failed,finished_at - started_at ≈ 0.4s(一进 train_step 就崩)。
根因
grpo_service._execute_grpo 通过 asyncio.to_thread(grpo_service.py:224)在 worker 线程跑训练。GRPOTrainer._sample_completions 调用 mlx_lm.generate.generate_step,其内部 with mx.stream(generation_stream) 使用的是 mlx_lm.generate 模块级的 generation_stream = mx.new_thread_local_stream(...)。
该 stream 是 thread-local:在服务主线程(import 期 / 推理热路径)建立后,worker 线程拿不到同一个 Stream 对象,于是 MLX 抛 There is no Stream(gpu, 6) in current thread.(索引 6 对应服务运行期由 async eval / wired_limit 流池建立的某个 stream)。
对比:SFT / DPO / reward 训练也在 to_thread 跑,但它们只用 mx.grad + 前向(不进 generate_step 的 thread-local stream 上下文),所以不触发本 bug。GRPO 是唯一调用 generate_step 采样的训练器,因此唯一暴露此问题。
建议修复
在 _execute_grpo(worker 线程入口)建立该线程的默认 stream,使 generate_step 内部的 with mx.stream(generation_stream) 可用:
def _execute_grpo(self, job):
import mlx.core as mx
_stream = mx.new_thread_local_stream(mx.default_device())
with mx.stream(_stream):
# ... 原有 load / train / save 逻辑 ...
(备选:在 _sample_completions 每次 generate_step 外包 with mx.stream(mx.new_thread_local_stream(...)),但入口处一次性建立更干净,且对 mx.grad 步也无害。)
验收
- 上述复现作业
status=completed,写出 adapter,progress.loss 有值、mean_reward 有值。
- fusion-trainer 下游 RLSL Phase 2 可经此端点跑真实 PPO-clipped 策略更新。
关联
Bug
GRPO 训练作业(
POST /admin/api/fine-tune/grpo/jobs,#363/PR#366)启动后立即失败:复现
服务在跑(已加载 VL-7B 等模型),创建一个最小 GRPO 作业:
→
status=failed,finished_at - started_at ≈ 0.4s(一进 train_step 就崩)。根因
grpo_service._execute_grpo通过asyncio.to_thread(grpo_service.py:224)在 worker 线程跑训练。GRPOTrainer._sample_completions调用mlx_lm.generate.generate_step,其内部with mx.stream(generation_stream)使用的是mlx_lm.generate模块级的generation_stream = mx.new_thread_local_stream(...)。该 stream 是 thread-local:在服务主线程(import 期 / 推理热路径)建立后,worker 线程拿不到同一个 Stream 对象,于是 MLX 抛
There is no Stream(gpu, 6) in current thread.(索引 6 对应服务运行期由 async eval /wired_limit流池建立的某个 stream)。对比:SFT / DPO / reward 训练也在
to_thread跑,但它们只用mx.grad+ 前向(不进generate_step的 thread-local stream 上下文),所以不触发本 bug。GRPO 是唯一调用generate_step采样的训练器,因此唯一暴露此问题。建议修复
在
_execute_grpo(worker 线程入口)建立该线程的默认 stream,使generate_step内部的with mx.stream(generation_stream)可用:(备选:在
_sample_completions每次generate_step外包with mx.stream(mx.new_thread_local_stream(...)),但入口处一次性建立更干净,且对mx.grad步也无害。)验收
status=completed,写出 adapter,progress.loss有值、mean_reward有值。关联