已关闭
[Question|问题咨询]: fp64→fp32 cast 对 fp32 次正规(subnormal)数被置零/截小,与 CPU/GPU 不一致,文档未描述,是否正常 #1567
StoneChan_创建于 8月21日关闭于 8月26日
8月21日 添加了label:question
8月21日 修改标题为 “[Question|问题咨询]: fp64→fp32 cast 对 fp32 次正规(subnormal)数被置零/截小,与 CPU/GPU 不一致,文档未描述,是否正常”,原标题为“[Question|问题咨询]: fp64→fp32 cast 对 fp32 次正规(subnormal)数被置零/截小,与 CPU/GPU 不一致”
8月21日 修改标题为 “[Question|问题咨询]: fp64→fp32 cast 对 fp32 次正规(subnormal)数被置零/截小,与 CPU/GPU 不一致,文档未描述,是否正常”,原标题为“[Question|问题咨询]: fp64→fp32 cast 对 fp32 次正规(subnormal)数被置零/截小,与 CPU/GPU 不一致”
8月21日 将 li-xingyue-lxy 设为负责人
StoneChan_
8月24日 评论:
8月24日 评论:
8月26日 issue状态由 进行中 改变为 已解决
8月26日 关闭了 issue


Welcome to ask questions and discuss with other members.
问题描述
一、问题概述
在 Ascend 上执行 fp64→fp32 的 dtype 转换(
tensor.float()/aclnnCast,内部走向量 cast/CVT 指令)时,凡是落在 fp32 次正规(subnormal/denormal)区间
(0, 1.18e-38)的输出值,与 CPU、GPU 的结果不一致:5.9e-39 ~ 1.18e-38)的一小部分被保留但值被截小。正规数区间则 bit 级完全一致。已确认不是舍入模式(round mode)选错,疑似硬件向量 cast 对次正规支持不足/默认 FTZ。
二、环境信息
double→float,即 fp64→fp32)三、复现步骤
export PYTHONPATH=... python reproduce.py # 见下方第六节脚本依赖:
import numpy as np, torch import torch_npu四、现象(实测数据)
在
[1.4e-45, 1.18e-38)(fp32 可表示次正规区间)内对数均匀采样 600 个点,逐点对比 CPU 与 NPU 的 fp64→fp32:关键点:CPU 保留的正次正规位置,NPU 100% 与其不一致(清 0 或截小)。
逐个样例:
在真实用例上的体现
all_Tensor.float_deterministic.json这套 fp64 用例(range=±inf,约 1.3%~1.5% 元素落在次正规区间),全部 17 条精度失败,其不一致元素 100% 集中在 fp32 次正规区间,正规区间 bit 级一致:
五、已做的定位(代码层面)
1. 触发的算子链
tensor.float()(fp64)在 NPU 上:aclnnInplaceCopy内部由于 dtype 不同调用l0op::Cast(op_api/aclnn_copy.cpp):if (dstDtype != srcDtype) { srcCast = l0op::Cast(srcContiguous, dstDtype, ...); // fp64->fp32 在这里 } l0op::ViewCopy(srcBroadcast, selfRef, ...); // 之后是同 dtype 纯搬移独立调用
aclnnCast(self.to(torch.float32))同样复现。2. round mode:fp64→fp32 配的是 RINT(与 CPU 一致)
op_kernel/arch35/cast_struct.h:CAST_POLICY_DEFINE(double, float, DT_DOUBLE, DT_FLOAT, CAST_TEMPLATE_DIRECT_CAST, ..., CAST_ROUND_MODE_RINT, CAST_ROUND_MODE_RINT, ...) // 就近舍入因此不是舍入策略导致(RINT 与 CPU/IEEE 默认一致,正规区间也因此 bit 级一致)。
六、最小复现脚本 reproduce.py
import numpy as np import torch import torch_npu FN32_MIN = 1.17549435e-38 # fp32 最小正规数 FN32_MIN_SUB = 1.40129846e-45 # fp32 最小次正规 = 2^-149 def make_probe(n): rng = np.random.default_rng(7) lo, hi = np.log10(FN32_MIN_SUB), np.log10(FN32_MIN) vals = 10.0 ** rng.uniform(lo, hi, n) return np.concatenate([vals, -vals]).astype(np.float64) x_np = make_probe(300) x = torch.from_numpy(x_np).double().to("npu:0") torch.npu.synchronize() cpu = x.cpu().to(torch.float32).numpy() npu = x.to(torch.float32).cpu().numpy() flushed = (cpu != 0) & (npu == 0) truncated = (cpu != 0) & (npu != 0) & (npu != cpu) same = (cpu == npu) print("CPU与NPU一致(保留) :", int(same.sum())) print("NPU清零(FTZ) :", int(flushed.sum())) print("NPU保留但截小 :", int(truncated.sum())) print("清零样本|x|范围 :", (np.abs(x_np)[flushed].min(), np.abs(x_np)[flushed].max()) if flushed.any() else None) print("截小样本|x|范围 :", (np.abs(x_np)[truncated].min(), np.abs(x_np)[truncated].max()) if truncated.any() else None)七、对比参考(CPU / GPU 行为)
同一份 fp64 输入、同样的
.to(torch.float32):ftz=false):保留全部次正规,与 CPU 一致(实测#cast差异=0)。八、想请教的问题
double→float)对 fp32 次正规数是否是硬件不支持/默认 FTZ(flush-to-zero)?~5.9e-39 ~ 1.18e-38)的“保留但截小”是什么机制?它和全量清零的分界点~5.5e-39~6.2e-39是如何决定的?九、其它
double → float(以及可能其它含次正规的窄化转换)。