已合并
docs: update torch.UntypedStorage support status #46583
docs: update torch.UntypedStorage support status #46583
已合并
gcw_CHjSLYhf创建于 9月18日
gcw_CHjSLYhf
9月18日

【合入来源】

【修改方案】

一. API 功能说明:

1. torch.UntypedStorage.filename

filename 是torch.UntypedStorage这个类的一个只读属性,用于获取与当前 Storage 关联的内存映射文件路径。因此和平台设备有关联,需要增加一个快速的验证性测试。

PyTorch 2.7 中的实现为:

@property
def filename(self):
    return self._get_filename()

因此它属于 Python 属性封装,但实际结果由底层 Storage 元数据接口 _get_filename() 提供。

2. torch.UntypedStorage.hpu

torch.UntypedStorage.hpu() 用于 返回一个在 HPU(Habana Gaudi 加速器)上的存储副本,如果该对象已在HPU内存中且位于正确的设备上,则不执行复制,直接返回原始对象。因为该API与HPU平台设备具有强绑定关系。

参数说明:

  • device:目标 HPU 设备编号,默认使用当前 HPU 设备;
  • non_blocking:如果源内存满足异步复制条件,则允许异步复制。

PyTorch 2.7 中的核心实现为:

def hpu(self, device=None, non_blocking=False):
    device2 = (
        torch.device("hpu", device)
        if device
        else torch.device("hpu")
    )
    return self.to(
        device=device2,
        non_blocking=non_blocking,
    )

3. torch.UntypedStorage.nbytes

torch.UntypedStorage.nbytes() 返回当前底层 Storage 实际占用的字节数,返回类型为 int。

但要注意,nbytes() 查询的是整个 Storage 的大小,而不是当前 Tensor View 的逻辑数据量。

例如:

base = torch.empty(16, dtype=torch.float32)
view = base[::2]

base.untyped_storage().nbytes() == 64
view.untyped_storage().nbytes() == 64

# view只有8个逻辑元素
view.numel() * view.element_size() == 32

这是因为 view 和 base 共享同一个底层 Storage。

接口本身主要读取底层 Storage 元数据,不执行计算算子,但它读取的是 NPU Storage 的实际字节范围,可以验证 NPU Tensor 到 UntypedStorage 的连接是否正确,因此需要增加 NPU 功能测试。

4. torch.UntypedStorage.new

torch.UntypedStorage.new在当前 Storage 所在设备上,创建一个新的、大小为 0 的 UntypedStorage。torch.UntypedStorage.new() 由底层 Storage 实现提供。NPU Storage 调用该接口时,需要正确处理 PrivateUse1/NPU 设备信息。因此需要补充这个API在torch-npu平台上的测试用例。

二. 上游用例检查情况:

检查 PyTorch 上游 Storage 相关测试后,结果如下:

API 上游是否存在独立直接用例 检查结果
torch.UntypedStorage.filename 未发现 上游核心test/test_torch.py中未发现直接读取.filename的独立用例
torch.UntypedStorage.hpu 未发现 上游核心Storage测试中未发现直接调用.hpu()的独立用例
torch.UntypedStorage.nbytes 存在部分覆盖 上游在Storage Meta测试中调用了s.nbytes()并比较不同Storage的字节数
torch.UntypedStorage.new 存在部分覆盖 上游在Storage Meta测试中调用了s.nbytes()并比较不同Storage的字节数

三. 资料情况检查:

  • torch.UntypedStorage.filename:在torch上游社区存在独立条目,同时在当前项目中该API资料已经存在,但是A5支持情况有误,因此需要进行资料修正。

image.png

  • torch.UntypedStorage.hpu:在torch上游社区存在独立条目,同时在当前项目中该API资料已经存在,由于该API与hpu平台存在硬编码,系统支持情况,因此不需要进行资料补充和修正。

image.png

  • torch.UntypedStorage.nbytes在torch上游社区存在独立条目,同时在当前项目中该API资料有误,因此需要进行资料修正。

