隐式数据类型提升介绍
================

本篇文章主要为你介绍飞桨的隐式类型提升机制，帮助你更好的使用飞桨。

一、背景
--------

类型提升是当两个不同数据类型进行运算(+/-/*//)时，自动计算结果应得的数据类型，这个操作在不同类型数据之间进行数据计算时，是必不可少的一个功能。

类型提升计算时，根据调用分别分为运算符重载、二元 API：

-  运算符重载方法指通过运算符直接进行计算，如 a+b, a-b 等调用方法；

-  二元 API 则是通过 API 的形式对函数进行调用，如 paddle.add(a,b)等。

隐式数据类型提升即为在不需要用户进行额外操作的情况下，自动的帮用户进行类型提升的行为。

二、飞桨的类型提升规则
------------------------------

飞桨的类型提升的支持范围和规则在 Tensor 之间，Tensor 和 Scalar 之间会有所不同，以下将分别展开介绍。

 ```python

    import paddle

    a = paddle.rand([3,3], dtype = 'float16')
    b = paddle.rand([3,3], dtype = 'float32')
    print (a + b) # 当 a 和 b 均为 Tensor 时，视为 Tensor 之间的计算

    a = paddle.rand([3,3], dtype = 'float16')
    b = 1.0
    print (a + b) # 当 a 和 b 其中任意一个为 Scalar 时，视为 Tensor 和 Scalar 之间的计算

 ```

1、 Tensor 之间的隐式类型提升规则介绍

-  由于模型训练中通常不会出现浮点型以外的不同类型之间的计算，为了更方便用户能够快速排查由于类型导致的问题，Tensor 之间的自动类型提升将仅支持浮点型之间的计算，以及复数和实数之间的计算，计算原则为返回两个 Tensor 中更大的数据类型，详情见下表：

+/-/* | bf16 | f16 | f32 | f64 | bool | u8 | i8 | i16 | i32 | i64 | c64 | c128 |
:-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: |
bf16 | bf16 | f32 | f32 | f64 | - | - | - | - | - | - | c64 | c128 |
f16 | f32 | f16 | f32 | f64 | - | - | - | - | - | - | c64 | c128 |
f32 | f32 | f32 | f32 | f64 | - | - | - | - | - | - | c64 | c128 |
f64 | f64 | f64 | f64 | f64 | - | - | - | - | - | - | c128 | c128 |
bool | - | - | - | - | - | - | - | - | - | - | c64 | c128 |
u8 | - | - | - | - | - | - | - | - | - | - | c64 | c128 |
i8 | - | - | - | - | - | - | - | - | - | - | c64 | c128 |
i16 | - | - | - | - | - | - | - | - | - | - | c64 | c128 |
i32 | - | - | - | - | - | - | - | - | - | - | c64 | c128 |
i64 | - | - | - | - | - | - | - | - | - | - | c64 | c128 |
c64 | c64 | c64 | c64 | c128 | c64 | c64 | c64 | c64 | c64 | c64 | c64 | c128 |
c128 | c128 | c128 | c128 | c128 | c128 | c128 | c128 | c128 | c128 | c128 | c128 | c128 |


-  以 paddle.add(a, b) 为例，上表中行为 a，列为 b。

以下是 Tensor 之间类型提升的样例：

 ```python

    import paddle

    # 浮点数间的计算
    a = paddle.rand([3,3], dtype = 'float16')
    b = paddle.rand([3,3], dtype = 'float32')
    c = a + b # 此时将自动进行类型提升，将 a 的数据类型 cast 为 float32，不需要用户进行额外操作
    print (c.dtype) # 输出类型为 float32

    a = paddle.rand([3,3], dtype = 'bfloat16')
    b = paddle.rand([3,3], dtype = 'float64')
    c = a + b # 此时将自动进行类型提升，将 a 的数据类型 cast 为 float64，不需要用户进行额外操作
    print (c.dtype) # 输出类型为 float64

    # 复数与实数之间的计算
    a = paddle.ones([3,3], dtype = 'complex64')
    b = paddle.rand([3,3], dtype = 'float64')
    c = a + b # 此时将自动进行类型提升，将 a 和 b 的数据类型 cast 为 complex128，不需要用户进行额外操作
    print (c.dtype) # 输出类型为 complex128

    # 复数之间的计算
    a = paddle.ones([3,3], dtype = 'complex128')
    b = paddle.ones([3,3], dtype = 'complex64')
    c = a + b # 此时将自动进行类型提升，将 b 的数据类型 cast 为 complex128，不需要用户进行额外操作
    print (c.dtype) # 输出类型为 complex128

 ```

2、 Tensor 与 Scalar 之间的隐式类型提升规则介绍

-  Tensor 与 Scalar 之间支持全类型自动类型提升，计算原则为在 Scalar 大类型一致（同为整型、浮点型等）时，向 Tensor 类型靠拢，否则与 Tensor 之间的计算结果一致。

-  其中视 int -> int64，float -> float32, bool -> bool, complex -> complex64。具体规则如下：

+/-/* | bool | int | float | complex |
:-----: | :-----: | :-----: | :-----: | :-----: |
bool | bool | i64 | f32 | c64 |
u8 | u8 | u8 | f32 | c64 |
i8 | i8 | i8 | f32 | c64 |
i16 | i16 | i16 | f32 | c64 |
i32 | i32 | i32 | f32 | c64 |
i64 | i64 | i64 | f32 | c64 |
bf16 | bf16 | bf16 | bf16 | c64 |
f16 | f16 | f16 | f16 | c64 |
f32 | f32 | f32 | f32 | c64 |
f64 | f64 | f64 | f64 | c128 |
c64 | c64 | c64 | c64 | c64 |
c128 | c128 | c128 | c128 | c128 |

以下是 Tensor 与 Scalar 之间类型提升的样例：

 ```python

    import paddle

    # 当 Scalar 在大类型上与 Tensor 类型一致时，结果返回 Tensor 的类型
    a = paddle.rand([3,3], dtype = 'float16')
    b = 1.0
    c = a + b # a 与 b 大类型一致，都为 float 类型，因此将 b 自动 cast 为 float16，不需要用户进行额外操作
    print (c.dtype) # 输出类型为 float16

    # 当 Scalar 在大类型上与 Tensor 类型不一致时，遵循 Tensor 之间的计算规则
    a = 1.0
    b = paddle.ones([3,3], dtype = 'int64')
    c = a + b # a 与 b 大类型不一致，将 b 自动 cast 为 float32，不需要用户进行额外操作
    print (c.dtype) # 输出类型为 float32

 ```

三、飞桨的隐式类型提升使用方法说明
------------------------------

1、对于支持隐式类型提升的情况

 ```python

    import paddle
    paddle.seed(2024)
    a = paddle.rand([3,3], dtype = 'float16')
    b = paddle.rand([3,3], dtype = 'float32')
    c = a + b # 此时将自动进行类型提升，将 a 的数据类型 cast 为 float32，不需要用户进行额外操作
    print (c.dtype) # float32

    # 符合交换律
    d = b + a
    print (d.dtype) # float32
    print (paddle.allclose(c, d)) # Tensor(shape=[], dtype=bool, place=Place(gpu:0), stop_gradient=True, True)

    # 与二元 API 计算结果一致
    e = paddle.add(a, b)
    print (e.dtype) # float32
    print (paddle.allclose(c, e)) # Tensor(shape=[], dtype=bool, place=Place(gpu:0), stop_gradient=True, True)

    # 与静态图计算结果一致
    paddle.enable_static()
    exe = paddle.static.Executor()
    train_program = paddle.static.Program()
    startup_program = paddle.static.Program()
    with paddle.static.program_guard(train_program, startup_program):
        paddle.seed(2024)
        a = paddle.rand([3,3], dtype = 'float16')
        b = paddle.rand([3,3], dtype = 'float32')
        f = paddle.add(a, b)
        res = exe.run(train_program, fetch_list=[f])
    print (res[0].dtype) # float32
    paddle.disable_static()
    print (paddle.allclose(c, paddle.to_tensor(res[0]))) # Tensor(shape=[], dtype=bool, place=Place(gpu:0), stop_gradient=True, True)

 ```

2、对于不支持隐式类型提升的情况

 ```python

    import paddle
    a = paddle.ones([3,3], dtype = 'int64')
    b = paddle.rand([3,3], dtype = 'float32')
    c = a + b # 此时由于不再支持 int 和 float 类型之间的自动类型提升，会报 Type Error

    # 对于不支持自动类型提升的情况，建议用户进行手动进行类型提升
    # 方法一：使用 astype
    a = a.astype("float32")
    a = a.astype(b.dtype)

    # 方法二：使用 cast
    a = paddle.cast(a, "float32")
    a = paddle.cast(a, b.dtype)

 ```

四、飞桨隐式类型提升的适用范围
------------------------------

截止到 Paddle 2.6 版本，具体支持的二元 API 及规则如下：

序号 | API | Tensor 之间的计算 | Tensor 和 Scalar 之间的计算 |
:-----: | :-----: | :-----: | :-----: |
1 | add | 通用规则 | 通用规则 |
2 | subtract | 通用规则 | 通用规则 |
3 | multiply | 通用规则 | 通用规则 |
4 | divide | 通用规则 | 除法规则 |
5 | floor_divide | 通用规则 | 通用规则 |
6 | pow | 通用规则 | 通用规则 |
7 | equal | 逻辑规则 | 逻辑规则 |
8 | not_equal | 逻辑规则 | 逻辑规则 |
9 | less_than | 逻辑规则 | 逻辑规则 |
10 | less_equal | 逻辑规则 | 逻辑规则 |
11 | greater_than | 逻辑规则 | 逻辑规则 |
12 | greater_equal | 逻辑规则 | 逻辑规则 |
13 | logical_and | 逻辑规则 | 逻辑规则 |
14 | logical_or | 逻辑规则 | 逻辑规则 |
15 | logical_xor | 逻辑规则 | 逻辑规则 |
16 | bitwise_and | - | 通用规则 |
17 | bitwise_or | - | 通用规则 |
18 | bitwise_xor | - | 通用规则 |
19 | where | 通用规则 | 通用规则 |
20 | fmax | 通用规则 | - |
21 | fmin | 通用规则 | - |
22 | logaddexp | 通用规则 | - |
23 | maximum | 通用规则 | - |
24 | minimum | 通用规则 | - |
25 | remainder(mod) | 通用规则 | 通用规则 |
26 | huber_loss | 通用规则 | - |
27 | nextafter | 通用规则 | - |
28 | atan2 | 通用规则 | - |
29 | poisson_nll_loss | 通用规则 | - |
30 | l1_loss | 通用规则 | - |
31 | huber_loss | 通用规则 | - |
32 | mse_loss | 通用规则 | - |

上表中存在两种特殊规则：

-  特殊规则-除法规则： 对于特殊 API divide 来说，其不会返回比 float 更小的类型。如 int32 / Scalar 返回 float32 。

 ```python

    import paddle
    a = paddle.ones([3,3], dtype = 'int32')
    b = 1
    c = a / b
    print (c.dtype) # float32

 ```

-  特殊规则-逻辑规则： 对于逻辑性 API 来说，由于复数类型无法直接进行逻辑运算，因此复数相关计算不在类型提升的支持范围内，在支持范围内其结果均返回 bool 值。

 ```python

    import paddle
    a = paddle.rand([3,3], dtype = 'float32')
    b = paddle.rand([3,3], dtype = 'float16')
    c = a == b
    print (c.dtype) # bool

 ```

五、总结
------------------------

Paddle 在支持隐式类型提升的基础上保证了计算符合交换律，运算符重载与二元 API 结果统一，动态图与静态图结果统一。本篇文章中明确了隐式类型提升的规则和支持范围，在介绍使用方法的同时根据当前版本总结了支持类型提升的二元 API 种类，希望能够借此增加用户在使用 Paddle 时的便利性。
