ICIP2020:VVC解码端基于GPU的运动补偿优化

​本文来自ICIP2020文章《OPTIMIZATION OF MOTION COMPENSATION BASED ON GPU AND CPU FOR VVC DECODING》

文章提出基于GPU的运动补偿来加速VVC解码。根据数据依赖重划分CU和根据不同条件采用不同线程组织方式来充分利用GPU性能。实验表明在NVIDIA GeForce RTX 2080Ti GPU上5ms可以完成UHD 4K视频的运动补偿,比CPU快16倍。

VVC中运动补偿

运动补偿是VVC中最耗时的部分。Fig.1展示的是VTM6.1中主要模块解码时间分布和帧间预测详细的时间分布。

图中的MC是传统的运动补偿。仿射运动补偿(Affine)和DMVR是VVC新增的技术。MV表示生成MV的过程。

Fig.2是在HEVC和VVC中应用AVX2指令后加速比的比较。可以看见,使用SIMD优化后VVC中新技术的加速比难以达到HEVC中的水平。

基于GPU的运动补偿

整体架构

由于特殊的架构,使得GPU处理包含大量预测方法的进程并不高效。第一,复杂的进程会使得编译器给它分配更多的thread block,这导致可以并行执行的thread block数量减少。第二,进程需要一个数据结构包含所有的预测信息,这使得CPU和GPU的通信代价变高。最后,对于运动补偿,不同的预测技术需要不同的计算资源。这为资源分配带来巨大的挑战。因此,本文提出的GPU实现方法根据预测技术将CU分为4类,DMVR CU仿射affine CU三角triangle CU普通CU

Fig.3展示了本文提出的基于GPU的运动补偿的4个步骤。第一,进行熵解码,初始化4类CU的预测信息包括运动信息、块划分信息和编码工具开关标志位。第二,预测信息相继传送到GPU。为了使传输与计算并行,所有内存复制指令都是异步的,并在一个名为copy stream的单独流中执行。当某类CU的相关信息传输完成后立刻开始进行运动补偿操作,减少了等待预测信息的时间。第三,运动补偿完成后,结果会被复制回CPU进行重构(Reconstruct)。第四,滤波(Filtering)后的结果会在下一帧进行熵解码时传输给GPU。为了避免不必要的通信,在slice header解析完成后即可开始参考像素传输,因为此时参考关系已经确定。因为剩余熵解码处理的时间远远大于传输一帧的时间所有传输时间被“隐藏”了。

扫描二维码关注公众号,回复: 12137994 查看本文章

亮度分量和色度分量的运动补偿被分到两个kernel中分别执行,这是因为它们块的尺寸不同。分开执行可以为不同分量分配不同的计算资源。而且,有的技术只针对亮度分量,可以把它从色度分量上移除来简化色度分量kernel。由于色度分量kernel需要的资源更少且不依赖于亮度kernel的结果(DMVR CU除外),两个kernel可以在两个流上并行执行,提高了GPU效率。

不同类型CU的运动补偿

内存访问延迟是影响kernel效率的主要因素。访问一次非cache的全局内存会导致400-600个时钟周期的延迟。有两种方法降低访存延迟,一种是创建足够多的thread,这样当一些thread在等待访存时其他thread可以正常执行。本文提出的基于GPU的运动补偿中,一个thread block负责一个亮度块或两个色度块的运动补偿。thread block数量和CTU数量相同。亮度块的thread block尺寸是128,色度块是64。另一种是利用GPU的多级内存结构。访问一次共享内存只需要几十个时钟周期,可以使用共享内存来缓存那些访问频繁的参考像素和存储临时结果。共享内存非常有限而每个thread block又必须分配同样大小的共享内存,为了减少每个thread block的共享内存我们将大的CU分成几个子块,增加可以并行执行的活跃thread block数量。

