取消
显示结果 
显示  仅  | 搜索替代 
您的意思是: 

Vitis AI - 如何利用张量提升内存使用效率

yolanda
Moderator
Moderator
0 0 282

 

 

BY Giovanni Guasti

注意:本论坛博客所有内容皆来源于Xilinx工程师,如需转载,请写明出处作者及赛灵思论坛链接并发邮件至cncrc@xilinx.com,未经Xilinx及著作权人许可,禁止用作商业用途 


在数据处理中,对原始数据进行重塑或重新排序并创建多个副本是很常见的行为。无论执行任何新步骤,都会创建新副本。随着程序的增大,占用的内存也会增大,我几乎从未考虑过这个问题,直到遇到了“内存不足”错误。

张量 (tensor) 的神奇之处在于多个张量可以引用同一存储空间(即包含给定类型的数字的连续内存区块)。此行为由 torch.storage 进行管理。

每个张量都包含 .storage 属性,用于显示内存中存储的张量内容。

在下一篇的文章中,我将聊一聊张量所具有的更神奇的属性,即跟踪上级操作,但在本文中,我将主要介绍内存优化方面的内容。

:全新 Vitis AI 1.2 发行版将首次为 PyTorch 提供支持。本文对于新增对此热门框架的支持表示祝贺,并提供了 1 个 PyTorch 专用的 Jupyter Notebook 格式示例。

输入 [1]:

 

import torch
a = torch.randint(0, 9, (5,3))
a

 

输出 [1]:

 

tensor([[4, 1, 6],
        [0, 8, 8],
        [1, 2, 1],
        [0, 5, 7],
        [0, 0, 7]])

 

输入 [2]:

 

a.storage() 

 

输出 [2]

 

 4
 1
 6
 0
 8
 8
 1
 2
 1
 0
 5
 7
 0
 0
 7
[torch.LongStorage of size 15]

 

输入 [3]:

 

a.shape

 

输出 [3]:

 

torch.Size([5, 3])

 

 

我们可能需要对原始“a”张量进行转置 (transpose) 和平展 (flatten) 处理。

何必为了相同数据浪费双倍内存?哪怕数据只是形状 (shape) 不同,也没有必要。

输入 [4]:

 

b = torch.transpose(a, 0, 1)
b

 

输出 [4]:

 

tensor([[4, 0, 1, 0, 0],
        [1, 8, 2, 5, 0],
        [6, 8, 1, 7, 7]])

 

a 和 b 确实是指向相同存储空间的张量。

两者表现方式不同,原因在于我们使用 stride 函数指令其按不同顺序读取该存储空间。

b 的 stride 值为 (1,3),即读取存储空间时,每隔 1 个元素都必须跳至下一行,并且每隔 3 个元素必须跳至下一列。

输入 [5]:

 

b.stride(), a.stride()

 

输出 [5]:

 

((1, 3), (3, 1))

 

 

我们可以从 a 或 b 访问数据,或者也可以从原始存储空间直接访问数据。

但如果从存储空间访问,则读取的值将不再是张量。

输入 [6]:

 

a[1,2], b[2,1], a.storage()[5], b.storage()[5] 

 

输出 [6]:

 

(tensor(8), tensor(8), 8, 

 

现在,令我感到疑惑不解的是,我发现这些张量的值神奇般地自行发生了改变:

更改 a 时,b 也变了。

输入 [7]:

 

a[0,0] = 10
b[0,0]

 

输出 [7]:

 

tensor(10)

 

发生这种状况的原因是因为,从内存角度来看,张量即经过排序的存储空间表示法。

从同一存储空间生成的 2 个张量并非独立张量,而且我必须牢记的是,当我每次更改 1 个张量后,指向相同存储空间的所有其它张量也都会被修改。

可见,即使高效的内存利用方式也难免有其缺点!

 

子集

通过原始数据的子集仍然能够有效利用内存。

新的张量仍然指向原始存储空间的子集。

输入 [8]:

 

c = a[0:2, 0:2]
c

 

输出 [8]:

 

tensor([[10,  1],
        [ 0,  8]])

 

输入 [9]:

 

c[0,0]=77
a

 

输出 [9]:

 

tensor([[77,  1,  6],
        [ 0,  8,  8],
        [ 1,  2,  1],
        [ 0,  5,  7],
        [ 0,  0,  7]])

 

 

Inplace 运算符

inplace 运算符即无需创建张量副本就可以直接对存储空间进行操作的函数。这些运算符通常具有易于识别的名称且以下划线结尾。

输入 [10]:

 

a.zero_()
b

 

输出 [10]:

 

tensor([[0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0]])

 

 

张量克隆

如果确实需要 1 个独立的新张量,可以对其进行克隆。

这样也会创建新的存储空间。

 

输入 [11]:

 

a_clone = a.clone()
a_clone[0,0] = 55
a_clone

 

输出 [11]:

 

tensor([[55,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0]])

 

输入 [12]:

 

a

 

输出 [12]:

 

tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])

 

 

为连续张量重组存储空间

部分函数仅适用于连续张量。

对 a 进行转置时,通过在 b 中分配来自存储空间的非连续矩阵值,生成了新的张量。

输入 [13]:

 

a.is_contiguous()

 

输出 [13]:

 

True

 

输入 [14]:

 

b.is_contiguous()

 

输出 [14]:

 

False

 

 

我们可将 b 设为连续张量,但这将导致 b 生成经过重组的新存储空间,从而导致 a 和 b 永远无法成为独立张量:

输入 [15]:

 

b = b.contiguous()
b[0,0] = 18
a[0,0]

 

输出 [15]:

 

tensor(0)

 

输入 [16]:

 

b.is_contiguous()

 

输出 [16]:

 

True

 

输入 [17]:

 

a.is_contiguous()

 

输出 [17]:

 

True

 

 

Original Source: https://forums.xilinx.com/t5/Design-and-Debug-Techniques-Blog/Vitis-AI-How-tensors-allow-for-efficent-memory-usage/ba-p/1120174