前言

在构建计算机视觉推理系统时,预处理环节往往成为制约整体吞吐量的隐形瓶颈。CANN作为昇腾AI处理器的软件栈核心,提供了丰富的算子库来支持各类AI应用开发。昇腾NPU凭借其专用的向量计算单元和图像加速引擎,为计算机视觉任务提供了区别于通用CPU的并行处理能力。ops-cv仓库正是基于这一能力构建的计算机视觉算子库,聚焦于图像预处理和目标检测前处理场景,将传统上依赖CPU串行执行的解码、缩放、裁剪、归一化等操作卸载到NPU执行。本文围绕ops-cv仓库展开,从实际工程角度分析其在视觉推理预处理链路中的集成方式、性能表现以及适用边界,为开发者在社区获取该仓库后快速搭建NPU加速的预处理流水线提供参考。

视觉推理预处理在CPU上的瓶颈——解码、缩放、归一化的串行开销

计算机视觉模型推理的全链路包含图像输入、预处理、模型推理、后处理四个阶段。预处理阶段通常占据相当比例的整体耗时,这一状况在批处理规模扩大时更为突出。CPU上的预处理流程一般遵循串行执行模式。图像解码依赖libjpeg、libpng等外部库,输出RGB或BGR格式的像素矩阵。缩放操作通过OpenCV的resize接口完成,其内部调用CPU上的插值计算内核。裁剪操作涉及内存区域的复制和重排。归一化步骤将像素值从0-255范围映射到模型输入所需的浮点范围,并在必要时转换通道顺序和数据类型。

这种串行模式面临几个维度的效率问题。CPU的SIMD单元虽能加速单图像操作,但面对多帧并发预处理请求时,核心数限制导致排队延迟。图像解码是典型的计算密集型任务,JPEG解码过程中的霍夫曼解码、逆量化、逆离散余弦变换等操作在CPU上消耗较多时钟周期。缩放操作在非线性插值算法下同样需要大量浮点运算。归一化看似简单,但当输入分辨率较高且批尺寸较大时,内存带宽压力随之上升。

更为关键的是,CPU预处理完成后,数据需要通过PCIe总线从系统内存传输到NPU显存(或NPU可访问的内存区域),这一传输过程引入额外延迟。在实时推理场景下,预处理延迟累积会直接导致推理引擎等待数据,造成算力资源闲置。将预处理操作前置到NPU端执行,不仅利用NPU的并行计算能力加速单个操作,还减少了数据在异构设备间的搬运次数,使预处理和推理之间的数据衔接更为紧凑。

批处理场景下的CPU预处理还面临核心调度开销。当系统同时处理多个推理请求时,每个请求的预处理任务需要分配到不同的CPU核心上执行。操作系统层面的线程调度、核心间缓存同步、内存一致性维护等操作会引入额外开销。这种开销在CPU核心数较多时并不线性下降,因为内存控制器和末级缓存的争用随核心数增加而加剧。在双路或四路CPU服务器上,跨NUMA节点的内存访问会进一步放大这一开销。

ops-cv算子概览与部署验证

ops-cv仓库提供了面向计算机视觉场景的NPU加速算子集合,覆盖图像预处理和目标检测前处理两类核心需求。图像预处理算子包含resize、crop、pad、normalize等基础操作,支持将多个操作组合为算子链一次性提交到NPU执行。目标检测前处理算子提供batch预处理、bbox坐标变换等能力,适配YOLO、Faster R-CNN等主流检测框架的输入规范。

仓库内的算子基于CANN的ACL(Ascend Computing Language)接口开发,通过TBE(Tensor Boost Engine)算子开发框架生成在特定昇腾NPU型号上优化的二进制代码。算子输入和输出均采用AscendCL的内存管理接口分配,确保数据在NPU侧的连续性,避免不必要的内存拷贝。TBE框架提供的自动算子融合能力允许开发者在高层接口中描述多个算子的组合关系,由框架在编译阶段完成算子融合优化,生成合并后的NPU内核代码。

ops-cv仓库的算子实现遵循CANN的算子开发规范。每个算子包含算子原型定义、算子实现代码和算子测试代码三个部分。算子原型定义描述算子的输入输出张量格式、属性参数和数据类型约束。算子实现代码包含主机侧和设备侧的代码,主机侧代码负责参数校验和内核启动配置,设备侧代码实现实际的计算逻辑。算子测试代码提供正确性验证和性能基准测试的实现。

