PyPTO cast 接口实战指南:UB Tile 逐元素数据类型转换与舍入模式详解
2026/9/19 14:02:40 网站建设 项目流程
  • 人工智能
  • 编译器
  • 模型编译
  • 高性能计算
  • 深度学习
  • CANN

【免费下载链接】pypto

PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。

项目地址:https://gitcode.com/cann/pypto
点击查看免费下载

导读

本文面向使用 CANN PyPTO(Parallel Tensor/Tile Operation 编程范式)在 Ascend 950 系列硬件上进行向量计算的开发者,系统讲解pypto_pro.language.cast这一 Tile 级逐元素类型转换接口的完整用法。文章覆盖接口函数原型、out/src/mode三参数的语义与约束、RoundMode 七种舍入模式的语义差异、30+ 条数据类型转换组合的完整支持矩阵、FP4 打包类型的特殊处理,以及 FP16→FP32 与 FP32→FP16 等实战代码示例,并辅以 Python 前端声明 与 NPU 代码生成映射 的源码级佐证。读完本文,你将能正确地在 PyPTO 内核中完成任意受支持的数据类型转换,并为精度敏感场景选择正确的舍入模式。

一、接口定位:Tile 级逐元素类型转换

pypto_pro.language.cast是 PyPTO Pro 语言中面向UB(Unified Buffer)Tile的数据类型转换接口,其行为可用以下公式概括:

$$ out_{i,j}=\operatorname{cast}{mode}(src{i,j}) $$

即:对源 Tile 有效区域内的每个元素逐一进行数据类型转换,并将结果写入目标 Tile 的对应逻辑坐标位置。目标数据类型完全由outTile 的dtype决定,接口本身不接收任何类型参数。

从源码看,该接口在 Python 前端中的声明位于 python/pypto_pro/language/_api.py:

@_api_decl def cast(out: Tile, src: Tile, *, mode: RoundMode = RoundMode.CAST_ROUND) -> None: """Cast Tile to a different data type. The target dtype is inferred from ``out`` tile's dtype. Args: out: Destination Tile (determines target dtype) src: Source Tile mode: Rounding mode — ``pl.RoundMode.CAST_NONE``, ``pl.RoundMode.CAST_RINT``, ``pl.RoundMode.CAST_ROUND``, ``pl.RoundMode.CAST_FLOOR``, ``pl.RoundMode.CAST_CEIL``, ``pl.RoundMode.CAST_TRUNC``, ``pl.RoundMode.CAST_ODD`` """

该函数在编译期被前端解析并生成 IR 调用,最终在 NPU 代码生成阶段映射为向量单元的单目转换指令。在 framework/src/codegen/npu/codegen_op_npu.cpp 的unaryOps_映射表中可以看到:

