Cosmos Curator 视频还没处理完,GPU 为什么先闲了?

0 阅读15分钟

Cosmos Curator 视频还没处理完,GPU 为什么先闲了?

视频没跑完,GPU先闲了

假设一批巡检录像已经下载完,切分和描述还在继续。监控上 GPU 利用率开始下降,输出目录却迟迟没有收齐。团队准备增加几个描述模型 Worker,希望把剩余任务尽快跑完。

加 Worker 可能有效,也可能把同一批等待换成更多进程等待。模型阶段拿不到输入、阶段内部取数据太慢、下游积压触发背压,以及任务尾段的缩容,都可能让 GPU 看起来不忙。仅凭利用率,无法判断应该把资源加在哪里。

Cosmos-Xenna 的固定源码里有一个具体的提醒:自动扩缩容的结果在应用前,可以根据尚未完成的工作限制删除 Worker。代码注释解释了它要处理的情形——上游结束后,测得的吞吐下降,但队列里还有工作,过快缩容会拖慢尾段。更容易漏掉的是,这项保护在所核对版本的配置类中默认关闭。源码里存在保护,和当前运行确实启用了保护,是两件事。

本文沿这个差异讨论一次瓶颈诊断应该怎样开始。核对范围是 Cosmos Curator v2.2.0 的固定提交 de89c5e4,以及其锁文件记录的 Cosmos-Xenna 0.4.3,对应官方标签提交 ec3468ec;资料访问日期为 2026-09-22。这不是最新版本声明,也没有在真实集群上复现故障或测量加速比。开头的运行现象是构造案例,下面将代码事实、解释和验证建议分别说明。1

利用率下降前,先看工作到了哪里

Cosmos Curator 的架构说明把一个阶段对应到一组有状态的 Ray Actor,可以把 Actor 理解成持续处理任务的 Worker。编排进程把完成的任务交给下一阶段,并根据阶段吞吐及资源需求调整 Worker 分配。阶段之间可以重叠执行,因此整批任务的结束时间并不由某一张 GPU 的忙碌程度单独决定。2

“描述”也未必只是一个 GPU 调用。固定实现中的本地 vLLM 路径包含 VllmPrepStage 和 VllmCaptionStage:前者申请配置指定的 CPU,后者申请配置指定的 GPU。准备输入和运行模型由不同阶段承担,意味着两边可以分别观察、分别调整。这里说的是该本地路径,不是所有 Caption 后端的统一结构;采用外部 API 时,瓶颈与资源归属会不同。3

假如模型 Worker 已经就绪,但它的输入队列一直没有可用任务,先加模型 Worker 很难补上供料。此时要向前看准备阶段:任务是否还未到达,正在解码或准备,还是停在另一个等待点。反过来,如果输入持续积压、模型 Worker 持续忙碌,而输出能够被下游及时接走,模型阶段的处理能力才更像当前需要验证的限制。

这些组合仍只是排查方向。输入队列短,可能是上游慢,也可能是批次已经接近结束;队列长,可能表示处理不足,也可能来自任务大小变化或临时突发。必须把同一时间窗口的输入、处理中工作、输出和 Worker 状态放在一起,才能避免从一张孤立图作结论。

因此,第一次记录应尽量保留阶段名称、时间范围、输入输出队列的变化,以及 Actor 的就绪、忙碌、空闲或待创建状态。官方观测指南提供了对应的阶段队列和 Actor 状态指标。数量目标已经提高而 Worker 仍待创建,与 Worker 已经就绪却拿不到工作,是两种不同问题,不能都写成“模型速度慢”。4

如果根本没有启动成功的 Worker,继续比较每任务耗时也缺少基础。先核对资源申请是否能满足、模型初始化是否完成、是否反复重启。这里不展开部署排错,但诊断必须把尚未开始处理与处理过程中太慢区分开,否则后面的调参没有明确对象。

传递的是引用,下一阶段仍然要取数据