在开始使用ops-cv之前,需要确认运行环境满足基本条件。昇腾NPU驱动和CANN软件栈已正确安装,且版本兼容性符合仓库文档的说明。Python环境需安装AscendCL的Python绑定包,具体名称以CANN发行版为准。ops-cv仓库本身以Python源代码形式提供,依赖AscendCL的底层接口完成算子调用。CANN的版本选择需与昇腾NPU的硬件型号匹配,不同型号的NPU支持的算子集合和性能特征存在差异。

# 环境检查脚本 - 验证CANN和昇腾NPU可用性
import ctypes
import sys
import os

def check_ascend_runtime():
    """检查昇腾运行时库是否可加载"""
    lib_paths = [
        "libascendcl.so",
        "/usr/local/Ascend/lib64/libascendcl.so",
        "/usr/local/Ascend/nnae/lib64/libascendcl.so",
    ]
    for lib in lib_paths:
        try:
            ctypes.CDLL(lib)
            print(f"AscendCL library loaded: {lib}")
            return True
        except OSError:
            continue
    print("AscendCL library not found in standard paths")
    return False

def check_npu_device():
    """检查NPU设备可见性和基本信息"""
    npu_visible = os.environ.get("ASCEND_VISIBLE_DEVICES", None)
    if npu_visible is not None:
        print(f"ASCEND_VISIBLE_DEVICES={npu_visible}")
    else:
        print("ASCEND_VISIBLE_DEVICES not set, using default device mapping")
    npu_device_id = os.environ.get("ASCEND_DEVICE_ID", "0")
    print(f"ASCEND_DEVICE_ID={npu_device_id}")

def check_cann_version():
    """尝试获取CANN版本信息"""
    try:
        import acl
        print("AscendCL Python binding (acl) loaded")
    except ImportError:
        try:
            import ascendcl
            print("AscendCL Python binding (ascendcl) loaded")
        except ImportError:
            print("AscendCL Python binding not found")
            return False
    return True

if __name__ == "__main__":
    if not check_ascend_runtime():
        sys.exit(1)
    check_npu_device()
    if not check_cann_version():
        sys.exit(1)
    print("Environment check passed")

# 昇腾NPU的运行时依赖动态链接库libascendcl.so,加载失败意味着CANN未安装或环境变量未配置。
# ASCEND_VISIBLE_DEVICES环境变量控制进程可见的NPU设备编号,多卡环境下需显式指定。
# 脚本在导入AscendCL Python包之前先检查底层C库,能够快速定位驱动层问题。
# CANN提供两种Python绑定包名(acl和ascendcl),不同版本命名不同,需同时尝试导入。

环境检查通过后,继续验证ops-cv仓库的Python导入是否正常工作。仓库的目录结构通常包含image、objdetect等子模块,对应不同类别的算子实现。导入过程中若出现符号未找到或版本不匹配的错误,需核对CANN版本和仓库要求的适配版本是否一致。ops-cv仓库的Python接口层对底层CANN接口进行了封装,简化了算子调用的代码复杂度,但这种封装也引入了额外的依赖层次,版本不匹配时错误信息可能不够直观。

# ops-cv仓库导入与版本检查
import sys
import os
import platform

# 将ops-cv仓库根目录加入导入路径
ops_cv_path = os.path.expanduser("~/workspace/ops-cv")
if ops_cv_path not in sys.path:
    sys.path.insert(0, ops_cv_path)

print(f"Python version: {platform.python_version()}")
print(f"ops-cv path: {ops_cv_path}")
print(f"ops-cv path exists: {os.path.exists(ops_cv_path)}")

try:
    from image import resize as cv_resize
    from image import crop as cv_crop
    from objdetect import batch_preprocess as det_preprocess
    print("ops-cv modules imported successfully")
    print(f"resize module location: {cv_resize.__file__}")
    print(f"crop module location: {cv_crop.__file__}")
except ImportError as e:
    print(f"Failed to import ops-cv modules: {e}")
    print("Check that ops-cv repository is correctly placed and CANN runtime is accessible")
    sys.exit(1)

