惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
The Last Watchdog
The Last Watchdog
C
Cisco Blogs
L
LINUX DO - 热门话题
T
Tenable Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cyberwarzone
Cyberwarzone
O
OpenAI News
The Hacker News
The Hacker News
V
Vulnerabilities – Threatpost
A
Arctic Wolf
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Vercel News
Vercel News
量子位
V2EX - 技术
V2EX - 技术
GbyAI
GbyAI
W
WeLiveSecurity
Recorded Future
Recorded Future
P
Proofpoint News Feed
Project Zero
Project Zero
P
Palo Alto Networks Blog
Microsoft Security Blog
Microsoft Security Blog
S
Security Affairs
Stack Overflow Blog
Stack Overflow Blog
B
Blog
Y
Y Combinator Blog
月光博客
月光博客
K
Kaspersky official blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Latest news
Latest news
Forbes - Security
Forbes - Security
T
Troy Hunt's Blog
T
The Blog of Author Tim Ferriss
C
Check Point Blog
D
DataBreaches.Net
MyScale Blog
MyScale Blog
博客园 - 三生石上(FineUI控件)
博客园 - 聂微东
D
Docker
P
Privacy International News Feed
C
Cybersecurity and Infrastructure Security Agency CISA
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
Cyber Attacks, Cyber Crime and Cyber Security
S
Securelist
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
雷峰网
雷峰网
SecWiki News
SecWiki News
腾讯CDC
博客园 - Franky

Пусть этот камень будет более крепким, чем человек

【琐记】烟火与尘埃 【Triton】Triton实现矩阵乘 【LLM推理加速】FlashAttention 【LLM推理加速】PagedAttention 【LLM推理加速】Online Softmax LLM基础知识【1】 Transformer模型 【AI编译】LayerGroup Tiling Tile的疑惑和思考 【AI编译】深度优先的Tile调度,万事大吉? 【AI编译】多级流水线Tile调度策略 【CUDA C++】GPU内存使用【3】 【AI编译】Cache缓存地址映射 【CUDA C++】GPU存储【2】 【CUDA C++】GPU基本介绍【1】 【00】0序章-不受欢迎的来客 【转载】我来了——持续低熵 【Halide】调度优化【2】 【感想】写作进度报告5 【Halide】调度优化【1】 【转载】北大中文男足战报2 【BYOC】TVM切分子图 【转载】北大中文男足战报1 SystemC 用寄存器同步建模方法 【脉动阵列】脉动阵列类型 【im2col】AScend conv accelerate 【感想】写作进度报告4 【BYOC】TVM添加自定义编译器 ccompiler 【感想】写作进度报告3 【Tengine】推理流程脑图【2】 【Tengine】推理流程脑图【1】 【NCNN】学习ncnn模型转换 【编译器】使用llvm编译自定义语言【3】编译 object 【编译器】使用llvm编译自定义语言【2】转llvm IR 【编译器】使用llvm编译自定义语言【1】构建AST 【AI编译】如何进行内存分配 【感想】写作进度报告2 【AI编译】layer-group之后如何tiling 【AI编译】如何进行layer-group 【量化】连续卷积层首尾量化的可行性 【Gemm】内存对齐 【gemm】Gemm计算加速 【TVM】通过代码学习编译流程【5】FuseOps 【TVM】通过代码学习编译流程【6】CodeGen 【TVM】通过代码学习类【3.5】Pass 【TVM】通过代码学习编译流程【4】BuildRelay 【AI编译】Tiling操作能优化什么时间 【TVM】通过代码学习编译流程【3】模型编译 【TVM】通过代码学习编译流程【2】模型转换 【TVM】通过代码学习编译流程【1】必要知识 【感想】写作进度报告1 【Winograd】卷积加速算法原理及实现 SystemC 等待异步事件解决方案 【TVM】Python脚本实现模型编译和保存 【推理引擎】常见AI推理框架 【3D建模】T110E3卡迪夫蓝调皮肤模型 【TVM】C++部署运行TVM 【推理引擎】NCNN和Tengine量化推理逻辑对比 【3D建模】IS-7攻城锤流纹岩皮肤展示 【TVM】根据例子走通代码库 博客汇总目录 【Im2Col】卷积加速算法【2】NHWC 【Im2Col】卷积加速算法【1】 NCHW openBlas库的安装与简单使用 C语言工程调用Cpp库解决方案 foo Hello World
【AI编译】张量生命周期管理
Post author: XianMu@Пусть этот камень будет более крепким, чем ч · 2025-08-31 · via Пусть этот камень будет более крепким, чем человек

