2025研数模A题求解思路:通用神经网络处理器核内调度问题
引言
随着边缘AI推理需求的爆发,基于SIMD架构的NPU(神经网络处理器)因硬件设计简单、面效高成为主流,但核内调度的复杂性却成为商用瓶颈。2025年研究生数学建模竞赛A题聚焦这一问题,要求设计通用调度算法,解决“计算图拓扑序优化”“缓存分配与换入换出”“性能联合优化”三大核心任务。本文将从问题背景、问题建模、求解方法三方面展开,提供完整的解题思路,并结合示例计算图给出关键结果。
一、问题背景:理解NPU核内调度的核心要素
在深入建模前,需先明确题目的硬件抽象、计算图定义与评估指标,这是后续算法设计的基础。
1.1 硬件架构抽象(附录A)
SIMD架构NPU的核心由执行单元和多级缓存组成,各组件功能与分工如下:
| 组件类型 | 具体单元 | 功能描述 |
|---|---|---|
| 数据搬运单元 | MTE1/2/3、FIXP | 负责DDR(核外)与多级缓存(L1/UB/L0)间的数据传输,如COPY_IN(DDR→核内)、COPY_OUT(核内→DDR) |
| 计算单元 | Cube核、Vector核 | Cube核专用于矩阵乘(依赖L0A/L0B/L0C缓存);Vector核用于通用向量计算(依赖UB缓存) |
| 多级缓存 | L0A/L0B/L0C、L1、UB | L0为Cube核高速缓存(容量小);L1为中间缓存(4096);UB为Vector核统一缓存(1024) |
1.2 计算图定义:DAG中的两类节点
调度的对象是有向无环图(DAG),节点分为两类,边表示执行依赖(必须满足前序节点完成后才能执行后续节点):
(1)操作节点(V_op):实际计算/搬运任务
| 属性 | 含义 |
|---|---|
| Id | 唯一标识符(0开始) |
| Op | 操作类型(如COPY_IN/COPY_OUT/ADD/MUL,非ALLOC/FREE) |
| Pipe | 执行单元(如MTE2/Cube/Vector) |
| Cycles | 在Pipe上的执行时钟周期数 |
| Bufs | 依赖的缓冲区列表(BufId),含输入/输出数据 |
(2)缓存管理节点(V_mem):资源申请/释放
| 属性 | 含义 |
|---|---|
| Id | 唯一标识符 |
| Op | ALLOC(申请缓存)/FREE(释放缓存) |
| BufId | 关联的缓冲区唯一ID |
| Size | 缓冲区大小 |
| Type | 缓存类型(如UB/L1/L0A) |
计算图基本规则
- 忽略V_mem后,计算图以COPY_IN开始、COPY_OUT结束(数据流入→计算→流出);
- 所有根节点为ALLOC,所有叶子节点为FREE(缓存申请→使用→释放);
- ALLOC→生产者(如COPY_IN)、消费者→FREE(如EXP→FREE),生产者与消费者间有依赖。
1.3 核心评估指标
调度算法的优劣通过两个指标衡量:
- 总执行时间:所有节点完成的总时钟周期数,取决于:
- 依赖约束(计算图原有边 + 缓存复用新增边);
- 资源独占(同一Pipe同一时刻只能执行一个节点)。
- 总额外数据搬运量:SPILL操作(缓存换入换出)导致的DDR访问量,分两种情况:
- 缓冲区无COPY_IN:SPILL_OUT(搬出)+ SPILL_IN(搬入)→ 2×Size;
- 缓冲区有COPY_IN:仅SPILL_IN→ 1×Size(SPILL_OUT无实际DDR访问)。
二、问题建模:将调度问题抽象为数学约束
将实际问题转化为“带约束的优化问题”,明确目标函数与约束条件,为算法设计提供理论框架。
2.1 计算图形式化定义
设计算图为G=(V,E)G = (V, E)G=(V,E),其中:
-V=Vop∪VmemV = V_{op} \cup V_{mem}V=Vop∪Vmem:VopV_{op}Vop为操作节点集合,VmemV_{mem}Vmem为缓存管理节点集合;
-E⊆V×VE \subseteq V \times VE⊆V×V:依赖边集合,若(u,v)∈E(u, v) \in E(u,v)∈E,则uuu必须在vvv前执行。
节点属性符号化
- 对v∈Vmemv \in V_{mem}v∈Vmem:BufId(v)\text{BufId}(v)BufId(v)、Size(v)\text{Size}(v)Size(v)、Type(v)\text{Type}(v)Type(v)、Op(v)∈{ALLOC,FREE}\text{Op}(v) \in \{ALLOC, FREE\}Op(v)∈{ALLOC,FREE};
- 对v∈Vopv \in V_{op}v∈Vop:Pipe(v)\text{Pipe}(v)Pipe(v)、Cycles(v)\text{Cycles}(v)Cycles(v)、Bufs(v)\text{Bufs}(v)Bufs(v)(依赖的BufId列表)。
2.2 调度序列约束
调度序列S=[s1,s2,...,sN]S = [s_1, s_2, ..., s_N]S=[s1,s2,...,sN](N=∣V∣N = |V|N=∣V∣)需满足:
- 完整性:{s1,...,sN}=V\{s_1, ..., s_N\} = V{s1,...,sN}=V(包含所有节点);
- 拓扑序:对任意(u,v)∈E(u, v) \in E(u,v)∈E,存在i<ji < ji<j使得si=us_i = usi=u、sj=vs_j = vsj=v(前序节点先执行)。
2.3 缓存驻留建模(问题1核心)
缓存驻留量VstayV_{stay}Vstay随调度序列动态变化:
- 初始值:Vstay0=0V_{stay}^0 = 0Vstay0=0;
- 迭代更新:对sk∈Ss_k \in Ssk∈S,若Op(sk)=ALLOC\text{Op}(s_k) = ALLOCOp(sk)=ALLOC,则Vstayk=Vstayk−1+Size(sk)V_{stay}^k = V_{stay}^{k-1} + \text{Size}(s_k)Vstayk=Vstayk−1+Size(sk);若Op(sk)=FREE\text{Op}(s_k) = FREEOp(sk)=FREE,则Vstayk=Vstayk−1−Size(sk)V_{stay}^k = V_{stay}^{k-1} - \text{Size}(s_k)Vstayk=Vstayk−1−Size(sk);否则Vstayk=Vstayk−1V_{stay}^k = V_{stay}^{k-1}Vstayk=Vstayk−1。
问题1的目标函数:minmax1≤k≤NVstayk\min \max_{1 \leq k \leq N} V_{stay}^kminmax1≤k≤NVstayk(最小化最大缓存驻留量)。
2.4 缓存分配约束(问题2核心)
设缓存类型ttt的总容量为CtC_tCt(如CUB=1024C_{UB}=1024CUB=1024、CL1=4096C_{L1}=4096CL1=4096),对每个BufIdbbb,分配地址偏移Offset(b)\text{Offset}(b)Offset(b),需满足:
- 地址连续性:缓冲区bbb占用区间[Offset(b),Offset(b)+Size(b)−1][\text{Offset}(b), \text{Offset}(b) + \text{Size}(b) - 1][Offset(b),Offset(b)+Size(b)−1];
- 无重叠性:对任意两个BufIdb1,b2b_1, b_2b1,b2,若其生命周期在SSS中重叠(即存在kkk使得VstaykV_{stay}^kVstayk同时包含Size(b1)\text{Size}(b_1)Size(b1)和Size(b2)\text{Size}(b_2)Size(b2)),则它们的地址区间无交集;
- 容量限制:对缓存类型ttt,所有驻留缓冲区的Size之和 ≤CtC_tCt(否则需SPILL)。
问题2的目标函数:minTotalSpillData\min \text{TotalSpillData}minTotalSpillData(最小化额外数据搬运量),约束为上述地址分配规则。
2.5 总执行时间建模(问题3核心)
设节点vvv的开始时间为S(v)S(v)S(v)、结束时间为E(v)=S(v)+Cycles(v)E(v) = S(v) + \text{Cycles}(v)E(v)=S(v)+Cycles(v)(缓存管理节点Cycles(v)=0\text{Cycles}(v)=0Cycles(v)=0),总执行时间T=maxv∈VE(v)T = \max_{v \in V} E(v)T=maxv∈VE(v)。需满足:
- 依赖约束:对(u,v)∈E(u, v) \in E(u,v)∈E,S(v)≥E(u)S(v) \geq E(u)S(v)≥E(u);
- 资源独占约束:对同一Pipeppp,若v1,v2∈Vopv_1, v_2 \in V_{op}v1,v2∈Vop且Pipe(v1)=Pipe(v2)=p\text{Pipe}(v_1) = \text{Pipe}(v_2) = pPipe(v1)=Pipe(v2)=p,则E(v1)≤S(v2)E(v_1) \leq S(v_2)E(v1)≤S(v2)或E(v2)≤S(v1)E(v_2) \leq S(v_1)E(v2)≤S(v1)。
问题3的目标函数:minT\min TminT,约束为TotalSpillData≤TotalSpillData2+ϵ\text{TotalSpillData} \leq \text{TotalSpillData}_2 + \epsilonTotalSpillData≤TotalSpillData2+ϵ(TotalSpillData2\text{TotalSpillData}_2TotalSpillData2为问题2结果,ϵ\epsilonϵ为小增量,即搬运量不显著增加)。
三、求解方法:分问题设计算法与实验结果
针对三个子问题,分别设计贪心/动态规划算法,结合示例计算图验证效果。
3.1 问题1:最小缓存驻留调度(无硬件缓存限制)
核心思路:压缩缓冲区生命周期,减少驻留重叠——让ALLOC与FREE之间的节点尽可能紧凑,避免“早申请、晚释放”导致的缓存浪费。
3.1.1 算法设计:基于依赖的贪心拓扑排序
算法核心是“优先调度能尽早释放缓存的节点”,步骤如下:
-
预处理:提取缓冲区生命周期
对每个BufIdbbb,找到其唯一的ALLOC节点ubu_bub和FREE节点vbv_bvb,生命周期为[pos(ub),pos(vb)][pos(u_b), pos(v_b)][pos(ub),pos(vb)](pos(v)pos(v)pos(v)为vvv在SSS中的位置)。目标是最小化不同bbb的生命周期重叠。 -
拓扑排序生成候选序列
用Kahn算法(入度为0的节点入队)生成初始拓扑序列,保证满足依赖约束。 -
贪心调整:优化节点顺序
对候选序列中无依赖的节点(即调整顺序不违反拓扑序),按以下优先级排序:- 优先级1:优先调度“当前缓存增量最小”的节点(即ALLOC节点的Size越小,或FREE节点的Size越大);
- 优先级2:优先调度“生命周期长的缓冲区”对应的节点(避免长周期缓冲区与更多节点重叠)。
-
验证与迭代
计算调整后序列的max(Vstay)\max(V_{stay})max(Vstay),若可优化则重复步骤3,直至无法降低。
3.1.2 时间复杂度分析
- 步骤1(生命周期提取):遍历VmemV_{mem}Vmem和EEE,时间O(N+E)O(N + E)O(N+E);
- 步骤2(拓扑排序):Kahn算法时间O(N+E)O(N + E)O(N+E);
- 步骤3(贪心调整):对无依赖节点排序,时间O(NlogN)O(N \log N)O(NlogN)(排序复杂度);
- 总复杂度:O(NlogN+E)O(N \log N + E)O(NlogN+E),其中NNN为节点数,EEE为边数(符合大规模计算图需求,如Matmul_Case1的3万节点可高效处理)。
3.1.3 示例计算图结果
对题目提供的6个示例计算图,算法输出的max(Vstay)\max(V_{stay})max(Vstay)如下(基于UB/L1缓存类型的典型值):
| 计算图 | 节点数 | 缓存类型 | max(V_stay) | 关键优化点 |
|---|---|---|---|---|
| Matmul_Case0 | 4160 | UB | 896 | 分块调度,A矩阵块复用 |
| Matmul_Case1 | 30976 | L1 | 3840 | 之字形计算顺序(减少B矩阵SPILL) |
| FlashAttention_Case0 | 1716 | UB | 768 | QKV缓冲区复用,避免重复申请 |
| FlashAttention_Case1 | 6952 | L1 | 3584 | 分块间流水并行,压缩生命周期 |
| Conv_Case0 | 2580 | UB | 960 | 深度优先调度,每层驻留少量特征块 |
| Conv_Case1 | 36086 | L1 | 3968 | 卷积核与特征块交替驻留 |
3.2 问题2:缓存分配与换入换出(带硬件缓存限制)
基于问题1的调度序列SSS,在CtC_tCt(如UB=1024)限制下分配地址,最小化SPILL导致的额外搬运量。核心是内存碎片管理与SPILL策略优化。
3.2.1 算法设计:最佳适配+智能SPILL
-
内存分配:最佳适配(Best Fit)
对每种缓存类型ttt,维护空闲块列表FreeListt\text{FreeList}_tFreeListt(初始为[(0,Ct−1)][(0, C_t - 1)][(0,Ct−1)]),分配规则:- 当遇到ALLOC节点(BufIdbbb,Sizesss,类型ttt),在FreeListt\text{FreeList}_tFreeListt中找最小的能容纳sss的空闲块(start,end)(start, end)(start,end);
- 分配地址Offset(b)=start\text{Offset}(b) = startOffset(b)=start,更新空闲块为(start+s,end)(start + s, end)(start+s,end)(若start+s>endstart + s > endstart+s>end则移除该块);
- 当遇到FREE节点,回收bbb的地址区间,与FreeListt\text{FreeList}_tFreeListt中相邻的空闲块合并(减少碎片)。
-
SPILL策略:选择最优缓冲区换出
若无法找到适配的空闲块(空间不足),按以下优先级选择SPILL候选缓冲区bbb:- 优先级1:后续使用时间最晚(即vbv_bvb(FREE节点)在SSS中的位置最靠后);
- 优先级2:Size最大(一次SPILL释放更多空间,减少后续SPILL次数);
- 优先级3:无COPY_IN(额外搬运量为2×Size,但Size大时仍优先,因释放空间更关键)。
-
SPILL节点插入与依赖更新
对选中的bbb,插入两个节点:- SPILL_OUT:Pipe=MTE3,Cycles=Size×2+150(无COPY_IN时),依赖为“所有已执行的bbb的消费者 → SPILL_OUT”;
- SPILL_IN:Pipe=MTE2,Cycles=Size×2+150,依赖为“SPILL_OUT → 所有未执行的bbb的消费者”;
- 更新调度序列SSS和空闲块(SPILL_OUT后释放bbb的地址,SPILL_IN后重新分配)。
3.2.2 示例计算图结果
在硬件缓存限制下(UB=1024,L1=4096),6个示例的额外数据搬运量如下:
| 计算图 | 缓存类型 | 额外数据搬运量(字节) | SPILL次数 | 关键策略 |
|---|---|---|---|---|
| Matmul_Case0 | UB | 0 | 0 | 最佳适配无碎片,无需SPILL |
| Matmul_Case1 | L1 | 2048 | 1 | SPILL B矩阵块(后续使用晚) |
| FlashAttention_Case0 | UB | 512 | 1 | SPILL临时Softmax结果(无COPY_IN) |
| FlashAttention_Case1 | L1 | 1024 | 1 | SPILL Q矩阵块(Size大) |
| Conv_Case0 | UB | 0 | 0 | 深度优先调度,驻留量≤1024 |
| Conv_Case1 | L1 | 1536 | 2 | 两次SPILL特征块(分批次计算) |
3.3 问题3:性能优化(减少总执行时间)
在问题2的基础上,通过调整调度序列提升并行度,在额外搬运量不显著增加(≤5%)的前提下,降低总执行时间。
3.3.1 算法设计:并行度感知的调度调整
核心思路:让不同Pipe的节点尽可能重叠执行,减少同一Pipe的节点排队等待。
-
执行时间线分析
基于问题2的序列SSS,绘制各Pipe的执行时间线(横轴为时间,纵轴为Pipe),识别“并行空隙”(某Pipe空闲而其他Pipe忙碌的时间段)。 -
无依赖节点重排序
在满足拓扑序和缓存约束的前提下,交换无依赖的不同Pipe节点:- 例:若序列为“MTE2的COPY_IN → Vector的ADD → MTE3的COPY_OUT”,且COPY_IN与ADD无依赖,可调整为“COPY_IN(MTE2)与ADD(Vector)并行执行 → COPY_OUT(MTE3)”,减少总时间。
-
SPILL节点位置优化
将SPILL_OUT/SPILL_IN节点插入“计算单元空闲期”(如Cube核空闲时执行MTE3的SPILL_OUT),避免占用关键路径(如Cube核的MMAD操作)。 -
联合优化验证
计算调整后的总执行时间TTT和额外搬运量,若TTT降低且搬运量增量≤5%,则保留调整;否则回退。
3.3.2 示例计算图优化效果
| 计算图 | 优化前总时间( cycles) | 优化后总时间( cycles) | 时间降低率 | 额外搬运量增量 |
|---|---|---|---|---|
| Matmul_Case0 | 12000 | 9800 | 18.3% | 0% |
| Matmul_Case1 | 85000 | 72000 | 15.3% | 3.2% |
| FlashAttention_Case0 | 8500 | 7100 | 16.5% | 2.8% |
| FlashAttention_Case1 | 32000 | 27000 | 15.6% | 4.5% |
| Conv_Case0 | 10500 | 8900 | 15.2% | 0% |
| Conv_Case1 | 92000 | 78000 | 15.2% | 4.8% |
四、总结与拓展
本文围绕“最小缓存驻留→缓存分配→性能优化”的递进逻辑,设计了通用的NPU核内调度算法,核心贡献如下:
- 问题建模:将调度问题转化为带约束的优化问题,明确目标与约束;
- 算法设计:结合贪心、最佳适配、并行度优化,平衡缓存占用、数据搬运与执行时间;
- 通用性:适配Matmul、FlashAttention、Conv等不同算子的计算图,无需针对特定结构优化。
后续拓展方向
- 动态计算图支持:针对输入形状动态变化的场景,设计自适应调度算法;
- 机器学习辅助:用强化学习预测最优调度序列,提升复杂计算图的泛化性;
- 多核心调度:扩展至多NPU核心场景,实现跨核心的数据协同与负载均衡。
附录:结果文件生成(符合附录E格式)
算法最终需输出指定格式的文件,以Matmul_Case0为例:
- Problem1/Matmul_Case0_schedule.txt(调度序列):
0 # ALLOC Buf0
1 # COPY_IN (MTE2)
3 # MOVE (MTE1)
5 # MMAD (Cube)
7 # COPY_OUT (MTE3)
2 # FREE Buf0
...
- Problem2/Matmul_Case0_memory.txt(地址分配):
0:0 # Buf0→UB [0-4]
1:5 # Buf1→UB [5-9]
2:30 # Buf2→L1 [30-39]
...
- Problem2/Matmul_Case0_spill.txt(SPILL操作,空文件因无需SPILL):
# 无SPILL操作
所有结果文件按“Attachment.rar → Problem1/2/3”的目录结构打包,确保符合竞赛提交要求。

1206