# 检查AscendCL Python接口可用性
try:
    import acl
    print("AscendCL Python binding loaded")
except ImportError:
    try:
        import ascendcl as acl
        print("AscendCL Python binding (ascendcl) loaded")
    except ImportError:
        print("AscendCL Python binding not found")
        print("Install CANN Python wheel package from official distribution")
        sys.exit(1)

# ops-cv以源码形式提供,需手动将仓库路径加入sys.path,与pip安装的包行为不同。
# 导入时指定子模块的具体函数名(如resize、crop),可在导入阶段发现符号缺失问题。
# AscendCL的Python绑定包名称在不同CANN版本中可能为acl或ascendcl,以实际发行版为准。
# 打印模块文件路径有助于定位导入的是否为正确版本的ops-cv,避免路径冲突导致导入错误版本。

图像处理算子链实战——Resize/Crop/Rotate的NPU加速

图像处理算子的NPU加速核心在于将多个预处理步骤合并为一次NPU内核调用。CPU上的预处理通常分步执行,每步产生中间结果并写回系统内存,之后再将中间结果读入缓存进行处理。NPU算子链通过内存视图复用和内核融合技术,在NPU的片上内存中完成多步计算,只将最终结果写回设备内存。这种执行模式减少了中间结果的显式存储和读取,降低了内存带宽压力。

以图像缩放和裁剪的组合操作为例。输入图像先缩放到目标尺寸,再从缩放结果中裁剪出检测区域。CPU实现需要两次内存读写和两次函数调用。ops-cv提供的算子链接口允许将resize和crop的参数一次性提交,NPU驱动内部完成算子融合分析,生成合并后的计算内核。实际加速效果取决于输入分辨率、目标分辨率、裁剪区域大小等参数,NPU的并行度优势在分辨率较高时更为明显。

算子融合的实现依赖于CANN的图执行引擎。当多个算子被组织为算子链时,CANN引擎会分析算子之间的数据共享关系,识别可以融合的算子对。融合决策考虑多个因素,包括算子之间的数据依赖关系、融合后内核的寄存器占用、片上内存容量限制等。不是所有的算子组合都能被融合,当算子之间的数据依赖过于复杂或融合后的内核超出硬件资源限制时,引擎会将算子链拆分为多个内核调用,但仍会优化内核之间的数据传递路径。

# 使用ops-cv执行图像Resize操作
import numpy as np
import acl
from image import resize as cv_resize

def npu_image_resize(input_image_path, target_height, target_width):
    """使用ops-cv的resize算子将图像缩放到目标尺寸"""
    import cv2
    cpu_image = cv2.imread(input_image_path)
    if cpu_image is None:
        raise ValueError(f"Failed to decode image: {input_image_path}")
    cpu_image = cv2.cvtColor(cpu_image, cv2.COLOR_BGR2RGB)
    input_height, input_width, channels = cpu_image.shape
    print(f"Input image shape: {input_height}x{input_width}x{channels}")

    # 将图像数据上传到NPU内存
    # 以下为接口调用示意,具体函数名以ops-cv仓库实际代码为准
    npu_input_buffer = None  # 通过acl.rt.malloc分配NPU内存
    # acl.rt.memcpy() 将cpu_image数据拷贝到npu_input_buffer

    # 调用ops-cv resize算子
    resize_params = {
        "input_height": input_height,
        "input_width": input_width,
        "output_height": target_height,
        "output_width": target_width,
        "interpolation": "bilinear",
        "channels": channels,
    }
    # npu_output = cv_resize.execute(npu_input_buffer, resize_params)
    print(f"Resize: {input_height}x{input_width} -> {target_height}x{target_width}")

    # 将NPU计算结果取回CPU内存
    # cpu_result = acl.util.ptr_to_numpy(npu_output, (target_height, target_width, channels), np.uint8)
    # return cpu_result

def npu_image_resize_with_crop(input_image_path, target_size, crop_region):
    """组合使用resize和crop算子"""
    # 先缩放再裁剪是目标检测前处理的常见模式
    # crop_region格式: (x1, y1, x2, y2)
    # 实际调用ops-cv的算子链接口
    pass

