【Bug已解决】CI fails with dev dependencies: RuntimeError: The size of tensor a (4) must match the size o
【Bug已解决】CI fails with dev dependencies: RuntimeError: The size of tensor a (4) must match the size of tensor b (8) at non-singleton dimension 1 解决方案原始报错CI fails with dev dependencies: RuntimeError: The size of tensor a (4) must match the size of tensor b (8) at non-singleton dimension 1 场景CI 流水线安装了一组开发依赖dev dependencies通常含未锁版本的框架开发版跑测试时两个张量在维度 1 上形状不一致4 vs 8抛RuntimeError。同样的代码在本地锁定版本能过只在 CI 的 dev 依赖环境下失败。 关键词依赖版本锁定、CI 可复现、dev vs prod 依赖、张量形状断言、环境一致性。一、现象长什么样CI 跑测试CI 安装dev依赖组含torch、transformers等的开发/未锁定版本测试里两个张量做运算期望维度 1 都是 4但一个实际是 8抛RuntimeError: The size of tensor a (4) must match the size of tensor b (8) at non-singleton dimension 1本地用锁定版本跑同测试通过回退 CI 到生产依赖版本测试也通过。根因是CI 的 dev 依赖装了一版与代码假设不同的框架框架内部某处如默认 head 维度、token 展开数从 4 变成了 8导致张量形状不匹配。本质不是算法错是环境不一致。二、背景为什么 dev 依赖会改张量形状深度学习框架的某些默认值/内部展开数会随版本变化。比如某版把某层的隐藏维默认从 4 改到 8或某 tokenizer 的图像 token 展开数变化。代码若隐式依赖这些默认值没显式传参固定版本一变形状就变。dev 依赖组通常为了尝试新特性装了未锁版本如torch2.x.dev或 git 主干而 prod/本地用requirements.lock锁了具体版本。于是 CI 和本地环境漂移张量形状假设被打破。三、根因依赖未锁定 形状假设隐式根因拆解dev 依赖未锁版本dev-requirements.txt写torch无版本装到形状默认不同的开发版。形状假设隐式代码依赖框架默认值如 head_dim4却没显式传版本一变就错。环境漂移CI 用 dev 组本地用 lock 文件两边框架版本不同。失败晚形状不匹配直到张量运算才炸没在构造处早校验。无复现保障CI 没用锁定文件每次可能装到不同版本。下面用最小模型复现版本不同导致形状假设被打破再给修复。四、最小可运行复现# 模拟不同框架版本下某默认维度不同 FRAMEWORK_HEAD_DIM 8 # CI 的 dev 版默认 8本地 lock 版是 4 def build_qkv(hidden): # 代码隐式依赖 head_dim4但没写死用了框架默认 head_dim FRAMEWORK_HEAD_DIM # 假设 hidden 按 4 算出的段数 assert hidden % 4 0, hidden 必须能被 4 整除 return hidden // 4, head_dim def combine(a, b): # a, b 期望在 dim1 同形 if a.shape[1] ! b.shape[1]: raise RuntimeError( fThe size of tensor a ({a.shape[1]}) must match the size fof tensor b ({b.shape[1]}) at non-singleton dimension 1) if __name__ __main__: import torch # 本地 lock 版 head_dim4 时a 的 dim14CI dev 版 head_dim8b 的 dim18 a torch.randn(2, 4) # 按本地假设构造 b torch.randn(2, 8) # CI dev 版默认值导致 try: combine(a, b) except RuntimeError as e: print(CI 失败:, e) # 形状 4 vs 8 不匹配运行抛形状不匹配——dev 版默认值让b的 dim1 成了 8与本地假设 4 冲突。五、方案锁定依赖版本CI 用 lock 文件第一层所有环境含 CI dev 组都必须有锁定版本dev 组不应装无版本约束的框架# requirements.lock —— 所有环境统一用 torch2.5.1 transformers4.46.0 # dev 组只能在此基础上加工具链不能改框架大版本 pytest8.3.0 ruff0.6.0# ci_install.sh 思路示意非 Python # pip install -r requirements.lock # pip install -r requirements.dev # dev 只加 lint/test 工具不改 torch 版本通过 lock 文件CI 装的框架版本与本地完全一致形状默认值不变4 vs 8 不再发生。六、方案dev / prod 依赖分离且互不污染框架第二层dev 依赖组只放工具链lint、test、coverage不放会改变运行时行为的框架未锁版本框架版本只由主 lock 管# 依赖分层示例 MAIN { # 运行时/训练必须锁定 torch: 2.5.1, transformers: 4.46.0, } DEV { # 仅开发工具不影响张量形状 pytest: 8.3.0, ruff: 0.6.0, coverage: 7.6.0, } def resolve(purpose): if purpose train: return MAIN if purpose ci-test: # 框架用 MAIN 的锁定版DEV 只补工具 return {**MAIN, **DEV} raise ValueError(purpose) if __name__ __main__: ci resolve(ci-test) assert ci[torch] 2.5.1 # CI 也用锁定版 print(CI 依赖:, ci)dev 组与框架版本解耦CI 不会再因 dev 依赖偷偷升级框架而形状漂移。七、方案形状断言早失败且信息明确第三层代码里对依赖框架默认值的形状加显式断言一旦版本导致默认值变化在构造处就报错而非运算时才炸import torch EXPECTED_HEAD_DIM 4 # 显式写死假设不靠框架默认 def build_heads(x: torch.Tensor): # 显式校验维度而不是假设框架默认 assert x.shape[1] % EXPECTED_HEAD_DIM 0, ( f输入 dim1{x.shape[1]} 不能被 head_dim{EXPECTED_HEAD_DIM} 整除 f可能框架版本导致默认值变化) return x.shape[1] // EXPECTED_HEAD_DIM def combine_safe(a: torch.Tensor, b: torch.Tensor): if a.shape[1] ! b.shape[1]: raise RuntimeError( fdim1 不匹配 a{a.shape[1]} b{b.shape[1]}请检查框架版本是否一致) return a b if __name__ __main__: a torch.randn(2, 4) b torch.randn(2, 4) print(heads:, build_heads(a)) # 构造处就校验 print(combine:, combine_safe(a, b).shape)显式断言把版本导致形状变的失败点前移到构造处且错误信息提示查框架版本排错更快。八、验证把环境/形状一致性锁进测试def test_locked_framework_in_ci(): ci resolve(ci-test) assert ci[torch] 2.5.1 # CI 与本地同版本 def test_head_dim_assertion_catches_drift(): x torch.randn(2, 8) # 模拟 dev 版默认值变 8 try: build_heads(x) assert False except AssertionError: pass # 构造处即发现不拖延到运算 if __name__ __main__: test_locked_framework_in_ci() test_head_dim_assertion_catches_drift() print(依赖锁定与形状断言测试通过。)九、排查清单CI dev 依赖张量形状错按顺序查环境对比本地能过、CI 失败先怀疑 CI 装的框架版本不同。依赖锁定CI 是否用 lock 文件dev 组是否装了无版本约束的框架依赖分层dev 组是否只含工具链没改运行时框架大版本形状假设代码是否隐式依赖框架默认值head_dim 等应显式写死。失败时机形状错在运算时才炸还是构造处有断言早失败更好排。复现CI 每次装的版本是否固定不固定则会时好时坏。错误信息形状报错是否提示可能版本不一致方便定位环境。十、小结CI 因 dev 依赖张量形状 4 vs 8 失败是依赖未锁定导致 CI 与本地框架版本漂移打破代码隐式的形状假设。修复三层锁定版本所有环境含 CI dev 组用同一 lock 文件框架版本固定依赖分层dev 组只放工具链不污染运行时框架版本形状断言对依赖默认值的形状显式校验版本漂移在构造处即暴露。核心原则CI 必须是可复现的依赖版本要锁定而非装最新。任何隐式依赖框架默认值如 head 维度、展开数的代码都要么显式写死、要么加断言——否则版本一变张量形状就会在 CI 上悄悄崩。