线程池已经收到工作,调用方怎样知道它结束了

第 08 节从事件循环一侧进入线程池:await asyncio.to_thread(...),或者 await loop.run_in_executor(...)。这一节站在线程池调用方一侧:直接提交同步函数,保存它的结果句柄,决定按什么顺序收取,最后收回线程资源。

先分清三件东西:线程池管理工作线程,提交的函数在某个工作线程中执行,concurrent.futures.Future 表示这次提交的状态及结果。函数抛出的异常也保存在 Future 中,由收取结果的一方处理。

submit 返回句柄,map 返回结果迭代器

with ThreadPoolExecutor(max_workers=2) as executor:
    future = executor.submit(read_file, rel_path)
    result = future.result()

submit(read_file, rel_path) 安排调用 read_file(rel_path),立即返回 Future;这时工作可能排队、已开始,甚至已经结束。max_workers 控制同时执行的工作线程数,不等于只接受这么多次提交。

若每次提交后立刻 result,调用线程会先等完这一份才提交下一份。需要并发时,应先提交多份,再收结果。提交过多又会积压参数和结果;Slime 的 delta 压缩用有限的 inflight 队列及时收取最早提交的工作。

源码入口:压缩工作先提交,限制在途量,finally 关闭池

UpdateWeightFromDiskDelta._encode_delta
slime/backends/megatron_utils/update_weight/update_weight_from_disk_delta.py · L273–L293
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码

        pool = ThreadPoolExecutor(max_workers=NUM_WORKERS)
        inflight: deque = deque()
        try:
            for name, tensor in self._iter_hf_tensors():
                flat = tensor.detach().contiguous().view(torch.uint8).reshape(-1)
                nbytes = int(flat.numel())
                if use_pinned and nbytes <= max_bytes:
                    buf = free_q.get()  # blocks when all buffers are in flight -> backpressures the gather
                    buf[:nbytes].copy_(flat, non_blocking=True)
                    accelerator.current_stream().synchronize()
                    payload, pinned = buf, True
                else:
                    payload, pinned = flat.cpu().numpy(), False
                self.total_bytes += nbytes
                inflight.append(pool.submit(diff_and_compress, name, payload, nbytes, pinned))
                if len(inflight) >= 2 * NUM_WORKERS:
                    collect(inflight.popleft())
            while inflight:
                collect(inflight.popleft())
        finally:
            pool.shutdown()

executor.map(fn, inputs) 适合对一组输入调用同一个函数,迭代时直接拿结果,不必自己管理每个 Future。它按输入顺序给出结果,后一个先完成也可能等前一个;函数异常在迭代到对应结果时重新抛出。Python 3.12 会立即收集输入并提交工作,不能把它当成自动限制在途数量的流式接口。Executor.submit / map 文档

Slime 的探针测试用 list(pool.map(..., _probes)) 并发检查各探针,然后收齐列表。转储工具则需要一完成就更新进度,使用后面介绍的 as_completed。

源码入口:并发验证各探针,收齐结果

before_train_step
tests/pipeline_rl_test_helpers.py · L159–L169
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码

        with ThreadPoolExecutor(max_workers=len(_probes)) as pool:
            _records["continuous"] = list(pool.map(lambda probe: probe.verify(), _probes))
        start_probes([probe.url for probe in _probes])
    elif rollout_id == 2 and step_id == 0:
        assert _probes
        with ThreadPoolExecutor(max_workers=len(_probes)) as pool:
            _records["next_update"] = list(
                pool.map(
                    lambda probe: probe.verify_flush() if args.flush_cache_interval == 2 else probe.verify(), _probes
                )
            )

先理解 result,再区分“完成”和“成功”

future.result(timeout=...) 在线程中同步等待:成功返回值,工作抛错则重新抛出该异常,等待超时抛出 TimeoutError,已取消则抛出 concurrent.futures.CancelledError。超时只结束本次等待,工作仍可能继续。

future.exception(timeout=...) 同样可以等待,但返回的是异常对象;成功时返回 None。它也会因等待超时或取消抛出对应异常。future.done() 只是非阻塞查询;成功、失败、成功取消都算 done,不能把 True 读成“结果成功”。Future 文档

在 Slime 中,微批次先 executor.submit(self._load_and_prepare) 预取,真正需要张量时 _get_cpu_tensor() 才调用 future.result()。它先短暂取得对象自己的锁以读取指针,然后离开锁再等待,避免等待期间一直占住共享状态的锁。