idle增长,不能直接归因网络

架构文档说明,编排器在阶段之间传递 Ray Object Reference,而不是在中央进程中搬运完整视频对象。这降低了中央编排直接处理大载荷的负担,但并没有消除下一阶段读取数据的工作。Worker 仍需取得对象,并完成必要的反序列化。2

文档把 Worker 内部描述为三个环节:从调度队列接收任务引用,调用 ray.get 取回并反序列化,再把准备好的任务交给 process_data。设计目标是让这些环节重叠。当数据准备比实际处理更快时,部分搬运开销可以被处理过程遮住;如果取数变慢,或者对象变大,这种重叠就可能不够用了。

所以“模型处理时间没变,GPU 却更闲”并不矛盾。模型真正拿到输入后的速度可以保持原样,批次之间却多出等待。此时若只优化模型内核,未必能缩短这段等待。应该继续看反序列化时间、跨阶段对象大小、数据位置以及相关时间线,判断等待发生在模型之前还是模型内部。

同节点优先也不意味着所有任务都只在本机流动。文档描述了亲和性调度的意图,但实际对象位置与 Worker 分布仍需从运行中确认。它给出的硬件带宽量级不能直接套到自己的网络、内存或存储上,更不能据此算出这批录像应有多少倍加速。

Curator 的 StageTimer 则提供了另一个需要读懂口径的数字。初始化后的调用中,idle 时间由本轮开始时刻减去上一次记录的活动时刻得到:5

self._idle_time_s = self._start - self._last_active_time

这段源码能证明计时边界,不能证明这段间隔全部由网络造成。间隔可能包含等待任务、调度和其他没有在该段处理中执行的时间。第一次初始化还走单独的 startup 计时分支,不能把首次加载与后续稳定处理混成一个平均值。

实际使用时,要返回当前阶段的计时调用位置,确认这条记录对应的是一次任务处理还是更细的样本循环。process_time 在该工具中同样是墙钟时间差,不是 GPU 核函数专属耗时。若想知道算子、同步或显存分配占多少,应使用更细的 GPU profile;若想知道哪个 Actor 在等谁,则需要跨进程时间线。先选对问题,再选观测工具。

上游慢下来,也可能是在等下游腾位置

上游停下来,可能在等下游

另一个容易误判的方向是背压。可以把它理解为:下游暂时消化不了时,系统限制上游继续往里塞工作,避免中间结果无止境积累。

所核对的 Xenna streaming 实现,在继续向一个阶段投递任务前,会同时考虑正在处理或排队的任务,以及该阶段已经完成、仍留在输出队列的工作。达到计算出来的排队上限后,投递循环就停止继续拉取输入。6

这段控制说明,上游不忙未必表示上游应该扩容。假设写出阶段消化变慢,描述阶段的输出开始堆积,继续给描述阶段增加输入,只会把更多结果压在等待区。此时前面的某些资源闲下来,可能正是流水线限制积压的表现。

但看到积压也不能立刻认定对象存储慢。写出路径本身可能包含序列化、文件准备或其他工作;下游还可能受 CPU、网络、错误重试等因素影响。可操作的下一步是先找到积压增长的边界,再检查对应阶段的耗时和等待来源,而不是根据“Writer”这个名字直接归因。

扩大队列上限有时能吸收短暂抖动,让阶段之间不必频繁断粮。不过,它改变的是允许积累多少工作,并没有自动提高下游持续处理能力。若瓶颈持续存在,积压更大还会增加内存和恢复负担。比较前后效果时,除了输出速度,也要看内存占用、尾段耗时和失败情况。

这也解释了为什么“让每张卡一直忙”不是完整目标。为了消耗更多 GPU 时间而制造更多中间结果,可能让最后一个文件更晚落下。更有用的目标是,在固定输入、固定输出要求和可接受资源占用下,整批任务能够稳定完成;GPU 利用率用于解释过程,不能单独替代这个结果。

自动缩容也需要知道还有多少工作没做

