分类: HPC & ML sys

  • Intel Core Ultra 笔记本处理器集成NPU初探(Intel AI Boost)

    Intel Core Ultra 笔记本处理器集成NPU初探(Intel AI Boost)

    (更新Roofline图)在笔记本市场中,Intel Ultra系列和AMD 8000系列不约而同的加入了集成NPU作为卖点(甚至对于AMD而言,是其7000系到8000的几乎唯一变化),各路数码新闻中鼓吹最多的便是所谓“AIPC”概念,但却往往对其NPU的具体用法语焉不详,或者将一些实际使用核显进行的推理归功于NPU。

    由于本人近日购入了Intel Core Ultra CPU的笔记本,外加科研需要,对其集成NPU进行了一些调研。

    省流:通常跑AI推理的实用性不如核显,目前仅支持静态shape的模型,因此无法做LLM推理,通常只做AI抠图这类简单任务。目前最大的用途是Win11自带的“工作室”效果功能,包含摄像头背景虚化、眼神接触、自动缩放取景三个功能,虽然这些CPU/GPU也能跑,但或许用NPU功耗更低。

    设备:华硕 ROG 幻16 air (GU605),CPU:Intel Core Ultra 9 185H

    打开Intel官网中Ultra 9 185H的资料,其对NPU的描述如下:

    注意最后一行,由于OpenVINO是Intel官方的推理框架,后续我们将用它来测试,OpenVINO显然是支持Ultra的NPU的,相关文档如下:https://docs.openvino.ai/2024/openvino-workflow/running-inference/inference-devices-and-modes/npu-device.html,可以看到Ultra中NPU的实际型号为NPU 3720

    测试环境实用Win11,其自带NPU的驱动(Linux下则需要自行编译),OpenVINO使用pip方式安装,具体请参考官方文档,注意其并非所有安装方式都有NPU支持。

    硬件属性

    使用如下代码检测NPU,并打印其硬件属性:

    import openvino as ov
    import openvino.properties as ovp
    print(f"{ov.__version__ = }")
    
    core = ov.Core()
    print(f"{core.available_devices = }")
    
    DEVICE = "NPU"
    if DEVICE in core.available_devices:
        print(f"- {DEVICE} found")
        supported_properties = core.get_property(DEVICE, ovp.supported_properties)
    
        for p, w in supported_properties.items():
            try:
                print(f"{p:>40} ({w}) = {core.get_property(DEVICE, p)}")
            except Exception as e:
                print(f"{p:>40} ({w}):", e)
    else:
        print(f"- {DEVICE} not found")

    在我的设备上输出如下:

    ov.__version__ = '2024.0.0-14509-34caeefd078-releases/2024/0'
    core.available_devices = ['CPU', 'GPU.0', 'GPU.1', 'GPU.2', 'GPU.3', 'NPU']
    - NPU found
                           AVAILABLE_DEVICES (RO) = ['3720']
                                   CACHE_DIR (RO) =
                          CACHING_PROPERTIES (RO) = {'DEVICE_ARCHITECTURE': 'RW', 'NPU_COMPILATION_MODE_PARAMS': 'RW', 'NPU_DMA_ENGINES': 'RW', 'NPU_DPU_GROUPS': 'RW', 'NPU_COMPILATION_MODE': 'RW', 'NPU_DRIVER_VERSION': 'RW', 'NPU_COMPILER_TYPE': 'RW', 'NPU_USE_ELF_COMPILER_BACKEND': 'RW'}
                         DEVICE_ARCHITECTURE (RO) = 3720
                                   DEVICE_ID (RW) =
                                 DEVICE_UUID (RO) = <已打码>
                          ENABLE_CPU_PINNING (RW) = False
                    EXCLUSIVE_ASYNC_REQUESTS (RW) = False
                            FULL_DEVICE_NAME (RO) = Intel(R) AI Boost
               INTERNAL_SUPPORTED_PROPERTIES (RO) = {'CACHING_PROPERTIES': 'RW'}
                                   LOG_LEVEL (RW) = Level.NO
                              MODEL_PRIORITY (RW) = Priority.MEDIUM
                            NPU_BACKEND_NAME (RO) = LEVEL0
                        NPU_COMPILATION_MODE (RW) =
                 NPU_COMPILATION_MODE_PARAMS (RW) =
                           NPU_COMPILER_TYPE (RW) = DRIVER
                   NPU_DEVICE_ALLOC_MEM_SIZE (RO) = 0
                   NPU_DEVICE_TOTAL_MEM_SIZE (RO) = 33554432
                             NPU_DMA_ENGINES (RW) = -1
                              NPU_DPU_GROUPS (RW) = -1
                          NPU_DRIVER_VERSION (RO) = 1688
                               NPU_MAX_TILES (RW) = -1
                                NPU_PLATFORM (RW) = AUTO_DETECT
                         NPU_PRINT_PROFILING (RW) = NONE
                   NPU_PROFILING_OUTPUT_FILE (RW) =
                          NPU_PROFILING_TYPE (RW): Unable to convert function return value to a Python type! The signature was       
            (self: openvino._pyopenvino.Core, device_name: str, property: str) -> object
                                NPU_STEPPING (RW) = -1
                NPU_USE_ELF_COMPILER_BACKEND (RW) = AUTO
                                 NUM_STREAMS (RO) = 1
            OPTIMAL_NUMBER_OF_INFER_REQUESTS (RO) = 1
                   OPTIMIZATION_CAPABILITIES (RO) = ['FP16', 'INT8', 'EXPORT_IMPORT']
                            PERFORMANCE_HINT (RW) = PerformanceMode.LATENCY
               PERFORMANCE_HINT_NUM_REQUESTS (RW) = 1
                                  PERF_COUNT (RW) = False
              RANGE_FOR_ASYNC_INFER_REQUESTS (RO) = (1, 10, 1)
                           RANGE_FOR_STREAMS (RO) = (1, 4)
                        SUPPORTED_PROPERTIES (RO) = {'AVAILABLE_DEVICES': 'RO', 'CACHE_DIR': 'RO', 'CACHING_PROPERTIES': 'RO', 'DEVICE_ARCHITECTURE': 'RO', 'DEVICE_ID': 'RW', 'DEVICE_UUID': 'RO', 'ENABLE_CPU_PINNING': 'RW', 'EXCLUSIVE_ASYNC_REQUESTS': 'RW', 'FULL_DEVICE_NAME': 'RO', 'INTERNAL_SUPPORTED_PROPERTIES': 'RO', 'LOG_LEVEL': 'RW', 'MODEL_PRIORITY': 'RW', 'NPU_BACKEND_NAME': 'RO', 'NPU_COMPILATION_MODE': 'RW', 'NPU_COMPILATION_MODE_PARAMS': 'RW', 'NPU_COMPILER_TYPE': 'RW', 'NPU_DEVICE_ALLOC_MEM_SIZE': 'RO', 'NPU_DEVICE_TOTAL_MEM_SIZE': 'RO', 'NPU_DMA_ENGINES': 'RW', 'NPU_DPU_GROUPS': 'RW', 'NPU_DRIVER_VERSION': 'RO', 'NPU_MAX_TILES': 'RW', 'NPU_PLATFORM': 'RW', 'NPU_PRINT_PROFILING': 'RW', 'NPU_PROFILING_OUTPUT_FILE': 'RW', 'NPU_PROFILING_TYPE': 'RW', 'NPU_STEPPING': 'RW', 'NPU_USE_ELF_COMPILER_BACKEND': 'RW', 'NUM_STREAMS': 'RO', 'OPTIMAL_NUMBER_OF_INFER_REQUESTS': 'RO', 'OPTIMIZATION_CAPABILITIES': 'RO', 'PERFORMANCE_HINT': 'RW', 'PERFORMANCE_HINT_NUM_REQUESTS': 'RW', 'PERF_COUNT': 'RW', 'RANGE_FOR_ASYNC_INFER_REQUESTS': 'RO', 'RANGE_FOR_STREAMS': 'RO', 'SUPPORTED_PROPERTIES': 'RO'}

    ResNet-50 单batch推理测试

    使用fp16精度,batchsize = 1,重复2000次推理,单次耗时如下:

    avg latency = 3.462ms

    根据估计的ResNet-50的计算量,其达到的roofline性能大致如下(不准确,仅预估):

    RESNET50_M_FLOP = 8206.520    # approximate
    RESNET50_M_MEM_RW = 105.763   # approximate
    
    avg latency = 0.0034620463848114014
    reached GFLOP/s: 2370.4246
    reached GB/s: 30.5493

    达到的fp16性能约为2.35 TFLOP/s,由于是bs=1的推理,离上限值应该还是有不小距离的

    功耗的话,用HWInfo64可看到“System Agent Power”在推理时从4w左右增加到10w左右,即6w,不确定这其中是否都是NPU的功耗,否则这个能效表现其实一般。不过在打开全部的“Windows工作室效果”时,System Agent功耗仅上升了1~2w,此时任务管理器中NPU利用率约为46%

    多模型Roofline测试

    这是多个模型在其上的测试,达到的FLOP/s和内存带宽基于理论估计,模型列表如下,不存在则表示(在当时)尚无法成功运行。

    可以看出,其优化程度较差,远远无法跑到理论性能fp16: 5.7 TFLOP/s, int8: 11.5TOP/s(计算方法:2048 fp16 MACs or 4096 int8 MACs per cycle @ 1.4GHz)。也有文章指出,其可能无法利用全部的内存带宽,具有远远更低的内存墙,但依然无法解释为何ResNet-34的性能也很低。

  • 论文简读:Benanza: 自动产生μBenchmark以测试GPU上深度学习模型的Latency下界和优化空间

    论文简读:Benanza: 自动产生μBenchmark以测试GPU上深度学习模型的Latency下界和优化空间

    时效性提醒:本文首次编写发布于3 年前。

    论文全名:Benanza: Automatic μBenchmark Generation to Compute “Lower-bound” Latency and Inform Optimizations of Deep Learning Models on GPUs

    原文链接:https://arxiv.org/pdf/1911.06922.pdf

    这周在组会上介绍了这篇ML sys的论文,正好来写成一篇博客。这篇文章他是发表在IPDPS 2020上的,不算新,但其中分析总结的若干在GPU上的优化策略还是有一定参考性。同时本文也是本博客第一篇关于高性能计算(HPC)和深度学习系统(ML sys)的博客。

    这篇文章设计了一种layer-level的profiling和analyze工具,叫做Benanza,它提出了一个“lower-bound” latency的概念,和实际测量的latency对比,用于分析深度学习推理框架的潜在优化空间。同时用BR(Benanza Ratio)表示优化程度(BR = lower-bound latency / measured latency),越接近1表示优化程度越高。

    这个“lower-bound” latency是通过将原模型的每一个算子,都是用cudnn和cublas中最优的kernel去跑,所得到的时间。具体的将(结合下图),它的benchmark流程(下图黑色箭头)接受一个ONNX格式的模型,解析并找出一系列不重复的算子(包括算子类型和shape),然后在cudnn和cublas中找到最优的实现,并将结果存入benchmark数据库。之后它的analyze流程(下图红色箭头)会首先让这个模型在某个框架中实际跑一下,记录其性能(measured latency),然后将其所有算子在benchmark数据库中的时间取出并求和,作为“lower-bound” latency,与measured latency做对比,反应其在各个角度的优化空间。

    Benchmark流程

    对于模型中的每个layer,它会生成一些列micro benchmark,同时也会涉及一些可能的优化手段,包括:

    1. 算法选择:对于CNN中的卷积而言,会有很多种可用的算法,包括Implicit GEMM (IGEMM), Implicit PreComputed GEMM (IPGEMM), GEMM, Direct (DRCT), FFT, Tiled FFT (TFFT), Winograd (WING), and Winograd Non-Fused (WINGNF)等,Benanza会把他们都跑一遍,选择一个最优的。

    2. 数据类型:Benanza跑了fp32和fp16下的对应kernel,对于fp16来讲,还分别跑了用和不用tensorcore的两种情况。

    3. 算子融合:Benanza也做了算子融合的尝试,主要利用的是cudnn的ConvolutionBiasActivationForward API。

    以上产生的micro benchmark中最后都会被存在工具的benchmark数据库中。

    Analysis流程

    这里首先会让ONNX模型在一个现有的推理框架中运行,paper中跑了MXNet,ONNX Runtime和Pytorch,然后发现MXNet的性能普遍最好(见下图),后续也默认以MXNet的测量值作为measured latency。

    接下来,结合各个优化点,对测量值和lower-bound latency进行比较:

    1. 并行算子执行 vs 串行算子执行 的latency

    对于一些模型,某些算子之间不存在数据依赖,是可以并行执行的,这里他假设并行情况下,算子可以以任意程度并行,最终整体的lower-bound latency是取单一的最长路径上的latency之和(下图红线):

    (这个我感觉不是很靠谱,现实中并行跑多个kernel肯定会影响latency,导致他这种算法的参考意义也不大)

    并行和串行的对比如下:

    2. batch size和系统比较

    他们对比了不同batch size下和在不同的架构的Nvidia GPU上的性能,他们观察到的比较有趣的一点是,系统的优化程度上:较新的架构 > 最新的架构 >> 旧架构。

    3. 卷积算法选择

    前面提到,卷积算法有很多种,会有不同的性能特性,比如winograd算法能2~3倍的降低运算量,不同算法的访存特性也会不同。他们对比了框架所选择的卷积算法和最有的卷积算法,发现框架的选择并不一定是最优的:

    框架中一般都是通过cudnn的GetConvolutionForwardAlgorithm API来选择卷积算法,其本身是一个启发式的算法,相比实测,自然可能不是最优的。

    4. 框架本身的开销

    这里他们发现并优化了一些框架中不高效的地方,包括MXNet ONNX Model Loader中不必要的 image_2d_pad_constant_kernel,和PyTorch cuDNN Wrapper中不必要的 cudaStreamWaitEvent。

    5. 算子融合

    他们发现MXNet、ONNX Runtime和PyTorch进行推理时都没有做算子融合,而它们假设在ResNet50中做了一个简单的Conv -> Bias算子融合,可以得到一定的性能提升

    6. Tensor Core的利用

    较新的Nvidia GPU上都有tensorcore,是专为深度学习优化的计算单元(主要是推理),支持fp16等较低的精度,可以高效的进行矩阵乘法等操作,FLOPS比常规单元高很多。因此如果推理能利用tensorcore,性能会有挺大的提升:

    另外他们还发现NHWC格式要比NHWC数据排布格式快一些,这也是Nvidia所建议的格式。

    最后,如果综合上述所有优化手段,带来的提升如下:

    个人评价

    这篇文章提出的分析工具还是挺有意思的,结合了模型的分析和实际的profiling,同时也算是总结了一遍目前在GPU推理框架上的常见的优化点。就他们所说,他们的主要贡献是在DL Benchmarking上提出了这套生成micro-benchmarks的方法,并在Performance Advising上提出了针对DL领域的优化指导。

    但是我个人还是对他们的工作有一些疑问,首先他们只在MXNet、ONNX Runtime和Pytorch上进行了推理性能的实测,但这些框架并不是目前认为的最高效的推理框架。比如在Nvidia GPU上,tensorrt显然是更加高效的,也是目前工业级部署常用的运行时。对于tensorrt而言,文章中很多优化操作都是倍实现了的,甚至要比文章中还要多和好,比如tensorrt能进行比较好的算子融合,也会选择最优的卷积算法,更不用说对于自家tensorcore的支持。如果用了tensorrt作为实测的框架,可能就测不出什么优化空间了,甚至比他的“lower-bound” latency还要好都是有可能的。当然对于学术界在Nvidia tensorrt上确实也没什么可优化的了,不可能卷的过Nvidia自己,因此他这套方案对于其他运行时,乃至于迁移到其他的GPU、DLA等平台,还是有他的意义的。

    另外文章中对于“并行执行算子”的优化空间的计算比较草率,上文也有提到。