NPU 3DGS 渲染优化实战:基于 Ascend C 的 CalcRender Alpha Blending 算子流水与剪枝设计
2026/9/18 20:58:48 网站建设 项目流程

NPU 3DGS 渲染优化实战:基于 Ascend C 的 CalcRender Alpha Blending 算子流水与剪枝设计

【免费下载链接】cann-recipes-spatial-intelligence本项目针对空间智能业务中的典型模型、加速算法,提供基于CANN平台的优化样例项目地址: https://gitcode.com/cann/cann-recipes-spatial-intelligence

导读

本文聚焦 CANN 平台上 3D 高斯泼溅(3DGS)渲染管线中核心的CalcRender(Alpha Blending)算子,针对 NPU SIMD(单指令多数据)向量计算架构,系统阐述该算子从"逐高斯球串行渲染"到"多高斯球批量流水 + 两级剪枝"的完整优化路径。读者通过本文可以掌握:Alpha Blending 在 SIMD 架构下的流水化实现原理、基于 PipeBarrier 频率优化的指令流水设计、Ping-Pong 双缓冲访存掩盖技巧,以及 Tile 级与 Sub Tile(像素行)级两级剪枝机制的实现细节与性能收益评估方法。文中所有结论均可在本仓库 algorithms/gaussian_splatting 与 ops/ascendc 的源码、测试用例中找到对应实现证据。

背景:3DGS 光栅化中的 Alpha Blending

3D 高斯泼溅(3DGS)将场景表达为一组带协方差、颜色与不透明度的 3D 高斯球。渲染时,给定特定相机视角与投影平面,需要把空间中的 3D 高斯球按深度顺序混合到每个像素上,从而生成该视角的渲染图像。这一"颜色累积"过程的核心步骤即为 Alpha Blending,它直接决定了渲染图像的质量与计算量。

在 SIMD 架构(NPU 向量核)上,Alpha Blending 的串行依赖特性与向量并行特性之间存在天然矛盾:混合过程要求高斯球严格按深度排序后依次累乘,而 SIMD 又希望同一时刻对大量像素执行相同运算。如何在保证数学等价的前提下,让向量计算单元满负荷运转,是本仓库 CalcRender 算子 优化的核心命题。本篇文档(gaussian_splatting_alpha_blending_optimization.md)即对该算子优化的具体阐述,主要包括三部分:3DGS 渲染流程介绍、Pipeline 流水优化和剪枝设计。

Alpha Blending 数学原理与算法流程

混合公式

给定按深度排序后的 N 个高斯球,像素最终颜色 C 由以下累乘公式决定:

$$ C = \sum_{i \in N} c_i \alpha_i \prod^{i-1}_{j=1}(1-\alpha_j) $$

其中每个高斯球对该像素的透明度由二维高斯概率密度决定:

$$ \alpha_i = o_i \exp(-\frac{1}{2} \delta ^ T * \Sigma * \delta) $$

  • $c_i$:第 i 个高斯球的颜色(RGB);
  • $\alpha_i$:第 i 个高斯球在当前像素处的透明度(alpha 值),由不透明度 $o_i$ 与二维投影协方差 $\Sigma$ 决定;
  • $\delta$:像素坐标与该高斯球投影中心(均值)的偏移;
  • $\prod^{i-1}_{j=1}(1-\alpha_j)$:前 i-1 个高斯球的累积透射率(transmittance),体现"遮挡"关系。

在 calc_render.py 的前向入口中可以看到,该公式所需的高斯属性被拼接为一个紧凑的张量gs,其每一行即一个高斯球的 10 维属性向量:means(2) + conic0/1/2(3) + opacity(1) + colors(3) + depth(1),与内核 calc_render_fwd_double_clip_gsids.cpp 中定义的ATTR_MEAN_XATTR_CONIC_0ATTR_OPACITYATTR_COLOR_RATTR_DEPTH等属性索引一一对应(NUM_GS_ATTRIBUTES = 10)。

整体算法流程

  1. 按 Tile 切分:按 tile 对 vector core 进行切分,每个核循环渲染被分配到的 tile 上的所有像素;
  2. 有序遍历高斯球:由于累乘操作的存在,必须保证对高斯球排序后依次渲染,因此用 for 循环遍历每个高斯球;
  3. 并行计算像素影响:对每个高斯球,并行计算其对 tile 上所有像素的影响,计算出 alpha 值和颜色贡献;
  4. 更新累积状态:对每个像素位置,更新累积颜色和透明度值。

