同一套 ComfyUI 工作流,第二次为什么快一倍?
TL;DR
- 场景:同一套 ComfyUI 工作流第二次运行耗时明显变短,最容易得出的结论是"采样器或注意力变快了",但缺乏任何阶段归属的耗时数据。
- 结论:第二次耗时变短可能来自模型常驻、CUDA kernel 编译预热、节点输入签名缓存命中、实际执行子图收缩,也可能来自原生帧之外的后处理被忽略。结论必须建立在互斥的阶段账本之上。
- 产出:互斥的阶段账本字典、四种运行模式(process_cold / model_cold_process_warm / model_warm_recompute / cache_hit_rerun)、十项实验矩阵、阶段绑定的完整 condition_id 字段,以及四类最常见错误结论的纠正方法。
版本矩阵
| 功能 | 状态 | 说明 |
|---|---|---|
| PyTorch GPU 运算是异步执行 | ✅ 已验证 | PyTorch 官方 CUDA 语义文档原文:默认情况下 GPU 操作都是异步执行的,没有同步的时间测量不准确 |
| torch.cuda.Event(enable_timing=True) 测量 GPU 时间 | ✅ 已验证 | 官方推荐方案:start.record() / end.record() / end.synchronize() / start.elapsed_time(end),单位毫秒 |
| torch.cuda.synchronize() 阻塞 CPU 直到所有 CUDA 任务完成 | ✅ 已验证 | 官方 API;用于性能测试、调试与多 GPU 场景;不推荐在常规训练中频繁调用 |
| CUDA Event 测同设备流上的 GPU 时间 | ✅ 已验证 | 官方说明:单设备单 stream 阶段有效;多 stream、异步 H2D/D2H、CPU/GPU 并行需 Nsight Systems |
| ComfyUI 节点缓存:仅重执行变化的图部分 | ✅ 已验证 | 官方文档原文:“Only parts of the graph that change from each execution to the next will be executed, if you submit the same graph twice only the first will be executed” |
| ComfyUI Asynchronous Queue system | ✅ 已验证 | ComfyUI GitHub README “Features” 部分明确列出 |
| executed 消息在无 UI 输出时不发送 | ✅ 已验证 | ComfyUI 官方消息文档明确说明:executed 不是"每个节点完成"事件,只在节点返回 UI 更新时发送 |
| ComfyUI v0.11.0 release date 2026-01-27 + EasyCache / Noise_EmptyNoise / WAN-VAE / LTX2 优化 | ⚠️ 待验证 | 上一轮 v0.11.0 摘要未在本轮 web search 结果中直接命中;建议核对 GitHub Release tag 原始页面 |
| 节点缓存默认 TTL / 失效策略 | ⚠️ 待验证 | 节点缓存依赖输入签名 + 节点定义指纹;默认 TTL 与失效策略需查 execution.py 当前实现 |
| EasyCache 在采样期间条件变化时的边缘处理 | ⚠️ 待验证 | v0.11.0 摘要描述"正确处理边缘情况",但具体场景与配置开关需查 commit diff |
| ComfyUI v0.16.3 LTX2 vocoder 修复(manual cast to conv_transpose1d) | ⚠️ 本文未引用 | 与本文主题不直接相关;列出以避免读者混淆版本号 |
发布边界:标题中的快一倍是待解释现象,不是作者实测承诺;任何性能结果都必须绑定工作流、硬件、版本和运行条件。
摘要
同一套 ComfyUI 工作流第二次运行明显更快,可能来自模型常驻、编译预热、节点缓存或实际执行子图变化,而不代表模型本身获得同等幅度加速。本文建立从队列、加载、条件编码、采样、VAE、后处理到文件交付的互斥阶段账本,并给出冷启动、热启动、缓存命中和切换模型的可复现比较矩阵。
关键词
ComfyUI、阶段计时、缓存命中、CUDA Events、性能工程
目录
- 一、先区分静态工作流与实际执行子图
- 二、正确理解 ComfyUI 的四类消息
- 三、把总耗时拆成互斥的阶段账本
- 四、CPU 计时不能直接代表 GPU 计时
- 五、冷启动、热启动与模型切换必须分栏
- 六、可直接执行的实验矩阵
- 七、阶段账本必须绑定完整条件
- 八、用阶段占比决定优化顺序
- 九、最小可落地的采集实现
- 十、四类最常见的错误结论
- 结语
同一套视频工作流,第一次运行很慢,第二次却明显变快。最容易得到的结论是"优化生效了",但这个结论通常没有回答最重要的问题:快在哪里?
第二次运行可能复用了节点输出,模型也可能已经驻留显存;CUDA 上下文、算子编译、磁盘页缓存和 FFmpeg 初始化也可能已经预热。另一种更隐蔽的情况是,测试只计到了原生帧生成,没有把插帧、超分、编码、落盘和上传算进去。总耗时变短是真实的,但它不能单独证明采样器、注意力实现或模型本身变快。
因此,从 Queue 到最终视频文件的性能分析,不能只记一个开始时间和一个结束时间。需要同时建立三个对象:本次请求真正执行了哪一张子图;每个工程阶段的边界是什么;所有对比运行遵守什么状态与配置协议。只有三者同时固定,耗时才可复现、可比较,也才能指导优化。
一、先区分静态工作流与实际执行子图
ComfyUI workflow 是由节点和连接组成的图。[S01][S02] 但保存在 JSON 里的完整图,不等于某一次请求真正执行的图。
第一层变化来自输出选择。Partial Execution 只执行所选输出节点所依赖的分支;完整执行才会请求全部输出分支。[^S04] 如果同一工作流同时有预览图、原生视频、插帧视频和最终交付文件,选择不同输出,本次请求的祖先节点集合就不同。
第二层变化来自缓存。节点是否需要重算,不只取决于节点名称,而取决于节点类型、输入、上游依赖以及节点定义的变化指纹。当前 ComfyUI 源码会为输入签名构造缓存键,并在执行前检查可复用结果。[S05][S06] 因此,修改种子可能只让采样及其下游重算,文本编码仍然命中;修改提示词则可能让条件编码、采样和后续分支全部失效。缓存模式、节点实现和自定义节点版本变化,也会改变实际子图。
可以把本次请求的候选子图写成:
候选子图 = 所选输出的全部祖先节点 实际执行子图 ≈ 候选子图 − 可复用缓存节点 + 动态展开节点这个公式只能用于设计,不应替代观测。最终必须保存三份清单:selected_outputs、cached_nodes和actual_executed_nodes。还要记录预期执行而未执行的节点,以及意外执行的节点。只看静态 workflow,无法解释两次运行为什么不同。
二、正确理解 ComfyUI 的四类消息
ComfyUI 的 WebSocket 消息适合建立运行状态,但不能直接当作通用节点计时器。[^S03]
| 消息 | 可安全解释的语义 | 不能据此推出 |
|---|---|---|
execution_cached | 执行开始阶段发现可复用输出的节点清单 | 这些节点构成了本次全部"未执行节点",或缓存命中等于完整推理 |
executing | 某节点即将被执行或调度 | GPU 已开始计算,更不能代表 GPU 已完成 |
executed | 节点产生了需要发给前端的 UI 输出 | 每个节点都已完成;无 UI 输出的节点通常没有该消息 |
execution_success | 本次 ComfyUI prompt 的必需执行无错误结束 | 外部后处理、独立 FFmpeg、上传或最终文件提交一定已经完成 |
官方消息文档明确说明,executed不是"每个节点完成"事件,而只在节点返回 UI 更新时发送。[^S03] 当前源码还会为缓存中的 UI 数据发送executed。[^S05] 因此,用相邻两个executed的到达时间推算节点耗时,会同时漏掉无 UI 节点、混入网络传输,并把缓存 UI 误认为真实执行。
executing更接近调度前信号。当前源码在调用节点函数前发送它,但 GPU 工作可能只是随后被异步入队。[^S05] 它可以帮助重建节点顺序,不能作为 GPU 完成边界。当前生命周期消息中的部分时间戳使用服务器墙上时钟,而精确耗时应使用同一进程的单调时钟。客户端收到 WebSocket 消息的时间还包含网络、事件循环和序列化延迟。
精确的 Queue 等待时间应在服务器端用同一个monotonic/perf_counter时钟记录:
T_queue = t_execution_start_server − t_queue_accepted_server客户端提交时间减服务器事件时间,只有在时钟经过同步且明确计入网络延迟时才有意义。否则应命名为"客户端观测等待",不能冒充服务器队列等待。
三、把总耗时拆成互斥的阶段账本
端到端边界应从请求被队列接受开始,到可交付文件完成提交为止:
T_total = t_delivery_committed − t_queue_accepted最小阶段字典如下。每个阶段都要有明确的开始事件、结束事件、责任组件和产物边界。
| 阶段 | 建议边界 | 需要单独记录的原因 |
|---|---|---|
queue_wait | 队列接受到执行开始 | 受并发、优先级和前序任务影响,与模型速度无关 |
executor_prepare | 执行开始到首个实际阶段开始 | 包含图准备、缓存检查、资源清理等调度成本 |
model_load_residency | 权重读取、反序列化、主机暂存、H2D、驻留或换入结束 | 冷启动和模型切换的主要差异在这里 |
conditioning | 文本、图像、控制条件和适配器编码 | 可能被上游缓存独立复用 |
sampling | 首个采样工作开始到最后一个采样工作完成 | 扩散或流模型的迭代主体,但占比必须实测 |
vae_decode | latent 解码开始到帧张量完成 | 分辨率、帧数、分块和精度都会改变该阶段 |
native_materialize | 帧张量到原生输出可用 | 包含 D2H、帧整理、原生文件写入等 |
postprocess | 插帧、超分、修复、色彩或其他处理 | 可按处理器继续拆分,不能并入原生生成 |
encode_mux | FFmpeg 启动到成功退出并关闭临时文件 | 编码器、预设、滤镜、像素格式和容器独立影响 |
delivery_commit | 临时产物完成到最终路径可见且校验通过 | 包含 flush、fsync、原子重命名、复制、上传或校验 |
queue_to_delivery | 队列接受到最终交付完成 | 用户真正等待的端到端总账 |
如果这些阶段严格串行,可以相加得到总耗时。若 GPU 计算、CPU 后处理、磁盘写入或多流复制发生重叠,就不能把所有子阶段时长机械相加。此时总账使用区间并集或关键路径;资源账本则分别报告 CPU wall、GPU device time、I/O time 和重叠区间。
FFmpeg 必须成为显式阶段。父进程用单调时钟记录子进程启动、退出和文件关闭;FFmpeg 的-progress可输出机器可读的周期状态,-benchmark或-benchmark_all可作为诊断补充。[^S12] 但最终编码墙钟仍应以父进程从启动到成功退出的区间为准。编码完成也不等于交付完成:跨文件系统复制、对象存储上传、校验和、原子发布和消费者可见性要放进delivery_commit。
四、CPU 计时不能直接代表 GPU 计时
CUDA 默认是异步执行。CPU 调用通常只是把 kernel、内存复制或库操作排入 GPU stream,然后立即返回。[^S09] 因此:
t0=perf_counter()run_sampling()t1=perf_counter()在没有同步边界时,往往测到的是提交工作所需的 CPU 时间,而不是采样在 GPU 上完成所需的时间。
对单设备、单一或明确 stream 的阶段,可使用 CUDA Event:
start=torch.cuda.Event(enable_timing=True)end=torch.cuda.Event(enable_timing=True)start.record()run_stage()end.record()end.synchronize()gpu_ms=start.elapsed_time(end)Event 必须记录在正确的 device 和 stream 上,结束 Event 同步后才能读取时间。[^S09] 如果阶段涉及多个 stream、异步 H2D/D2H、模型换入换出、CPU/GPU 并行或第三方 CUDA 库,单对 Event 可能覆盖不全。此时应使用 Nsight Systems 捕获 CUDA API、kernel、memory copy、context 和 stream 时间线,并用 NVTX range 标注run_id/stage_id/node_id。[^S10]
不要在每个节点后强制torch.cuda.synchronize()作为常规基准。它会打破原本的流水和重叠,改变被测系统。应建立两套运行:
- 低侵入基线运行:只记录服务器单调时钟、实际子图、阶段边界、FFmpeg 和产物提交。
- 剖析运行:增加 CUDA Event、NVTX 与 Nsight,用于解释 GPU 关键路径,并单独标记 Profiler 配置和开销。
显存也要分层。PyTorch memory snapshot 只能看见由 PyTorch allocator 管理的 CUDA 内存,直接 CUDA API、NCCL 或其他库的分配可能不可见。[^S11] 账本应同时保留 PyTorch allocated/reserved 峰值、设备或进程级显存观测,以及需要时的 Nsight memory trace;三者不能互相冒充。
五、冷启动、热启动与模型切换必须分栏
"冷启动一次、热启动三次"仍然太含糊。至少定义以下状态:
| 状态 | 操作定义 | 报告名称 |
|---|---|---|
| 新进程启动 | 新建 ComfyUI 进程;CUDA context、运行时和模型尚未初始化 | process_cold |
| 进程已热、目标模型未驻留 | 服务仍在,但目标模型从未加载或已被明确卸载 | model_cold_process_warm |
| 模型驻留且强制重算 | 模型、上下文和必要编译已热;通过--cache-none或受控失效保证推理真实执行 | model_warm_recompute |
| 完全相同请求命中缓存 | 保持 prompt 与输入不变,允许节点输出复用 | cache_hit_rerun |
| A→B→A 切换 | 先热 A,再运行 B,再强制 A 重算 | model_switch_A_B_A |
cache_hit_rerun是缓存收益测试,不是模型推理速度测试。热模型测试也不能只修改种子后假定全图重算:上游条件编码可能仍然命中。必须用execution_cached、executing序列和服务器端埋点核对实际子图。
“新进程"也不等于物理冷机。操作系统页缓存、模型文件缓存和容器镜像层可能仍然热。如果没有在专用环境中控制这些状态,应写成"进程冷启动,OS 页缓存未控制”,不能写成"完全冷启动"。清理系统页缓存会影响整机,不应在共享生产机上作为默认操作。
六、可直接执行的实验矩阵
先固定一个condition_id,再执行以下矩阵。所有单元使用相同输入资产、输出参数和交付边界;有意改变的变量只能写在该单元的差异列。
| 单元 | 启动与缓存状态 | 执行范围 | 目的 |
|---|---|---|---|
| M01 | process_cold,固定缓存模式 | 完整工作流到最终交付 | 测进程冷启动端到端 |
| M02 | 与 M01 同进程,相同请求,允许缓存 | 完整工作流到最终交付 | 测缓存复用收益,不参与推理速度排名 |
| M03 | model_warm_recompute | 完整工作流到最终交付 | 测热模型真实重算基线 |
| M04 | 热模型,--cache-none | 完整工作流到最终交付 | 建立不依赖节点输出缓存的执行器基线 |
| M05 | 热模型、强制重算 | Partial Execution 到原生生成输出 | 隔离原生生成链 |
| M06 | 热模型、强制重算 | 原生生成加全部后处理,编码前停止 | 计算后处理增量 |
| M07 | 热模型、强制重算 | 到 FFmpeg 临时文件成功关闭 | 计算编码与封装增量 |
| M08 | 热模型、强制重算 | 到最终路径提交并校验 | 计算交付增量 |
| M09 | model_switch_A_B_A,每次强制重算 | 完整工作流到最终交付 | 测换模、卸载和重新驻留成本 |
| M10 | 固定并发和前序负载 | 完整工作流到最终交付 | 单独研究队列,不与单请求算力比较混合 |
执行规则:
- M01 使用五次独立进程启动;每次记录 OS 页缓存是否受控。
- M03—M08 先做三次不计入结果的稳定运行,再做十次测量运行。
- M09 做五个完整 A→B→A 周期,按三个位置分别报告。
- 比较两个实现时,使用同一输入、种子序列和
condition_id配对;交错运行两种实现,降低温度、后台负载和时间漂移造成的偏差。 - 报告每阶段的 P50、P90、最小值、最大值和离散度,同时保留每个原始 run。不能只报"最好一次"。
- 基线运行与 Nsight 运行分开;若必须比较 Profiler 前后差异,单独记录 trace 选项和采样开销。
这些数字是实验次数与报告规则,不是性能结果。任何真正的秒数、帧率或显存数字都必须引用一个完整的condition_id。
七、阶段账本必须绑定完整条件
每个性能结果至少绑定以下字段:
run_id, condition_id, prompt_id workflow_hash, api_prompt_hash, selected_outputs expected_nodes, cached_nodes, actual_executed_nodes comfy_commit, frontend_version, custom_node_lock_hash model_hashes, vae_hash, text_encoder_hash, lora_hashes gpu_uuid, gpu_model, vram, power_limit, clocks cpu, ram, driver, cuda, pytorch precision, quantization, attention_backend, compile_mode cache_mode, startup_class, model_residency_before width, height, frames, fps, batch, seed steps, sampler, scheduler, cfg vae_tiling, preview_mode postprocess_chain_and_parameters ffmpeg_version_and_build, codec, preset, quality pixel_format, filters, container, threads, hwaccel temp_storage, final_storage, filesystem, delivery_semantics queue_policy, concurrency, background_load clock_domain, profiler_mode, trace_path artifact_bytes, artifact_hash, status, error_stage事件行还应包含ts_mono_ns、ts_wall、source、event_type、node_id、node_class、stage_id、gpu_device、gpu_stream和产物路径。持续时间只用同一 clock domain 相减;墙上时钟只用于跨系统关联。
空白结果表可以这样保存:
| run_id | condition_id | stage_id | start_mono_ns | end_mono_ns | cpu_wall_ms | gpu_ms | cache_state | artifact_hash | status |
|---|---|---|---|---|---|---|---|---|---|
<待测> | <条件哈希> | <阶段> |
配置必须固化到可哈希清单。硬件、模型、分辨率、帧数、步数之外,缓存模式、所选输出、FFmpeg 参数、存储位置和最终交付语义同样属于实验条件。少掉任何一个,比较都可能失真。
八、用阶段占比决定优化顺序
拿到阶段账本后,先计算:
阶段占比 s_i = T_i / T_total如果阶段i被加速k倍,其他阶段不变,端到端加速上限为:
S_total ≤ 1 / ((1 − s_i) + s_i / k)如果一个阶段被完全消除,理论上限是:
S_total ≤ 1 / (1 − s_i)这一步把"某个 kernel 快了多少"转换成"用户最终少等多少"。如果采样只占本工作流端到端时间的一部分,再激进的采样优化也无法消除排队、加载、VAE、后处理、编码和交付。反过来,如果换模型主要减少了加载时间,它对热模型连续队列可能没有价值,却可能显著改善频繁切换的多模型服务。
优化报告应同时回答四个问题:
- 哪个阶段是当前条件下的主导阶段?
- 改动减少了哪个阶段,是否把成本转移到另一个阶段?
- 实际执行子图、缓存命中和交付边界是否一致?
- 速度变化是否伴随质量、失败率、显存、功耗或产物语义变化?
九、最小可落地的采集实现
不需要先修改所有节点。可以从四层探针开始。
第一层放在 ComfyUI 服务端入口。请求通过/prompt验证并进入队列时,生成或接收run_id,记录queue_accepted_mono_ns、队列位置、所选输出和 prompt 哈希。执行器开始处理时记录execution_start_mono_ns,成功、错误和中断都写入同一运行记录。这样 Queue 等待不依赖浏览器时间,也不会把网络往返混入服务器队列。
第二层放在阶段包装器。不要按每个节点强制同步,而是给模型加载、条件编码、采样、VAE、后处理等稳定语义阶段建立开始与结束钩子。包装器同时写入节点 ID、节点类型、缓存状态、CPU 单调时间和可选 CUDA Event。自定义节点若把加载、推理、编码塞在一个函数里,必须在函数内部继续分段;"一个节点一个阶段"只是实现巧合,不是账本原则。
第三层放在外部进程和文件系统。FFmpeg 启动时记录命令模板哈希,而不是只保存一条可能含敏感路径的完整命令;解析-progress,记录退出码。输出先写临时文件,成功关闭后再执行规定的交付动作。只有最终路径可读、大小非零、容器探测通过,并在协议要求时完成校验和或上传确认,才写入delivery_committed_mono_ns。
第四层是离线归并器。它按run_id合并 ComfyUI 生命周期、阶段事件、CUDA/Nsight trace、FFmpeg 事件和产物记录,检查时钟域、区间重叠、缺失结束事件、缓存清单与实际执行节点之间的矛盾。原始事件不可被聚合表覆盖;重新定义阶段后,应能从原始事件重算历史结果。
采集器还应设置失败闭环。节点异常、OOM、取消、FFmpeg 非零退出、文件损坏和上传失败都要保留已经消耗的阶段时间。只统计成功任务会系统性低估不稳定方案的真实成本。失败运行可以不进入"成功任务延迟"分布,但必须进入失败率、浪费 GPU 时间和端到端可靠性报告。
十、四类最常见的错误结论
第一类是把缓存命中当成模型加速。纠正方法是同时展示cached_nodes与actual_executed_nodes,并用model_warm_recompute作为推理比较基线。
第二类是把executing到下一条消息的间隔当作节点 GPU 时间。下一条消息可能被异步工作、网络、UI 更新或别的节点调度影响。GPU 阶段使用 Event 或时间线,节点消息只负责关联。
第三类是只测到execution_success。如果编码、复制或上传在 ComfyUI 之外,成功消息只是中间边界。端到端终点必须是双方约定的交付提交事件。
第四类是把 Profiler 运行直接与无 Profiler 运行混在同一分布。CUDA trace、Event trace、内存追踪和频繁同步都可能带来开销甚至改变调度。剖析结果用于归因,低侵入基线用于排名,两者通过相同condition_id关联,但不能混算。
结语
"从 Queue 到视频文件"不是一个计时点,而是一条可审计的执行链。ComfyUI 消息负责说明调度与缓存状态,服务器单调时钟负责端到端阶段边界,CUDA Event 和 Nsight 负责解释异步 GPU 时间线,FFmpeg 与交付提交负责补齐生成系统之外的最后一段。
只有把静态 workflow 还原为实际执行子图,把冷启动、热重算、缓存命中和模型切换分开,把原生生成、后处理、编码和最终交付分账,性能数字才具有可复现的条件,也才可能指向正确的优化对象。否则,"第二次快一倍"只说明第二次不同,不能说明究竟优化了什么。
FAQ
为什么不能把 executed 当作节点完成事件?
ComfyUI 官方说明它只在节点返回 UI 更新时发送;完整节点边界应结合 executing、缓存列表和执行器侧观测。
Python 计时是否完全没用?
有用,但 GPU 阶段需要同步或使用 CUDA Events,否则容易只测到异步提交时间。
性能报告最少要写哪些条件?
硬件、软件版本、工作流哈希、模型、分辨率、帧数、步数、缓存与冷暖启动状态。
错误速查卡
| 症状 | 根因 | 定位 | 修复 |
|---|---|---|---|
| 第二次明显更快被简单归因为"采样器/注意力变快" | 缺乏阶段归属,模型常驻、编译预热、节点缓存、执行子图变化都被混在一起 | 用execution_cached/executing序列 + 阶段账本核对实际子图 | 用model_warm_recompute作为推理速度基线;M01/M03 配对报告 |
| 阶段直接相加得到总耗时 | 实际存在 GPU / CPU / I/O 重叠 | 检查 Nsight 时间线或分段交叉事件 | 使用区间并集或关键路径,资源账本分别报告 CPU / GPU / I/O |
| 把静态 workflow JSON 当作实际执行子图 | 缓存命中 + Partial Execution + 动态展开会改变祖先闭包 | 比对selected_outputs/cached_nodes/actual_executed_nodes三份清单 | 每次运行保存三份清单 + 预期未执行节点 / 意外执行节点 |
CPUperf_counter测得的时间远低于实际 GPU 耗时 | CUDA 默认异步,没有同步边界 | 在阶段前后各加一次torch.cuda.synchronize()验证 | 用 CUDA Event 或 Nsight;CPU wall clock 仅作低侵入基线 |
t1 - t0测得时间比实际长很多 | CUDA Event 同步、Profiler 工具或频繁synchronize改变了被测系统 | 跑两套:低侵入基线 + Profiling run | 两套分别报告;不要在同一分布中混算 |
用相邻两个executed消息的到达间隔当节点 GPU 时间 | executed只在节点返回 UI 输出时发送;可能漏掉无 UI 节点、混入缓存 UI | 检查execution_cached列表 + 显式executed节点 | 节点消息只用于关联;GPU 时间以 Event / Nsight 为准 |
把客户端executing时间当作 GPU 完成边界 | WebSocket 消息含网络、事件循环与序列化延迟 | 同时记录服务器端monotonic戳 | 服务器端用t_execution_start_server − t_queue_accepted_server |
execution_success当作"视频已可读" | 编码、复制、上传都在 ComfyUI 之外 | 检查delivery_committed_mono_ns与artifact_hash | 端到端终点必须以交付提交事件为准 |
| 把 Profiler 运行与无 Profiler 运行混在同一分布 | Trace/Event/内存追踪都可能改变调度 | 用同一condition_id关联 | Profiler 结果用于归因,基线用于排名 |
cache_hit_rerun被当作模型加速 | 大量节点被缓存命中,推理几乎没跑 | 比对cached_nodes与actual_executed_nodes | 用model_warm_recompute+--cache-none测推理速度 |
| “新进程"被写成"完全冷启动” | OS 页缓存、模型文件缓存、容器镜像层可能仍然热 | 检查 OS 页缓存是否受控 | 显式标注"进程冷启动,OS 页缓存未控制" |
| 失败任务没有阶段数据 | 只统计成功任务 | 检查采集器是否在异常/OOM/取消路径上写记录 | 失败运行进入失败率 + 浪费 GPU 时间报告 |
| 阶段间缺乏唯一 ID 关联 | 多 run / 多 stage / 多 stream 事件无法对齐 | 检查run_id/condition_id/stage_id是否贯穿所有事件 | 所有事件必须带三个 ID +ts_mono_ns+source |
| 优化报告"采样快 2 倍",但端到端几乎没变 | 采样只占端到端时间的一小部分 | 用S_total ≤ 1/((1−s_i)+s_i/k)估算上限 | 先看阶段占比再决定优化对象 |
| 把 PyTorch memory snapshot 当作全部显存 | snapshot 只能看到 PyTorch allocator 管理的内存 | 同时记录设备级观测与 Nsight memory trace | 三者分别报告,不互相冒充 |
| 节点缓存"默认开"被假设成"永远命中" | 缓存依赖输入签名 + 节点定义指纹 | 修改提示词后看execution_cached列表 | 显式记录cache_mode与cached_nodes哈希 |
报告里没有--cache-none也没有--force-fp16等运行参数 | 实验条件字段缺失 | 检查 condition_id 必填字段清单 | 固化所有可哈希条件到 condition_id |