1. 从一次本地复现失败说起:slime 的 SGLang-Native 推理链路到底长什么样
如果你最近在折腾 slime 框架,大概率会遇到一个很具体的场景:照着train_async.py的启动脚本把 Ray 集群拉起来,SGLang 引擎也起来了,但请求发出去之后要么卡在 router 那一层,要么直接报local proxy failed,日志里翻半天也看不出是 Ray 调度的问题还是 SGLang 引擎没注册上。我试过在单机 8 卡上复现 slime 的 rollout 链路,前两次都栽在“引擎起来了但 router 的 worker 列表是空的”这个坑上。
slime 是一个为 RL Scaling 设计的 SGLang-Native 后训练框架,它的推理侧不走 SPMD 那套把引擎嵌进训练进程的老路,而是把 SGLang 当成独立的 HTTP 服务来用。这意味着一次 rollout 会横跨五类进程:driver、RolloutManager、SGLangEngine、sgl-router、以及真正吃 GPU 的 SGLang HTTP Server 子进程。跨进程通信同时用了 Ray RPC 和 HTTP 两套机制,任何一环没对齐,请求就断在半路。
这篇文章面向的是想在本地把 slime 推理服务跑起来、并且完成一次端到端调用的开发者。我会把重点放在三件事上:Ray 集群和 placement group 怎么切分 GPU、SGLang 引擎参数怎么配、以及怎么用 TaoToken 的统一 Key 把模型调用接进来做验证。读完你应该能在自己的机器上复现出一条完整的“用户函数 → router → SGLang 引擎 → 返回结果”的链路,并且知道每一步失败时该看哪个日志。
需要先说明的是,slime 的推理架构里,RolloutManager 是 0 GPU 的 Ray Actor,只做调度;SGLangEngine 是占 0.2 GPU 的 Ray Actor,本身不跑推理,只负责拉起 SGLang 子进程并向 router 注册;真正吃实卡的是multiprocessing.Processspawn 出来的 SGLang HTTP Server。这个分工是后面所有配置和排障的基础,记不住的话很容易把“引擎没起来”和“引擎起来了但没注册”搞混。
2. 前置准备:Ray 集群、SGLang 引擎与 TaoToken 统一 Key 的接入位置
在动手改配置之前,先把本地环境的三块拼图摆清楚:Ray 负责资源编排,SGLang 负责推理执行,TaoToken 负责把模型调用统一到一个 Key 上。这三者不是替代关系,而是各管一段。
Ray 这一侧,你需要一个能跑起来的 Ray 集群。单机多卡的话,ray start --head就够了,关键是让 placement group 能拿到全部 GPU。slime 的做法是只创建一个覆盖全部 GPU 的 placement group,然后靠偏移量切片把 actor 和 rollout 分开。本文配置下num_gpus = 1×4 + 4 = 8,actor 拿前 4 张,rollout 拿后 4 张,物理隔离靠的是索引数组不相交,而不是两个独立的资源池。
SGLang 这一侧,引擎是以 HTTP Server 形式独立运行的。slime 通过launch_server_process用multiprocessing.Process把它 spawn 出来,launch_server直接来自sglang.srt.entrypoints.http_server,没有任何包装。这就是“SGLang-Native”在进程层面的兑现——slime 不重新实现推理引擎,只是启动它、配置它、通过接口往 router 的 worker 列表里增删它。
TaoToken 这一侧,它的角色是统一模型调用的入口。在本地验证阶段,你可能会同时用到多个模型(actor 采样、reference 算 KL、reward model 打分),如果每个模型都单独配一套 Key 和 Base URL,管理起来很乱。TaoToken 提供统一的 API Key 和 Base URL,把模型对话、Coding Plan、API Keys 管理都收敛到一个控制台里。接入位置就在你的用户函数或验证脚本里,把原本指向本地 SGLang 引擎的请求,改成走 TaoToken 的统一入口来做对照验证。
具体来说,TaoToken 的 API 地址是https://taotoken.net/api,控制台在https://taotoken.net/console,API Keys 管理在https://taotoken.net/api-keys。模型对话入口在https://taotoken.net/model-chat,Coding Plan 在https://taotoken.net/coding-plan,接入文档在https://taotoken.net/doc。这些地址在后面的配置片段里会用到。
有一点要提前说清楚:TaoToken 在这里的作用是提供统一的模型调用入口和 Key 管理,不是替代你本地的 SGLang 引擎。本地 SGLang 负责跑你自己的权重和推理,TaoToken 负责在验证阶段提供一个稳定的对照调用通道,两者是互补的。你可以在本地引擎没起来的时候,先用 TaoToken 的统一 Key 验证请求链路和参数格式是否正确,再去调本地引擎。
环境依赖上,你需要 Ray、SGLang、httpx、requests 这几个包。SGLang 的版本要和 slime 对齐,否则ServerArgs的字段可能对不上。我踩过的坑是 SGLang 版本偏新,_compute_server_args里有些字段名变了,导致引擎启动参数校验失败。建议先把 SGLang 版本固定到 slime 官方文档推荐的版本。
3. 可复制配置:Ray 启动、SGLang 引擎参数与 TaoToken 接入片段
这一节给的是可以直接复制粘贴的配置。分三块:Ray 集群启动、SGLang 引擎参数、以及 TaoToken 统一 Key 的接入片段。
先看 Ray 集群启动。单机 8 卡的话,直接:
ray start --head --num-gpus=8 --port=6379如果你是多机,head 节点用上面的命令,worker 节点用:
ray start --address='<head-ip>:6379' --num-gpus=8启动之后用ray status确认 GPU 数量对得上。slime 的 placement group 会一次性申请全部 GPU,如果ray status里显示的 GPU 少于预期,placement group 创建会直接失败。
接下来是 SGLang 引擎参数。slime 通过_compute_server_args计算完整的ServerArgs,你需要在启动脚本里把关键参数传对。参考 slime 的train_async.py启动方式,核心参数如下:
python3 train_async.py \ --actor-num-nodes 1 \ --actor-num-gpus-per-node 4 \ --rollout-num-gpus 4 \ --rollout-num-gpus-per-engine 2 \ --n-samples-per-prompt 8 \ --rollout-batch-size 32 \ --sglang-server-concurrency 64 \ --update-weights-interval 1这里--rollout-num-gpus-per-engine 2意味着每个 SGLang 引擎的 TP=2,--rollout-num-gpus 4意味着起 2 个引擎。--sglang-server-concurrency会直接影响 HTTP 连接池容量,公式是sglang_server_concurrency * rollout_num_gpus // rollout_num_gpus_per_engine,本文配置下就是64 * 4 // 2 = 128。
然后是 TaoToken 统一 Key 的接入片段。在你的验证脚本或用户函数里,把模型调用的 Base URL 和 Key 配成 TaoToken 的:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的TaoToken统一Key", "model_id": "你的模型ID", "timeout": 600, "max_retries": 3 }如果你用的是 Claude Code 或者类似的 coding agent 工具,配置方式类似,把 Base URL 指向https://taotoken.net/api,Key 用 TaoToken 控制台生成的统一 Key,Model ID 填你要调用的模型。这里要注意三件套必须齐全:Base URL、Key、Model ID,缺一个都会报 401 或 model not found。
对于需要在本地做端到端验证的场景,我建议把 TaoToken 的调用和本地 SGLang 的调用分开写,先验证 TaoToken 通道能通,再验证本地引擎通道能通,最后再合起来跑完整的 rollout。这样出问题的时候能快速定位是哪一段断了。
配置写完之后,检查一下args.sglang_router_ip和args.sglang_router_port这两个字段。slime 在启动第一个模型的时候会把 router 地址写进这两个字段,用户函数直接用它们拼 URL 就行。多模型场景下完整版是args.sglang_model_routers,以模型名为 key 存了所有 router 地址。
4. 验证请求:从用户函数到 SGLang 引擎的完整链路与预期输出
配置就绪之后,跑一次端到端调用,看请求是不是真的从用户函数走到了 SGLang 引擎再返回。这一节给的是验证动作和每一步的预期输出。
第一步,确认 router 的 worker 列表里有引擎。引擎注册发生在SGLangEngine.init()→_init_normal()里,先launch_server_process拉起子进程,内部_wait_server_healthy阻塞等到/health返回 200,然后 node-0 才向 router 发POST /workers。你可以直接查 router 的 worker 列表:
curl http://<router-ip>:<router-port>/workers预期输出是一个 JSON 数组,里面每个元素对应一个已注册的引擎,包含url和worker_type字段。如果返回空数组,说明引擎还没注册上,去查 SGLangEngine 的日志里有没有_wait_server_healthy超时。
第二步,直接向 router 发一个生成请求,验证数据路径:
curl -X POST http://<router-ip>:<router-port>/generate \ -H "Content-Type: application/json" \ -d '{ "text": "你好,请用一句话介绍你自己", "sampling_params": { "max_new_tokens": 64, "temperature": 0.7 } }'预期输出是 SGLang 引擎返回的生成结果,包含text字段和meta_info。如果这一步报local proxy failed,通常是 router 的 worker 列表为空,或者引擎的 host:port 注册错了。如果报超时,检查_post的max_retries=60是不是被耗尽了,以及引擎是不是正在 offload/onload。
第三步,用 TaoToken 统一 Key 做对照调用,验证请求参数格式:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的TaoToken统一Key" \ -H "Content-Type: application/json" \ -d '{ "model": "你的模型ID", "messages": [{"role": "user", "content": "你好"}], "max_tokens": 64 }'预期输出是标准的 chat completions 格式。这一步的目的是确认你的请求参数格式没问题,如果 TaoToken 通道能通而本地引擎通道不通,问题就在本地引擎或 router 那一侧。
第四步,跑一次完整的 rollout,观察generate_and_rm的调用链。在用户函数里加日志,确认await post(router_url, payload)被调用,并且返回了结果。预期在日志里看到:generate_rollout_async进入双层 while,generate_and_rm_group用asyncio.gather并发,generate_and_rm里三层并发嵌套,最后_post带着重试把请求发出去。
如果一切正常,你会看到 rollout 产出的list[Sample]经过“展平 + 裁剪 → convert → DP 切分”三步,变成训练侧可消费的数据。到这一步,端到端推理调用就算完成了。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
这一节把本地复现时最容易撞上的几个报错列出来,对照真实日志给排查方向。
401 Unauthorized。这个通常出现在 TaoToken 通道上,原因就三个:Key 没填、Key 填错、或者 Base URL 和 Key 不匹配。检查你的配置里base_url是不是https://taotoken.net/api,Key 是不是从https://taotoken.net/api-keys生成的。如果是 Claude Code 或类似工具,确认三件套齐全:Base URL、Key、Model ID。少任何一个都会 401 或 model not found。
local proxy failed。这个报错在 router 转发请求到引擎的时候出现,根因是 router 的 worker 列表里没有可用引擎,或者引擎的 host:port 注册错了。排查顺序:先curl http://<router-ip>:<router-port>/workers看列表是否为空;如果为空,去 SGLangEngine 日志里找_wait_server_healthy和POST /workers的记录;如果注册了但还是失败,检查引擎的server_host和server_port是不是和实际监听的一致。IPv6 环境下要注意_format_v6_uri有没有把地址格式化对。
reading choices 相关报错。这个一般出现在解析模型返回结果的时候,说明返回的 JSON 结构和你预期的对不上。如果你用的是 OpenAI 兼容格式,检查返回里有没有choices字段。本地 SGLang 引擎返回的格式和 TaoToken 返回的格式可能略有差异,用户函数里解析的时候要做兼容。建议先用curl单独验证两个通道的返回结构,再写解析逻辑。
OAuth 相关报错。如果你用的是 Claude Code 或 Codex 这类工具,OAuth 报错通常和认证方式有关。检查你的auth.json或 settings 配置里,Base URL 是不是指向了https://taotoken.net/api,Key 是不是用的 TaoToken 统一 Key。有些工具会缓存旧的 OAuth token,清一下缓存再试。如果是 Codex 的auth.json,确认里面的base_url和api_key字段都配对了。
引擎起来了但 GPU 没吃满。这个不是报错,但很常见。检查--rollout-num-gpus-per-engine和--rollout-num-gpus的比例,本文配置下是 4/2=2 个引擎。如果引擎数量不对,placement group 的切片可能没对齐。另外确认SGLangEngine占的是 0.2 GPU 占位,真正吃实卡的是 HTTP Server 子进程,nvidia-smi里看到的应该是子进程在占显存。
请求卡住不返回。检查_post的max_retries=60和await asyncio.sleep(1),如果引擎正在 offload/onload 或者 weight update 打断,请求会重试将近一分钟。如果超过这个时间还没返回,去看引擎日志里是不是有flush_cache失败或者pause_generation卡住。flush_cache有 60 次重试循环,有 pending 请求时会失败,这是设计内的行为。
6. 把统一 Key 接进你的 slime 验证流程
本地复现 slime 推理链路的时候,最耗时间的往往不是写代码,而是排查“请求到底断在哪一段”。Ray 调度、router 注册、SGLang 引擎健康检查、HTTP 客户端重试,每一层都有自己的失败模式。把 TaoToken 的统一 Key 接进来,最大的好处是你有了一个稳定的对照通道:当本地引擎通道出问题时,先用 TaoToken 通道验证请求参数和返回格式,能快速排除掉“参数写错了”这类低级问题。
具体操作上,我建议在验证脚本里同时保留两个通道的调用:一个走本地 router,一个走 TaoToken。先跑 TaoToken 通道确认参数格式没问题,再跑本地通道确认引擎和 router 都正常,最后合起来跑完整 rollout。这样出问题的时候,你能立刻判断是本地环境的问题还是请求本身的问题。
TaoToken 的接入文档在https://taotoken.net/doc,里面有完整的 API 说明和示例。API Keys 在https://taotoken.net/api-keys管理,模型对话入口在https://taotoken.net/model-chat,长期编码或 Agent 场景可以看https://taotoken.net/coding-plan。控制台在https://taotoken.net/console。
最后留一个实用技巧:slime 的_post里trust_env=False是有意为之的,避免HTTP_PROXY把内网直连请求绕到代理上。如果你在本地验证时发现请求莫名其妙走了代理,检查一下环境变量里有没有HTTP_PROXY或HTTPS_PROXY,有的话在验证脚本里显式清掉。这个坑我在第一次复现的时候踩过,请求一直超时,最后发现是环境变量在作怪。