image.png

  • torch.UntypedStorage.new torch上游社区存在独立条目,同时在当前项目中该API资料有误,因此需要进行资料修正。

image.png

【资料变更】

修改以下文件:

docs/zh/api/native_api/pytorch_2-7-1/torch-Storage.md
docs/zh/api/native_api/pytorch_2-11-0/torch-Storage.md
docs/zh/api/native_api/pytorch_2-12-0/torch-Storage.md
docs/zh/api/native_api/pytorch_2-13-0/torch-Storage.md

【接口变更】

不涉及

【功能验证】

1. 测试场景

torch.UntypedStorage API 功能性验证

1. 测试场景

编号 API 测试场景 预期结果
1 torch.UntypedStorage.filename 获取普通 CPU Tensor 底层 Storage 的 filename 普通 CPU Storage 未关联内存映射文件,返回 None
2 torch.UntypedStorage.filename 使用 from_file(shared=True) 创建共享文件映射 Storage filename 返回字符串,并且规范化路径与映射文件路径一致
3 torch.UntypedStorage.filename 使用 from_file(shared=False) 创建私有文件映射 Storage 私有映射的 filename 返回 None
4 torch.UntypedStorage.filename 获取 NPU Tensor 底层 Storage 的 filename Storage 位于 NPU,且未关联 CPU 文件映射,返回 None
5 torch.UntypedStorage.hpu 在纯 torch-npu 环境中调用默认参数 hpu() 因未加载 HPU 设备模块,抛出包含 HPU device module is not loaded 的 AssertionError
6 torch.UntypedStorage.hpu 指定 device=1, non_blocking=True 调用 hpu() 参数可以被接口正常解析;因未加载 HPU 模块,抛出预期 AssertionError
7 torch.UntypedStorage.nbytes 验证 float16 Tensor 的 CPU/NPU Storage 字节数 CPU、NPU 结果均等于 numel × element_size
8 torch.UntypedStorage.nbytes 验证 float32 Tensor 的 CPU/NPU Storage 字节数 CPU、NPU 结果均等于 numel × element_size
9 torch.UntypedStorage.nbytes 验证 int32 Tensor 的 CPU/NPU Storage 字节数 CPU、NPU 结果均等于 numel × element_size
10 torch.UntypedStorage.nbytes 验证 int64 Tensor 的 CPU/NPU Storage 字节数 CPU、NPU 结果均等于 numel × element_size
11 torch.UntypedStorage.nbytes 直接在 CPU/NPU 上构造指定字节数的 UntypedStorage nbytes()、size() 和 len() 均返回构造时指定的字节数
12 torch.UntypedStorage.nbytes 创建大小为 0 的空 NPU Storage nbytes()、size() 和 len() 均返回 0
13 torch.UntypedStorage.nbytes 获取非连续 NPU Tensor View 的底层 Storage 字节数 View 与原 Tensor 共享底层 Storage,返回整个底层 Storage 的字节数
14 torch.UntypedStorage.new 对 CPU Storage 调用 new() 返回独立、大小为 0、设备类型仍为 CPU 的 UntypedStorage
15 torch.UntypedStorage.new 对 NPU Storage 调用 new() 返回独立、大小为 0、设备类型仍为 NPU 的 UntypedStorage
16 torch.UntypedStorage.new 对 new() 返回的 Storage 执行 resize_() 新 Storage 的大小发生变化,原 Storage 的大小不受影响
17 torch.UntypedStorage.new 对共享文件映射 Storage 调用 new() 返回普通空 CPU Storage,不继承原 Storage 的文件映射关系

2. 测试核心代码