# 前言

本篇文章总结【张量生命周期优化】算法。

作为初学者,错误在所难免,还望不吝赐教。

# 介绍

AI 编译器设计中用于神经网络模型推理的内存管理模块, 通常被称为内存分配规划 或 张量生命周期优化。
为了减少内存占用,提高内存利用率,对 Tensor 的内存分配和生命周期的管理必不可少。

内存分配就像是个拼图游戏,在二维坐标中,横轴是时间,纵轴是内容空间,然后将神经网络模型推理过程中需要用到的内存块, 拼到这个二维坐标中。

神经网络模型推理主要涉及三部分 Tensor 内存占用:输入输出 tensor、权重 tensor、临时中间 tensor。将这些 tensor 拼接到如下的坐标轴中,横轴是内存空间,纵轴是周期 TimeStep,合理分配,以最大化内存利用率。

如下图

算能无切分情况的分配

图源 - 无切分情况的分配

按照神经网络的执行顺序(拓扑顺序)来划分周期,以下面一个网络结构为例,周期可以如下划分。

算能LMEM的ID分配

图源 - LMEM 的 ID 分配

算能TimeStep分配

图源 - TimeStep 分配

# 算法

只考虑静态内存分配。动态内存分配的 tensor 大小不固定、形状不固定,可能需要在推理过程中边推理边分配内存。但是静态情况下,需要开辟的空间时已知的。

# 贪心算法

贪心算法的 “贪心” 体现在:在为当前要分配的张量做决策时,算法只着眼于 “当前这一步” 所能找到的最优解(即最能节省当前内存空间的放置位置),而不会为了全局最优(整个推理过程的绝对最低峰值内存)去考虑未来的张量该如何分配。这是一种局部最优(Local Optimal)策略,期望通过每一步的局部最优选择,最终得到一个接近全局最优的可行解。
1. 生命周期分析:首先,遍历计算图,为每个张量(Tensor)确定其 “出生时间”(定义或计算完成时)和 “死亡时间”(最后一次被使用时)。
2. 排序:将所有的张量按照其 “出生时间” 排序。
3. 分配:模拟一个从低地址到高地址的内存空间,可以用一个链表来存放。初始情况下链表中只有一个拥有所有空间、未占用的内存块。按顺序处理每个张量:检查当前所有未分配内存块,找到一个足够大的空闲内存块来存放新张量。张量生命周期结束时,即使释放对应的内存块,如果释放时前后内存连续,可以将其融合为一个大的内存块。
寻找空闲块的策略:

  • 首次适应(First-Fit):从低地址开始扫描,找到第一个足够大的空闲块就分配。
  • 最佳适应(Best-Fit):扫描所有空闲块,找到能满足需求且大小最接近的空闲块进行分配,以减少内存碎片。
  • 最坏适应(Worst-Fit):总是选择最大的空闲块进行分配,试图避免产生非常小的碎片。

贪心算法的优点:
高效:它的决策非常快,通常是 O (n) 或 O (n log n) 的复杂度,适合在编译器中快速完成内存规划。

有效:对于大多数神经网络计算图,张量的生命周期分布使得这种 “短视” 的策略能得出一个非常不错、甚至是最优的解。

实现简单:逻辑清晰,易于实现和调试。

贪心的局限性:

局部最优:由于缺乏全局视野,它无法保证结果一定是全局最优(峰值内存最低)的。在某些特定的张量生命周期和大小分布下,它可能会产生碎片或做出次优的决策。

受分配策略影响:使用 First-Fit, Best-Fit, 还是 Worst-Fit,会得到不同的结果,需要根据实际情况进行选择。

比如贪心算法会造成左图的情况,其显然不如右侧的分配方式好。

分配示例

# 束搜索算法