# NPU内存分配使用acl.rt.malloc而非标准malloc,确保内存在NPU可访问的地址空间。
# 插值算法参数(bilinear/nearest)影响NPU内核选择,双线性插值消耗更多计算资源。
# 输入图像的通道顺序(RGB/BGR)必须与算子预期格式一致,否则输出颜色异常。
# 算子链执行前需确保输入张量在NPU内存中,CPU内存中的数据需通过acl.rt.memcpy拷贝。

上述代码展示了resize算子的基本调用流程。实际工程中,图像解码步骤同样是预处理的耗时环节。ops-cv仓库中若包含NPU图像解码算子,可将JPEG/PNG解码直接放到NPU执行,进一步缩短数据路径。解码算子输出直接在NPU内存中,后续resize、crop等操作无需额外的数据搬运。这种全NPU侧的预处理链路在批量处理场景下优势更为突出,因为数据在NPU内存中的驻留时间更长,减少了与CPU内存之间的数据交换频率。

裁剪操作的NPU实现需要处理边界条件和内存对齐要求。昇腾NPU的内存访问效率在地址对齐到特定字节边界时达到最优。裁剪算子在计算输出区域的内存偏移时,会将起始地址对齐到NPU硬件要求的内存对齐粒度。这一操作对上层调用者透明,但理解其对性能的影响有助于在批量处理时合理安排裁剪区域的分布,减少跨对齐边界的零散访问。当批量图像中的裁剪区域大小不一致时,对齐操作可能导致部分图像的输出缓冲区存在填充字节,增加内存占用。

旋转操作在NPU上的实现依赖矩阵转置和插值计算的结合。常见旋转角度(90度、180度、270度)可通过内存重排完成,无需插值计算,执行效率接近一次内存拷贝操作。任意角度旋转需要计算变换后的像素坐标并进行双线性插值,计算量接近缩放操作。ops-cv仓库对常见旋转角度提供优化路径,调用时通过参数指定旋转角度,算子内部选择对应的内核实现。对于目标检测场景,旋转操作的使用频率低于缩放和裁剪,但在某些特定应用(如旋转不变性要求的目标检测)中仍有其价值。

目标检测预处理流水线构建与推理引擎对接

目标检测模型的预处理流程比单图像分类模型更为复杂。检测模型通常接受批量图像输入,每张图像需要经过缩放到统一尺寸、填充到固定长宽比、归一化、维度重排(HWC到CHW)等步骤。这些步骤在CPU上逐一执行会引入多轮内存拷贝。在NPU上构建预处理流水线,将这些步骤融合为有限次数的内核调用,是提升端到端吞吐量的有效路径。

构建预处理流水线的首要工作是定义各步骤的执行顺序和参数。ops-cv仓库的objdetect模块提供了面向检测场景的批处理预处理接口。该接口接受批量图像路径或已解码的图像数据,输出可直接送入推理引擎的张量。推理引擎若为昇腾CANN的离线模型推理接口(acl.mdl系列接口),则预处理输出的张量格式需要和模型输入描述一致,包括数据类型、通道顺序、归一化参数等。格式不匹配会在推理阶段触发运行时错误,或导致推理结果异常。

批处理预处理接口的设计需要考虑输入数据的组织方式。常见的输入组织方式包括文件名列表、已解码的图像数组、或预分配的NPU内存缓冲区。文件名列表方式最为简便,但要求预处理接口内部完成图像解码,增加了接口复杂度和错误处理难度。已解码的图像数组方式将解码步骤外置,由调用者负责解码操作,预处理接口专注于图像变换操作。预分配的NPU内存缓冲区方式最为高效,但要求调用者管理NPU内存的生命周期,增加了集成复杂度。

# 目标检测预处理性能测试与NPU vs CPU对比
import time
import numpy as np
import cv2
import os
import glob

def cpu_preprocess_batch(image_paths, target_size, mean, std):
    """CPU预处理流水线:解码 -> 缩放 -> 归一化 -> 维度重排"""
    batch_tensor = []
    for img_path in image_paths:
        img = cv2.imread(img_path)
        if img is None:
            print(f"Warning: failed to decode {img_path}")
            continue
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.resize(img, (target_size[1], target_size[0]))
        img = img.astype(np.float32) / 255.0
        img = (img - mean) / std
        img = np.transpose(img, (2, 0, 1))
        batch_tensor.append(img)
    if len(batch_tensor) == 0:
        return None
    return np.stack(batch_tensor, axis=0)

