十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MindSpore大模型计算复杂度评估:从FLOPs理论到Profiler实测

MindSpore大模型计算复杂度评估:从FLOPs理论到Profiler实测 评估一个大模型有多“重”你最先想到的是什么参数量训练数据量说实话这两项都只能算纸面指标。我在昇思MindSpore上做大模型训练和部署评估时真正绕不开的是计算复杂度——FLOPs、MACs、每秒浮点运算次数这些硬指标。它决定了一件事模型在指定算力上要跑多久、成本多少、能不能在预算内稳定落地。这篇文章就把MindSpore大模型的计算复杂度评估方法完整过一遍理论和实测两个角度都讲。适合正在做大模型训练调优、推理部署预算评估或者想搞明白Profiler里那一堆数字到底怎么用的同学。我尽量少讲空话多给能直接抄作业的公式、代码和排查经验。1. 为什么计算复杂度是评估大模型的关键指标1.1 先分清三个容易被搞混的概念计算复杂度这个词在社区里经常被滥用我在不少文档里见过把FLOPs直接说成“浮点运算速率”的也有把参数量当成复杂度来写报告的全都不严格。实际工作中我们至少要分清三个概念FLOPs、MACs和参数量。FLOPs是浮动操作数也就是一次浮点运算通常在深度学习里把一次乘加组合明确记成2次操作。这也是为什么同一个模型在不同资料里的FLOPs数字可能差一倍因为有人把乘加算成1次有人算成2次。MindSpore框架和多数性能工具的统计口径是1次乘加计2 FLOPs也就是一次乘法和一次加法分别计数这点在做横向对比时一定要先统一口径。MACs是乘加操作数实际上就是误差累积的复杂度度量。FLOPs和MACs的关系很直白FLOPs大致等于MACs乘以2。拿矩阵乘法举例一个A与B相乘的矩阵运算输出里的每个元素都做了一次乘法和一次加法所以单独算乘法次数就是MACs乘加都算就是FLOPs。参数量则是存储复杂度它描述的是“要装下这些权重需要多少显存或内存空间”并不直接等价于计算量。三个指标的关系可以这样理解参数量是仓库里的货物件数FLOPs是把货物搬到位要消耗的体力MACs是搬东西的步数。仓库大不一定费力搬得远才费力。一个稀疏大模型参数量可能很大但实际有效计算量却不高就是这个道理。1.2 复杂度不是“数字好看”它直接决定工程决策计算复杂度在大模型场景下直接决定了几个非常现实的工程决策。第一是训练成本预估。训练一个大模型的总FLOPs除以硬件算力就能粗略算出理论训练时间再乘上电费、机时费、人力成本就是项目预算的底牌。没有这个估算预算就是拍脑袋。第二是推理延迟预估。线上推理场景里一次前向的FLOPs决定了单条请求的底层计算代价配合吞吐量需求就能倒推服务器数量。如果估算误差超过一倍扩容和采购方案就会出大问题。第三是显存规划和Batch Size选择。FLOPs虽然不直接等于显存占用但它定义了一天训练步数而显存占用和Batch Size直接挂钩。当我们通过复杂度计算发现单卡Batch Size太小导致算力利用率不足时就需要重新设计并行策略。第四是硬件选型。不同AI加速卡的理论峰值算力差异很大计算复杂度可以帮助我们量化每卡的实际有效算力利用率判断是计算瓶颈还是通信瓶颈。这一点在真实集群上特别重要有时候多买一张卡不如调优一个算子。2. 理论复杂度分析手工推演Transformer的FLOPs2.1 从矩阵乘理解FLOPs怎么来大多数大模型的骨架都是TransformerTransformer的计算主体又是线性层Linear和注意力机制。要手工估算FLOPs核心是会算线性层的复杂度。一个输入形状为[B, S, H_in]的矩阵乘以一个形状为[H_in, H_out]的权重矩阵输出形状为[B, S, H_out]。输出里的每一个元素都做了H_in次乘法和H_in次加法所以单个输出元素的FLOPs是2乘以H_in。整个操作的FLOPs就是2乘以B乘以S乘以H_in乘以H_out。这个式子就是后面所有推导的基础你只要记住“矩阵乘法的FLOPs等于2乘以输入形状乘以输出形状”就够了。下面的推导全部从这个规律展开。2.2 拆解Transformer单层的算力去向以自回归Decoder层为例输入是形状为[B, S, H]的隐向量序列H是隐藏维度。第一块消耗是自注意力部分包含三个投影矩阵QKV、注意力加权和输出投影每步的FLOPs如下表所示计算模块FLOPs公式备注QKV投影6 乘以 B S H^2三个矩阵乘各自为2BSH^2QK^T点积2 乘以 B S^2 H计算注意力分数矩阵Softmax与V加权2 乘以 B S^2 H与V矩阵相乘输出投影2 乘以 B S H^2注意力结果回到原维度MLP第一层8 乘以 B S H^2升维到4HMLP第二层8 乘以 B S H^2降维回H把注意力部分加起来是8BSH^2加4BS^2H。MLP两块加起来是16BSH^2。所以一个Decoder层的总FLOPs应该是24BSH^2加4BS^2H。这里有个细节值得说明在自回归生成场景下注意力矩阵只计算下三角区域严格讲QK^T和V加权这两项只有刚才公式的一半。但由于FlashAttention等融合算子在实现上并不显式构造完整S乘S矩阵很多人在理论预估时依然按完整矩阵的复杂度做上界计算。如果做精细预算可以把因果掩码带来的减半特性加上大约能省去总FLOPs的5%到15%序列越长这个比例越显著。另一个细节是激活函数。GELU、Swish这些非线性操作本身也有计算量但相对矩阵乘动辄10的15次方的量级可以忽略。LayerNorm和RMSNorm的复杂度同样极小工程估算时通常省去不记。2.3 用参数量快速估算训练与推理总复杂度理论分析除了逐层推导还有一个更常用的经验公式每个Token每参数近似需要2次FLOPs用于前向推理训练时前向加反向大约是6次FLOPs每Token每参数。因此两个快速估算公式很重要一是推理总FLOPs约等于2ND二是训练总FLOPs约等于6ND其中N是全量可训练参数D是处理的总Token数。以7B模型训练1T Token为例训练总FLOPs等于6乘以7e9乘以1e12结果约4.2e22。这个数量级估算在项目立项阶段非常有用只需要知道参数量和数据处理量就能算出预算区间不需要完整建模。参数量怎么估算也有规律。一个Transformer Decoder单层的参数量包含QKV投影、输出投影、MLP两个线性层以及若干归一化层。矩阵类参数的求和是4H^2加8H^2再加若干小项就是12H^2这个量级。加上词嵌入层V乘H的参数量模型总参数大约可以表示为VH加12L H^2V是词表大小L是层数。当H和L已知时这个公式能快速判断一个模型结构的“重量级”。在MindSpore中获取实测参数量很简单可以直接遍历网络得到准确数字。用net.trainable_params()返回所有可训练参数每个参数对象的size属性就是该参数量。一行代码把所有参数大小加总得到的就是模型的真实参数量不需要用手工公式硬推。3. 实测计算复杂度MindSpore Profiler实战3.1 Profiler能采集哪些关键数据理论计算能给出一个上界或期望值但真实硬件上的表现还得靠实测。MindSpore自带的Profiler是我最常用的性能与复杂度测量工具它在不同版本里功能有所差异核心能力却保持稳定能采集到训练任务各阶段的耗时统计、算子级耗时、AI Core利用率、通信耗时以及内存占用情况。算子级耗时是最直接的线索。Profiler会把每个算子的执行时间按序记录排序后就能看到哪些算子是大头。AI Core利用率则反映内核单元的整体忙碌程度它和理论峰值相乘就得到实际有效算力。内存数据帮助判断显存是否打满而通信数据能展示集合通信在训练步长中的占比。这些指标本质上帮助我们回答三个问题时间花在哪了、算力用满没有、内存够不够。测量计算复杂度时最关键的输出是算子耗时和AI Core利用率。3.2 实操打点、采集、用MindInsight看指标MindSpore Profiler的使用流程比较固定我以2.x版本的实践为例。首先要导入Profiler类在训练循环外创建实例并指定输出目录。启动采集时调用profiler.start()训练若干step后调用profiler.end()最后调用profiler.analyse()。以下代码是一个可运行的最小模板from mindspore import Profiler, context context.set_context(modecontext.GRAPH_MODE, device_targetAscend) profiler Profiler(output_path./profiler_result) profiler.start() # 此处执行训练循环推荐跑20到50个step for step, (data, label) in enumerate(train_dataset): loss, grads train_step(data, label) if step 30: break profiler.end() profiler.analyse()运行结束后output_path目录下会生成一系列中间文件和汇总数据。我不想把每个文件名写得像背诵文档因为不同MindSpore版本之间文件名确实有变动甚至个别字段会有调整。更稳妥的做法是直接用MindInsight看板查看它具有可视化和汇总功能能直接展示Step Trace分析、算子耗时Top N、AI Core利用率和内存视图。启动MindInsight的命令也不复杂在profiler_result的上级目录执行mindinsight start --port 8080然后在浏览器打开对应地址点击“性能分析”标签就能看到刚才采集的数据。第一次用时建议重点看三个页面算子耗时排行榜、Step Trace各阶段耗时占比、AI Core利用率曲线。这三个页面对应着定位计算密集还是通信密集的核心证据。3.3 从Profiler数据反推实测FLOPs与利用率实测计算复杂度不能只停留在看耗时上还要把耗时转化成FLOPs和算力利用率。转换公式其实很朴素实际有效算力等于理论峰值乘AI Core利用率。通过这个公式可以直接从Profiler给出的利用率反推实际FLOPs。举个例子假设某款训练卡标称FP16理论算力400 TFLOPSProfiler显示AI Core利用率是40%那当前任务实际达到的有效算力就是160 TFLOPS。用这个耗时和实际有效算力相乘就得到执行的总浮点操作数。比如一个step用了0.5秒那么实际FLOPs大约等于160e12乘0.5结果是8e13这个量级。不过这里有个坑MindSpore Profiler报告里的AI Core利用率往往指的是内核单元在一个step中的平均活动比例它本身不区分有效计算和由于数据复用不足产生的“空转”所以用它算出的FLOPs会偏保守。反向更常用也更可靠的办法是先按理论公式算出该step的期望FLOPs再除以实测耗时推算出当前任务要求硬件达到的有效算力最后对比硬件理论峰值得出数据。这样得到的利用率是端到端的有效算力利用率包含通信、调度、数据读取全部开销是最接近工程预算用的指标。具体到MindSpore模型单步期望FLOPs的计算可以直接套用前面的公式。给定batch size、序列长度、层数、隐藏维度和并行规模先算出单卡上的理论FLOPs再按卡数扩展。这里的batch size如果用的是per-device值就再乘上设备数量这样就把数据并行带来的总吞吐量也放进去了。4. 理论值和实测值为什么对不上差异归因4.1 五个被忽略的“算力黑洞”我见过很多同学拿着理论FLOPs兴冲冲地给项目排期结果实测单步耗时是理论值的两倍以上立刻怀疑公式错了。公式多半没错错的是忽略了真实训练中的额外开销。下面五个因素是我实际踩过坑后总结的最大来源。第一是通信开销。数据并行和流水线并行的每一步都有通信集合通信耗时完全不在理论FLOPs里体现。卡一多通信占比能轻松超过20%这对最终耗时的影响非常大。第二是反向传播。很多人用2ND估算推理FLOPs后直接当训练算忘了反向过程大约是前向的2倍所以训练才是6ND。如果只按前向算理论值直接缩小三倍。第三是激活重计算。为了省显存不少模型训练会开启重计算把一部分中间激活丢弃后再在前向反向过程中重新计算。这会让总体FLOPs多出约30%到50%是理论估算完全没覆盖的。第四是算子调度和内核启动开销。小算子也在重复申请内核GPU或AI芯片的空闲间隙累积起来非常可观。尤其在GRAPH_MODE下图编译和算子融合策略会放大或缩小这种开销这需要实测确认。第五是数据加载瓶颈。就算计算层再快如果数据供不上AI Core也会空转等待。这类瓶颈在Profiler里通常表现为Step Trace里Host CPU耗时偏长或者数据队列经常为空。4.2 一次7B模型训练实测复盘为了说明差异有多大我分享一次实际的训练任务复盘。场景是一个7B参数规模模型序列长度4096单卡Batch Size为1共16卡训练。当时我们的目标是踩出一个稳定Step并确认硬件利用率是否在合理范围。理论部分先计算单步训练FLOPs。每步处理的Token数是16卡乘以4096等于65536个Token。参数规模是70亿套用6ND公式单步FLOPs等于6乘7e9乘65536大约2.75e15 FLOPs。如果硬件理论峰值是400 TFLOPS16卡合计6.4e15 FLOPs/s那么理论上限时间约0.43秒一步。实测结果是多少稳定Step耗时大约0.86秒。用期望FLOPs除以实测耗时得到有效算力约3.2e15 FLOPs/s再用有效算力除以16卡理论峰值得到约50%的端到端有效算力利用率。从实战角度来说50%并不算拉胯但要继续优化就有空间了。于是打开Profiler逐项排查发现通信耗时占总Step时间的15%左右激活重计算让总FLOPs增加了约40%数据加载偶尔出现晚到现象。这三项合起来基本解释了那0.43秒的差距。如果只信理论公式很难定位到通信和重计算这两个具体优化切入点。理论值负责“设定预期”实测数据负责“验证归因”两者配合才是完整评估。4.3 不同并行策略对计算复杂度的影响这里额外说一句大模型训练中并行策略的选择也会影响有效计算复杂度。MindSpore支持数据并行、算子级并行、流水线并行和多种混合并行每种并行会带来不同的通信开销模式。通信开销的差异也让同一套理论FLOPs在不同并行度下得到的利用率完全不同。理论上FLOPs是固定的实际耗时却受通信和负载平衡制约。如果发现模型能力上不去不建议盲目增加设备数量而应该把Profiler切到通信视图看看集合通信的耗时曲线是否随卡数增长过快。这个经验在大规模集群上尤其宝贵。5. 常见问题与排查技巧实录5.1 问题速查表实测过程中会遇到一堆看起来莫名其妙但又相似的问题。我把高频问题整理成一张速查表方便排查时按图索骥现象可能原因排查手段AI Core利用率只有20%到30%数据加载慢、算子过小、通信阻塞查看Step Trace各阶段耗时检查数据队列深度Step耗时远高于理论预期激活重计算开启、通信占比高对比开关重计算前后的Step耗时显存增长过快Batch Size过大、激活保存过多、存在显存碎片检查内存视图适度开启重计算或梯度累积Profiler结果文件为空版本接口变化、开启时机错误检查版本差异让Profiler在训练首个Step前创建GPU模式下AI Core利用率低算子未充分融合、小算子过多用MindInsight算子耗时Top N评估算子融合效果不同卡上Step耗时差异大负载不均衡、网络拓扑差异检查各卡Timeline确认通信耗时分布这些现象背后的关键是一定要先确认瓶颈处在数据供给、计算层还是通信层。看到AI Core利用率低就先冲去调算子是不值得提倡的做法。5.2 几条实用排查经验我在多次性能分析实战中积累了几条带“底层经验”色彩的小技巧写在这里供参考。第一不要只看平均利用率。Profiler给出的是一个训练周期的平均值但大模型训练过程中会有预热、脉冲式通信等波动。建议把多个Step的AI Core利用率曲线导出做对比稳定期的高利用率才是真实水平。第二理论FLOPs计算建议单独封装成一个函数。把参数量、序列长度、Batch Size、卡数和是否开启重计算作为参数这样在调整并行配置时能立刻看到理论变化也方便和每次Profiler实测结果对照。下面是一个简化版思路def estimate_train_flops(num_params, num_tokens, recompute_ratio1.3): # 基础训练FLOPs约为6ND开启重计算后额外增加约30% return 6.0 * num_params * num_tokens * recompute_ratio第三MindSpore ProFiler在Ascend平台和GPU平台上的字段名与颗粒度不同跨平台对比时不要直接用栏位名硬套建议统一先换算成Step耗时和有效算力再比较。第四内存评估建议看Profiler统计的峰值而非均值。显存出现瞬时峰值会导致OOM均值一点意义都没有。用峰值除以单卡显存总量乘以卡数得到的“显存水位”才是配置并行方案的真实依据。第五版本变更经常影响Profiler行为。MindSpore 2.2之后开始对配置方式做收拢老代码里直接传参的写法可能会被标记废弃。团队多人协作时建议把Profiler采集脚本单独固定下来不要和训练主逻辑混在一起否则排障时定位慢不说还容易误改动训练逻辑。从工程实践角度把性能分析脚本独立出来是一个很值得保持的好习惯。
返回列表