从内核的Process()实现可见,tile 的像素坐标(tileCoordX/tileCoordY)、累积颜色(colorR/G/B)、累积透射率对数(ln1SubAlphaSum)与误差项(error)均以 Ping/Pong 双缓冲形式驻留在 UB(Unified Buffer)中,外层循环按 tile 切分、内层循环按高斯球遍历,与上述流程完全对应(calc_render_fwd_double_clip_gsids.cpp)。其中透射率采用对数域累积(ln(1-α)累加后再Exp),配合 Kahan 求和风格的误差补偿变量error,在向量化计算中保持数值稳定性,这正是测试用例 test_calc_render_fwd.py 中 CPU 参考实现与 NPU 输出能够逐元素对齐的前提。

SIMD 架构下的实现难点

将上述串行依赖的混合算法映射到 SIMD 向量架构,主要面临三个难点:

  1. 流水设计复杂:由于 SIMD 架构的并行计算特性,流水设计需要充分考虑数据依赖和计算顺序,确保各个计算单元能够高效协同工作;
  2. 内存访问开销与计算开销相互掩盖:高斯球数量众多,且一个 tile 内需要渲染的高斯球在内存中是不连续的(需经gsIds间接寻址),导致内存访问开销较大,影响计算效率;
  3. 高斯球冗余计算:每个高斯球都需要对 tile 上的所有像素计算影响,而单个高斯球实际覆盖范围远小于整个 tile,产生大量冗余计算。

针对难点 2 与难点 3,仓库在光栅化前的 GaussianFilter(视锥剔除)算子 中已先行过滤掉对当前帧无贡献的高斯;本文则聚焦CalcRender算子内部,通过流水优化(难点 1、2)与两级剪枝(难点 3)进一步压榨性能。

高斯球流水优化

指令流水并行:减少 PipeBarrier 插入频率

由于高斯球之间以及单个高斯球内部计算存在前后依赖,在 vector 指令之间经常需要插入PipeBarrier保证数据正确性,导致流水效率较低。根据 msprof op simulator 的仿真结果:对 64 个 float 数计算 VMuls 需要 15ns,而对 2048 个 float 数计算 VMuls 并不是 64 个数的 32 倍(480ns),而只需 51ns——因为其中包含了指令解码和数据预取的开销。这直观说明:固定开销占比越高、指令粒度越细,流水停顿的代价越大,因此减少屏障插入频率是提升流水效率的关键杠杆。

最开始的逐高斯球流水设计中,每处理一个高斯球,各步骤运算之间都需要插入PipeBarrier。优化思路是:将多个高斯球中没有依赖的部分计算合并在一起,减少屏障频率。具体做法是:

  • 将每次处理的高斯球数量增加到4 个(对应内核常量NUM_GS_PER_LOOP = 4,见 calc_render_fwd_double_clip_gsids.cpp);
  • 每个高斯球的计算分为两个阶段:第一阶段计算 α 值,第二阶段计算颜色贡献
  • 第一阶段并行处理 4 个高斯球,计算各自的 α 并暂存;
  • 第二阶段依次处理这 4 个高斯球,计算颜色贡献并更新累积颜色与透明度。

这样每处理完 4 个高斯球才需要插入一次PipeBarrier,大幅减少了屏障插入次数。

在内核实现中可以看到流水化的直接证据:x_1..x_4y_1..y_4gaussWeight_1..4alpha_1..4等 4 组临时缓冲(SharedBuffInit),以及主循环中"先对 4 个高斯球批量求 x/y 偏移 → 批量求二次型 → 批量 Exp → 批量求 alpha → 批量 Mins(0.999)"的 SIMD 指令序列(L454-L528),中间只穿插少量必要的同步点。

该优化带来的代价是UB 空间使用量增加:需要存储 4 组中间结果。实现上必须仔细复用 UB 空间,例如在进行颜色渲染时复用前一个高斯球的 α 计算空间(内核中alphaT_alpha_1共享同一块 UB 偏移,见 SharedBuffInit)。

图:多高斯球批量流水设计——通过合并无依赖的 α 计算、减少 PipeBarrier 插入频率,让指令解码与数据预取开销被摊薄到更多有效计算上。

Double Buffer 优化:掩盖内存访问开销

第二个优化手段是使用Double buffer(Ping-Pong 缓冲)提前读取高斯球属性,掩盖内存访问延迟。具体做法:在处理当前批次高斯球的同时,提前读取下一个高斯球的属性值到第二块 buffer 中;处理下一个高斯球时直接从已写好的 buffer 读取属性,避免内存访问延迟。