class TestUntypedStorageAPIs(TestCase):

    def test_untyped_storage_filename(self):
        # 场景1:普通CPU Storage不关联内存映射文件
        cpu_storage = torch.empty(
            8,
            dtype=torch.float32,
            device="cpu",
        ).untyped_storage()

        self.assertEqual(cpu_storage.device.type, "cpu")
        self.assertIsNone(cpu_storage.filename)

        # 场景2和3:共享文件映射与私有文件映射Storage
        with tempfile.TemporaryDirectory() as temp_dir:
            file_path = os.path.join(
                temp_dir,
                "untyped_storage.bin",
            )

            shared_storage = torch.UntypedStorage.from_file(
                file_path,
                shared=True,
                nbytes=32,
            )

            self.assertEqual(shared_storage.device.type, "cpu")
            self.assertEqual(shared_storage.nbytes(), 32)
            self.assertIsInstance(
                shared_storage.filename,
                str,
            )
            self.assertEqual(
                os.path.realpath(shared_storage.filename),
                os.path.realpath(file_path),
            )

            # 释放共享映射后再创建私有映射
            del shared_storage

            private_storage = torch.UntypedStorage.from_file(
                file_path,
                shared=False,
                nbytes=32,
            )

            self.assertEqual(private_storage.device.type, "cpu")
            self.assertEqual(private_storage.nbytes(), 32)
            self.assertIsNone(private_storage.filename)

            del private_storage

        # 场景4:NPU Storage不关联CPU内存映射文件
        npu_storage = torch.empty(
            8,
            dtype=torch.float32,
            device="npu",
        ).untyped_storage()

        self.assertEqual(npu_storage.device.type, "npu")
        self.assertIsNone(npu_storage.filename)

    def test_untyped_storage_hpu(self):
        npu_storage = torch.empty(
            8,
            dtype=torch.float32,
            device="npu",
        ).untyped_storage()

        self.assertEqual(
            npu_storage.device.type,
            "npu",
        )

        # 场景5:在torch-npu平台使用默认参数调用hpu()
        with self.assertRaisesRegex(
            AssertionError,
            r"HPU device module is not loaded",
        ):
            npu_storage.hpu()

        # 场景6:在torch-npu平台指定device和non_blocking参数
        with self.assertRaisesRegex(
            AssertionError,
            r"HPU device module is not loaded",
        ):
            npu_storage.hpu(
                device=1,
                non_blocking=True,
            )

    def test_untyped_storage_nbytes_for_tensor_dtypes(self):
        test_cases = [
            (torch.float16, 6),
            (torch.float32, 6),
            (torch.int32, 6),
            (torch.int64, 6),
        ]

        # 场景7至10:不同dtype的CPU/NPU Storage字节数
        for dtype, numel in test_cases:
            with self.subTest(dtype=dtype):
                cpu_tensor = torch.empty(
                    numel,
                    dtype=dtype,
                    device="cpu",
                )
                npu_tensor = torch.empty(
                    numel,
                    dtype=dtype,
                    device="npu",
                )

                cpu_storage = cpu_tensor.untyped_storage()
                npu_storage = npu_tensor.untyped_storage()

                expected_nbytes = (
                    numel * cpu_tensor.element_size()
                )

                self.assertEqual(
                    cpu_storage.nbytes(),
                    expected_nbytes,
                )
                self.assertEqual(
                    npu_storage.nbytes(),
                    expected_nbytes,
                )
                self.assertEqual(
                    cpu_storage.nbytes(),
                    npu_storage.nbytes(),
                )

                # UntypedStorage的长度单位也是字节
                self.assertEqual(
                    len(cpu_storage),
                    expected_nbytes,
                )
                self.assertEqual(
                    len(npu_storage),
                    expected_nbytes,
                )

    def test_untyped_storage_nbytes_for_direct_storage(self):
        # 场景11:直接构造指定字节数的CPU/NPU Storage
        for device in ["cpu", "npu"]:
            with self.subTest(device=device):
                storage = torch.UntypedStorage(
                    37,
                    device=device,
                )

                self.assertEqual(
                    storage.device.type,
                    device,
                )
                self.assertEqual(storage.nbytes(), 37)
                self.assertEqual(storage.size(), 37)
                self.assertEqual(len(storage), 37)

    def test_untyped_storage_nbytes_for_empty_storage(self):
        # 场景12:空NPU Storage的字节数为0
        empty_npu_storage = torch.UntypedStorage(
            0,
            device="npu",
        )
        self.assertEqual(
            empty_npu_storage.nbytes(),
            0,
        )
        self.assertEqual(
            empty_npu_storage.size(),
            0,
        )
        self.assertEqual(
            len(empty_npu_storage),
            0,
        )

    def test_untyped_storage_nbytes_for_tensor_view(self):
        # 场景13:Tensor View返回整个底层Storage的字节数
        base_tensor = torch.empty(
            (4, 4),
            dtype=torch.float32,
            device="npu",
        )
        view_tensor = base_tensor[:, ::2]

        base_storage = base_tensor.untyped_storage()
        view_storage = view_tensor.untyped_storage()

        base_expected_nbytes = (
            base_tensor.numel()
            * base_tensor.element_size()
        )
        view_logical_nbytes = (
            view_tensor.numel()
            * view_tensor.element_size()
        )

        self.assertEqual(
            base_storage.nbytes(),
            base_expected_nbytes,
        )
        self.assertEqual(
            view_storage.data_ptr(),
            base_storage.data_ptr(),
        )
        self.assertEqual(
            view_storage.nbytes(),
            base_storage.nbytes(),
        )
        self.assertGreater(
            view_storage.nbytes(),
            view_logical_nbytes,
        )

    def test_untyped_storage_new_for_cpu_and_npu(self):
        # 验证普通CPU/NPU Storage调用new()后的基本属性
        for device in ["cpu", "npu"]:
            with self.subTest(device=device):
                source_storage = torch.empty(
                    8,
                    dtype=torch.float32,
                    device=device,
                ).untyped_storage()

                source_nbytes = source_storage.nbytes()
                new_storage = source_storage.new()

                self.assertIsInstance(
                    new_storage,
                    torch.UntypedStorage,
                )
                self.assertIsNot(
                    new_storage,
                    source_storage,
                )
                self.assertEqual(
                    new_storage.device.type,
                    source_storage.device.type,
                )
                self.assertEqual(
                    new_storage.nbytes(),
                    0,
                )
                self.assertEqual(
                    len(new_storage),
                    0,
                )

                # 调用new()不能改变原Storage
                self.assertEqual(
                    source_storage.nbytes(),
                    source_nbytes,
                )

    def test_untyped_storage_new_invalid_argument(self):
        # new()不接受size等位置参数
        source_storage = torch.empty(
            8,
            dtype=torch.float32,
            device="npu",
        ).untyped_storage()

        with self.assertRaises(TypeError):
            source_storage.new(1)