def npu_preprocess_batch(image_paths, target_size, mean, std):
    """NPU预处理流水线(示意接口,依ops-cv实际API调整)"""
    # 实际调用ops-cv objdetect.batch_preprocess接口
    # batch_tensor = det_preprocess.execute(
    #     image_paths=image_paths,
    #     target_height=target_size[0],
    #     target_width=target_size[1],
    #     mean=mean.tolist() if isinstance(mean, np.ndarray) else mean,
    #     std=std.tolist() if isinstance(std, np.ndarray) else std,
    #     output_layout="CHW",
    #     output_dtype="float32",
    # )
    # return batch_tensor
    pass

def benchmark_preprocessing(batch_size, image_dir, target_size, num_iterations=10):
    """对比CPU和NPU预处理的端到端耗时"""
    image_paths = sorted(glob.glob(os.path.join(image_dir, "*.jpg")))
    if len(image_paths) < batch_size:
        image_paths = image_paths * (batch_size // max(len(image_paths), 1) + 1)
    image_paths = image_paths[:batch_size]

    mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)
    std = np.array([0.229, 0.224, 0.225], dtype=np.float32)

    # CPU基准测试
    cpu_times = []
    for i in range(num_iterations):
        cpu_start = time.time()
        cpu_result = cpu_preprocess_batch(image_paths, target_size, mean, std)
        cpu_elapsed = time.time() - cpu_start
        cpu_times.append(cpu_elapsed)
    cpu_avg = sum(cpu_times) / len(cpu_times)
    cpu_std_time = np.std(cpu_times)

    print(f"Batch size: {batch_size}")
    print(f"CPU preprocessing average time: {cpu_avg:.3f}s ± {cpu_std_time:.3f}s")
    if cpu_result is not None:
        print(f"CPU output shape: {cpu_result.shape}, dtype: {cpu_result.dtype}")

    # NPU测试代码框架(需根据实际部署环境补充)
    print("NPU preprocessing benchmark requires deployed ops-cv environment")
    return cpu_avg

# CPU预处理使用OpenCV的resize,其内部多线程执行受OMP_NUM_THREADS环境变量影响。
# 归一化参数mean和std依模型训练时的配置而定,预处理和训练配置不匹配导致推理精度下降。
# 维度重排(HWC->CHW)在CPU上通过numpy.transpose完成,大张量时产生临时内存开销。
# 多次迭代取平均能减少单次测试的噪声影响,std反映测试结果的一致性。

性能测试代码框架提供了CPU和NPU预处理流程的对比基准。在实际部署中,NPU预处理的优势不仅体现在单批次处理耗时的减少,还体现在推理引擎等待数据的延迟降低。当预处理和推理均在NPU上执行时,中间数据可驻留在NPU内存中,推理引擎直接引用预处理算子的输出缓冲区作为输入,省去数据在CPU和NPU之间的往返拷贝。这种零拷贝数据传递模式在高吞吐量推理服务中价值显著,因为数据搬运延迟在频繁的小批量推理请求中会累积为可观的整体延迟。

对接推理引擎时需注意数据格式的匹配。CANN离线模型推理接口要求输入张量的描述信息(dtype、shape、format)与模型文件中的输入节点定义一致。ops-cv的预处理算子输出默认格式可能为NCHW布局的float32张量,而某些检测模型接受NHWC布局或uint8类型的输入。在搭建流水线时需插入格式转换算子,或在调用预处理接口时指定输出格式参数。这一步骤若处理不当,会在推理阶段触发运行时错误,或引入隐式的格式转换开销。

NPU预处理 vs CPU预处理的效率对比

效率对比需要从多个维度展开,单纯比较单张图像处理耗时不能完整反映NPU加速的收益和局限。以下表格从实际部署角度列出多个影响预处理效率的因素,对比CPU和NPU方案在各维度上的表现差异。这些维度的选取基于实际工程部署中的常见瓶颈点,每个维度的测试条件会因具体硬件配置和软件版本而有差异。

