[Bug已解决] torch.nanmedian 指定 dim=None 报命名维度 RuntimeError(省略 dim 正常)解决方案

[Bug已解决] torch.nanmedian 指定 dim=None 报命名维度 RuntimeError(省略 dim 正常)解决方案

一、现象长什么样

你用torch.nanmedian(忽略 NaN 的中位数)时,发现一个诡异不一致:

x = torch.randn(3, 4) torch.nanmedian(x) # ✅ 正常 torch.nanmedian(x, dim=None) # ❌ 报 RuntimeError(named-dimension 相关)

也就是官方描述的:

torch.nanmedian(..., dim=None) raises a named-dimension RuntimeError while omitting dim works

dim=None语义上应该等于「省略 dim」(都对整个张量变平求中位数)。但带命名维度(named tensor)的张量上,显式传dim=None会触发一个命名维度相关的 RuntimeError,而省略 dim 却正常。这是 PyTorch 内部对「dim=None参数」和「命名维度传播」的处理不一致导致的 bug。

本文讲清楚 named tensor、nanmedian 的 dim 语义、为什么dim=None会炸,以及如何规避。


二、torch.nanmedian 与 dim 语义

torch.nanmedian(input, dim=None)

  • dim=None(或省略):把整个 input 变平成一维,返回单个中位数标量
  • dim=k:沿第 k 维求中位数,返回(values, indices)

数学上dim=None和省略 dim 完全等价。所以你自然会写torch.nanmedian(x, dim=None),期望和省略一样。

但问题出在:当x是**命名张量(named tensor,带维度名字如CHW)**时,PyTorch 内部对dim=None的处理路径和「省略 dim」走了不同分支,其中dim=None路径试图去处理「命名维度」,却没正确处理None,于是 RuntimeError。


三、命名张量(named tensor)是什么

PyTorch 支持给维度起名字:

import torch x = torch.randn(3, 4, names=("C", "H")) print(x.names) # ('C', 'H')

命名维度让你可以「按名字」做操作(如x.align_to(...)),更可读、更不容易搞错轴。但命名张量对很多算子的支持是「逐步补全」的,某些参数组合(如dim=None)的处理有 bug。


四、可运行:复现不一致

下面脚本复现「省略 dim 正常、dim=None 报错」(纯 CPU 可跑):

import torch def demo(): x = torch.randn(3, 4, names=("C", "H")) # 命名张量 print("张量名字:", x.names) # 省略 dim:正常 try: r1 = torch.nanmedian(x) print("省略 dim 成功:", r1) except Exception as e: print("省略 dim 也报错:", e) # 显式 dim=None:可能 RuntimeError try: r2 = torch.nanmedian(x, dim=None) print("dim=None 成功:", r2) except Exception as e: print("dim=None 报错:", type(e).__name__, "->", str(e)[:150]) if __name__ == "__main__": demo()

如果你看到「省略 dim 成功,dim=None 报错」,就复现了该不一致。


五、解决方案一:省略 dim,不显式传 None

最直接规避:既然省略 dim 正常工作,就别写dim=None

# ✅ 正确:省略 dim median = torch.nanmedian(x) # ❌ 触发 bug:显式 dim=None # median = torch.nanmedian(x, dim=None)

代码语义完全一样,但绕开了损坏的dim=None路径。


六、解决方案二:先 rename 掉维度名,再传 dim=None

如果业务逻辑上你必须显式传dim=None(比如封装函数时参数透传),可以先把命名维度去掉(.rename(None)),用普通张量走dim=None

import torch def safe_nanmedian(x, dim=None): if dim is None: # 去掉维度名,规避 named-dimension 对 dim=None 的 bug return torch.nanmedian(x.rename(None)) return torch.nanmedian(x, dim=dim) x = torch.randn(3, 4, names=("C", "H")) print(safe_nanmedian(x, dim=None)) # 正常

rename(None)把命名维度去掉,变成普通张量,dim=None路径就走「省略 dim」同款逻辑,不再报错。


七、解决方案三:用 flat 后的中位数(等价写法)

如果你想明确表达「对整体求中位数」,也可以自己.flatten()再求,语义完全等价且避开 dim 参数:

import torch def median_overall(x): flat = x.flatten() # flatten 后是一维,求中位数返回标量 return torch.nanmedian(flat) x = torch.randn(3, 4, names=("C", "H")) print("整体中位数:", median_overall(x))

flatten()把命名维度展平成一维(名字丢失),nanmedian在一维上求中位数,等价于dim=None


八、解决方案四:非命名张量不受影响

如果你根本不需要命名维度,就用普通张量,dim=None在所有版本都正常:

import torch x = torch.randn(3, 4) # 无名字 print(torch.nanmedian(x, dim=None)) # 正常

所以这条 bug 只在「命名张量 + dim=None」组合下触发。如果你代码里大量用 named tensor,就需要注意这个坑。


九、解决方案五:升级 PyTorch

torch.nanmedian(..., dim=None)的命名维度 bug 是具体的算子实现缺口,新版本可能已修复「dim=None与命名维度」的交互。查看版本:

import torch print("PyTorch:", torch.__version__)

十、如何判断你踩的是同一条

  • 用的是torch.nanmedian(不是普通median);
  • 张量是命名张量(有names);
  • 显式传dim=None报错,省略 dim 却正常;
  • 错误是命名维度(named-dimension)相关的 RuntimeError。

命中即说明踩中该dim=None+ named tensor 的不一致 bug。


十一、小结

torch.nanmedian(..., dim=None)在命名张量上报命名维度 RuntimeError、而省略 dim 正常,是算子对dim=None参数与命名维度交互处理不一致的 bug。应对:

  1. 省略 dim,不显式传None(第五节,最稳);
  2. 必须传None就先rename(None)去掉维度名(第六节);
  3. .flatten()后求中位数,等价且避开 dim 参数(第七节);
  4. 不用命名张量时dim=None本就正常(第八节);
  5. 升级到修复该交互的 PyTorch(第九节)。

命名张量是个好特性,能让轴操作更不易错。但它和很多算子的「边界参数」(如dim=Nonedim='C'字符串形式)支持仍在补全中。遇到「命名张量 + 某参数」诡异报错时,先去掉维度名试试,往往就能定位是不是同一类 bug。