先说判断方向:Matrix-Game3.5 这类流式交互场景里,显存占用和单帧延迟是两条独立的曲线,混在一起看容易得出错误结论。显存不够时的典型现象是加载失败、首帧 OOM 或跑一段时间后中断;显存够但交互卡,通常是单帧耗时超出了帧间预算,而不是显存问题。建议先把权重常驻与单帧临时占用分开记录,再在显存曲线上标出峰值出现的阶段,最后测三档采样步长下的单帧耗时,用这两组结果决定先降分辨率还是先降步长。下面给出的采样命令、打点骨架和记录字段都可以直接改成本机配置执行,结论需要结合自己的卡型和分辨率确认。
如果进程显存峰值出现在首帧或连续输出阶段并接近卡容量,先降分辨率或减少并行帧数,再看延迟;如果显存余量稳定,但单帧耗时随采样步长明显上升,先降步长。两类问题的观测点不同:显存看进程级峰值和它出现的阶段,延迟看同一分辨率下每档步长复跑后的单帧耗时。判断前要确认采样间隔够密、每档都跑过预热帧,否则首帧峰值和冷启动开销会污染结果。
分清权重常驻显存与单帧推理的临时占用
权重常驻显存指模型加载完成后、没有任何请求在跑时就已占住的部分,包括模型权重和框架预分配的状态缓存。它的观测时刻是加载完成、静置几秒、确认没有推理任务时,用这个静置读数代表常驻基线。单帧推理的临时占用指一次前向中申请、推理结束后释放或回到缓存池的部分,包括激活、中间张量、编解码缓冲和帧间状态,它的观测时刻是单帧推理期间,首帧要单独看一次。
读数的选择上,nvidia-smi 的全局 used 包含其他进程和 CUDA context 开销,不能直接当成 Matrix-Game3.5 的占用。建议同时取两个读数:进程级占用用 nvidia-smi `--query-compute-apps` 拿,框架侧用显存统计接口拿(下面给的是 PyTorch 常见写法,如果你的后端不是这套,替换成对应的显存查询函数即可)。
# 进程级显存采样,单帧很短时把间隔调小
nvidia-smi `--query-compute-apps`=pid,process_name,used_memory \
`--format`=csv,noheader,nounits -l 1 | tee mem_trace.csv
# 通用打点骨架,按实际框架替换显存读数接口
def mark(tag):
info = {
"tag": tag,
"alloc_mb": torch.cuda.memory_allocated() / 1024**2,
"peak_mb": torch.cuda.max_memory_allocated() / 1024**2,
"reserved_mb": torch.cuda.memory_reserved() / 1024**2,
}
log_line(info)
mark("load_done")
mark("first_frame_start")
run_one_frame()
mark("first_frame_end")
每次记录建议固定这些字段:timestamp、stage(load / idle / first_frame / steady)、step、resolution、weights_resident_mb、peak_extra_mb、total_used_mb、note。其中 weights_resident_mb 取静置读数,peak_extra_mb 用单帧峰值减去静置基线得到。把常驻和临时拆成两列之后,才能判断显存问题到底出在模型本身太重,还是出在帧间状态没释放。
在显存曲线上标出峰值出现的阶段
采样方式建议主用进程内打点,外部轮询做交叉验证。原因是外部 nvidia-smi 轮询的间隔往往大于单帧耗时,容易漏掉首帧的尖峰;进程内打点能在每帧开始和结束时各记一次,抓的是真实峰值。两种方式都跑一遍,如果曲线形状差异很大,说明外部采样间隔需要调小。
阶段划分点至少标三个:加载开始到加载完成、首帧开始到首帧结束、连续输出稳定后(比如第 3 帧之后)。峰值标注方法是在曲线上标出每段的 max 值及其横坐标,并注明是进程级还是框架侧读数。建议在记录里加一列 peak_stage,把峰值归到具体阶段,而不是只留一个总峰值。
曲线读法可以这样用:如果峰值只在加载阶段出现、之后回落并保持平稳,说明这是加载态开销,与流式交互的稳态表现关系不大;如果峰值集中在首帧、第二帧之后明显下降,多半是首帧的缓存初始化和预分配,优化方向是预热帧而不是缩模型;如果连续输出阶段峰值持续抬升,说明帧间状态或缓存没有及时释放,这才是流式场景需要优先处理的显存问题。这三种形状对应的动作完全不同,只看一个总分不清。
测三档采样步长下的单帧耗时
三档步长以当前默认值为中心取,比如默认 8 就取 4 / 8 / 16,前提是模型和采样器支持这个区间。设置方式通常在推理配置或采样参数里,下面是一个可替换的配置骨架,三档复跑时只改 sample_steps 一行,其余保持不动,避免分辨率或批大小混入变量。
inference:
sample_steps: 8 # 复跑时依次改为 4 / 8 / 16
resolution: 720p
frames_per_batch: 1
warmup_frames: 2 # 预热帧不计入耗时统计
每档建议先丢 1-2 帧预热,再连续跑不少于 10 帧,整档重复 3 次,记录中位数和最大值,不要只跑一次就下结论。记录格式可以固定成一张表:step、run_id、resolution、first_frame_ms、p50_frame_ms、max_frame_ms、peak_mem_mb、note。首帧单独成列,方便和稳态帧区分开。
判断是否线性变化,看相邻两档的单位步长耗时:把 8 到 16 的耗时差除以 8,把 4 到 8 的耗时差除以 4,比较这两个商。如果两者接近,说明单帧耗时对步长近似线性,降低步长能较可预期地降低延迟;如果高步长区间的单位步长耗时明显大于低步长区间,说明已经开始非线性上升,这时降步长的收益会比按比例估算的更明显;如果三档耗时几乎持平,说明瓶颈不在采样步长上,继续调步长不会有改善。
按曲线结果决定先优化显存还是先优化延迟
判定规则可以简化成两条。第一条:如果进程显存峰值接近卡容量、留不出安全余量,或已经出现 OOM、分配到共享内存的情况,属于显存触顶,优先降显存。具体动作是先把输出分辨率下调一档,再把 frames_per_batch 或并行帧数降到 1,必要时缩短帧间状态缓存长度,然后重跑显存曲线确认峰值阶段是否改变。第二条:如果显存余量稳定,但单帧耗时随步长陡增,优先降延迟。具体动作是先把 sample_steps 降到上一档,再检查首帧之外是否有可跳过的重复计算,改完后用同一张三档耗时表复测,确认单位步长耗时是否回到接近线性的区间。
如果两条都不成立——显存不紧张、耗时对步长也不敏感,问题通常在首帧预热、编解码或传输链路上,不建议再往显存方向加投入。这种时候更值得做的是把 warmup_frames 加上、把首帧耗时单独统计出来,看交互卡顿是否只发生在会话开头。所有调整都建议一次只改一个变量,改完回到显存曲线和三档耗时表复测,避免多个参数一起动之后分不清是哪一项起了作用。