维度 CPU预处理 NPU预处理(ops-cv) 差异来源
单图像缩放(1080p->224x224) 依赖OpenCV SIMD优化,单核耗时在毫秒级 NPU内核并行度高,单图像耗时与CPU接近或略高 图像分辨率较小时NPU启动内核的固定开销占比大
批量图像预处理(batch=32) 多核并行受CPU核心数限制,扩展曲线在8核后趋于平缓 NPU批处理算子内部充分并行化,吞吐量随batch增大而提升 NPU的SIMT架构适合大规模数据并行任务
预处理+推理端到端延迟 数据需从CPU内存拷贝到NPU内存,拷贝延迟在毫秒级 预处理输出直接驻留NPU内存,推理引擎零拷贝读取 减少PCIe数据传输次数是降低端到端延迟的关键
内存占用峰值 每步预处理产生中间张量,峰值内存为各步输出之和 算子融合减少中间张量,峰值内存接近最终输出大小 NPU片上内存复用降低显存占用压力
图像解码(JPEG) 使用libjpeg-turbo可发挥多核优势,CPU解码延迟稳定 NPU解码算子依赖硬件JPEG引擎,高分辨率图像收益明显 解码加速效果与图像压缩比、色彩空间转换需求相关
动态输入尺寸处理 CPU可灵活处理任意尺寸,运行时开销可忽略 NPU算子针对固定尺寸编译内核,动态尺寸触发重新编译 CANN支持动态shape,但首次执行有编译延迟
小批量实时推理(batch=1, 30fps) CPU单帧处理耗时稳定在10ms以内,满足实时要求 NPU内核启动开销在batch=1时占比高,单帧耗时可能高于CPU 小批量场景下NPU并行度优势无法发挥,收益有限
预处理精度(浮点运算) OpenCV使用CPU浮点单元,精度与IEEE 754一致 NPU使用半精度或混合精度计算,极端情况下有微小差异 检测模型对预处理精度敏感度低,分类模型需验证精度收敛性
多模型并发预处理 多进程CPU预处理受系统调度和内存带宽竞争影响 NPU支持多流并发执行,流间资源隔离由驱动层保证 NPU的硬件调度器在多流场景下的效率取决于具体型号
长时运行稳定性 CPU预处理稳定性高,长期运行无性能衰减 NPU预处理受驱动稳定性和温度影响,需监控运行状态 散热条件不足时NPU可能降频,影响长期运行性能一致性

表格中"小批量实时推理"一行显示NPU预处理在batch=1时的收益有限。这一现象源于GPU/NPU类设备的并行计算特性。内核启动本身有固定开销(在微秒到毫秒级,依平台和负载而异),当计算任务规模较小时,内核启动开销在总耗时中的占比上升,抵消了并行计算带来的加速效果。在工程部署中,若应用场景确实以单帧实时处理为主,且CPU预处理已满足延迟要求,则引入NPU预处理的价值不大。NPU预处理更适合批量处理场景或与其他NPU计算任务(如推理、后处理)组成全NPU流水线的情况。

另一个需关注的维度是动态输入尺寸。目标检测模型在实际部署时常需处理不同长宽比的输入图像。CPU预处理可对任意尺寸的图像即时计算,无需提前编译。NPU算子在面对未见过的输入尺寸时,若使用动态shape机制,首次执行会触发算子编译流程,编译耗时从数百毫秒到数秒不等,依算子复杂度而定。在延迟敏感的服务中,这一编译延迟会导致首帧处理时间过长。缓解方案包括提前对常用输入尺寸进行算子预热,或使用NPU支持的最大分辨率统一缩放输入图像,牺牲部分精度换取稳定的推理延迟。

结尾

ops-cv仓库为昇腾NPU上的计算机视觉预处理提供了工程可行的加速路径。从本文的分析可以看出,NPU预处理的收益集中在批量处理和高分辨率图像场景,瓶颈则出现在小批量动态输入和算子首次编译延迟上。在实际集成过程中,建议先针对目标场景的批量大小分布和输入尺寸分布做基准测试,确认NPU预处理在端到端链路中的加速比是否达到预期。ops-cv仓库的算子接口会随CANN版本迭代而更新,使用时需关注仓库的版本标签和CANN版本的对应关系。将预处理和推理部署在同一个NPU设备上,最大化减少数据搬运开销,是发挥ops-cv加速效果的工程前提。


仓库链接:https://atomgit.com/cann/ops-cv

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