源码入口:预取句柄,在实际消费位置收取结果

RoutedExpertsMicrobatch.prefetch / _get_cpu_tensor
slime/utils/routed_experts.py · L229–L248
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码

    def prefetch(self) -> None:
        with self._lock:
            if self._cpu_tensor is not None or self._future is not None:
                return
            executor = self.prefetcher.executor if self.prefetcher is not None else None
            if executor is not None:
                self._future = executor.submit(self._load_and_prepare)

    def _get_cpu_tensor(self) -> torch.Tensor:
        with self._lock:
            tensor = self._cpu_tensor
            future = self._future
        if tensor is None:
            tensor = future.result() if future is not None else self._load_and_prepare()
            with self._lock:
                self._cpu_tensor = tensor
                self._future = None
        if self.prefetcher is not None:
            self.prefetcher.prefetch_after(self.index)
        return tensor

排队可以取消,开始执行后不能靠 cancel 强停

future.cancel() 尝试阻止尚未开始的调用,成功返回 True;正在执行或已经完成时返回 False。running() 表示工作已进入执行,cancelled() 表示成功取消。不要写“调用了 cancel,所以线程已停止”。需要停止长工作时,必须让工作函数自己检查停止信号并返回,或为 I/O 设置合理超时。

时刻Future 状态cancel 结果谁决定工作结束
提交后仍排队尚未运行可以成功执行器不再启动该调用
函数已开始running 为真False工作函数自己返回或抛错
函数返回或抛错done 为真False已结束,可取结果或异常
排队时成功取消cancelled 与 done 为真已取消result 会抛 CancelledError

Slime 的微批次 release() 清掉自身指针后,对尚未 done 的 Future 尝试 cancel;这只是尽力取消预取。如果预取已经运行,cancel 无法打断它。

源码入口:释放微批次时的尽力取消

RoutedExpertsMicrobatch.release
slime/utils/routed_experts.py · L265–L271
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码

    def release(self) -> None:
        with self._lock:
            future = self._future
            self._future = None
            self._cpu_tensor = None
        if future is not None and not future.done():
            future.cancel()

不想反复轮询 done,可以 future.add_done_callback(callback):完成或成功取消时,回调收到这个 Future。若注册时已完成,回调会立即执行。它不会自动切到 asyncio 事件循环;回调要短小,不要假定固定线程,也不要在里面等同一线程池的其他未完成工作。result() 返回并不保证回调所有副作用也已完成;本节示例另用 Event 确认回调结束。

这一节补充 exception、running、cancelled 和同步 Future 的回调,帮助完整解释状态;固定版本中上述预取主线直接使用的是 submit、result、done、cancel 与 shutdown。不要把 asyncio.Task.add_done_callback 的出现自动计到这个 Future 类型上。

wait 返回集合,FIRST_COMPLETED 不保证只有一份结果

done, pending = wait(futures, return_when=FIRST_COMPLETED)

这里的函数与常量来自 concurrent.futures。调用线程同步等到至少一项完成,再拿到两个集合。返回时可能已经多项完成;done 没有提交顺序,集合中的 Future 还要调用 result 才得到值。等待超时可以返回空 done,pending 中的工作也不会自动取消。

Slime 的测试把远程调用替换成 run_coroutine_threadsafe() 返回的同步 Future,再用 concurrent.futures.wait(..., FIRST_COMPLETED) 模拟 ray.wait。同一个对象来自跨线程协程提交,也仍然要按同步 Future 的规则收取结果。

源码入口:测试替身怎样模拟 ray.wait / get / cancel

scheduler_factory: remote / ray_wait
tests/test_distributed_rollout.py · L646–L659
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码

    def remote(method):
        return SimpleNamespace(remote=lambda *args: asyncio.run_coroutine_threadsafe(method(*args), loop))

    def ray_wait(refs, num_returns, timeout):
        done, pending = wait(refs, timeout=timeout, return_when=FIRST_COMPLETED)
        return list(done)[:num_returns], list(pending)

    def kill(worker, no_restart=True):
        if worker.actor.producer is not None:
            loop.call_soon_threadsafe(worker.actor.producer.cancel)

    monkeypatch.setattr(ray, "wait", ray_wait)
    monkeypatch.setattr(ray, "get", lambda ref: ref.result())
    monkeypatch.setattr(ray, "cancel", lambda ref: ref.cancel())

