项目组织架构
这个项目的组织架构清晰地划分了各个模块的功能,旨在支持针对特定硬件架构和工作负载(如卷积神经网络CNN层)的高效映射和性能分析。下面是每个目录的详细解释:
workload/(解析配置)
- 内容: 包含描述工作负载形状的数据结构和函数。例如,定义如何表示CNN层的尺寸、卷积核大小等,以及如何解析具体的工作负载定义文件到这些数据结构中。
mapsaces/
- 内容: 定义了映射空间,即给定硬件架构和问题形状(如CNN层)下的所有合法映射集合。这里包含针对不同架构类别的映射空间构建类,其中“Uber”映射空间是一个通用类,能够处理广泛类型的架构。
mapping/(存储各类硬件信息)
- 内容: 包含描述映射的代码,映射定义了在特定硬件架构上执行问题嵌套(如CNN层)的特定tiling(数据分块)和调度模式。包括描述映射结构和nest结构的代码。
loop-analysis/
- 内容: 提供了分析特定映射的数值属性的工具,例如每个嵌套级别上操作数体积的形状和大小、在不同嵌套级别间传输数据子集所需的读写次数等。
model/
- 内容: 实现了一个广泛的加速器架构类的通用分析模型。基于提供的参数构造模板化模型,能够“评估”映射在模拟架构上的性能,输出诸如周期计数、对各种硬件结构的访问次数等统计数据。该模块大量依赖于循环分析例程进行复杂计算。
pat/ (external)
- 内容: 提供技术参数和用于能量和面积建模的插值例程。从分析性能模型获得的访问计数可以应用到这些参数导出的访问成本上,从而得到能量消耗的预测。这是一个外部依赖项,可能是指外部库或工具。
search/
- 内容: 包含用于寻找最优映射的搜索算法。这些算法利用前面模块定义的映射空间、映射描述和性能模型,来自动化寻找最佳映射策略的过程。
applications/
- 内容: 包含使用timeloop基础设施的应用示例。其中,“timeloop”主应用在
applications/mapper.hpp
中定义,展示了如何整合上述所有模块来解决实际的映射优化问题。
compound-config/(配置解析工具类)
- 内容: 提供了一个包装类,以便透明地支持yaml和libconfig两种配置文件格式的输入,增加了配置灵活性和兼容性。
这个项目的结构支持从定义工作负载到搜索最优映射的端到端流程。
workload
在这个项目中,工作负载到操作空间的映射过程主要是通过一系列步骤来完成的,旨在将抽象的计算任务(工作负载)转化为更加具体且可操作的数据处理框架(操作空间)。
1. 定义工作负载(Workload)
workload.cpp文件
首先,项目中有一个“工作负载”概念,它就像是一个蓝图,描述了我们要解决的具体计算问题是什么样的。比如,对于一个卷积神经网络(CNN)层,工作负载会说明这个层有多少输入通道、输出通道、卷积核的大小、步长等。这个信息通常来源于一个配置文件,里面详细定义了计算任务的维度(比如图像的宽、高、通道数)、系数(比如滤波器的数量)以及其他特性(比如数据空间)。
2. 解析问题形状(Shape)
problem-shape.cpp文件
接下来,有一个“问题形状”(Shape)的概念,它定义了计算任务的基本结构,包括维度、系数、数据空间等。通过解析配置文件,项目能够知道每个维度的名称(比如用单个字母表示)、系数的默认值以及数据空间的性质(是否可读写,如何映射到计算维度)。
3. 创建操作空间(OperationSpace)
operation-space.cpp文件
有了工作负载和问题形状的详细信息后,就可以创建操作空间了。操作空间实质上是工作负载在数据处理层面的具象化,它告诉我们每个数据空间(比如输入、输出、权重等)内部的操作分布情况。想象一下,操作空间是把抽象的计算需求分解到具体的数据块上,每个数据块代表一部分计算任务。
映射过程
-
数据空间映射:为了从工作负载映射到操作空间,项目首先会根据工作负载中的维度和系数,通过“投影”(Projection)过程,将操作点(即计算任务中的一个基本单位)映射到各个数据空间。这个过程涉及到将问题空间中的一个点(例如,卷积运算的一个位置)转换为不同数据空间中的对应位置。简单来说,如果某个操作涉及到多个维度,那么这个操作在每个数据空间的表现可能不同,映射就是找出这种对应关系。
-
范围确定:在某些情况下,我们不仅要知道单个操作点如何映射,还需要知道整个操作空间的范围,即操作点的最小值和最大值。这涉及到将工作负载定义的范围(比如,卷积层处理的图像区域)映射到各个数据空间,计算出每个数据空间的起始点和结束点。
-
操作空间操作:最后,操作空间可以进一步用于分析和优化,比如计算操作点总数、判断操作空间是否为空、比较不同操作空间的差异等。这有助于我们了解计算资源的利用情况,为调度、并行化、内存管理等提供依据。
总之,工作负载到操作空间的映射,就是将一个抽象的计算任务按照其结构特点,分解并映射到具体的数据处理单元上,使得我们能更细致地分析和优化计算任务在实际硬件上的执行方式。这个过程是优化算法性能、减少计算延迟、提高资源利用率的关键步骤。
mapping
mapping
Mapping
结构体
-
成员变量:
id
: 一个uint128_t
类型的唯一标识符,用于区分不同的映射配置。loop::Nest loop_nest
: 引用了前文提到的loop::Nest
类型,描述了循环嵌套的结构,包含一系列循环及其属性。tiling::CompoundMaskNest datatype_bypass_nest
: 描述了数据类型绕过(可能指特定数据类型不参与某些计算或存储操作)的掩码嵌套结构,这在优化内存访问模式时非常有用,比如数据旁路技术。
-
序列化支持: 使用了
boost::serialization
库来支持Mapping
结构体的序列化与反序列化,这对于存储或网络传输映射配置非常关键。这里定义了对datatype_bypass_nest
的序列化逻辑,使用数组的形式进行存储。 -
成员函数:
FormatAsConstraints
: 将映射信息格式化为约束条件,并写入给定的libconfig::Setting
对象中。这通常用于将映射表示为求解器可以理解的约束问题。PrintAsConstraints
: 将映射约束条件输出到指定的文件中,这可能是为了后续分析或验证。PrintYaml
,PrettyPrint
,PrintWhoopNest
: 分别提供了以YAML格式、人类可读的格式和Whoop系统特定格式输出映射信息的方法,便于查看和调试。PrintCompact
: 返回映射的一个紧凑字符串表示,可能用于日志记录或简短展示。- 友元函数
operator<<
: 重载了输出流操作符,使得可以直接将Mapping
对象输出到std::ostream
,提高了代码的可读性和灵活性。
外部依赖
- 头文件: 包含了对于I/O操作、大整数运算(
boost/multiprecision/cpp_int.hpp
)、配置文件解析(libconfig.h++
)和YAML数据序列化(yaml-cpp/yaml.h
)的支持。 - 命名空间: 使用了
boost::multiprecision
命名空间来处理大整数(如映射ID),这在需要大量映射配置时特别有用。
总结来说,这个Mapping
结构体和其关联函数构成了一个复杂系统的一部分,用于设计和分析高性能计算应用中的循环映射策略,这些策略对于优化数据访问模式、减少内存带宽需求和提高计算效率至关重要。
loop
loop.cpp文件(loop.hpp文件)
loop.hpp
文件定义了一个关键类Descriptor
,用于描述单个循环级别(loop level)的属性,这是映射和调度算法中处理数据访问和计算循环的基础组件。以下是Descriptor
类及其相关功能的分析:
Descriptor
类
-
成员变量:
dimension
: 表示当前循环维度对应的ID,来自problem::Shape::DimensionID
,这有助于追踪是哪个具体维度的循环。start
,end
,stride
: 分别表示循环的起始点、结束点和步长,共同定义了循环的迭代范围。spacetime_dimension
: 使用自定义的spacetime::Dimension
枚举类型,指明循环是在时空维度中的哪个部分执行(例如,时间或空间X/Y轴),这对于理解循环在物理硬件上的映射至关重要。
-
构造函数:
- 提供了多个构造函数,允许以不同的方式初始化循环描述符。例如,可以仅指定维度和结束点,其他默认为时空维度为时间、起始点为0、步长为1;也可以完整指定所有属性。
-
操作方法:
operator==
: 重载等于运算符,用于比较两个循环描述符是否相等。Print
系列方法:用于以不同格式输出循环描述符的信息,如标准输出格式、简短紧凑格式或Whoop(一种特定格式)格式,便于调试和记录。serialize
: 提供序列化支持,使得Descriptor
对象可以通过Boost.Serialization库进行序列化和反序列化,这对于持久化存储或网络传输循环描述非常有用。
-
全局函数:
std::ostream& operator<<(std::ostream& out, const Descriptor& loop)
: 重载输出流操作符,允许直接将Descriptor
对象输出到流中。bool IsSpatial(spacetime::Dimension dim)
和bool IsSpatialX(spacetime::Dimension dim)
: 静态函数,用于判断给定的时空维度是否为空间维度或特定的空间X维度,这对于循环调度和映射决策逻辑很有帮助。
总结
loop.hpp
文件主要关注于定义和操作循环描述符,这些描述符构成了映射策略的核心元素,描述了如何遍历多维数据结构中的各个维度。通过这些描述符,可以详细地控制循环的执行范围、顺序和空间时间布局,对于实现高效的并行计算和数据移动策略至关重要。此外,通过集成Boost.Serialization,这些描述符可以方便地用于状态保存、恢复或跨进程通信,增加了程序的灵活性和扩展性。
nest
nest.hpp
文件定义了与循环嵌套(loop nest)相关的数据结构和操作,循环嵌套是高性能计算领域中描述计算任务迭代结构的一种重要方式。以下是该文件中的关键内容:
NestConfig
- 类型定义 (
typedef
): 定义了一个类型别名NestConfig
,它是一个二维Descriptor
向量。每个外层向量代表一个嵌套层级,内层向量则包含了该层级中所有循环的描述符。这用于描述整个计算任务的多层循环结构。
Nest
类
-
成员变量:
loops
: 一个Descriptor
向量,存储了构成循环嵌套的每个循环的描述信息。storage_tiling_boundaries
: 一个uint64_t
向量,记录了循环嵌套中存储层级切分的位置,即在哪些循环之后进行了存储级别的切换,这对于映射到具有多级存储的硬件架构上非常重要。
-
构造函数: 提供了默认构造函数来初始化一个空的循环嵌套。
-
操作方法:
operator==
: 重载等于运算符,用于比较两个Nest
对象是否相等。AddLoop
: 添加一个循环描述符到嵌套中,有两种重载形式,一种直接接受Descriptor
对象,另一种则允许分别指定循环的各个属性(维度、起始点、结束点、步长、时空维度)。AddStorageTilingBoundary
: 在循环嵌套中添加一个存储层级切分边界,用于标记不同存储层级间的转换。- 输出操作符重载: 支持以不同格式打印
Nest
对象的内容,包括简洁格式、详细的“PrettyPrint”格式以及专为Whoop系统设计的输出格式。 PrintCompact
和PrintWhoopNest
: 分别用于生成循环嵌套的紧凑字符串表示和适用于Whoop系统的输出,后者还考虑了存储层级名称、复合掩码(tiling masks)、瓦片大小(tile sizes)和使用的实例数(utilized instances)。
功能概述
nest.hpp
文件通过NestConfig
和Nest
类,提供了对循环嵌套结构的定义、构建和输出能力。
parser
parser.cpp文件
这个parse.cpp
文件是项目中负责解析用户定义的映射指令并根据这些指令生成映射结构的部分。映射在这里指的是如何将计算任务(如数据处理的循环结构)分配到硬件的不同存储层级和计算资源上,以优化性能。下面是对主要部分的分析:
主要目标
- 解析配置:从给定的配置文件中读取用户定义的映射指令,这些指令告诉程序如何在不同的存储层级上组织计算循环(即“子嵌套”)。
- 生成映射结构:基于解析结果,生成一个
Mapping
结构,该结构包含了如何遍历数据空间的循环信息,以及数据类型在不同存储层级上的处理方式(如是否绕过某些层级)。
关键过程
-
初始化:首先,根据硬件架构规格(
arch_specs
)初始化ArchProperties
对象,并传入工作负载信息。此外,初始化一些数据结构来保存用户定义的因子、排列、空间分割以及数据类型旁路设置。 -
解析映射指令:
- 遍历配置文件中的所有映射指令。
- 确定指令类型(是时间局部的“temporal”,空间局部的“spatial”,还是关于数据类型的“datatype”、“bypass”)。
- 根据指令类型,调用相应函数解析用户指定的因子(如何分割维度)、排列(维度顺序)、空间分割(如何在X/Y轴上分布计算)以及数据类型旁路设置。
- 通过
FindTargetTilingLevel
函数找到指令针对的存储层级,并转换为映射层级ID。
-
构造映射:
- 根据用户提供的信息构建每个嵌套层级的循环描述符,包括循环的维度、起止点、步长和空间时间维度。
- 验证所有维度的因子乘积是否与工作负载的维度大小相匹配,确保映射的正确性。
- 将子嵌套按层级连接起来形成最终的映射循环嵌套结构。
- 处理数据类型旁路设置,决定哪些数据在哪些存储层级上参与计算或绕过。
-
错误检查和处理:在解析和构造过程中,如果有不符合预期的配置(如缺少必要的映射信息),程序会输出错误信息并终止执行。
实现细节
- 使用了递归下降的方式解析配置,通过查找特定的键值对(如"type", “target”, "factors"等)来提取指令信息。
- 利用了正则表达式库
<regex>
,尽管代码中没有直接展示使用正则表达式的部分,这通常表明在解析或验证配置数据时可能有更复杂的文本处理逻辑。 - 强调了对用户输入的严格验证,确保了映射配置的准确无误。
总的来说,parse.cpp
文件是映射策略实现的关键,它将用户定义的高级映射策略转换为实际的执行计划。
loop-analysis
tiling
tiling.cpp
文件主要关注于循环展开(tiling)策略的数据结构定义及一些基本操作,循环展开是一种常用的优化技术,通过将大的数据集分割成较小的、可管理的块(tiles)来提高缓存局部性和并行性。以下是文件内容的分析:
数据结构定义
-
TileInfo
结构体: 用于存储单个tile(数据块)的详细信息,包括:- 基本属性:大小、分区大小、是否启用分布式多播、访问计数(不同多播因子下的)、散射因子、累积跳跃次数等。
- 访问统计:内容访问、填充操作、链路传输、跨tile访问和填充。
- 循环嵌套信息:子循环描述符、复制因子(空间元素数量)、扇出(到下一层的元素分布)、分布式扇出(多播时的最大范围)。
- 辅助属性:是否位于存储边界、是否为主空间tile、数据密度等。
- 提供了获取总访问量、加权访问量的方法以及重置和验证tile信息的方法。
-
BodyInfo
结构体: 存储有关循环体的信息,如复制因子和访问计数,以及各数据空间的数据密度。 -
复合类型定义:
CompoundTile
、CompoundMask
:分别为不同数据空间的TileInfo
和掩码信息。CompoundTileNest
、CompoundMaskNest
:定义了一组CompoundTile
和CompoundMask
,对应不同循环嵌套层级。NestOfCompoundTiles
、NestOfCompoundMasks
:是CompoundTileNest
和CompoundMaskNest
的向量,用于描述整个循环嵌套结构的tiling配置。
函数声明
-
序列化支持: 为
TileInfo
结构体添加了Boost.Serialization支持,允许对象的序列化和反序列化。 -
操作函数:
<
运算符重载:用于比较两个TileInfo
对象。- 输出流操作符重载:允许将
TileInfo
对象格式化输出到流中。 CollapseTiles
:函数用于根据指定的tiling层级数、tile掩码和分布支持情况,折叠(简化)CompoundTileNest
,可能涉及到去除某些层级的tiling。TransposeTiles
、TransposeMasks
:分别对tiling配置和掩码配置进行转置操作,可能用于调整tiling配置的维度顺序。
目的与作用
此文件的主要目的是定义和操作与循环展开相关的数据结构,以支持高性能计算和并行计算中自动或手动的tiling策略选择和优化。通过这些数据结构和操作,可以分析和评估不同tiling方案对性能(如内存访问、并行效率)的影响,进而指导算法或程序的优化。例如,TileInfo
结构体详细记录了每个tile的访问模式和资源使用情况,这对于理解和优化内存访问模式至关重要。而CollapseTiles
和转置函数则提供了调整tiling配置灵活性的方法,有助于探索更高效的数据布局和执行策略。
loop-states
该代码段定义了位于analysis
命名空间下的两个结构体——ElementState
和LoopState
,它们主要用于分析和追踪循环展开(tiling)过程中的数据状态和活动,特别是在高性能计算和并行计算领域。下面是详细分析:
ElementState
结构体
-
目的: 表示单个循环级别(loop level)内单一空间元素(spatial element)的实时状态,是循环优化、数据局部性分析和性能评估的基础单元。
-
成员变量:
last_point_set
: 记录最近一次设置的运算空间(OperationSpace),反映了该元素最后一次迭代时的工作负载覆盖范围。max_size
: 为每种数据类型存储最大数据大小,有助于评估存储需求。- 多播功能:
accesses
,scatter_factors
,cumulative_hops
: 分别记录不同数据类型在不同多播因子下的访问次数、散射因子(可能用于衡量数据分布)和累积跳跃次数(可能关联数据访问距离或复杂度)。delta_histograms
: 存储访问次数的变化分布,用以分析访问模式的波动。
- PE活动(Processing Element Activity):
prev_point_sets
: 记录时间维度上最近几次迭代中,向下一循环级别传递的数据操作空间,每个向量元素对应下一空间元素的历史状态。link_transfers
: 记录不同数据类型通过链接在空间元素间传输的次数,这有助于评估通信开销。
data_densities
: 存储每个空间元素内的数据密度信息,对评估缓存利用和内存访问效率有重要作用。
-
方法:
Reset()
: 重置所有状态变量,以便开始新的分析周期或初始化。
LoopState
类
-
目的: 表示单个循环级别的整体状态,汇总了该级别内所有空间元素的
ElementState
。 -
成员变量:
level
: 循环级别的索引。descriptor
: 描述该循环级别的详细信息,如维度、起始点、结束点等。live_state
: 一个ElementState
向量,每个元素对应一个空间位置的状态。
-
特性:
- 提供了序列化支持,通过Boost.Serialization库实现了
LoopState
对象的序列化和反序列化,这在存储或网络传输分析结果时非常有用。
- 提供了序列化支持,通过Boost.Serialization库实现了
总结
loop-states
文件中的结构定义支持了对循环展开过程中数据访问、内存使用和计算活动的精细分析。ElementState
深入到单个空间元素的微观层面,而LoopState
则整合了同一循环级别的整体视图。这些信息对于指导循环优化策略、决定最优tiling尺寸、评估并行计算效率和内存访问模式至关重要,尤其是在面对大规模数据并行和高性能计算挑战时。
buffer
该buffer.hpp
文件定义了一个名为BufferLevel
的类,它继承自Level
类,主要关注于模型化内存缓冲区层次(如SRAM或DRAM)的设计、规格定义、性能统计与评估。此类在高性能计算系统设计中用于分析和优化内存层次结构,确保数据存储和传输效率。以下是关键部分的分析:
BufferLevel
类概览
- 目的: 定义缓冲区层次(如缓存或内存层次中的某一级)的行为、规格(
Specs
)和统计信息(Stats
),并提供评估其性能和资源使用的方法。
Technology
枚举
- 定义: 定义了两种内存技术类型:
SRAM
和DRAM
,用于区分不同类型的缓冲区或存储层次。
Specs
结构体
-
功能: 描述缓冲区级别的详细规格,包括名称、技术类型、大小、字位宽、地址生成位宽、块大小、簇大小、实例数量、二维网格布局(meshX, meshY)、读写带宽、多重缓冲策略、最小利用率、端口数量、银行数量等。还包括压缩率、压缩数据空间、与之相连的网络名称等高级特性。
-
物理属性: 如访问能量、面积、地址生成能量,这些属性通常由技术模型决定。
-
序列化: 支持通过
boost::serialization
进行序列化和反序列化,便于保存和恢复状态。
Stats
结构体
-
内容: 记录了缓冲区的运行时统计信息,如每个数据空间的保持状态、分区大小、已用容量、读写次数、填充次数、地址生成次数、时间减少次数、带宽使用、能耗等。
-
性能指标: 包括周期数、减速比等,以及与数据压缩、地址生成相关的能耗。
-
序列化: 同样支持序列化,便于状态持久化或通信。
BufferLevel
类成员
-
构造函数与析构函数: 提供默认构造函数、基于规格的构造函数和析构函数。
-
连接网络: 方法允许缓冲层连接到读、填、更新和排空网络,以模拟实际的系统交互。
-
评估与性能计算: 包含计算访问次数、性能、能量消耗的方法,以及针对数据压缩、地址生成的能量计算。
-
属性访问: 提供获取规格、统计信息、网络连接等的方法。
-
资源与性能指标: 实现了获取面积、能量、周期数、访问次数、利用率等的方法。
-
打印与输出: 重载了
<<
运算符以输出缓冲区级别的详细信息,以及提供打印方法。