内核中这一点体现得非常明确:

  • 高斯属性缓冲分为gsAttr1..4Ping_gsAttr1..4Pong_两组(UbBuffInit);
  • 主循环迭代尾部执行gsPing = !gsPing翻转缓冲角色(L756),使当前批次的 MTE2(GM→UB 搬运)与下一批次的向量计算重叠;
  • 像素级状态(tileCoordcolorR/G/Bdepthln1SubAlphaSumerror)同样采用PingBuffInit/PongBuffInit双份布局,并通过EVENT_ID6/EVENT_ID7两个事件(pingId_/pongId_)配合SetFlag/WaitFlag实现 MTE2、Vector、MTE3 三段流水间的精确同步。

图:使用 Ping-Pong 双缓冲后,"搬入—计算—搬出"由串行变为重叠执行,消除了数据搬移的等待时间。

高斯球剪枝设计

3DGS 渲染过程中,许多高斯球对当前 tile 的像素其实没有贡献,造成大量冗余计算。为提升效率,算子设计了两级剪枝机制:Tile 级别剪枝与 Sub Tile(像素行)级别剪枝。

Tile 级别剪枝:累积透明度达到阈值即提前结束

根据渲染公式,当累积透明度(透射率)降到一定阈值以下时,后续高斯球对像素块的贡献可以忽略不计。因此引入 Tile 级剪枝:当某个 tile 的累积透明度达到预设阈值时,提前结束对该 tile 的渲染,避免不必要的计算。

受限于 SIMD 架构,无法对每个像素单独剪枝,只能在 tile 级别剪枝——即只有所有像素的累积透明度都达到阈值,才能结束渲染。具体实现流程:

  1. 使用CompareScalar将 tile 上所有像素的累积透明度与阈值比较,生成掩码(按位写回,表示每个像素是否达标);
  2. 使用ReinterpretCast将掩码向量转为 64 位整数,方便后续判断;
  3. for 循环将掩码整数与UINT64_MAX按位与:若结果相等,说明所有像素都已达标,可以结束渲染;
  4. 记录终止高斯球 ID:为在反向传播中正确计算梯度,需要记录每个 tile 终止渲染的高斯球 ID;反向梯度传播时从该 ID 继续计算梯度,保证梯度正确性。

内核实现与文档步骤完全一致:AscendC::CompareScalar(clipIs_, T_, 0.01f, CMPMODE::LT, calPixel_)判断累积透射率T_ = exp(ln1SubAlphaSum)是否小于 0.01(L413-L426),随后ReinterpretCast<uint64_t>并逐个与UINT64_MAX比较;一旦判定全部达标,则把当前高斯球下标i写入gsClipIndexbreak(L427-L434)。该gsClipIndex作为算子输出(DT_INT64,见 calc_render_fwd_double_clip_gsids.cpp)保存,供反向算子使用。

由于 Tile 级剪枝要求所有像素同时达标才生效,剪枝效果有限(实验结果中性能收益约 5%~10%)。为进一步提升效率,引入像素行级剪枝。

Sub Tile 级别剪枝:基于高斯球影响范围的像素行剪枝

Sub Tile 级剪枝基于高斯球影响范围:计算每个高斯球在投影平面上的影响范围,若某像素行完全位于影响范围之外,则该高斯球对该行无贡献,可跳过该行计算。

由于 SIMD 架构需要连续地址计算,无法对每个像素单独细粒度剪枝,因此采用对像素行进行剪枝的做法。通过分析高斯球的协方差矩阵可算出其在投影平面上的影响范围,从而实现像素行级剪枝。实验结果评估显示:每个高斯球实际只影响 6×6 左右的像素,而预设 tile size 为 32×32,因此每个高斯球实际只影响 tile 上约 20% 的像素行。

具体实现流程:

  1. 在整个 tile 上计算 α 值(高斯球在投影平面上的影响范围),确定其覆盖的像素行范围;
  2. 使用WholeReduceMax计算每个像素行 α 的最大值;
  3. 使用CompareScalar将每个像素行的最大 α 与阈值比较,生成掩码,表示每个像素行是否需要进行渲染计算;
  4. 将掩码向量重新解释为 64 位整数,用位运算判断像素行状态:先用ScalarCountLeadingZero计算掩码整数的前导零数量——由于小端存储特性,第一个 1 所在的位置实际对应最后一个需要渲染的像素行
  5. 再用ScalarGetCountOfValue<1>计算掩码中 1 的数量,即需要渲染的像素行数量;由这两者确定需要渲染的像素行范围,跳过不必要的计算;
  6. 记录像素行范围供反向使用:在 UB 空间用UInt8保存每个高斯球的起始像素行与结束像素行索引,每累计 1024 个高斯球后写回 GM 空间,方便反向传播时读取。