队列未清空,缩容要看配置

流水线刚启动、稳定供料和最后收尾,对同一个吞吐数字有不同解释。稳定阶段可以观察持续输入如何被处理;到了尾段,上游已经没有新任务,输入速率下降本来就是正常现象。若只看这一刻的新输入速度,容易低估队列里尚未完成的工作。

Xenna 的 apply_autoscale_result_if_ready 中存在一个针对待处理积压的缩容保护。它在应用扩缩容建议前读取已有 Actor、正在处理的槽位和排队工作,对仍活跃的阶段限制删除 Worker 的数量。它减少的是过激的删除建议,不是主动创建无限的新 Worker,也不是保证所有尾段都最快完成。6

源码还明确区分了两种队列单位:阶段前面的队列按单个样本计数,Actor pool 内部排队的对象已经按 batch 组织。合并积压时,需要先统一单位:

backlog_samples = upstream_queue_lens[idx] + pre_pool_queued * stage_batch_sizes[idx]

这不是可以直接拿去估算视频小时的公式。这里的 sample、Task 和 batch 是执行框架中的处理单位,未必分别对应一段原始录像、一帧画面或一个描述窗口。它说明的是一个更具体的问题:即便都显示“队列长度”,两个数也不一定能直接相加。

保护也不会阻止已经结束的阶段释放资源。该分支会检查阶段完成标志;完成阶段可以绕过这项限制。已经没有上游工作、也没有在途与排队任务,和仍有待处理积压,是不同的收尾状态。

最关键的配置边界在 StreamingSpecificSpec:7

enable_backlog_aware_scaledown: bool = False

因此,不能从“升级到了这个版本”直接推出“当前流水线有这项保护”。这行只给出配置类默认值,调用方仍可能覆盖。诊断时应该查看实际构造出来的 Pipeline 配置,并对应到这次执行使用的 Xenna 版本。锁文件能说明固定材料里的依赖记录,也不能替代运行环境核验。

如果怀疑尾段缩容影响了完成时间,可以把输入停止、Actor 数量变化、仍待处理的工作和最终输出时间排在同一条时间线上。若资源先下降而积压仍持续存在,这才形成了一个值得对照验证的假设。即便如此,也不能只改一个开关就宣布故障解决:要检查剩余资源是否合适,是否带来内存压力,以及同一批任务是否真的更快、完整地结束。

两个阶段都叫“一条任务”,工作量可能完全不同

官方架构文档用长短视频解释动态分块:一段较长视频切出很多 Clip 后,可以拆成多个较小任务交给后续阶段。这是控制单任务工作量和内存压力的机制,同时也改变了任务数量的含义。2

前一个阶段处理完一条原视频任务,后一个阶段可能收到多条分块任务。此时不能看到后一个阶段每秒完成的任务数更多,就说它吞吐更高;也不能直接用两段的任务耗时判断谁最慢。除了任务数,还要知道一条任务里包含多少片段、多少视频时长,以及是否产生不同数量的模型窗口。

固定版本的监控源码把 pipeline_actor_process_time 定义为一个 Actor 处理一条任务所需的时间,并通过处理速度的倒数设置该指标。它不是整个阶段所有 Worker 的总服务能力,也不是每秒完成多少小时视频。只看指标名中的 process time,而忽略单位和汇总方法,很容易比较错对象。8

对前面的构造案例,可以固定一批源录像和切分配置,再记录阶段完成量与任务大小。若这轮仅增加了描述 Worker,就尽量保持模型、抽帧、窗口和输出要求不变。否则,“变快”可能来自少处理了画面、减少了描述内容,或者改变了分块,而不是资源分配更合理。

输入分布也需要相似。平均任务很快,少数长视频却拖住尾段时,平均值会掩盖最后仍未完成的对象。除了整体趋势,可以单独返回耗时较长的任务,核对其视频时长、编码、分辨率和窗口数量。这里不提供通用阈值:不同输入和处理路径,不能共用一个凭空设定的“正常耗时”。

