这次从一份工作走到结果
不先阅读全仓文件清单。先跟随 _GenerationActor 的一条主线,把每个原生机制放回它承担的职责中。
| 顺序 | 到这里做什么 | Python 机制 | 读完返回哪里 |
|---|---|---|---|
| 1 | 调用方要求开始生产 | 跨线程提交、wrap_future | _start() |
| 2 | 建立结果缓冲并启动生产者 | Queue、create_task | _produce() |
| 3 | 获取样本、安排生成任务 | await、Task、gather | 各生成槽位 |
| 4 | 发布结果并等待消费 | Queue.put 的异步等待 | 消费者 _next() |
| 5 | 同时观察结果和生产者状态 | wait、FIRST_COMPLETED、result | 调用方 |
| 6 | 关闭时处理剩余任务 | cancel、gather、关闭执行器 | 退出生产流程 |
先走正常路径
_start 接收 rollout 编号,建立有界队列,再安排 _produce。生产者创建多个生成槽位;每个槽位取得结果后放入输出队列。如果消费者慢,put 的等待会把压力传回生产者。
调用方通过 _next 取结果。它不是简单地永远等待 Queue.get,还同时观察生产者:生产者失败时,消费者需要得知失败,而不是卡在一个不会再被填入的队列上。
展开 Slime 源码:_GenerationActor.start / _start / _next
_GenerationActor.start / _start / _nextslime/rollout/fully_async_distributed.py · L279–L317
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
async def start(self, rollout_id):
future = asyncio.run_coroutine_threadsafe(self._start(rollout_id), get_async_loop().loop)
await asyncio.wrap_future(future)
async def _start(self, rollout_id):
self.rollout_id = rollout_id
self.running = True
if self.producer is None or self.producer.done():
if self.producer is not None:
self.producer.result()
# Keep completed results bounded even when training stops consuming.
# At most one queued result plus one result per pool slot stays local.
self.outputs = asyncio.Queue(maxsize=1)
self.producer = asyncio.create_task(self._produce())
async def pause(self):
future = asyncio.run_coroutine_threadsafe(self._pause(), get_async_loop().loop)
await asyncio.wrap_future(future)
async def _pause(self):
self.running = False
async def next(self):
future = asyncio.run_coroutine_threadsafe(self._next(), get_async_loop().loop)
return await asyncio.wrap_future(future)
async def _next(self):
take = asyncio.create_task(self.outputs.get())
try:
await asyncio.wait((take, self.producer), return_when=asyncio.FIRST_COMPLETED)
if take.done():
return take.result()
self.producer.result() # Propagate custom generation errors.
if not self.outputs.empty():
return await take
return None
finally:
take.cancel()
await asyncio.gather(take, return_exceptions=True)展开 Slime 源码:_GenerationActor._produce
_GenerationActor._produceslime/rollout/fully_async_distributed.py · L319–L342
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
async def _produce(self):
async def generate_slot(group):
while self.running or group is not None:
output = await self._generate_group(self.rollout_id, group)
group = None
if output is not None: # Aborted groups have been returned to the durable queue.
await self.outputs.put(output)
del output
tasks = []
try:
for offset in range(0, self.capacity, 64):
if not self.running:
break
groups = await self.data_source.get_samples_async(min(64, self.capacity - offset))
tasks.extend(asyncio.create_task(generate_slot(group)) for group in groups)
# Spread startup callbacks across loop turns to reduce asyncio
# pressure. Each slot then pulls its own next group immediately.
await asyncio.sleep(0.001)
await asyncio.gather(*tasks)
finally:
for task in tasks:
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)展开 Slime 源码:_GenerationActor.close / _close
_GenerationActor.close / _closeslime/rollout/fully_async_distributed.py · L434–L447
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码
async def close(self):
future = asyncio.run_coroutine_threadsafe(self._close(), get_async_loop().loop)
await asyncio.wrap_future(future)
async def _close(self):
self.running = False
if self.producer is not None:
self.producer.cancel()
await asyncio.gather(self.producer, return_exceptions=True)
if getattr(self, "_rebuffer_task", None) is not None:
await self._rebuffer_task
# to_thread writes can outlive cancelled awaiters. Drain them before
# closing their files or killing this process.
await asyncio.get_running_loop().shutdown_default_executor()再补失败与清理
finally 取消并等待不再需要的取数任务;生产者的 finally 清理自己创建的任务。这里要逐层追踪所有权:一个函数创建了哪些后台任务,就要找到这些任务在退出时由谁收回。
遇到 to_thread 再去读数据读取或发布逻辑;遇到生成器再进入模型请求。完成一个职责后返回这里,继续跟踪结果,而不是同时展开所有业务模块。
用五个问题检查理解
- 去掉 Queue 的容量限制,慢消费者会给生产者带来什么影响?
- 如果只等
outputs.get(),生产者先异常退出会发生什么? take.cancel()之后为什么还有gather(take, return_exceptions=True)?- 把阻塞读取直接放进
_produce(),其他任务会受什么影响? shield保护写入时,调用方取消和工作线程退出分别由谁处理?
对照思路
分别检查积压结果、永远等待、取消收尾、事件循环阻塞和线程工作所有权。不要用“这是异步函数”替代对具体调用位置的分析。
下一步可到全部用法索引按 API 回查;那里保留固定版本的全量位置,本页只负责把任务流程读连贯。
本节对应的全部源码位置
下面把同类用法在 Slime 中的每个扫描位置接回本节讲解;点击文件名可查看完整源码快照。重复出现的 API 共用解释,具体输入与清理由所在函数决定。
展开本节 0 条源码记录
| 记录 / 讲解 | 源码位置 | 原代码 |
|---|
专题首页 · 上一节 · 下一节:Event 通知与异步互斥 · 源码与教程对照 · 全部用法