【Bug已解决】SmolVLM: Error due to incorrect padding calculation in video processor 解决方案
一、现象长什么样
用 SmolVLM(HuggingFace 的小视觉语言模型)处理视频输入时,video processor 在把多段不同长度/分辨率的视频 batch 化时报错:
RuntimeError: stack expects each tensor to be equal size, but got [3, 8, 224, 224] and [3, 12, 224, 224]或:
ValueError: padding size should be non-negative, got -N又或者:不报错但模型输出乱(因为 padding 把帧塞到了错误的轴,时间维和空间维混淆)。
最迷惑的是:处理单张图像没事,一上视频(多帧序列)就炸。本质:video processor 要把一个 batch 里不同帧数的视频对齐(pad 到最大帧数),但 padding 计算用了错误的维度/符号,导致要么 pad 出负尺寸(-N),要么把帧数维和空间维搞混,stack 时形状不一致。
SmolVLM 的视频输入是[batch, frames, channels, H, W](或[B, F, C, H, W])五维。padding 必须作用在 **frames(时间)维**,把短视频补到最长视频的帧数。如果代码误把"帧数差"用到了空间维、或在计算max_frames - this_frames` 时符号反了(得到负数),就触发上述错误。
二、背景
video processor 的核心任务是:给定一个 batch 的视频,每个视频解码出不同数量的帧(如视频 A 8 帧、视频 B 12 帧),需要把它们对齐成统一形状才能torch.stack成五维 batch。
正确做法:
- 找出 batch 里最大帧数
F_max; - 对每个视频,计算
pad = F_max - F_i(应为非负); - 在该视频的frames 维(第 1 维)末尾补
pad个"全零帧"或复制帧; stack成[B, F_max, C, H, W]。
SmolVLM的这个 padding 计算 bug 常见在:
- 维度错:把
pad应用到H/W(空间维)或C,而非 frames 维; - 符号错:
pad = F_i - F_max(帧数少的视频得到负数),F.pad收到负值 →ValueError: padding size should be non-negative; - 先 stack 后 pad:想先把不同帧数的 tensor stack 再 pad,但 stack 本身就要求等尺寸,于是
RuntimeError: stack expects equal size; - padding_side 处理错:left padding 时帧插在前面,index 计算错误。
下面用可运行代码复现"padding 用错维度/符号导致 stack 失败"。
三、根因
根因一句话:SmolVLM 的 video processor 在把不同帧数的视频对齐时,padding 计算用了错误的维度(把帧数差用到空间维)或错误符号(得到负 pad),导致 stack 形状不一致或padding size negative错误。
三个具体失配:
- padding 维错:pad 应用到空间维而非 frames(时间)维。
- 符号错:
pad = F_i - F_max为负,触发ValueError: negative padding。 - 先 stack 后 pad:不等尺寸就 stack,直接
RuntimeError。
四、最小可运行复现
用纯 Python 模拟"padding 维度/符号算错导致对齐失败":
from dataclasses import dataclass from typing import List def bad_pad(videos: List[int]): """模拟错误:用帧数差去 pad 空间维 + 符号反了。""" f_max = max(videos) results = [] for f in videos: pad = f - f_max # 错误符号:帧数少 -> 负数 if pad < 0: raise ValueError(f"padding size should be non-negative, got {pad}") results.append(f + pad) return results def good_pad(videos: List[int]): f_max = max(videos) # 正确:pad = f_max - f(非负),作用在 frames 维 return [f + (f_max - f) for f in videos] def main(): videos = [8, 12, 10] # 不同帧数 try: bad_pad(videos) except ValueError as e: print("复现到报错:", e) print("正确对齐后各视频帧数:", good_pad(videos)) # [12,12,12] if __name__ == "__main__": main()运行会打印复现到报错: padding size should be non-negative, got -4——正是 video processor padding 符号错的本质。
五、解决方案(第一层:最小直接修复)
最立竿见影的修复:padding 必须在 frames(时间)维、用pad = F_max - F_i(恒非负),且先 pad 到统一帧数再 stack,绝不先 stack。
import torch def collate_videos(video_tensors): """修复:按 frames 维 pad 到最大帧数,再 stack。""" # video_tensors: list of [F_i, C, H, W] f_max = max(v.shape[0] for v in video_tensors) padded = [] for v in video_tensors: pad_len = f_max - v.shape[0] if pad_len > 0: # 在 frames 维(第 0 维)末尾补零帧 zero = torch.zeros(pad_len, *v.shape[1:], dtype=v.dtype) v = torch.cat([v, zero], dim=0) padded.append(v) return torch.stack(padded, dim=0) # [B, F_max, C, H, W] def main(): a = torch.randn(8, 3, 224, 224) b = torch.randn(12, 3, 224, 224) batch = collate_videos([a, b]) print("batch 形状:", tuple(batch.shape)) # [2, 12, 3, 224, 224] if __name__ == "__main__": main()第一层修复让 padding 维正确、符号非负、先 pad 后 stack,video processor 不再报错。
六、解决方案(第二层:结构性改进)
把"视频 batch 对齐"收口成一个VideoCollator,固化"求 max 帧数 → frames 维 pad → stack"的流程,并校验输入维度,避免维度/符号再错。
import torch from dataclasses import dataclass from typing import List @dataclass class VideoCollator: pad_value: float = 0.0 def collate(self, videos: List[torch.Tensor]) -> torch.Tensor: # 校验输入都是 [F, C, H, W] for v in videos: if v.dim() != 4: raise ValueError(f"视频张量应为 4 维 [F,C,H,W],实际 {v.dim()}") f_max = max(v.shape[0] for v in videos) out = [] for v in videos: pad = f_max - v.shape[0] if pad > 0: z = torch.full((pad, *v.shape[1:]), self.pad_value, dtype=v.dtype) v = torch.cat([v, z], dim=0) out.append(v) return torch.stack(out, dim=0) def main(): c = VideoCollator() vids = [torch.randn(8, 3, 224, 224), torch.randn(10, 3, 224, 224)] batch = c.collate(vids) print("VideoCollator 输出:", tuple(batch.shape)) # [2, 10, 3, 224, 224] if __name__ == "__main__": main()第二层的关键是VideoCollator把"维度校验 + frames 维 pad + stack"固化,任何维度/符号错误都在 collate 内被拦截,不会传到模型。
七、解决方案(第三层:断言 / CI 守护)
加 pytest 守护:(1) 不同帧数视频 collate 后形状统一为[B, F_max, C, H, W];(2) pad 长度恒非负;(3) 输入维度错时 collator 应报错。
import torch import pytest def collate(videos): f_max = max(v.shape[0] for v in videos) out = [] for v in videos: pad = f_max - v.shape[0] if pad > 0: z = torch.zeros(pad, *v.shape[1:], dtype=v.dtype) v = torch.cat([v, z], dim=0) out.append(v) return torch.stack(out) def test_uniform_shape(): a = torch.randn(8, 3, 224, 224) b = torch.randn(12, 3, 224, 224) batch = collate([a, b]) assert tuple(batch.shape) == (2, 12, 3, 224, 224) def test_pad_non_negative(): videos = [torch.randn(8, 3, 224, 224), torch.randn(12, 3, 224, 224)] f_max = max(v.shape[0] for v in videos) for v in videos: assert (f_max - v.shape[0]) >= 0 def test_wrong_dim_raises(): with pytest.raises(Exception): collate([torch.randn(3, 224, 224)]) # 3 维,缺 frames 维 if __name__ == "__main__": pytest.main([__file__, "-q"])CI 里test_uniform_shape+test_pad_non_negative通过,就能保证 video processor 的 padding 计算正确,杜绝 SmolVLM 视频对齐的回归。
八、排查清单
SmolVLM 视频处理器报 padding 错误时,按此顺序查:
- 确认是视频(多帧)而非图像:图像无 frames 维,视频才有,padding 只在视频路径触发。
- 打印各视频帧数:看是否不等长(不同帧数 batch 化必 pad)。
- 检查 pad 符号:确认
pad = F_max - F_i非负,不是反过来。 - 检查 pad 维度:pad 必须作用在 frames(时间)维,不是 H/W/C。
- 先 pad 后 stack:绝不等尺寸就
torch.stack。 - 用 VideoCollator 兜底:统一"求 max → frames 维 pad → stack",并校验维度。
- 检查 padding_side:left pad 时帧插在前面,index 计算别错。
九、小结
SmolVLM 视频处理器因 padding 计算错误报错,根因不在模型,而在video processor 把不同帧数的视频对齐时,padding 计算用了错误的维度(把帧数差用到空间维)或错误符号(F_i - F_max得到负 pad),且可能先 stack 后 pad(stack 要求等尺寸)——视频是五维[B,F,C,H,W],padding 必须作用在 frames 维、用非负长度,先 pad 再 stack。
修复三层:第一层,padding 在 frames 维、用F_max - F_i非负、先 pad 后 stack;第二层用VideoCollator固化"维度校验 + frames 维 pad + stack";第三层用 pytest 断言"对齐后形状统一、pad 非负、错维报错"。记住:视频对齐 pad 的是时间维(帧数),不是空间维;pad 长度永远是 max 减自己,非负。