束搜索是贪心算法的一种优雅扩展,它在 “每一步的局部最优” 和 “穷举搜索的全局最优” 之间找到了一个非常好的平衡点。

  • 贪心算法:在每一步,只选择当前看起来最好的一个选项,然后走下去,永不回头。它没有前瞻性。
  • 穷举搜索:考虑所有可能的路径,最终一定能找到全局最优解,但计算成本无法承受。
  • 束搜索:是两者的折中。它在每一步不是只保留 1 个候选解,而是保留最好的 k 个候选解(k 称为束宽)。这个 k 是一个可调的参数,k=1 时就是贪心算法,k=∞ 时就是穷举搜索。

假设我们按张量的开始时间排序后,得到一个待分配序列 [T1, T2, T3, ..., Tn],束宽为 k。

1. 初始化:

  • 创建一个集合 beam ,它代表当前代的候选状态。初始时,它只包含一个状态:空的内存空间,峰值内存为 0,下一个要分配的是 T1。
  • beam = [initial_state]
    2. 循环(对于每一个要分配的张量):

a. 生成候选:对于 beam 中的每一个当前状态,生成所有可能分配下一个张量的方式。每个方式都会产生一个新的后继状态。

  • 例如,从当前状态 S 出发,分配 Ti 可能有 3 种方式:放入空闲块 A、放入空闲块 B、放到堆顶。这会生成 3 个新的状态 S_A, S_B, S_C。
    b. 排序与剪枝:
  • 将所有新生成的后继状态(来自 beam 中所有父状态)合并到一个大列表中。
  • 根据评估函数(即当前的峰值内存)对这个大列表进行排序,选出最好的 k 个状态。
  • 丢弃掉所有其他状态。
  • 更新 beam 为这 k 个最好的后继状态。
    c. 前进:处理下一个张量 T (i+1)。
    3. 终止:
  • 当所有张量都分配完毕后, beam 集合中的 k 个状态就代表了 k 个完整的内存分配方案。
  • 从中选择峰值内存最小的那个方案作为最终结果。

# 总结:

还有很多其他的算法,总结如下:

算法总结

# 绘图

可以通过绘制甘特图来检查 Tensor 生命周期的分配情况。可以将信息输出为以下 .csv 文件:

tensor_name,size,offset,start_time,end_time
conv1_weight,102400,0,1,10
layer1_activation,204800,102400,5,15
pool1_output,102400,102400,16,25
conv2_weight,204800,0,11,30
layer2_activation,409600,204800,20,35
output,102400,614400,36,40

使用 Python 库来绘制甘特图,如下代码已经将数据进行归一化。

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import pandas as pd
import numpy as np
df = pd.read_csv('memory_plan.csv')
max_time = df['end_time'].max()
max_memory = (df['offset'] + df['size']).max()
df['norm_start'] = df['start_time'] / max_time
df['norm_end'] = df['end_time'] / max_time
df['norm_offset'] = df['offset'] / max_memory
df['norm_size'] = df['size'] / max_memory
fig, ax = plt.subplots(figsize=(14, 8))  
colors = plt.cm.tab10(np.linspace(0, 1, len(df)))
for i, row in df.iterrows():
    rect = patches.Rectangle(
        (row['norm_start'], row['norm_offset']),  
        row['norm_end'] - row['norm_start'],      
        row['norm_size'],                         
        linewidth=1, edgecolor='black', facecolor=colors[i],
        alpha=0.7,
        label=row['tensor_name']
    )
    ax.add_patch(rect)
    
    
    ax.text(
        (row['norm_start'] + row['norm_end'])/2,
        row['norm_offset'] + row['norm_size']/2,
        row['tensor_name'], 
        ha='center', 
        va='center', 
        fontsize=9,
        bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.7)
    )
ax.set_xlabel('Normalized Time Step')
ax.set_ylabel('Normalized Memory Address')
ax.set_title('Neural Network Memory Allocation Timeline\nPeak Memory: {:,} Bytes'.format(max_memory))
ax.grid(True, linestyle='--', alpha=0.7)
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.tight_layout()
plt.savefig('memory_timeline_normalized.png', dpi=150, bbox_inches='tight')
plt.show()

# 后记

本博客目前以及可预期的将来都不会支持评论功能。各位大侠如若有指教和问题,可以在我的 github 项目 或随便一个项目下提出 issue,并指明哪一篇博客,我看到一定及时回复!