内核实现中,WholeReduceMax(tmpRes_1, alpha_1, ...)按行归约出每行最大 α(L531-L538),随后CompareScalar(..., 0.01f, CMPMODE::GE, ...)判断"行内最大 α ≥ 0.01"的像素行予以保留(L541-L548);ScalarCountLeadingZeroScalarGetCountOfValue<1>计算出行范围sta_pix/end_pix(L553-L556);alphaClipIndexUB_UInt8成对写入起止行索引并分批写回 GM(L558-L566),对应输出alphaClipIndexDT_UINT8,见 calc_render_fwd_double_clip_gsids.cpp)。

图:像素行级剪枝——先以像素网格上的行最大 α 生成掩码,再通过掩码位运算确定需要渲染的像素行范围(浅色区域为待处理像素)。

剪枝后的实际计算

剪枝确定有效行范围[sta_pix, end_pix)后,后续所有向量运算(如Axpy(colorR, alphaT_, colorR属性)Ln(ln1SubAlpha_)、误差补偿Sub(error, ...)等)都只在calPixel_1 = (end_pix1 - sta_pix1) * nPixel_1d_个连续元素上执行(L576-L672)。同时内核还做了以下细节处理:

  • α 上限截断alpha = min(alpha, 0.999f),与文档实验中的"不透明度阈值 0.99"配合,防止数值饱和(L524-L528);
  • 行剪枝失效保护:当某高斯球calPixel_1 == 0(没有任何像素行被保留)时,写入占位索引(nPixel_1d_, 0)并跳过其颜色/深度累积(L559-L564);
  • 末批(tail)处理:主循环按NUM_GS_PER_LOOP批量推进后,剩余不足 4 个的高斯球进入单高斯球 tail 循环,同样复用行剪枝逻辑(L759-L889)。

反向传播:如何利用剪枝信息正确计算梯度

两级剪枝的正确性依赖反向算子对剪枝信息的完整还原,仓库中对应实现为 calc_render_bwd_var_clip_gsids.cpp,其 host 侧入口calc_render_bwd_var_clip_gsids(CalcRender.cpp)将gsClipIndexgsIds拼接后一并传给内核。反向内核的关键行为:

  1. 从终止 ID 处反向遍历:前向记录的gsClipIndex(每个 tile 终止渲染的高斯球 ID)被用作反向循环的起点currOffset,反向循环for (i = currOffset - 1; i >= prevOffset; i--)从后向前重算透射率与梯度(L282),从而跳过前向已被剪枝的高斯球,这是反向收益的核心来源;
  2. 还原像素行范围:读取前向写回的alphaClipIndex(UInt8 起止行),换算回像素区间sta_pix1/end_pix1,只对有效像素计算梯度,无效行直接continue(L296-L300);
  3. 原子累加梯度:由于同一高斯球可能被多个 tile 引用,梯度写回使用SetAtomicAdd<float>()原子加(L475-L480),保证跨 tile 梯度正确聚合。

前向输出的lastCumsumerror也会被反向内核读回,用于数值稳定地重建累积透射率(Kahan 补偿风格,L338-L351)。

算子工程结构:从 Python 到内核的完整调用链

CalcRender算子在仓库中的完整实现链路如下,便于读者按图索骥:

层次文件职责
Python 封装calc_render.pytorch.autograd.Function,拼装gs张量(10 属性),forward 调用前向算子,backward 调用反向算子并拆分梯度
C++ 算子入口CalcRender.cppaclnnCalcRenderFwdDoubleClipGsids/aclnnCalcRenderBwdVarClipGsids的 ATen 封装,负责设备校验与输出张量分配
Host tilingcalc_render_fwd_double_clip_gsids.cpp读取nPixeltileNumnGaussSetBlockDim(GetCoreNumAiv())按 AIV 向量核数设置 block 维度
前向内核calc_render_fwd_double_clip_gsids.cpp本文所述的流水优化与两级剪枝实现(KERNEL_TYPE_AIV_ONLY
反向内核calc_render_bwd_var_clip_gsids.cpp利用gsClipIndex/alphaClipIndex还原剪枝范围并反向求梯度
测试用例test_calc_render_fwd.py、test_calc_render_bwd.py以 102000 个高斯、tile size 32 为例,与 CPU 参考实现比对颜色/深度/梯度

测试用例 test_calc_render_fwd.py 的用例规模为[102000, 32*3, 32*4, 32](10.2 万高斯、96×128 像素、tile size 32),其 CPU 参考实现render_sort_kvreshape(alpha.shape[0], 16, 64)max_values >= 0.01transparency >= 0.01等逻辑(test_calc_render_fwd.py),恰好对应内核中"行最大 α ≥ 0.01 保留"与"透射率 < 0.01 剪枝"两条阈值策略,可作为理解剪枝语义的旁证。

实验结果分析

优化组合效果

在包含 13 万个高斯球的场景(实际过滤后需渲染的高斯球数仅为 67917 个)上训练 30000 个 iters,评估流水优化与剪枝设计对渲染效率的提升。实验中选取 α 阈值为 0.004、不透明度阈值为 0.99,结果如下(正向耗时收益约 10%,反向耗时收益约 30%):

优化方法PSNR前向 device 耗时 (ms)反向 device 耗时 (ms)
无优化25.0084.80718.710
流水优化25.0054.37917.565
剪枝优化25.0105.10113.141
流水+剪枝24.9744.49513.107

分析要点:

  • 流水优化在几乎不损失 PSNR(25.008 → 25.005)的前提下将前向耗时从 4.807ms 降到 4.379ms,说明减少 PipeBarrier 频率带来了可观的指令级并行收益;
  • 剪枝优化单独使用前向耗时反而略升(5.101ms),这是因为剪枝判断本身引入了额外的比较与位运算开销,但其反向收益显著(18.710 → 13.141ms),反向遍历范围的大幅缩短抵消了判断开销;
  • 流水 + 剪枝组合在 PSNR 基本持平(24.974)的情况下,前向 4.495ms、反向 13.107ms,是综合收益最高的配置。

不同 α 阈值对剪枝效果的影响

仅评估(不重新训练)不同 α 阈值对剪枝效果的影响,结果如下:

阈值选取PSNR前向 device 耗时 (ms)反向 device 耗时 (ms)
025.0824.37918.710
0.00425.0094.49513.107
0.0124.9494.35811.267
0.0224.8724.2369.827
0.0524.6724.1307.937
0.124.2743.5391.410
0.223.3093.7641.043

可以看出:阈值提高对反向传播的收益提升明显。在选取阈值为 0.1 时,device 反向耗时仅为原来的 7.5%(18.710 → 1.410ms),而 PSNR 仅下降 3.3%(25.082 → 24.274)。这为不同精度/性能需求的场景提供了阈值调优空间:追求渲染质量时可取较低阈值(如 0.004),追求端到端训练吞吐时可取较高阈值(如 0.1)。

总结

本文围绕 3DGS 渲染核心算子CalcRender,完整还原了其在 NPU SIMD 架构下的优化脉络:

  1. 流水优化:将逐高斯球计算重构为"4 高斯球批量 α 计算 + 顺序颜色累积"的两阶段流水,显著减少PipeBarrier插入频率;配合 Ping-Pong 双缓冲实现属性预取,掩盖非连续内存访问开销;
  2. 两级剪枝:Tile 级剪枝在累积透射率全面达标后提前终止渲染;Sub Tile 级剪枝利用"行最大 α"掩码 + 位运算高效确定每个高斯球的有效像素行范围,将计算量压缩到实际影响范围(约 tile 的 20% 像素行);
  3. 剪枝信息贯通正反向:前向输出的gsClipIndex(终止高斯球 ID)与alphaClipIndex(起止行索引)被反向算子读取,实现梯度计算的正确还原与反向计算量的进一步削减;
  4. 可复现的实验基准:13 万高斯场景下流水+剪枝组合使前向耗时约降 10%、反向约降 30%;α 阈值调至 0.1 时反向耗时可降至原来的 7.5% 而 PSNR 仅降 3.3%。

上述设计与数据全部对应本仓库可验证的实现:内核源码见 calc_render_fwd_double_clip_gsids.cpp 与 calc_render_bwd_var_clip_gsids.cpp,配套测试见 ops/ascendc/tests,读者可结合测试用例自行复现验证。若需进一步了解渲染前的视锥剔除优化(GaussianFilter算子),可参考 gaussian_splatting_culling_optimization.md。

【免费下载链接】cann-recipes-spatial-intelligence本项目针对空间智能业务中的典型模型、加速算法,提供基于CANN平台的优化样例项目地址: https://gitcode.com/cann/cann-recipes-spatial-intelligence

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询