3. 测试命令

source /usr/local/Ascend/ascend-toolkit/set_env.sh
TORCH_DEVICE_BACKEND_AUTOLOAD=1 python pytorch-zjs/test/untypedStorage/test_untypedstorage_filename_hpu_nbytes_new.py -v

测试结果

A2/A3芯片

torch: 2.7.1+cpu
torch_npu: 2.7.1

image.png

torch: 2.11.0+cpu
torch_npu: 2.11.0

image.png

torch: 2.12.0+cpu
torch_npu: 2.12.0

image.png

A5芯片

v2.7.1

image.png

v2.11.0

image.png

v2.12.0

image.png

测试结论

torch.UntypedStorage.filename、torch.UntypedStorage.nbytes、 torch.UntypedStorage.new在torch-npu平台均完整支持。
torch.UntypedStorage.hpu 在torch-npu平台不支持,AssertionError断言符合预期。

【CheckList】

PR提交人对以下CheckList自检项进行全量自检,自检通过或不涉及,均修改 [ ] 为 [x]

likedislike
Pull Request已成功合入, 合并人@ascend-robot
(感谢 gcw_CHjSLYhf 的贡献)
Ggcw_CHjSLYhf
9月18日 创建了 pull request,commit d94392e9
Ggcw_CHjSLYhf
9月18日 关联了issue:8月社区任务第一期-Ascend for PyTorch API 一致性开发(78),8月社区任务第一期-Ascend for PyTorch API 一致性开发(89),8月社区任务第一期-Ascend for PyTorch API 一致性开发(99),8月社区任务第一期-Ascend for PyTorch API 一致性开发(100)
atomgit-bot
atomgit-bot
9月18日 评论:

变更摘要

本 PR 为资料(文档)更新,围绕 torch.UntypedStorage 系列 API 在 torch-npu 平台的支持情况,对多个 PyTorch 版本文档中的 torch-Storage.md 进行修正。涉及修正的 API 包括 torch.UntypedStorage.filename、torch.UntypedStorage.nbytes 和 torch.UntypedStorage.new,主要纠正其中关于 A5 芯片支持情况的描述错误;torch.UntypedStorage.hpu 因与 HPU 平台存在硬编码绑定,其系统支持情况无需修改。PR 说明中同时给出了相应的功能验证场景与测试结论,用于佐证文档修正依据。

主要改动

  • 修正 torch.UntypedStorage.filename 支持情况: 该项目已有该 API 资料条目,但其中 A5 支持情况有误,本次在文档中予以纠正。
  • 修正 torch.UntypedStorage.nbytes 资料错误: 针对 nbytes() 返回底层 Storage 实际字节数的语义,修正文档中存在的描述错误。
  • 修正 torch.UntypedStorage.new 资料错误: 修正 new() 在当前 Storage 所在设备上创建大小为 0 的新 UntypedStorage 相关文档描述。
  • 保持 torch.UntypedStorage.hpu 资料不变: 因该 API 与 HPU 平台存在硬编码绑定,系统支持情况无需补充或修正。
  • 同步更新四个版本目录文档: 改动覆盖 pytorch_2-7-1、pytorch_2-11-0、pytorch_2-12-0、pytorch_2-13-0 四个版本下的 torch-Storage.md。
likedislike
不准确?
atomgit-bot
atomgit-bot
9月18日 评论:

🤖 本次改动均为数据 / 生成 / 二进制文件(如测试用例、锁文件、媒体资源),没有可审查的代码逻辑,已跳过 AI 代码审查。