as_completed 让调用方一份一份处理完成的 Future

concurrent.futures.as_completed(futures) 返回普通迭代器,每次给出一份已经结束或取消的原 Future;调用方用 future.result() 取值、处理异常,再更新进度。若开始迭代前多项已经完成,不应依赖它们之间的细微先后次序。

Slime 的并行转换工具先提交文件读取、参数转换或文件保存,再遍历 as_completed。读取先完成哪份就缓存哪份,保存先完成哪份就报告哪份,不必等待输入列表中排在前面的慢工作。

源码入口:并行读文件,按完成顺序进入缓存

WrappedStorageReader._parallel_preload_files
tools/convert_torch_dist_to_hf_parallel.py · L147–L161
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码

        with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            futures = {
                executor.submit(read_file, rel_path, full_path, ops): rel_path
                for rel_path, (full_path, ops) in [
                    (r, (self.fs.concat_path(self.path, r), ops)) for r, ops in file_ops.items()
                ]
            }
            for future in tqdm(as_completed(futures), total=len(futures), desc="Pre-loading files"):
                result = future.result()
                if result:
                    rel_path, data, is_full_file = result
                    with self._cache_lock:
                        # Cache by file path - for chunked files, store the full file
                        self._read_cache[rel_path] = data

同一文件还在参数转换与文件保存使用同一模式。另两个 FP8 / INT4 转换入口保存提交列表后逐个 result,顺序与 as_completed 不同,但调用方都负责取异常。

最后关闭线程池,并等待它负责的工作

shutdown(wait=True) 停止接受新提交,等待已提交工作结束并释放资源。cancel_futures=True 额外取消还没开始的工作,正在执行的仍要结束。wait=False 让当前调用尽快返回,不会杀死工作线程,整个 Python 程序仍须等待这些工作退出。退出 with ThreadPoolExecutor(...) 相当于等待式关闭。

Slime 的微批次预取器先设 _closed,防止再取得它的执行器,然后 shutdown(wait=True, cancel_futures=True),最后释放各份数据。QueueReader.close() 则先等专用 reader 的同步读取结束,再归还租约、停止心跳线程。两处顺序都表达资源所有权:线程还在用的资源,不能先当成空闲资源清理。

源码入口:预取器与 QueueReader 的关闭顺序

RoutedExpertsMicrobatchPrefetcher.close
slime/utils/routed_experts.py · L184–L191
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码

    def close(self) -> None:
        if self._closed:
            return
        self._closed = True
        if self._executor is not None:
            self._executor.shutdown(wait=True, cancel_futures=True)
        for source in self.sources:
            source.release()

QueueReader.close
slime/data/queue_data_source.py · L1280–L1289
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码

    def close(self):
        if self._closed:
            return
        self._closed = True
        self._reader.shutdown(wait=True)
        with self._lock:
            leases = list(self._leases.values())
        ray.get(self.controller.release.remote(leases))
        self._stop.set()
        self._heartbeats.join(timeout=5)

为什么测试里还会直接 Future()

业务提交通常由 executor 创建 Future。Slime 的测试也直接构造 Future() 模拟一个没有完成的远程请求,在清理时用 set_result(None) 解除它;这属于测试替身,不是在启动一条工作线程。直接构造后如果没人设置结果、设置异常或取消,它就会一直未完成。

set_result(value) 和 set_exception(error) 是执行器实现与测试设置完成状态的接口;不要对 submit 返回、正在由线程池管理的 Future 再手动设置结果。测试专用方法说明

源码入口:未完成的测试请求与 finally 清理

test_slow_worker_control_does_not_block_collection_or_close
tests/test_distributed_rollout.py · L808–L843
快照 2f2318653f6f794dddd321eff7c9d4b7b174643f· 查看固定版本源码

