TIK作用域

什么是TIK作用域

TIK的作用域即一个TIK变量有效的区域,又可叫生命周期或活跃状态。

一个TIK变量在申请时被创建,至它所在的代码块末尾时被释放,在创建到释放之间处于活跃状态,并且只有在活跃状态时/生命周期/作用域内才能被访问。

在TIK中访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。

Scalar的作用域

Scalar的作用域/生命周期符合如下规则:

  1. Scalar自申请时被创建,跳出所在代码块,则被释放,创建与释放之间的状态称为活跃状态。
  2. Scalar只有在活跃状态时能被访问。

Scalar的作用域/生命周期示例如图1所示,该图显示了S0,S1,S2三个变量的活跃状态。

图1 Scalar生命周期示例

TIK中,定义函数、for循环、if判断等都会引入新的作用域,外部无法访问在它们的作用域内定义的变量,例如:

g_scalar = tik_instance.Scalar(dtype = "int16", init_value = 1)                    # 全局作用域,g_scalar在全局都能被访问
def tik_func_1():
    func_scalar = tik_instance.Scalar(dtype = "int16", init_value = 2)             # func_scalar 仅在函数tik_func_1的作用域内被访问,此处可访问g_scalar和func_scalar
    with tik_instance.if_scope(func_scalar < 5):                                    
       if_scalar = tik_instance.Scalar(dtype = "int16", init_value = 3)            # if_scalar仅在if的作用域内被访问,此处可访问g_scalar,func_scalar和if_scalar
            with tik_instance.for_range(0, 4) as i:                                 
                for_scalar = tik_instance.Scalar(dtype = "int16", init_value = 4)   # for_scalar仅在for的作用域内被访问,此处可访问g_scalar,func_scalar,if_scalar和for_scalar

Tensor的作用域

Tensor的作用域/生命周期符合如下规则:

  1. Tensor自申请时被创建,跳出所在代码块,则被释放,创建与释放之间的状态称为活跃状态。
  2. Tensor只有在活跃状态时能被访问。
  3. 任何时刻,活跃状态的Tensor所占用的buffer总大小,不超过对应物理buffer的总大小。

Tensor的作用域/生命周期示例如图2所示。

图2 Tensor生命周期示例

上述示例中,代码被分成了5个时段,分别用1-5表示,每个时段活跃的Tensor以及总共使用的UB大小如表1所示。

表1 每个时段Tensor所占UB大小

时段

活跃Tensor

使用UB总大小

1

B0

256*2 Byte

2

B0,B1

256*2*2 Byte

3

B0,B1,B2

256*3*2 Byte

4

B0,B1,B3

256*3*2 Byte

5

B0,B4

256*2*2 Byte

实际开发活动中,输入张量shape可能超过Unified Buffer容量上限,必须经过多次传输才能完成计算;此时,为最大化利用Unified Buffer存储空间,数据定义指定的shape大小一般为Unified Buffer允许的最大值。代码示例如下:

# 获取Unified Buffer空间大小,单位为Byte
ub_size_bytes = tbe.common.platform.get_soc_spec("UB_SIZE")           # 例如:unified buffer空间大小为:128 Bytes
 
# Unified Buffer上数据读取和写入必须32 Bytes对齐,一个block的大小为32 Bytes
block_byte_size = 32
 
# 根据输入的数据类型dtype_x,计算一个block可以存放多少个对应的元素
def get_bit_len(dtype):
    index = 0
    for i in dtype:
        if i.isdigit():
            break
        index += 1
    return int(dtype[index:])

dtype_bytes_size = get_bit_len(dtype_x) // 8   # 将bit换算成byte,输入数据类型为int16,一个int16的数为 2 Bytes
data_each_block = block_byte_size // dtype_bytes_size         # 则一个block可以放 32/2=16 个元素
 
# 计算在Unified Buffer上需要分别分配多少空间,并进行32Bytes对齐
ub_tensor_size = (ub_size_bytes // dtype_bytes_size //        # 则 ub_tensor_size = 128 // 2 // 16 * 16 = 64
data_each_block * data_each_block)                            # 即 最多定义包含64个int16的元素的Tensor
 
# 在Unified Buffer上创建tensor input_x_ub
input_x_ub = tik_instance.Tensor(dtype_x, (ub_tensor_size,), 
name="input_x_ub", scope=tik.scope_ubuf)

另外,用户也可通过地址复用(或称地址重叠)以节省Unified Buffer存储空间。以矢量单目运算为例,当地址复用满足相应约束时,用户可以定义一个Tensor供源操作数与目的操作数同时使用,此时能节省一倍的存储空间。

'''
示例:
tensor_a 既是源操作数,也是目的操作数
下面这条语句将tensor_a的值修改为它的绝对值:
before: tensor_a = [-3, -2, -1, 0, 1, 2, 3, ...]
after: tensor_a = [3, 2, 1, 0, 1, 2, 3, ...]
'''
tensor_a = tik_instance.Tensor("int16", (128,), name="tensor_a", scope=tik.scope_ubuf)
tik_instance.vec_abs(128, tensor_a, tensor_a, 1, 8, 8)

需特别注意因地址对齐导致的超出对应内存类型的总容量时,会引起编译报错。

例如:在下图示例中,假设UB的空间大小为1024B(B为Byte的简写),申请两个UB Buffer的tensor_a和tensor_b,大小分别为1008B和16B,内存分配时按照32Byte对齐要求向上对齐到1024B和32B,实际要求内存共1056B,大于UB Buffer的内存总容量1024B。

图3 因对齐产生的空间超限

用户定义的Tensor在内存分配时会对起始地址进行对齐,不同scope的对齐要求如下表:

表2 不同scope的对齐要求

scope

对齐要求

Unified Buffer

Atlas 200/300/500 推理产品,要求32Byte对齐

Atlas 训练系列产品,要求32Byte对齐

Atlas推理系列产品AI Core,要求32Byte对齐

Atlas推理系列产品Vector Core,要求32Byte对齐

Atlas A2训练系列产品,要求32Byte对齐

L1 Buffer

512Byte对齐

L1OUT Buffer

float16类型数据要求512Byte对齐;float32/int32/uint32类型数据要求1024Byte对齐

Global Memory

暂无对齐要求

使用TIK数据计算和数据搬运接口时,目的操作数和源操作数地址偏移对齐要求和上表保持一致,如果TIK指令接口中已说明操作数起始地址对齐要求,则以具体指令中的说明为准。