likedislike
不准确?
ascend-robotascend-robot成员
9月18日 添加了label:ascend-cla/yes
此处折叠了79条消息 查看更多
AtlasAccountAtlasAccount成员
14 天前 删除了label:ci-pipeline-running
AtlasAccountAtlasAccount成员
14 天前 添加了label:ci-pipeline-passed
AtlasAccount
AtlasAccount成员
14 天前 评论:
流水线 PR-pipeline_pytorch#71984 [ commitID:1e11092d ] 已完成
>>>代码风格自动修复执行成功(无修复内容)
阶段 任务名 状态 详情
编译构建 Build_X86 ⚪ IGNORED >>>
Build_ARM ⚪ IGNORED >>>
Build_X86_torchair ⚪ IGNORED >>>
Build_ARM_torchair ⚪ IGNORED >>>
patch_test ⚪ IGNORED >>>
Build_X86_213 ⚪ IGNORED >>>
Build_ARM_213 ⚪ IGNORED >>>
恶意代码检查 Antipoison ✅ COMPLETED >>>
编码安全与规范检查 check_error ✅ COMPLETED >>>
lintrunner ✅ COMPLETED >>>
开源片段检查 SCA ✅ COMPLETED >>>
开发者测试 UT_ARM_A2_Part_01 ⚪ IGNORED >>>
UT_ARM_A2_Part_02 ⚪ IGNORED >>>
UT_ARM_A2_Part_03 ⚪ IGNORED >>>
UT_inductor_Part_01 ⚪ IGNORED >>>
UT_inductor_Part_02 ⚪ IGNORED >>>
UT_inductor_Part_03 ⚪ IGNORED >>>
UT_inductor_Part_04 ⚪ IGNORED >>>
UT_DIST_ARM_Part_01 ⚪ IGNORED >>>
UT_DIST_ARM_Part_02 ⚪ IGNORED >>>
UT_DIST_ARM_Part_03 ⚪ IGNORED >>>
UT_DIST_ARM_Part_04 ⚪ IGNORED >>>
UT_ARM_A2_Select_Part_01 ⚪ IGNORED >>>
UT_ARM_A2_Select_Part_02 ⚪ IGNORED >>>
UT_inductor_Part1_213 ⚪ IGNORED >>>
流水线 PR-pipeline_pytorch ✅ COMPLETED >>>
此流水线已支持下列评论快捷指令,仅PR创建者和白名单成员[wujinyuan1, huangjingwei, liangsongwei, yashi999, culechan, Dring, wuyouqi1, L1919_snow, qq_52711437, WhiteNight12, nomiz, xiu_21, ffmh, wanglijun55, hss-shuai, husichao, smallsilly, lanshaozuishuai, jimmyisme1, lzy0920232, alpha-junh, Sunshine_Youngster, wei_zhuoyi, zhangyihuiben, zyw-hw, zzzkeke, rmch, yangch0324, LucciC, AACAES, renyujin, wjlflyer, senzhen-town, pengjingyou, qsc97, limuan, yule100, xiaoqi-zhou, kuhn7, chenxingying, hanye02, zichun_ye, anyrenwei, kkjocker, wangzili121, Lu_G, yvjc, puddingfjz, HandsoemLemon, bigprestigee1, huawuyi, zhenyu10, dairenjie, du-jin-hang, zou-jieyu, adelaideliu, TrHan, wanlinan, Windwindzzz, pengqihw, kisnwang, yuheng_wang, honghao_wang, jizewei, zhangguoguang, sunyu-xuan, chenrayray, hbhu_bin, liujunzhu, c_34, LiNuoh, maoyuanpeng1, zzhongmin, zhaoyu65, bellatan, jiabaolin, zhuofanshen, wencaiwen, lu_zhuge, caoshuyang, molly12, lyx324521, LQ1206, gitcode-chenjiao, cai-weiwei1989, CHDong, ogqin, yuanlipingGit, xuqinglin1, lqz2, zouwei1, chaoluoa, paradox325, jackzhang1116, yaoyao, akh, yujiacheng, dengjie0116, Hubert11111, Shine_Ws, wslhj555, longqiand, OYtao666, JiaqingQiang, luyyyy, Kingbelial, zhanghaiyu0101, wenxp1018, yanliu-luoluo, ksun_sekiro, liyong328, wgzheng, tangky, vivi_is_coding, aoiaoisola, weixin_44494597, wangmengmengwang65667, hid57809721, qq_35468730, comeonup, C547032, gcw_m5OQChA4, yao_yao_ling_xian, cnnbwcy, szqfes_12, cora_19, cann_lilin, can, shawnylee233, fanglanyue0916, hhz0, LiNuohang, taohuoquan, Jesse, WSs_321, SCh_zx, lvjiangdong, TonyYA, shi-yufeng99, qiaoyaodan, stonexxx, jimmycao9929, zzll, qindong0, luqichao, xinyuzhuo, liujm2001, dezheng889, zhucehw, HinPeng, rain-666, lsy_0111, a_knight, glf2016, SoraAzzz]评论有效
  • compile、compile_inductor、compile_torchair : 运行流水线
  • retry : 重试流水线所有失败子任务
  • retry <任务名> : 仅重试指定失败子任务
  • stop : 停止流水线
likedislike
ascend-robotascend-robot成员
14 天前 合入了pull request
AtlasAccount
AtlasAccount成员
14 天前 评论:
流水线 pytorch_gitcode_PR_multiVersion-onetrack#310 [ commitID:1e11092d ] 运行失败
likedislike