def test_slow_worker_control_does_not_block_collection_or_close(scheduler_factory, control):
    scheduler, _, _ = scheduler_factory(3, [1, 1])
    stalled = Future()
    entered = threading.Event()
    original = getattr(scheduler.workers[0], control).remote

    def stall(*args):
        entered.set()
        return stalled

    if control == "pause":
        scheduler.generate(0, prefetch=2)
    getattr(scheduler.workers[0], control).remote = stall
    with ThreadPoolExecutor(2) as callers:
        request = (
            callers.submit(scheduler.generate, 0, prefetch=2)
            if control == "start"
            else callers.submit(scheduler.pause)
        )
        closing = None
        try:
            assert entered.wait(5)
            if control == "start":
                result = request.result(timeout=2)
                assert all(group[0].metadata["worker"] == 1 for group in result.samples)
            closing = callers.submit(scheduler.close)
            closing.result(timeout=2)
            if control == "pause":
                with pytest.raises(RuntimeError, match="scheduler is closed"):
                    request.result(timeout=2)
        finally:
            # Also let the old, blocking implementation exit after test failure.
            if not stalled.done():
                stalled.set_result(None)
            getattr(scheduler.workers[0], control).remote = original
            scheduler.close()

与 asyncio 同名接口对照

问题concurrent.futuresasyncio(Python 3.12)
等一个结果Future.result 会阻塞调用线程await Future/Task 挂起当前任务;未完成时直接 result 会抛 InvalidStateError
等一组wait 是同步函数,返回 Future 集合await asyncio.wait,返回 Task/Future 集合
按完成次序消费as_completed 产出原 Future,再 result普通迭代 asyncio.as_completed 产出要 await 的协程,不能把它当成原 Task
接入事件循环不能直接 await 同步 Future用 asyncio.wrap_future 适配,或从 run_in_executor 取得异步 Future
取消阻止尚未开始的函数调用向任务传递取消请求,协程还要在挂起点处理并清理

在 async def 内直接调用同步 Future.result 或 concurrent.futures.wait,会阻塞事件循环线程。选择哪一套,要看自己拿到的对象以及当前运行位置,不能只看方法名。异步 Future 对照

运行一个不靠睡眠排序的实验

下载 example.py,运行 python3 -W error example.py。只使用标准库,推荐 Python 3.12,最低 Python 3.11。

read_file 沿用转换工具中的名称,但把真实磁盘读取换成 Event 挡板;这是语义实验。第一组只有一个工作线程,先确认 first 已运行,再提交 queued,所以能确定哪个无法取消、哪个还在排队。第二组有两个线程,显式先放行 fast,收取后再放行 slow。

running True done False
cancel running False
cancel queued True done True
queued result CancelledError
result timeout; work still running True
result first
callback ['first']
success exception None
raised cannot read broken
stored exception ValueError
map [3, 1, 2]
first completed ['fast']
pending 1
as_completed fast
as_completed slow
shutdown finished
展开完整可运行示例
"""Observe executor futures without depending on sleep or worker speed."""

import threading
from concurrent.futures import (
    FIRST_COMPLETED,
    CancelledError,
    ThreadPoolExecutor,
    as_completed,
    wait,
)


def read_file(rel_path, started, release):
    # API experiment: replace disk I/O with an explicitly controlled boundary.
    started.set()
    release.wait()
    if rel_path == "broken":
        raise ValueError("cannot read broken")
    return rel_path


def main():
    started, release, callback_done = (threading.Event() for _ in range(3))
    callback_results = []

    def on_done(future):
        callback_results.append(future.result())
        callback_done.set()

    with ThreadPoolExecutor(max_workers=1) as executor:
        running = executor.submit(read_file, "first", started, release)
        started.wait()
        queued = executor.submit(read_file, "queued", started, release)
        print("running", running.running(), "done", running.done())
        print("cancel running", running.cancel())
        print("cancel queued", queued.cancel(), "done", queued.done())
        try:
            queued.result()
        except CancelledError:
            print("queued result CancelledError")
        try:
            running.result(timeout=0)
        except TimeoutError:
            print("result timeout; work still running", running.running())

        running.add_done_callback(on_done)
        release.set()
        print("result", running.result())
        callback_done.wait()  # result() alone does not promise callback completion.
        print("callback", callback_results)
        print("success exception", running.exception())

        failed = executor.submit(read_file, "broken", started, release)
        try:
            failed.result()
        except ValueError as error:
            print("raised", str(error))
        print("stored exception", type(failed.exception()).__name__)
        print("map", list(executor.map(abs, [-3, -1, -2])))

    executor = ThreadPoolExecutor(max_workers=2)
    slow_started, fast_started = threading.Event(), threading.Event()
    slow_release, fast_release = threading.Event(), threading.Event()
    try:
        slow = executor.submit(read_file, "slow", slow_started, slow_release)
        fast = executor.submit(read_file, "fast", fast_started, fast_release)
        slow_started.wait()
        fast_started.wait()
        fast_release.set()
        done, pending = wait([slow, fast], return_when=FIRST_COMPLETED)
        print("first completed", [future.result() for future in done])
        print("pending", len(pending))

        completed = as_completed([slow, fast])
        print("as_completed", next(completed).result())
        slow_release.set()
        print("as_completed", next(completed).result())
    finally:
        # Also unblock workers if an assertion or print fails during the demo.
        slow_release.set()
        fast_release.set()
        executor.shutdown(wait=True, cancel_futures=True)
    print("shutdown finished")


