【Bug已解决】[Bug]: GLM5.2 RuntimeError: The expanded size of the tensor (10210) must match the existing

发布时间:2026/7/28 1:02:44
【Bug已解决】[Bug]: GLM5.2 RuntimeError: The expanded size of the tensor (10210) must match the existing 【Bug已解决】[Bug]: GLM5.2 RuntimeError: The expanded size of the tensor (10210) must match the existing size (16384) at non-singleton dimension 1. Target sizes: [12, 10210]. Tensor sizes: [12, 16384] 解决方案一、现象长什么样在 vLLM 上跑 GLM-5.2一个超长上下文模型时推理到某个实际序列长度如 10210报错RuntimeError: The expanded size of the tensor (10210) must match the existing size (16384) at non-singleton dimension 1. Target sizes: [12, 10210]. Tensor sizes: [12, 16384]几个典型表征只在序列长度达到某个值时出现短序列正常说明问题在张量按最大长度16384预分配但运行时又被要求 expand 到实际长度10210两者冲突。报错在torch.expandexpand要求非 singleton 维度的大小必须等于原大小或 1。这里原有张量维度 1 是 16384预分配上限目标却是 10210 —— 既不等于 16384 也不等于 1于是报错。通常是 position_ids / attention mask / rotary 相关张量这些张量常按max_model_len如 16384预分配然后在运行时对实际序列做expand复用实际长度 ≠ 预分配长度时就炸。这不是模型问题而是某个按最大长度预分配、再用expand复用到的张量在运行时实际序列长度 ≠ 预分配长度时expand 的语义被误用。下面给出定位与修复。二、背景vLLM 为了效率常按max_model_len如 16384预分配位置/掩码类张量然后在每个请求上复用——常见手法是对预分配张量做tensor.expand(batch, actual_len)来避免重新分配。但expand的语义是广播它只把大小为 1 的维度展开到目标大小已经在维度上非 1 的大小必须保持原值。如果预分配张量的第 1 维已经是 16384非 1你expand(..., 10210)要求它变成 10210就违反规则 → RuntimeError。正确做法有两种预分配时用 singleton 维度如果打算expand预分配时第 1 维应为 1[12, 1]运行时expand(12, 10210)合法1→10210 广播或不用 expand用切片/索引实际序列长度确定后直接tensor[:, :actual_len]取前 actual_len 列而不是 expand 到另一个长度。GLM-5.2 这个案例预分配成了[12, 16384]非 1运行时要expand到[12, 10210]失败。根因是预分配维度与 expand 语义不匹配。修复就是要么预分配 singleton 维度 expand要么用切片代替 expand。下面用可运行代码复现并修复。三、根因拆成两条根因预分配张量第 1 维是 16384非 singleton却被 expand 到 10210expand要求非 singleton 维度大小不变。预分配[12,16384]后expand(12,10210)非法。根因是预分配维度选择错误应为 1 才能 expand。用 expand 做按实际长度截取是误用实际想要的是取前 actual_len 列该用切片[:, :actual_len]而非expand广播。根因是混淆了 expand广播与 slice截取的语义。修复方向预分配张量时若计划expand复用第 1 维设为 1singleton运行时expand到实际长度或干脆不用 expand直接切片到实际长度最稳妥无论预分配多大都能取。四、最小可运行复现下面复现expand非 singleton 维度到不同大小的错误import torch def naive_reuse(preallocated, actual_len): 现状预分配 [12,16384]运行时 expand 到 [12, actual_len]。 # preallocated.shape [12, 16384] return preallocated.expand(12, actual_len) # 非 singleton 维 16384 → 10210 非法 pre torch.zeros(12, 16384) try: naive_reuse(pre, 10210) except RuntimeError as e: print(复现:, e) # expanded size 10210 must match 16384复现: ...must match 16384即复现了 GLM-5.2 的报错维度/数字不同但本质一致。下面用切片修复。五、解决方案第一层最小直接修复最小修复不要用expand去截取不同长度直接用切片[:, :actual_len]或预分配 singleton 维度再用expand。def reuse_by_slice(preallocated, actual_len): 正确用切片取前 actual_len 列无论预分配多大都能取。 return preallocated[:, :actual_len] def reuse_by_expand(preallocated_singleton, actual_len): 正确预分配时第 1 维为 1singleton运行时 expand 合法。 # preallocated_singleton.shape [12, 1] return preallocated_singleton.expand(12, actual_len) # 1 → actual_len 广播 # 复现修复 pre torch.zeros(12, 16384) out reuse_by_slice(pre, 10210) print(切片复用形状:, tuple(out.shape)) # [12, 10210] # 若用 expand预分配必须 singleton pre_s torch.zeros(12, 1) out2 reuse_by_expand(pre_s, 10210) print(expand 复用形状:, tuple(out2.shape)) # [12, 10210]这一层改动让按实际序列长度复用预分配张量既可用切片最稳也可用正确的 singleton-expand不再触发 RuntimeError。六、解决方案第二层结构化改进把序列长度自适应张量复用做成结构化组件统一一个LengthAdaptiveTensor预分配按 max_len运行时用切片取实际长度不依赖 expand 语义最稳并支持实际长度超过预分配的告警/动态扩容。import torch from dataclasses import dataclass dataclass class SeqTensorConfig: batch: int max_len: int feat: int 1 # 如 position 的额外维 class LengthAdaptiveTensor: def __init__(self, cfg: SeqTensorConfig, init_fn): # 预分配 [batch, max_len, feat]运行时切片到实际长度 self.cfg cfg self.data init_fn((cfg.batch, cfg.max_len, cfg.feat)) def for_length(self, actual_len: int) - torch.Tensor: if actual_len self.cfg.max_len: raise ValueError( f实际长度 {actual_len} 超过预分配 max_len {self.cfg.max_len}。 f请调大 max_model_len 或启用动态扩容。) # 切片取前 actual_len不依赖 expand语义清晰且无维度冲突 return self.data[:, :actual_len, :] # 用法position_ids / mask 类张量 cfg SeqTensorConfig(batch12, max_len16384, feat1) lat LengthAdaptiveTensor(cfg, lambda s: torch.arange(s[1]).unsqueeze(0) .unsqueeze(-1).expand(s[0], s[1], s[2]).clone()) seg lat.for_length(10210) print(实际序列张量形状:, tuple(seg.shape)) # [12, 10210, 1]LengthAdaptiveTensor把预分配 按实际长度切片集中运行时永远用[:, :actual_len]而非expand到不同长度彻底规避维度冲突。七、解决方案第三层断言 / CI 守护张量 expand 错误最怕线上长序列才崩。用断言守两条不变量def check_seq_tensor_invariants(cfg: SeqTensorConfig, actual_len: int): lat LengthAdaptiveTensor(cfg, lambda s: torch.zeros(s)) seg lat.for_length(actual_len) # 不变量 1输出第 1 维必须等于实际长度不是预分配 max_len assert seg.shape[1] actual_len, f输出长度 {seg.shape[1]} ! {actual_len} # 不变量 2不得用 expand 到不同非 singleton 大小隐含用切片 # 通过输出形状正确间接保证 assert seg.shape[1] cfg.max_len return True def test_seq_tensor_reuse(): cfg SeqTensorConfig(batch12, max_len16384, feat1) check_seq_tensor_invariants(cfg, 10210) check_seq_tensor_invariants(cfg, 500) # 超 max_len 应清晰报错而非 expand 崩 try: LengthAdaptiveTensor(cfg, lambda s: torch.zeros(s)).for_length(20000) raise AssertionError(超长应被拦) except ValueError: pass print(OK: 序列长度自适应张量不变量通过) if __name__ __main__: test_seq_tensor_reuse()把test_seq_tensor_reuse接进 CI覆盖短/实际/超长三类长度锁死切片复用逻辑。八、排查清单GLM-5.2 报 expanded size ... must match ... at non-singleton dimension 1按序查先确认是expand到不同非 singleton 大小报错明确说expanded size X must match existing Y。X实际长度Y预分配长度。两者不等且都非 1 → expand 非法。找预分配张量搜torch.zeros(..., 16384)/ 按 max_model_len 分配的 position_ids / attention_mask / rotary 相关张量。别用 expand 截取用切片tensor[:, :actual_len]取前 actual_len 列无论预分配多大都合法是最稳的修复。若坚持用 expand预分配必须 singleton第 1 维设为 1[12,1]运行时expand(12, actual_len)才合法1→actual_len 广播。统一用LengthAdaptiveTensor所有按序列长度复用预分配张量的地方走同一组件避免有的用 expand、有的用切片、风格混乱踩坑。超长告警实际长度超过预分配 max_len 时清晰报错或动态扩容别让expand到更大长度再次触发。CI 接test_seq_tensor_reuse覆盖短/实际/超长三类锁死输出长度实际长度、不用非法 expand。九、小结GLM-5.2 报 expanded size (10210) must match existing size (16384) 的根因是某个按max_model_len(16384) 预分配、第 1 维非 singleton 的张量在运行时被用expand到实际长度(10210)而expand不允许非 singleton 维度变大小。三层修复第一层reuse_by_slice用[:, :actual_len]切片取实际长度最稳不依赖 expand 语义若用 expand预分配必须 singleton 维度[12,1]第二层LengthAdaptiveTensor结构化组件预分配按 max_len、运行时统一切片到实际长度、超长清晰报错第三层CI 断言守住输出第 1 维实际长度 / 超长被拦任何非法 expand 立即红。落实后GLM-5.2 在任意实际序列长度下位置/掩码类张量都用切片复用预分配缓冲不再因expand非 singleton 维度冲突而崩溃。