原帖想把 [2,3] 直接 expand 成 [4,3];第 0 维已经有两个不同的行,不能作为广播单例扩展:
RuntimeError: The expanded size of the tensor (4) must match the existing size (2) at non-singleton dimension 0. Target sizes: [4, 3]. Tensor sizes: [2, 3]
expand 表达广播,不表达任意行复制
原题第 0 维有两行不同数据,不能直接从 2 广播成 4。新增长度 1 的轴可以 expand,但随后合并轴可能需要物化。应先确定期望行顺序与是否需要独立存储,再选 repeat、repeat_interleave 或保留额外广播轴。
expand 创建共享存储的视图,不是“任意复制数据”接口。先看要扩展的维度是不是 1。
import torch
x = torch.tensor([[4., 5., 6.], [1., 2., 3.]])
print("josh050608")
print("shape/stride:", x.shape, x.stride())
y = x.repeat(2, 1)
assert y.shape == (4, 3)
print(y)
这会按整块顺序得到第一行、第二行、第一行、第二行。如果希望第一行连续出现两次,再第二行连续出现两次,应使用 x.repeat_interleave(2, dim=0),二者不是同一种排列。
原帖的 x.unsqueeze(0).expand(2,-1,-1) 可以先得到 [2,2,3] 视图,因为新增的第 0 维原本长度为 1。但随后 reshape 为 [4,3] 不保证仍然零拷贝;这种布局可能需要实际物化。
如果下游能接受额外的批次维,可以保留 expand 的三维视图;如果必须要独立的 [4,3] 数据,repeat 的分配往往正是所需行为。不要向 expand 出来的共享视图做原地写入,多个逻辑位置可能指向同一存储。
验证行排列与任务含义一致,再检查下游操作的输入布局。形状变大不等于得到独立副本,也不能把 expand 当成免费的通用数据扩容。
同时检查值、顺序和 stride
下面完整例子用原题两行数据比较三种写法,验证它们的语义:
import torch
x = torch.tensor([[4., 5., 6.], [1., 2., 3.]])
block_repeat = x.repeat(2, 1)
row_repeat = x.repeat_interleave(2, dim=0)
expanded = x.unsqueeze(0).expand(2, -1, -1)
merged = expanded.reshape(4, 3)
assert block_repeat[:, 0].tolist() == [4., 1., 4., 1.]
assert row_repeat[:, 0].tolist() == [4., 4., 1., 1.]
assert torch.equal(merged, block_repeat)
assert expanded.stride()[0] == 0
independent = expanded.clone()
independent[0, 0, 0] = 99
assert x[0, 0].item() == 4
print("expanded/merged strides:", expanded.stride(), merged.stride())
expand 的新增轴 stride 为 0,多个逻辑位置引用相同数据,因此不能把它当作独立可写副本。若下游需要修改,先明确 clone;若只做支持广播的运算,保留额外轴可以避免不必要复制。
reshape 的接口不保证零拷贝,能否成为视图由布局决定。不要用“代码里没有 repeat”证明没有额外分配,也不要只为追求零拷贝改变模型期待的样本顺序。
验收先对照输出行顺序,再检查下游批次轴含义。若重复样本用于训练,还需确认目标标签按相同顺序重复;只把特征扩到 4 行而标签仍是 2 行,会引出下一处接口错误。
参考:原始求助帖;技术依据:Tensor.expand(2.5)、Tensor.repeat(2.5)、Tensor.view(2.5)。