if __name__ == "__main__":
    main()
复述检查

done 为真仍可能失败或已取消;result 才给出结果或重新抛错。map 保留输入顺序,as_completed 便于先处理完成项。result 的超时和 shutdown(wait=False) 都不能强停线程。在线程池工作函数内部等待同一池的后续工作,可能用尽所有工作线程而死锁。

书籍核对与本节判断

书上怎么说: as_completed 在工作完成后产出 Future。[依据 A]

我的判断: 本节用 Event 建立确定顺序,取消边界、回调和 shutdown 语义按 Python 3.12 文档核对;书中关于回调执行线程的描述不作为“总在原工作线程中运行”的保证。

依据 A · 第 20 章《并发执行器》› Where Are the Futures?,锚点 where-are-the-futures。英文 corpus/en/ch20.md:246–338,中文 corpus/zh/ch20.md:236–323。书籍基于 Python 3.10;本节验证环境 Python 3.12.3。

原文:“as_completed yields futures as they are completed.”

对照:“as_completed 函数在 future 对象运行结束后产出 future 对象。”

本节对应的全部源码位置

下面把同类用法在 Slime 中的每个扫描位置接回本节讲解;点击文件名可查看完整源码快照。重复出现的 API 共用解释,具体输入与清理由所在函数决定。

