[Bug已解决] torch.kron 对非连续(non-contiguous)输入报错 RuntimeError 解决方案
一、现象长什么样
你想算两个矩阵的 Kronecker 积(克罗内克积),用了 torch.kron:
import torch
A = torch.randn(2, 3)
B = torch.randn(4, 5)
C = torch.kron(A, B) # 期望 (8, 15) 的 Kronecker 积
但当你的输入是非连续(non-contiguous)张量时,却崩了:
RuntimeError: torch.kron ... (non-contiguous input)
...
[Bug] torch.kron fails with RuntimeError on non-contiguous inputs
本 issue(pytorch/pytorch#185650)说的是:torch.kron 在某些实现路径上,遇到非连续输入(如 transpose / narrow / view 后未 contiguous() 的张量)会直接 RuntimeError,而不是自动处理。
本文聚焦:kron 是什么、为什么非连续会崩、怎么正确传入连续张量 / 用替代实现。
二、背景:torch.kron 与连续性
Kronecker 积:若 A 是 m×n,B 是 p×q,则 kron(A,B) 是 mp×nq 的块矩阵,每块是 A[i,j] * B。
torch.kron(A, B) 内部实现会逐块展开 A 的每个元素乘以 B 并拼接到结果。为了高效,它常假设输入在内存里是连续布局(contiguous),这样可以用 view / reshape / 步长运算直接拼块。
非连续张量怎么来的?很多操作会返回一个「视图」而非新连续张量:
import torch
x = torch.randn(3, 4)
xt = x.t() # 转置 → 非连续(stride 变了)
print(xt.is_contiguous()) # False
xn = x[:, 1:3] # 切片 → 非连续
print(xn.is_contiguous()) # False
当 kron 拿到这种非连续张量,内部若用「基于连续布局的块拼接」而未先 .contiguous(),就会访问到错误内存或触发断言 → RuntimeError。
三、为什么非连续会触发 RuntimeError
kron 的经典实现思路之一:把 A reshape 成 (m, n, 1, 1),B reshape 成 (1, 1, p, q),再 broadcast 乘、reshape 成 (mp, nq)。这个套路要求 A、B 都能按预期 reshape——而 reshape 在「非连续且无法零拷贝推断」时可能失败或给出错误布局,于是实现里加了个 TORCH_CHECK(input.is_contiguous()),不满足就 RuntimeError。
换句话说:kron 的实现在入口没自动 .contiguous(),而是直接检查连续性并拒绝非连续输入。这是实现不友好(不是 bug 逻辑错,但用户体验差——其它算子如 matmul 会自动处理)。
四、最小可运行复现(无需 GPU)
下面演示「连续 OK、非连续 RuntimeError」的边界:
import torch
def demo_kron():
A = torch.randn(2, 3)
B = torch.randn(4, 5)
# 连续输入:正常
print("连续 kron 形状:", torch.kron(A, B).shape) # (8, 15)
# 非连续输入:可能 RuntimeError
A_t = A.t() # 非连续
B_n = B[:, 1:4] # 非连续
try:
out = torch.kron(A_t, B_n)
print("非连续 kron 形状:", out.shape)
except RuntimeError as e:
print("[复现] 非连续输入触发 RuntimeError:", e)
def demo_contiguous_fix():
A_t = torch.randn(2, 3).t() # 非连续
B_n = torch.randn(4, 5)[:, 1:4] # 非连续
# 修复:先 .contiguous()
out = torch.kron(A_t.contiguous(), B_n.contiguous())
print("修复后 kron 形状:", out.shape)
if __name__ == "__main__":
demo_kron()
demo_contiguous_fix()
要点:kron 对连续输入稳定;非连续直接在入口 RuntimeError;.contiguous() 即可修。
五、解决方案一:调用前 .contiguous()(最直接)
每次传 kron 前,确保 A、B 连续:
import torch
def safe_kron(A, B):
return torch.kron(A.contiguous(), B.contiguous())
A = torch.randn(2, 3).t() # 非连续
B = torch.randn(4, 5)[:, 1:4] # 非连续
out = safe_kron(A, B)
print(out.shape)
代价:contiguous() 会多一次内存拷贝(若原本非连续)。但正确性第一。
六、解决方案二:自己实现 kron(不依赖连续性)
若你不想每次 .contiguous(),或想完全规避该实现路径,可手写 Kronecker 积(用 einsum / outer 思路),它对连续性更鲁棒:
import torch
def manual_kron(A, B):
"""手写 Kronecker 积,兼容非连续输入。"""
# 用外积思路:A[i,j] * B 拼成块
# 形状展开:(mA, nA, pB, qB) -> (mA*pB, nA*qB)
mA, nA = A.shape[-2], A.shape[-1]
pB, qB = B.shape[-2], B.shape[-1]
# einsum 构造块矩阵:a(i,j) * b(k,l)
out = torch.einsum("ij,kl->ikjl", A, B).reshape(mA * pB, nA * qB)
return out
A = torch.randn(2, 3).t() # 非连续
B = torch.randn(4, 5)[:, 1:4] # 非连续
out = manual_kron(A, B)
print("手写 kron 形状:", out.shape)
# 与官方对比(连续输入)
A2, B2 = A.contiguous(), B.contiguous()
ref = torch.kron(A2, B2)
print("与官方一致:", torch.allclose(out, ref))
einsum 对连续性更宽容,且 reshape 在 einsum 输出(连续)上是安全的。这样彻底绕开 kron 的连续性检查。
七、解决方案三:封装成自动 contiguous 的模块/函数
团队里统一用封装版本,避免每个调用点都记得 .contiguous():
import torch
class KronModule(torch.nn.Module):
"""包装 kron,自动处理连续性。"""
def forward(self, A, B):
return torch.kron(A.contiguous(), B.contiguous())
# 或用 monkeypatch 风格的工具函数
def kron_safe(A, B):
A = A.contiguous() if not A.is_contiguous() else A
B = B.contiguous() if not B.is_contiguous() else B
return torch.kron(A, B)
这样既保留官方 kron 的高性能实现,又自动规避非连续报错。
八、解决方案四:升级到已修复版本
#185650 是已知 bug,官方可能改为「内部自动 .contiguous()」而非拒绝。升级:
pip install --upgrade torch
python -c "import torch; print(torch.__version__)"
判断修复:同样的非连续输入 torch.kron 不再 RuntimeError,结果正确。修复前,用上面 .contiguous() / 手写实现绕过。
九、排查清单
- 报
torch.kron ... non-contiguous/ 对非连续输入 RuntimeError → 确认 #185650。 - 检查输入是否来自
t()/narrow/view/ 切片 → 这些产生非连续视图。 - 修复:调用前
.contiguous(),或用safe_kron封装自动处理。 - 彻底绕开:用
einsum("ij,kl->ikjl")手写 Kronecker 积,对连续性鲁棒。 - 升级:关注该 bug 是否被改为内部自动 contiguous。
- 通用经验:凡是遇到「对连续性敏感」的算子报错,先
.contiguous()试,常能解决。
十、小结
[Bug] torch.kron fails with RuntimeError on non-contiguous inputs(#185650)的本质是:torch.kron 的实现入口直接检查输入连续性,遇到非连续张量(transpose / 切片 / view 后的视图)就 RuntimeError 拒绝,而没有像 matmul 那样自动 .contiguous() 处理。这不是数值错误,而是实现不友好导致的可用性 bug。
应对:
- 直接修复:调用前
A.contiguous()/B.contiguous(),或封装safe_kron自动处理; - 彻底绕开:用
torch.einsum("ij,kl->ikjl", A, B).reshape(...)手写 Kronecker 积,对连续性鲁棒; - 等升级:官方可能改为内部自动 contiguous,修复后非连续输入也能直接用。
记住:转置、切片、view 都会产生非连续视图;凡是遇到「对连续性敏感」的算子报错,先
.contiguous()一试,常常药到病除。


295

被折叠的 条评论
为什么被折叠?