unaryOps_({ // cast op {Opcode::OP_CAST, [this]() { return GenCastOp(); }}, ...

cast在硬件指令层面与EXPSQRTABS等一元向量算子同级,属于向量(SIMD)计算单元的原生操作。

产品支持情况

产品形态支持情况
Ascend 950PR / Ascend 950DT支持
Atlas A3 训练系列产品 / Atlas A3 推理系列产品不支持
Atlas A2 训练系列产品 / Atlas A2 推理系列产品不支持

使用前请务必确认目标硬件为 Ascend 950 系列。

二、函数原型与参数说明

函数原型

pypto_pro.language.cast( out: Tile, src: Tile, *, mode: RoundMode = pl.RoundMode.CAST_ROUND, ) -> None

参数语义

参数输入/输出说明
out输出目标操作数,须为 UB Tile,目标数据类型由out.dtype确定。out.valid_shape指定本次转换的有效区域;src必须包含与该区域对应的有效元素,且源、目的 Tile 的形状和布局应满足逐元素对应访问要求。调用完成后,out的有效区域被转换结果覆盖。
src输入源操作数,须为 UB Tile。其有效区域必须覆盖out.valid_shape指定的转换区域,并保证该区域内的每个元素均可由out中的相同逻辑坐标访问。源、目的 Tile 的物理 shape 和行跨度可以不同,但均须为转换区域提供足够的存储空间。
mode输入(可选)pypto_pro.language.RoundMode 枚举类型,必须是编译期可确定的 RoundMode 枚举值。默认值为pypto_pro.language.RoundMode.CAST_ROUND。该参数只在转换路径涉及舍入时影响结果。

mode是唯一的关键字参数(*之后),调用时必须使用关键字形式传入,如mode=pl.RoundMode.CAST_RINT

三、三个容易踩坑的接口使用约束

1. 目标类型不通过参数传入

本接口没有dtypetarget_type参数,目标数据类型由预先创建的outTile 确定。这与 Tensor 前端的pypto.cast(src, dtype)风格不同,不能写成:

dst = pypto_pro.language.cast(src, pl.DT_FP32) # 错误写法

2. 接口不返回新 Tile

转换结果直接写入out,接口返回None。调用模式是先建 Tile、再调用、后使用

pl.cast(out_tile, src_tile, mode=pl.RoundMode.CAST_RINT) # 正确

3. 同类型转换不是通用拷贝

当前仅支持DT_FP32 -> DT_FP32这一种同类型转换,且该路径执行的是舍入操作而非原值拷贝(即对 FP32 数据按指定模式重新舍入)。其他同类型组合(如 FP16→FP16、INT32→INT32)均不支持。若需要数据搬运,应使用其他拷贝/搬运接口。

四、RoundMode 舍入模式详解

mode参数的类型为pypto_pro.language.RoundMode枚举,其完整定义见 RoundMode 文档,七个枚举值的语义如下:

枚举值说明示例
CAST_NONE不显式指定舍入规则,具体转换行为由使用该枚举的接口定义-
CAST_RINT舍入到最近值,中间值取偶数(银行家舍入)2.5 → 2,3.5 → 4
CAST_ROUND舍入到最近值,中间值远离零2.5 → 3,-2.5 → -3
CAST_FLOOR向负无穷方向舍入1.6 → 1,-1.6 → -2
CAST_CEIL向正无穷方向舍入1.6 → 2,-1.6 → -1
CAST_TRUNC向零方向舍入1.6 → 1,-1.6 → -1
CAST_ODD发生精度丢失时,将结果的最低有效位设为 1DT_FP32 转 DT_FP16:1.0001 → 1.0009765625

CAST_RINTCAST_ROUND的区别在于中间值(恰好在两个可表示值正中间)的处理方向:CAST_RINT取偶数,CAST_ROUND远离零。CAST_TRUNCCAST_FLOOR在正数域行为一致,但在负数域不同(-1.6 分别为 -1 与 -2)。

cast 接口特有的舍入规则

  • 不涉及舍入的路径(类型扩展、整数窄化):mode不参与结果计算,建议使用CAST_NONE表达意图。
  • DT_FP16/DT_FP32 -> DT_HF8路径:固定采用CAST_ROUND,传入其他mode不会改变该路径的舍入行为。
  • CAST_ODD的适用范围:除DT_FP32 -> DT_FP16外,对其他路径传入CAST_ODD不会执行奇数舍入,而会回退到CAST_NONE对应的默认规则——浮点数转整数使用CAST_TRUNC,其他路径使用CAST_RINT

接口默认值与缺省行为

接口缺省传入CAST_ROUND(与 python/pypto_pro/language/_api.py 中的默认参数一致)。但需要注意:缺省值并不等于所有路径都按“中间值远离零”执行,因为各路径对CAST_ROUND的实际处理遵循上节规则。因此,在不需要舍入的扩展/窄化路径上显式传入CAST_NONE是更清晰的表达方式。

五、支持的数据类型转换矩阵(完整版)

下表为源/目的数据类型完整支持组合,未列出的组合不支持。“支持的有效舍入模式”指能够实际改变舍入结果的模式;CAST_NONE可用于所有列出的转换,并按上一节规则处理。

源数据类型目的数据类型支持的有效舍入模式特殊说明
DT_FP32DT_FP32CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNC同类型舍入,不是原值拷贝
DT_FP32DT_FP16CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNC、CAST_ODD-
DT_FP32DT_BF16CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNC-
DT_FP32DT_INT16、DT_INT32、DT_INT64CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNCCAST_NONE 使用 CAST_TRUNC
DT_FP32DT_FP8E4M3FN、DT_FP8E5M2CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNC-
DT_FP32DT_HF8固定 CAST_ROUND其他 mode 不改变舍入行为
DT_FP16DT_FP32不涉及舍入类型扩展,mode 不影响结果
DT_FP16DT_INT8、DT_UINT8、DT_INT16、DT_INT32CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNCCAST_NONE 使用 CAST_TRUNC
DT_FP16DT_HF8固定 CAST_ROUND其他 mode 不改变舍入行为
DT_BF16DT_FP32不涉及舍入类型扩展,mode 不影响结果
DT_BF16DT_INT32CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNCCAST_NONE 使用 CAST_TRUNC
DT_BF16DT_FP16CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNC-
DT_BF16DT_FP4E2M1、DT_FP4E1M2CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNCFP4 为打包类型,两个元素共用一个字节
DT_FP4E2M1、DT_FP4E1M2DT_BF16不涉及舍入类型扩展,mode 不影响结果
DT_UINT8DT_FP16、DT_UINT16不涉及舍入类型扩展,mode 不影响结果
DT_INT8DT_FP16、DT_INT16、DT_INT32不涉及舍入类型扩展,mode 不影响结果
DT_INT16DT_FP16CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNC-
DT_INT16DT_FP32、DT_UINT32、DT_INT32不涉及舍入类型扩展,mode 不影响结果
DT_INT16DT_UINT8不涉及舍入整数窄化,mode 不影响结果
DT_INT32DT_FP32CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNC-
DT_INT32DT_INT64不涉及舍入类型扩展,mode 不影响结果
DT_INT32DT_UINT8、DT_INT16、DT_UINT16不涉及舍入整数窄化,mode 不影响结果
DT_UINT32DT_UINT8、DT_UINT16、DT_INT16不涉及舍入整数窄化,mode 不影响结果
DT_INT64DT_FP32CAST_RINT、CAST_ROUND、CAST_FLOOR、CAST_CEIL、CAST_TRUNC-
DT_INT64DT_INT32不涉及舍入整数窄化,mode 不影响结果
DT_FP8E4M3FN、DT_FP8E5M2、DT_HF8DT_FP32不涉及舍入类型扩展,mode 不影响结果

读取矩阵的三个要点

  1. “不涉及舍入”列:类型扩展(如 FP16→FP32、INT8→INT16)与整数窄化(如 INT16→UINT8、INT32→INT16)不产生精度舍入,任何mode均不影响结果。
  2. 浮点→整数列:当使用CAST_NONE(或传入无效的CAST_ODD回退)时,浮点转整数默认使用CAST_TRUNC(向零截断),这是最容易忽略的默认行为。
  3. FP8/FP4 系列DT_FP8E4M3FNDT_FP8E5M2DT_HF8是三种不同的 8 位浮点格式,DT_FP4E2M1DT_FP4E1M2为 4 位浮点格式,各有独立的转换路径。

六、其他约束:存储、地址与编译期要求

UB 存储与 valid_shape 约束

  • outsrc必须均为 UB Tile。转换范围由out.valid_shape确定,src的有效区域必须覆盖该范围;源、目的 Tile 的物理 shape 和行跨度可以不同,但必须保证转换区域内的元素能够按相同逻辑坐标逐元素对应。接口只定义out有效区域内的转换结果,不应依赖该区域之外的内容。
  • 若使用valid_shape局部转换,务必保证src对应区域的数据已就绪(通常先执行pl.load再执行pl.cast)。

原地转换与地址重叠

  • DT_FP32 -> DT_FP16支持outsrc使用完全相同的 UB 起始地址进行原地转换
  • 除该场景外,不保证源、目的存储区域发生部分重叠或完全重叠时的转换结果;使用其他数据类型组合时,应为outsrc分配互不重叠的 UB 存储区域。

FP4 打包类型的 shape 要求

DT_FP4E2M1DT_FP4E1M2

  • Tile 的 shape 按逻辑元素计数,两个相邻元素打包在一个字节中,因此物理 shape 的最后一维必须为 2 的倍数
  • 执行DT_BF16 -> FP4转换时,valid_shape最后一维也应为偶数,以保证每个有效元素均能组成完整的打包字节。

mode 必须是编译期常量

mode必须在编译期确定,不能使用运行期 Scalar 或 Tensor 值动态选择。这保证了编译器可以在代码生成阶段将舍入模式直接编码进OP_CAST指令属性中(参见 codegen_op_npu.cpp 的GenCastOp分发逻辑)。

七、调用示例

7.1 完整内核:FP16→FP32 类型扩展

以下示例展示了在一个@pl.jit内核中完成从 GM(Global Memory)加载 FP16 数据、转换为 FP32 并写回的完整流程:

import pypto_pro.language as pl @pl.jit(auto_mutex=True) def cast_kernel( src: pl.Tensor[[64, 128], pl.DT_FP16], out: pl.Tensor[[64, 128], pl.DT_FP32], ): src_type = pl.TileType( shape=[64, 128], dtype=pl.DT_FP16, target_memory=pl.MemorySpace.Vec, ) out_type = pl.TileType( shape=[64, 128], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Vec, ) src_tile = pl.make_tile_group(type=src_type, addrs=0x0000, mutex_ids=[0]) out_tile = pl.make_tile_group(type=out_type, addrs=0x4000, mutex_ids=[1]) with pl.section_vector(): src_current = src_tile.current() out_current = out_tile.current() pl.load(src_current, src, [0, 0]) pl.cast(out_current, src_current, mode=pl.RoundMode.CAST_NONE) pl.store(out, out_current, [0, 0])

代码要点:

  • Tile 声明src_typeout_type均为target_memory=pl.MemorySpace.Vec(UB 空间),分别以0x00000x4000起始地址创建 Tile Group,并通过mutex_ids声明互斥,避免读写冲突;
  • 转换区域src_tile.current()/out_tile.current()取得当前迭代的 Tile 视图,pl.load将 GM 数据载入 UB,随后pl.cast执行转换,最后pl.store写回 GM;
  • mode 选择:FP16→FP32 是类型扩展、不涉及舍入,因此显式使用CAST_NONE表达意图,与转换矩阵中“mode 不影响结果”的说明一致。

该内核的典型运行结果(示意,节选):

输入数据src:[[-4 -3.75 -3.5 -3.25 -3 -2.75 -2.5 -2.25 ...], [28 28.25 28.5 28.75 29 29.25 29.5 29.75 ...], [60 60.25 60.5 60.75 61 61.25 61.5 61.75 ...], [92 92.25 92.5 92.75 93 93.25 93.5 93.75 ...], ...] 输出数据out:[[-4 -3.75 -3.5 -3.25 -3 -2.75 -2.5 -2.25 ...], [28 28.25 28.5 28.75 29 29.25 29.5 29.75 ...], [60 60.25 60.5 60.75 61 61.25 61.5 61.75 ...], [92 92.25 92.5 92.75 93 93.25 93.5 93.75 ...], ...]

可以看到,FP16 值(如 -4、-3.75)扩展为 FP32 后数值完全一致,符合类型扩展的预期。

7.2 舍入模式选择示例

在实际内核中,pl.cast通常作用于已加载到 UB 的 Tile:

# FP32 -> FP16:舍入到最近值,中间值取偶数。 pl.cast(dst_fp16, src_fp32, mode=pl.RoundMode.CAST_RINT) # FP32 -> INT32:向负无穷方向舍入。 # 例如,1.6转换为1,-1.6转换为-2。 pl.cast(dst_int32, src_fp32, mode=pl.RoundMode.CAST_FLOOR) # FP16 -> FP32:类型扩展,不需要舍入。 pl.cast(dst_fp32, src_fp16, mode=pl.RoundMode.CAST_NONE)

选型建议

  • 浮点→浮点降精度(如 FP32→FP16、FP32→BF16):默认CAST_ROUND即可;若需确定性统计行为可选CAST_RINT(银行家舍入);需要向下/向上取整时选CAST_FLOOR/CAST_CEIL
  • 浮点→整数:明确指定CAST_TRUNC(截断)或CAST_FLOOR/CAST_CEIL(按需定向舍入),避免依赖默认的回退规则;
  • 需要“远离零”语义(如量化场景)且不关心偶数规则时,CAST_ROUND是直观选择。

八、相关接口与进一步阅读

cast属于 SIMD-API 中的类型转换(Type Conversion)接口族,同目录下还包含融合转换类接口,便于在向量计算流水线中减少中间 Tile 的读写:

  • 融合算子(定义于 python/pypto_pro/language/_api.py):add_relu_castsub_relu_castmul_cast等将计算与转换合并为单条指令;
  • SIMT 标量场景:pl.simt.cast(value, dtype, mode=...)提供标量级转换,其参数校验实现在 python/pypto_pro/ir/op/simt_ops.py,注意该接口仅能在@pl.vector_function(mode="simt")函数内使用,且value必须是标量表达式、dtype必须是pl.DT_*值;
  • 舍入模式枚举的通用语义,参见 RoundMode 文档;
  • 更多向量计算接口(addmulexprelu等)可查阅 SIMD-API 索引。

九、总结

pypto_pro.language.cast是 PyPTO 向量编程中唯一面向 Tile 的逐元素类型转换接口,其核心设计可归纳为三点:目标类型由outTile 决定、结果写回out而非返回新 Tile、舍入行为由编译期常量mode控制。使用时的关键决策路径是:先确认目标硬件为 Ascend 950 系列,再核对目标类型组合是否出现在支持矩阵中,然后根据转换路径(扩展/窄化/降精度/整数化)选择合适的舍入模式,最后为outsrc分配满足重叠规则的 UB 存储。掌握上述规则,即可在量化、精度混合计算和算子融合场景中安全高效地完成各类类型转换。

  • 人工智能
  • 编译器
  • 模型编译
  • 高性能计算
  • 深度学习
  • CANN

【免费下载链接】pypto

PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。

项目地址:https://gitcode.com/cann/pypto
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询