展开本节 65 条源码记录
记录 / 讲解源码位置原代码
occ-000474
有接收者线索
slime/agent/aiohttp_threaded.py:39
AppHandle.stop
fut.result(timeout=10)
occ-000643slime/backends/megatron_utils/update_weight/update_weight_from_disk_delta.py:10
<module>
from concurrent.futures import ThreadPoolExecutor
occ-000651slime/backends/megatron_utils/update_weight/update_weight_from_disk_delta.py:273
UpdateWeightFromDiskDelta._encode_delta
pool = ThreadPoolExecutor(max_workers=NUM_WORKERS)
occ-000653
有接收者线索
slime/backends/megatron_utils/update_weight/update_weight_from_disk_delta.py:287
UpdateWeightFromDiskDelta._encode_delta
inflight.append(pool.submit(diff_and_compress, name, payload, nbytes, pinned))
occ-000654
有接收者线索
slime/backends/megatron_utils/update_weight/update_weight_from_disk_delta.py:293
UpdateWeightFromDiskDelta._encode_delta
pool.shutdown()
occ-000749slime/data/queue_data_source.py:19
<module>
from concurrent.futures import ThreadPoolExecutor
occ-000800slime/data/queue_data_source.py:1035
QueueReader.__init__
self._reader = ThreadPoolExecutor(max_workers=1, thread_name_prefix="queue-input")
occ-000814
有接收者线索
slime/data/queue_data_source.py:1284
QueueReader.close
self._reader.shutdown(wait=True)
occ-001213
有接收者线索
slime/utils/async_utils.py:58
AsyncLoopThread.run
return asyncio.run_coroutine_threadsafe(coro, self.loop).result()
occ-001297slime/utils/routed_experts.py:5
<module>
from concurrent.futures import Future, ThreadPoolExecutor
occ-001298slime/utils/routed_experts.py:5
<module>
from concurrent.futures import Future, ThreadPoolExecutor
occ-001299slime/utils/routed_experts.py:149
RoutedExpertsMicrobatchPrefetcher.__init__
ThreadPoolExecutor(
occ-001301slime/utils/routed_experts.py:181
RoutedExpertsMicrobatchPrefetcher
def executor(self) -> ThreadPoolExecutor | None:
occ-001304slime/utils/routed_experts.py:211
RoutedExpertsMicrobatch.__init__
self._future: Future | None = None
occ-001405tests/pipeline_rl_test_helpers.py:8
<module>
from concurrent.futures import ThreadPoolExecutor
occ-001422tests/pipeline_rl_test_helpers.py:159
before_train_step
with ThreadPoolExecutor(max_workers=len(_probes)) as pool:
occ-001423
有接收者线索
tests/pipeline_rl_test_helpers.py:160
before_train_step
_records["continuous"] = list(pool.map(lambda probe: probe.verify(), _probes))
occ-001424tests/pipeline_rl_test_helpers.py:164
before_train_step
with ThreadPoolExecutor(max_workers=len(_probes)) as pool:
occ-001425
有接收者线索
tests/pipeline_rl_test_helpers.py:166
before_train_step
pool.map(
occ-001634tests/test_distributed_rollout.py:12
<module>
from concurrent.futures import FIRST_COMPLETED, Future, ThreadPoolExecutor, wait
occ-001635tests/test_distributed_rollout.py:12
<module>
from concurrent.futures import FIRST_COMPLETED, Future, ThreadPoolExecutor, wait
occ-001636tests/test_distributed_rollout.py:12
<module>
from concurrent.futures import FIRST_COMPLETED, Future, ThreadPoolExecutor, wait
occ-001637tests/test_distributed_rollout.py:12
<module>
from concurrent.futures import FIRST_COMPLETED, Future, ThreadPoolExecutor, wait
occ-001671tests/test_distributed_rollout.py:650
scheduler_factory.ray_wait
done, pending = wait(refs, timeout=timeout, return_when=FIRST_COMPLETED)
occ-001672tests/test_distributed_rollout.py:650
scheduler_factory.ray_wait
done, pending = wait(refs, timeout=timeout, return_when=FIRST_COMPLETED)
occ-001689
有接收者线索
tests/test_distributed_rollout.py:737
scheduler_factory
asyncio.run_coroutine_threadsafe(cleanup(), loop).result(timeout=10)
occ-001691tests/test_distributed_rollout.py:793
test_async_scheduler_fast_worker_fills_batch_without_waiting_for_slow_worker
with ThreadPoolExecutor(1) as consumer:
occ-001692
有接收者线索
tests/test_distributed_rollout.py:794
test_async_scheduler_fast_worker_fills_batch_without_waiting_for_slow_worker
future = consumer.submit(scheduler.generate, 0, prefetch=2)
occ-001693
有接收者线索
tests/test_distributed_rollout.py:796
test_async_scheduler_fast_worker_fills_batch_without_waiting_for_slow_worker
result = future.result(timeout=5)
occ-001696tests/test_distributed_rollout.py:810
test_slow_worker_control_does_not_block_collection_or_close
stalled = Future()
occ-001699tests/test_distributed_rollout.py:821
test_slow_worker_control_does_not_block_collection_or_close
with ThreadPoolExecutor(2) as callers:
occ-001700
有接收者线索
tests/test_distributed_rollout.py:823
test_slow_worker_control_does_not_block_collection_or_close
callers.submit(scheduler.generate, 0, prefetch=2)
occ-001701
有接收者线索
tests/test_distributed_rollout.py:825
test_slow_worker_control_does_not_block_collection_or_close
else callers.submit(scheduler.pause)
occ-001704
有接收者线索
tests/test_distributed_rollout.py:833
test_slow_worker_control_does_not_block_collection_or_close
closing = callers.submit(scheduler.close)
occ-001705
有接收者线索
tests/test_distributed_rollout.py:834
test_slow_worker_control_does_not_block_collection_or_close
closing.result(timeout=2)
occ-001707
有接收者线索
tests/test_distributed_rollout.py:840
test_slow_worker_control_does_not_block_collection_or_close
if not stalled.done():
occ-001708
有接收者线索
tests/test_distributed_rollout.py:841
test_slow_worker_control_does_not_block_collection_or_close
stalled.set_result(None)
occ-001710tests/test_distributed_rollout.py:884
test_scheduler_close_unblocks_waiting_consumer
with ThreadPoolExecutor(1) as consumer:
occ-001711
有接收者线索
tests/test_distributed_rollout.py:885
test_scheduler_close_unblocks_waiting_consumer
future = consumer.submit(scheduler.generate, 0, prefetch=1)
occ-001714
有接收者线索
tests/test_distributed_rollout.py:894
test_scheduler_close_unblocks_waiting_consumer
future.result(timeout=5)
occ-001759tests/test_distributed_rollout.py:1471
test_two_ray_nodes_generate_transfer_and_restore
with ThreadPoolExecutor(1) as consumer:
occ-001760
有接收者线索
tests/test_distributed_rollout.py:1472
test_two_ray_nodes_generate_transfer_and_restore
future = consumer.submit(fetch, 2)
occ-001763
有接收者线索
tests/test_distributed_rollout.py:1498
test_two_ray_nodes_generate_transfer_and_restore
after_failure = future.result(timeout=30)
occ-001794tests/test_distributed_rollout.py:2049
test_admission_pause_acknowledges_workers_without_waiting_for_responses
with ThreadPoolExecutor(1) as pool:
occ-001795
有接收者线索
tests/test_distributed_rollout.py:2050
test_admission_pause_acknowledges_workers_without_waiting_for_responses
paused = pool.submit(scheduler.pause, drain=False)
occ-001796
有接收者线索
tests/test_distributed_rollout.py:2052
test_admission_pause_acknowledges_workers_without_waiting_for_responses
assert paused.result(timeout=3) is False
occ-002250tools/convert_hf_to_fp8.py:23
<module>
from concurrent.futures import ThreadPoolExecutor
occ-002252tools/convert_hf_to_fp8.py:178
convert_fp8
with ThreadPoolExecutor(max_workers=max_workers) as executor:
occ-002253
有接收者线索
tools/convert_hf_to_fp8.py:181
convert_fp8
future = executor.submit(
occ-002254
有接收者线索
tools/convert_hf_to_fp8.py:187
convert_fp8
future.result()
occ-002256tools/convert_hf_to_int4_direct.py:17
<module>
from concurrent.futures import ThreadPoolExecutor
occ-002258tools/convert_hf_to_int4_direct.py:224
convert_int4
with ThreadPoolExecutor(max_workers=max_workers) as executor:
occ-002259
有接收者线索
tools/convert_hf_to_int4_direct.py:227
convert_int4
future = executor.submit(
occ-002260
有接收者线索
tools/convert_hf_to_int4_direct.py:240
convert_int4
future.result()
occ-002263tools/convert_torch_dist_to_hf_parallel.py:10
<module>
from concurrent.futures import ThreadPoolExecutor, as_completed
occ-002264tools/convert_torch_dist_to_hf_parallel.py:10
<module>
from concurrent.futures import ThreadPoolExecutor, as_completed
occ-002268tools/convert_torch_dist_to_hf_parallel.py:147
WrappedStorageReader._parallel_preload_files
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
occ-002269
有接收者线索
tools/convert_torch_dist_to_hf_parallel.py:149
WrappedStorageReader._parallel_preload_files.<comprehension>
executor.submit(read_file, rel_path, full_path, ops): rel_path
occ-002270tools/convert_torch_dist_to_hf_parallel.py:154
WrappedStorageReader._parallel_preload_files
for future in tqdm(as_completed(futures), total=len(futures), desc="Pre-loading files"):
occ-002273tools/convert_torch_dist_to_hf_parallel.py:296
save_tensors
with ThreadPoolExecutor(max_workers=max_workers) as executor:
occ-002274
有接收者线索
tools/convert_torch_dist_to_hf_parallel.py:297
save_tensors.<comprehension>
futures = {executor.submit(process_and_collect, (name, param)): name for name, param in param_list}
occ-002275tools/convert_torch_dist_to_hf_parallel.py:298
save_tensors
for future in tqdm(as_completed(futures), total=len(futures), desc="Converting parameters"):
occ-002277tools/convert_torch_dist_to_hf_parallel.py:336
save_tensors
with ThreadPoolExecutor(max_workers=min(max_workers, num_files)) as executor:
occ-002278
有接收者线索
tools/convert_torch_dist_to_hf_parallel.py:337
save_tensors.<comprehension>
futures = [executor.submit(save_file, i, tensors) for i, tensors in enumerate(modeltensors)]
occ-002279tools/convert_torch_dist_to_hf_parallel.py:338
save_tensors
for future in tqdm(as_completed(futures), total=len(futures), desc="Saving files"):

专题首页 · 上一节 · 下一节:18 · 进程生命周期:从队列工作到超时回收 · 源码与教程对照 · 全部用法