过滤策略还会改变进入后续阶段的数量。本篇不重新讨论哪些片段有价值,但性能比较必须说明两轮是否保留相同内容。用更激进的过滤换来更少输出,是处理范围改变;是否接受这种改变需要业务判断,不能被算成同等工作量下的纯性能优化。

让一次调整对应一条可以被否定的判断

让一次调整有可核对的依据

完成前面的观察后,诊断记录应该能说清一个具体假设,例如:“描述 Worker 大部分时间没有输入,而准备阶段持续积压,所以先验证供料能力。”这比“GPU 利用率低,可能哪里有瓶颈”更容易决定下一步,也更容易被新的证据推翻。

一份记录可以同时保留如下内容。它是作者建议的工作产物,不是官方已经生成的测试报告:

记录项本轮需要说清什么
输入与处理范围哪批源录像,哪些切分、窗口、过滤和模型设置固定?
观察窗口启动、稳定供料还是收尾,队列和Actor状态如何变化?
当前判断认为哪一段限制进度,哪些观测支持它,哪些反例会推翻它?
单一调整只改变哪个Worker分配、配置或处理步骤?
完成证据最终输出是否齐全,总耗时、尾段、内存与错误是否改善?

如果输入少、流程简单,先用固定 Worker 数和基本计时,可能比立刻研究自动扩缩容更容易定位问题。增加观测也有代价:CPU、内存、GPU profile 和分布式 trace 回答不同问题,采集及保存产物都会增加工作。官方指南提供这些能力,并不意味着每一轮都应全部开启。4

先用队列、Worker 状态和轻量计时缩小范围,再对可疑阶段做细查。比如已经确认取数等待突出,再检查数据位置和反序列化;已经看到模型持续有输入且处理很慢,再看 GPU 算子与同步。保留原始记录,让下一轮能够在相同条件下比较,而不是只截图一张更漂亮的利用率曲线。

最后应回答的仍是开头那个问题:这批录像为什么没有处理完,哪一段证据支持当前调整?如果观察只能证明“GPU 闲”,就继续定位等待发生在哪里。只有输入范围、队列变化、执行配置和实际输出能够相互对应,扩容或调参才有可核对的依据。

Footnotes

  1. NVIDIA,Cosmos Curator固定v2.2.0提交de89c5e4f76219c3f2985dc8fda790a74ab8bab0,pixi.lock记录cosmos-xenna 0.4.3;Cosmos-Xenna官方v0.4.3标签解析到ec3468ec71a3882814cc4b89e91812ba7f922c95。这组材料不表示运行环境已验证。github.com/nvidia-cosm… ↩

  2. NVIDIA,Architecture Guide,固定Curator提交,Stage Actor、Object Reference、内部取数流水线与动态分块。github.com/nvidia-cosm… ↩ ↩2 ↩3

  3. NVIDIA,vllm_caption_stage.py,固定Curator提交,VllmPrepStage.resources与VllmCaptionStage.resources。github.com/nvidia-cosm… ↩

  4. NVIDIA,Observability Guide,固定Curator提交,阶段队列、Actor状态、分层profiling与tracing。github.com/nvidia-cosm… ↩ ↩2

  5. NVIDIA,performance_utils.py,固定Curator提交,StageTimer.reinit与log_stats计时口径。github.com/nvidia-cosm… ↩

  6. NVIDIA,streaming.py,固定Xenna提交,背压投递、apply_autoscale_result_if_ready与积压单位归一。github.com/nvidia-cosm… ↩ ↩2

  7. NVIDIA,specs.py,固定Xenna提交,StreamingSpecificSpec.enable_backlog_aware_scaledown默认值。github.com/nvidia-cosm… ↩

  8. NVIDIA,monitoring.py,固定Xenna提交,pipeline_actor_process_time定义与处理速度倒数赋值。github.com/nvidia-cosm… ↩