考虑到DMVR的特性,将DMVR CU分为几个不大于16x16的子块。对于亮度DMVR块的运动补偿,第一步是搜索一个使双向预测信号SAD最小的修正MV。在计算由初始MV得到的两个块的SAD时,我们需要尽快得到SAD值,因此每个warp计算1/4个块得到四个SAD值在共享内存中通过原子指令相加。如果SAD大于阈值,需要计算另外24个SAD。在这种情况下,需要减少块级同步和原子指令引起的内存操作冲突。所以,每个warp负责在同一时间计算两个整块的SAD。24个SAD存储在共享内存中。在计算过程中,每个warp最小的SAD也被存在共享内存中。当24个SAD都得到后,仅需要3次比较就可以得到最终修正MV。修正MV会被存到全局内存用于后续计算。因为不同kernel中的thread只能通过全局内存进行通信,所以DMVR色度kernel和亮度kernel在同一个stream中,以确保在所有修正MV确定后色度块才开始运动补偿。此外,DMVR亮度kernel完成后修正MV缓存会异步传输到CPU用于其他帧MV生成。当MV修正后,进行插值和加权平均计算。thread的组织和计算第一个SAD类似,每个warp计算1/4个块。尤其是对于使用双向光流法的块,预测像素需要基于梯度进一步修正。梯度计算和SAD类似,但是在生成预测偏移值时每个warp负责一个6x6的子块。在计算梯度的自相关和互相关时,在warp shuffle指令的帮助下,warp中的不同thread能够通过寄存器交换数据,这比其他存储器的延迟更低。值得注意的是,梯度也存储在参考像素和临时像素缓冲区中,因为这些缓冲区中的数据将不再被使用。

对于使用仿射运动补偿的CU,每个4x4的子块都有自己的MV。由于最小的内存事务是32字节,使用4x4的块作为运动补偿的基本单元效率不高。所以我们把仿射CU分为几个不大于16x8的块,每个warp处理一个4x4子块的运动补偿。4x4子块的预测像素存储在共享内存中,当所有子块都完成后会被写到全局存储区。对于使用双向光流的块不必存储梯度,我们可以直接计算预测偏移值并把它加到预测结果上从而减少访存时间。此外,在计算色度块的MV时要重新计算对应的亮度块的MV,这引入了冗余计算但是去除了亮度和色度间的依赖。

对于普通CU,由于可能会使用双向光流法我们将其分为不大于16x16的子块。普通CU的运动补偿过程和完成MV修正后的DMVR CU一样。对于三角CU,分割后原三角块的形状很难确定。在三角kernel中原始尺寸不变,并且不适应共享内存。考虑到三角块数量很少,我们在GPU色度stream上对三角块进行运动补偿计算。这个kernel需要的资源很少,所以它可以在DMVR块运动补偿期间完成。

实验结果

实验配置是RA模式,测试序列是class A中的4K序列。算法在CUDA 10.2上实现并集成到VTM6.1,运动补偿模块在GPU上计算,其他模块包括熵解码、帧内预测、反变换、反量化和环路滤波都在CPU上进行。

表1是解码不同序列的加速情况。我们的算法在NVIDIA GeForce RTX 2080Ti上实现,有4352个CUDA cores和1545 MHz boost clock。比较的基准解码速度是 Intel Core I7-8700K CPU最大超频是4.70 GHz。由表1可以看到,我们的解码器平均提速46%。

表2是运动补偿占用时间的百分比。Campfire序列的大部分CU没有使用帧间预测所以加速比比其他序列低。此外,随着QP增大码率会降低这使得编码器倾向于减少使用帧内预测更多的使用skip或merge模式。所以加速比随着码率降低而提高。

表3是各类型块详细的执行时间,三角CU的时间完全被隐藏了所以表中只有三类块的时间和运动补偿的总时间(包括内存传输时间)。最终运动补偿可以在约5ms内完成加速了约16倍。

感兴趣的请关注微信公众号Video Coding

猜你喜欢

转载自blog.csdn.net/Dillon2015/article/details/110204043