01 · 每做完一件事就等待,会留下空档
假设每批数据要CPU准备2 ms,设备计算6 ms。若CPU等设备完全做完才准备下一批,三批需要24 ms;若下一批的准备不依赖当前设备结果,CPU可以在设备计算时提前准备,三批在20 ms结束。
异步指发出任务后调用方可继续工作;重叠指两段实际执行时间同时发生。前者给后者创造机会,但需要独立依赖、足够资源和正确生命周期。
02 · 用时间线找出可重叠部分
CPU准备与设备计算,怎样错开推进
一个资源负责一个阶段,批次独立,缓冲充足;未计复制,下一批准备不依赖当前计算结果。无运行时开销与争用,不代表实测。本例CPU依次在0–2、2–4、4–6 ms准备三批;设备依次在2–8、8–14、14–20 ms执行。并没有让同一批数据尚未准备好就开始计算,也没有假设设备同时执行三份计算。
在足够缓冲、固定耗时和独立批次下,先支付一次准备与计算的填充成本,再由较慢阶段决定稳态速度。CPU准备变成8 ms、设备仍6 ms时,三批总计30 ms,瓶颈转为CPU;单独让设备更快无法消除它。
03 · 一次调用有四个不同的完成点
| 状态 | 含义 | 能否消费结果 |
|---|---|---|
| 调用已返回 | 主机已下发或排队任务 | 不能据此断言设备结果就绪。 |
| 输入已就绪 | 设备可读到本任务所需数据 | 还需执行计算。 |
| 设备计算已完成 | 设备输出已产生 | CPU读取前可能还需结果复制。 |
| 结果复制/处理完成 | CPU可按接口约定读取并反馈 | 之后再更新相关请求状态。 |
例如异步D2H是设备到主机的结果复制。在复制完成前就遍历CPU结果,可能读到未完成内容。正确做法是等待这个结果的完成通知,不一定要阻塞所有无关工作。
依据:PyTorch · pin_memory与non_blocking使用指南 ↗ · PyTorch 2.14 · CUDA stream、等待与内存生命周期 ↗
04 · stream与event,让等待落在真正的依赖上
CUDA stream可理解为有序任务队列;event是某个位置的完成标记。复制队列完成批次2输入后记录事件,计算队列等这个事件,再使用批次2。这是设备依赖,不等同于CPU把所有工作都等完。
copy_queue:复制输入2 → 记录ready_2
compute_queue:计算输入1 → 等待ready_2 → 计算输入2
CPU:准备输入3、处理已完成的输入0结果
复用输入1槽位前:确认使用它的计算完成
CPU读取结果1前:确认D2H复制完成不同队列只是允许并行,不保证硬件一定能同时执行。分配器回收与用户缓冲池还要遵守使用队列的生命周期约定;等待事件保证就绪,与保留内存不被回收是相关但不同的问题。
05 · 自回归下一token,哪些工作不能提前
同一个请求,第n步生成的token决定第n+1步的输入。所以第n+1步不能用尚不存在的token做真正计算。可以提前分配容量、整理不依赖新token的元数据,或者处理其他独立请求。
| 后续工作 | 是否可与当前步重叠 | 条件 |
|---|---|---|
| 另一个独立请求的分词/解析 | 可能 | CPU资源和服务调度允许。 |
| 准备固定缓冲与不变元数据 | 可能 | 不读取本步尚未生成的结果。 |
| 同请求下一步实际token输入计算 | 必须等待token依赖 | 设备上采样后保留token可以减少CPU往返。 |
| CPU停止检查/响应处理与后续设备工作 | 特定引擎可支持 | 必须处理停止、取消和可能多算一步的状态。 |
TensorRT-LLM的Overlap Scheduler描述了设备当前计算与CPU处理前一轮结果的流水组织。它用引擎管理的设备输出和异步状态衔接,不是把未知token预测为已知输入。
如果CPU的EOS/自定义停止判断尚未完成就推进后续工作,系统可能多算一部分。这需要引擎处理结果丢弃、KV释放、取消和请求状态,不应把可能多算的token发给用户。
06 · 训练能提前加载数据,但不能越过更新边界
下一批的读取、解压与分词通常不依赖当前权重更新,因而可以提前。但下一次前向究竟应使用更新前还是更新后的参数,是训练算法的要求。数据预取不授权改变参数版本。
梯度归约、裁剪、优化器更新及分片参数就绪也有依赖。能重叠哪一段,应由并行/分片框架管理,而不是在不同线程任意调用同一个模型。流水线训练的microbatch排程见M040。
07 · 常见的“异步却没有收益”与状态错误
- 每下发一次就全设备同步:把可独立部分又串起来。
- 频繁读取设备标量到CPU:例如热点里的.item()或结果转列表,可能触发等待。
- 同队列顺序复制/计算:主机提前返回,不代表设备时间重叠。
- 多队列争用同一带宽或执行资源:单独工作时的耗时不能直接套用。
- 取消后立刻回收缓冲:仍在执行的任务可能继续读写。
- 结果处理乱序却用相同请求编号:可能把内容发给错误请求。
同步也有必要用途,例如真实计时、检查设备错误和CPU消费结果。目标是把等待放在需要结果的边界,保留其他独立工作推进,而不是删掉全部同步。
08 · 用就绪队列组织可独立的工作
while service_has_work:
retire_completed_tasks_and_safe_buffers()
prepare_jobs_whose_inputs_do_not_need_pending_results()
submit_ready_device_tasks_with_dependencies()
process_completed_results_with_request_ids()
# 没有可推进的工作时,等待对应完成事件;避免无界忙轮询。下方CPU脚本验证两/三阶段流水的依赖,故意尝试过早覆盖缓冲和在结果就绪前使用它,检查这些行为被拒绝。它不启动异步设备工作。
NVIDIA可从CUDA stream语义和TensorRT-LLM引擎实现检查依赖;昇腾可从ACL异步复制及相应框架的队列/完成机制核对。相同“异步”概念不等于相同支持组合或API。
依据:PyTorch 2.14 · CUDA stream、等待与内存生命周期 ↗ · TensorRT-LLM · Overlap Scheduler ↗ · CANN Community 9.1.0 · aclrtMemcpyAsync与Host内存约束 ↗
09 · 用端到端时间线验证,而非只计调用返回
- 记录CPU准备、设备提交、计算、结果复制和响应处理时间。
- 沿真实依赖找关键路径,确认设备空档因哪一步造成。
- 检查队列长度、在途任务与内存峰值,限制预取和排队。
- 服务测TTFT、生成间隔、吞吐与尾延迟;批次更满未必单请求更快。
- 测试EOS、自定义停止、取消、异常和长短请求混合,确认输出归属及缓冲释放。
自查:调用返回与任务完成为什么不是同一时刻?为什么CPU可以准备下一批数据,却不能提前得到同请求的下一个token?为什么“多一个stream”不保证重叠或正确结果?
流程位置与关联方法
这个方法可能出现在多个位置;计算线包含模型计算与算子实现,执行线包含运行时、内存和通信。
资料与核查范围
原理依据以原始论文和官方文档为主;算例与时间线是教学推导。文档核查日期:2026-10-10。引用 main / latest 文档时,实际接口以安装版本为准。
- TensorRT-LLM · Overlap Scheduler ↗
- PyTorch 2.14 · CUDA stream、等待与内存生命周期 ↗
- PyTorch · pin_memory与non_blocking使用指南 ↗
- CANN Community 9.1.0 · aclrtMemcpyAsync与Host内存约束 ↗
代码为接入片段或教学伪代码;本页没有声称完成 NVIDIA / 昇腾硬件实测。