ops-cv:AI 处理器上的高性能计算机视觉算子库
CANN 组织链接: https://atomgit.com/cann
ops-cv 仓库链接: https://atomgit.com/cann/ops-cv
在计算机视觉(Computer Vision, CV)领域,从图像采集到模型推理的整个流程中,往往伴随着大量的图像预处理、后处理以及各种基础图像操作。这些操作对于 AI 模型的性能和效率至关重要,尤其是在需要实时处理或处理大规模图像数据集的场景下。ops-cv 正是 CANN 软件栈中针对 AI 处理器优化的计算机视觉算子库。它旨在将传统的 CV 算法和深度学习任务中常见的图像处理操作,以高度优化的方式在 AI 处理器上高效执行。
ops-cv 提供了丰富的图像处理算子,涵盖了从基础的像素操作(如颜色空间转换、裁剪、缩放)到更复杂的图像增强(如高斯模糊、边缘检测)等。这些算子充分利用了 AI 处理器独特的计算能力和并行架构,确保了视觉数据能够以最低的延迟和最高的吞吐量进行处理。通过使用 ops-cv,开发者可以显著加速其计算机视觉应用的端到端性能,简化开发流程,并确保 AI 模型能够接收到高质量、预处理完善的数据。本文将深入探讨 ops-cv 的技术内涵、其在 AI 计算机视觉生态中的价值以及如何赋能高性能 AI 视觉应用的开发。
1. 引言:ops-cv 在 AI 计算机视觉中的核心价值
计算机视觉作为人工智能最活跃的领域之一,其应用的广泛性要求底层算子库具备卓越的性能和灵活性。ops-cv 正是为此而生。
1.1 计算机视觉工作流的算力挑战
现代计算机视觉任务,如高分辨率图像分析、实时视频流处理、大规模数据集训练等,对计算资源的需求达到了前所未有的高度:
- 数据量爆炸式增长:从 4K 图像到 8K 视频,视觉数据在分辨率和帧率上的提升,带来了巨大的数据处理压力。传统的 CPU 处理方式往往成为性能瓶颈。
- 预处理复杂性:深度学习模型通常对输入数据格式有严格要求,需要进行归一化、裁剪、缩放、颜色空间转换等一系列复杂的预处理步骤,这些操作通常是串行的,且计算量不容小觑。
- 实时性要求:自动驾驶、智能安防等应用场景对图像处理的实时性有着极高的要求,毫秒级的延迟都可能导致严重的后果。
1.2 ops-cv 的诞生与使命
ops-cv 的出现,正是为了解决 AI 处理器在计算机视觉工作流中面临的这些挑战,其核心使命在于:
- 加速视觉数据处理:将传统的图像处理算子移植并优化到 AI 处理器上,充分利用其强大的并行计算能力,大幅提升处理速度。
- 优化端到端性能:作为 AI 模型推理或训练的前置和后置环节,ops-cv 确保视觉数据在进入 AI 模型前得到高效处理,减少整体工作流的延迟。
- 降低开发门槛:提供一套统一且易用的 API 接口,让开发者能够便捷地调用高性能的 CV 算子,而无需深入了解 AI 处理器的底层细节。
1.3 AI 处理器上视觉应用的关键使能者
ops-cv 不仅仅是一个工具库,它是 AI 处理器在视觉智能领域发挥最大效能的关键环节:
- 释放 AI 算力潜力:通过将 CV 算子卸载到 AI 处理器执行,CPU 可以专注于其他任务,从而释放整个系统的算力潜力。
- 构建高性能视觉流水线:ops-cv 使得在 AI 处理器上构建从数据输入到结果输出的完整、高性能视觉处理流水线成为可能。
- 赋能广泛应用场景:无论是智能制造的缺陷检测、智慧城市的视频分析,还是医疗影像的辅助诊断,ops-cv 都为这些 AI 视觉应用提供了坚实高效的底层支持。
2. 核心功能:丰富的计算机视觉算子集
ops-cv 提供了涵盖图像处理各个环节的丰富算子,以满足不同计算机视觉任务的需求。
2.1 基础图像处理算子
这些算子构成了图像处理的基石,是许多高级算法的前提:
- 颜色空间转换:支持主流的颜色空间转换,如 RGB 到 YUV/HSV/Gray,反之亦然。这对于不同应用场景和模型输入格式的统一至关重要。
- 图像缩放与裁剪:提供多种插值算法(如最近邻、双线性、双三次)的图像缩放功能,以及灵活的图像裁剪操作。这是深度学习模型输入尺寸调整的常用操作。
- 像素级操作:包括亮度、对比度调整,通道分离与合并,以及基本的逻辑(AND, OR, XOR)和算术(加、减、乘、除)运算,用于图像增强或特征提取。
2.2 深度学习预处理加速
深度学习模型对输入数据有严格的要求,ops-cv 提供了专为模型预处理优化的算子:
- 归一化与标准化:快速对图像像素值进行归一化(如[0,1]或[-1,1])和标准化(减均值除方差),以适应模型训练的需求。
- 数据类型转换:支持多种数据类型(如 uint8, float16, float32)之间的转换,便于在 AI 处理器上高效地进行混合精度计算。
- 通道重排:在常见的 HWC(高宽通道)和 CHW(通道高宽)等布局之间进行快速切换,以匹配不同深度学习框架或模型的输入要求。
2.3 图像变换与几何操作
除了基础操作,ops-cv 还提供了一系列复杂的几何变换和图像增强算子:
- 图像旋转与翻转:支持任意角度的图像旋转,以及水平/垂直翻转操作,常用于数据增强以增加模型的鲁棒性。
- 仿射变换与透视变换:提供更灵活的几何变换能力,如平移、旋转、缩放、剪切等组合变换,以及透视变换,用于图像校正或特征配准。
- 滤波器与卷积核:包括高斯模糊、均值滤波、中值滤波、Sobel/Canny 边缘检测等常见图像滤波操作,这些操作在特征提取和图像去噪中广泛应用。
3. 性能优化:深度融合 AI 处理器架构
ops-cv 的核心优势在于其在 AI 处理器上的极致性能表现。这得益于其深度融合 AI 处理器架构的优化设计。
3.1 充分利用硬件并行计算能力
AI 处理器具备独特的并行计算单元(如向量计算单元、矩阵计算单元),ops-cv 算子通过以下方式充分利用这些资源:
- 向量化操作:针对图像像素的并行处理需求,ops-cv 算子被设计成高度向量化的代码,能够在 AI 处理器的向量计算单元上高效执行,一次处理多个数据。
- 矩阵乘法加速:对于一些依赖于卷积(本质是矩阵乘法)的图像处理任务,ops-cv 会将其映射到 AI 处理器的矩阵计算单元上,实现极致的吞吐量。
- 流水线优化:通过对算子内部的计算过程进行精细的流水线设计,减少数据依赖和计算等待,提高计算单元的利用率。
3.2 低延迟与高吞吐设计
ops-cv 的算子设计目标是同时实现低延迟和高吞吐,以满足实时性和大规模处理的需求:
- 最小化数据搬运:通过原地(In-place)操作、零拷贝(Zero-copy)技术以及高效的内存管理,减少不必要的数据在不同内存层次间的传输,降低延迟。
- 指令级优化:算子实现会进行指令级别的优化,包括缓存友好性设计、分支预测优化等,确保 AI 处理器能够以最快的速度执行指令。
- 任务并发执行:ops-cv 算子能够与 AI 处理器上的其他计算任务并发执行,例如在执行图像缩放时,可以同时进行模型推理,从而提升整体系统的并发能力。
3.3 内存访问与数据流优化
高效的内存访问模式是 AI 处理器性能的关键,ops-cv 在此方面做了大量工作:
- 片上缓存利用:ops-cv 算子能够智能地利用 AI 处理器提供的多级片上缓存(如 L1 Cache、Unified Buffer),将热点数据保留在靠近计算单元的存储器中,减少对带宽有限的全局显存的访问。
- 数据预取与合并访问:通过数据预取机制,ops-cv 能够在计算单元需要数据之前将其载入缓存。同时,它会尽量将分散的内存访问转换为合并访问,以充分利用内存带宽。
- 内存池管理:对于频繁申请和释放的临时内存,ops-cv 可能采用内存池技术,减少系统调用开销和内存碎片,提高内存分配效率。
4. 易用性与集成:简化 AI 视觉开发流程
ops-cv 不仅注重性能,也致力于提供良好的开发体验,通过统一的接口和无缝集成简化 AI 视觉应用的开发流程。
4.1 统一的 API 接口设计
ops-cv 提供了清晰、一致且易于使用的 API 接口,这对于开发者来说是极其重要的:
- 面向对象/函数式风格:API 可能采用面向对象或函数式的编程风格,以结构化的方式提供算子调用接口,例如
cv::resize(input_tensor, output_tensor, interpolation_mode)。 - 参数配置灵活性:算子通常提供丰富的参数选项,允许开发者根据具体需求精细调整行为,如缩放的插值方法、裁剪的区域、滤波器的核大小等。
- 错误处理机制:API 具备健壮的错误处理机制,通过返回状态码或抛出异常,清晰地告知开发者操作结果,便于调试和故障定位。
4.2 与 CANN 软件栈无缝对接
作为 CANN 软件栈的一部分,ops-cv 与其他组件实现了深度集成,形成一个完整的 AI 计算生态:
- 与 CANN Runtime 协同:ops-cv 算子可以直接被 CANN Runtime 调用和调度执行。这意味着在 AI 处理器上运行的深度学习模型,可以无缝地调用 ops-cv 进行前处理或后处理。
- 设备内存共享:ops-cv 算子可以直接操作 AI 处理器设备上的内存。它接收来自模型的输出或提供给模型的输入,都是设备内存上的张量,避免了主机-设备之间不必要的数据拷贝。
- 流与事件同步:ops-cv 的操作可以被提交到 CANN Runtime 的流中,并利用事件机制与其他计算任务进行异步同步,实现计算与图像处理的并行。
4.3 支持主流深度学习框架
为了方便开发者将其整合到现有项目中,ops-cv 提供了与主流深度学习框架集成的可能性:
- 适配框架生态:ops-cv 算子可以通过扩展或插件的形式,被 PyTorch、TensorFlow、MindSpore 等框架调用,成为其自定义算子或特定硬件后端的一部分。
- 统一数据表示:ops-cv 致力于与框架内部的数据表示(如 Tensor)兼容,减少数据转换的开销和复杂性。
- 简化模型部署:在部署深度学习模型时,ops-cv 可以作为模型预处理和后处理的加速组件,从而实现更高效、更完整的端到端 AI 视觉解决方案。
5. 拓展与定制:满足多样化的视觉应用需求
AI 视觉应用领域不断创新,ops-cv 不仅提供标准算子,也支持开发者进行拓展和定制,以满足特定的业务需求。
5.1 自定义算子开发机制
在某些特定场景下,标准算子可能无法满足需求。ops-cv 生态提供机制允许开发者创建自己的高性能算子:
- 基于 Ascend C 开发:开发者可以利用专为 AI 处理器设计的 Ascend C 语言,结合 asc-devkit 提供的底层接口,编写定制化的图像处理 Kernel。
- 注册与集成:提供接口将新开发的自定义算子注册到 ops-cv 库中,使其能够被上层应用和框架统一调用。
- 性能调优工具:配套的性能分析工具可以帮助开发者诊断自定义算子的性能瓶颈,并进行迭代优化。
5.2 灵活的参数配置与组合
ops-cv 的算子提供了高度可配置的参数,允许开发者根据具体应用场景进行灵活调整:
- 插值算法选择:图像缩放时,可选择不同的插值算法(如最近邻、双线性、双三次),以平衡性能和图像质量。
- 边界处理模式:卷积、滤波等操作在处理图像边界时,支持多种填充模式(如常数填充、反射填充、重复填充)。
- 算子组合优化:ops-cv 可能支持将多个串行的图像处理算子进行融合(Operator Fusion),减少中间数据的生成和内存访问,进一步提升性能。
5.3 社区贡献与共建
为了构建一个充满活力的生态系统,ops-cv 鼓励开发者社区的参与和贡献:
- 开源合作模式:通过开放的仓库,ops-cv 欢迎社区开发者贡献新的算子、优化现有算子、报告问题或提出功能建议。
- 共享最佳实践:社区可以共享基于 ops-cv 实现的各种图像处理流水线和应用案例,促进知识交流和技术进步。
- 持续创新驱动:来自社区的反馈和需求,将成为 ops-cv 未来版本迭代和功能创新的重要驱动力。
6. 未来展望:持续赋能视觉智能的广阔未来
ops-cv 将持续演进,以适应 AI 视觉领域的快速发展,并为 AI 处理器在视觉智能上的应用提供更强大、更高效的支持。
6.1 支持新型视觉任务与算法
随着计算机视觉技术的不断突破,ops-cv 将积极拥抱并支持新的发展方向:
- 3D 视觉与点云处理:针对 3D 视觉(如 LiDAR、深度相机)产生的点云数据,ops-cv 将探索提供相应的点云处理算子,以加速其在 AI 处理器上的应用。
- 视频流媒体处理:针对视频数据流的实时处理需求,ops-cv 将优化时间序列上的算子,支持更高效的视频帧处理、光流估计等。
- 神经渲染与生成式 AI:随着生成式 AI 在视觉领域的兴起,ops-cv 将研究如何加速神经渲染、图像生成等任务中涉及的底层图像操作。
6.2 软硬协同的持续演进
ops-cv 的未来发展将更加注重与 AI 处理器硬件的深度协同设计,实现软硬件一体化优化:
- 专用硬件加速单元集成:随着 AI 处理器集成更多针对特定 CV 任务的硬件加速单元,ops-cv 将及时适配这些新单元,充分发挥其性能。
- 异构计算融合:在 AI 处理器、CPU 和其他加速器混合的异构计算环境中,ops-cv 将探索更智能的负载均衡和任务调度策略,优化跨类型设备的图像处理流水线。
- 编译器与运行时优化联动:ops-cv 将与 CANN 编译器和运行时系统紧密合作,共同实现端到端的数据流分析和优化,进一步减少通信和内存开销。
6.3 赋能更广阔的 AI 视觉生态
ops-cv 致力于构建开放、繁荣的 AI 视觉生态,为各行各业提供强劲动力:
- 赋能垂直行业应用:与工业、医疗、农业、零售等垂直行业的合作伙伴紧密合作,根据其特定需求,提供定制化的视觉算子和解决方案。
- 推动边缘 AI 视觉发展:通过在资源受限的边缘 AI 处理器上提供高性能的 CV 算子,ops-cv 将助力边缘智能视觉应用的大规模部署。
- 促进研究与创新:为学术界和研究机构提供高效的图像处理基础设施,加速新的计算机视觉算法的验证和原型开发。
附录:ops-cv 概念性 C++ API 交互示例
以下是一个概念性的 C++ 代码片段,旨在说明一个计算机视觉应用或深度学习框架如何可能与 ops-cv 库提供的 API 进行交互,以完成一个图像缩放(Resize)操作。此示例着重于展示 API 的调用模式和核心概念,它并非直接可编译运行的代码,因为它省略了所有必要的头文件定义、完整的错误处理、设备初始化和上下文配置。其目的仅仅是展示如何通过 ops-cv 抽象地调用高性能图像处理功能。
#include <iostream>
#include <vector>
#include <string>
#include <memory> // For std::unique_ptr
#include <thread> // For std::this_thread::sleep_for
#include <chrono> // For std::chrono::milliseconds
// 概念性:AscDevKit 库,用于底层设备和内存管理
namespace AscDevKit {
// 假设 AscDevKit 提供了 Device、Context、Stream 等类
// 以及 MallocDeviceMemory、FreeDeviceMemory、MemcpyHostToDeviceAsync 等函数
// 这里只声明需要的概念性类和函数,以简化示例
class Context { public: int device_id; std::string handle; Context(int dev_id) : device_id(dev_id), handle("Context_Dev" + std::to_string(dev_id)) {} };
class Stream { public: std::string handle; Stream(const std::string& ctx_h) : handle("Stream_" + ctx_h) {} };
enum Status { OK = 0, ERROR_GENERAL = 1 };
Status MallocDeviceMemory(void** dev_ptr, size_t size_bytes) {
std::cout << "[AscDevKit] 在设备上分配 " << size_bytes << " 字节内存..." << std::endl;
*dev_ptr = reinterpret_cast<void*>(0x20000000ULL + (rand() % 0x100000ULL)); // 概念性地址
std::this_thread::sleep_for(std::chrono::milliseconds(5));
return OK;
}
Status FreeDeviceMemory(void* dev_ptr) {
if (!dev_ptr) return OK;
std::cout << "[AscDevKit] 释放设备内存 " << dev_ptr << "..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(2));
return OK;
}
Status MemcpyHostToDeviceAsync(void* dev_ptr, const void* host_ptr, size_t size_bytes, const Stream& stream) {
std::cout << "[AscDevKit] 异步拷贝 " << size_bytes << " 字节从 Host " << host_ptr << " 到 Device " << dev_ptr << "..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(15));
return OK;
}
Status MemcpyDeviceToHostAsync(void* host_ptr, const void* dev_ptr, size_t size_bytes, const Stream& stream) {
std::cout << "[AscDevKit] 异步拷贝 " << size_bytes << " 字节从 Device " << dev_ptr << " 到 Host " << host_ptr << "..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(15));
return OK;
}
Status StreamSynchronize(const Stream& stream) {
std::cout << "[AscDevKit] 同步流 " << stream.handle << "..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
return OK;
}
} // namespace AscDevKit
// 概念性:ops-cv 库的 API 接口头文件
namespace OpsCV {
// 概念性:图像数据结构,通常是 AI 处理器上的 Tensor
struct ImageTensor {
void* device_ptr; // AI 处理器内存地址
int width;
int height;
int channels;
// ... 其他属性如数据类型、步长等
};
// 概念性:插值方式枚举
enum InterpolationMode {
NEAREST_NEIGHBOR,
BILINEAR,
BICUBIC
};
// 概念性:函数返回状态
enum Status {
OK = 0,
ERROR_UNSUPPORTED_MODE,
ERROR_INVALID_INPUT,
// ... 其他错误码
};
// --- 图像处理算子 API 示例 ---
// 图像缩放算子
// input_img_tensor: 输入图像在设备上的 Tensor 信息
// output_img_tensor: 输出图像在设备上的 Tensor 信息
// interpolation_mode: 缩放使用的插值方法
// stream: 用于异步提交任务的 AI 处理器流
Status Resize(const ImageTensor& input_img_tensor,
ImageTensor& output_img_tensor,
InterpolationMode interpolation_mode,
const AscDevKit::Stream& stream) {
std::cout << "[OpsCV] 在流 " << stream.handle << " 上执行图像缩放操作:" << std::endl;
std::cout << " - 输入: W=" << input_img_tensor.width << ", H=" << input_img_tensor.height << ", C=" << input_img_tensor.channels << std::endl;
std::cout << " - 输出: W=" << output_img_tensor.width << ", H=" << output_img_tensor.height << ", C=" << output_img_tensor.channels << std::endl;
std::cout << " - 插值模式: " << interpolation_mode << std::endl;
// 实际会在这里调用 AI 处理器底层的 Kernel 来执行缩放
// 这个过程是异步的,并将任务添加到 stream 中
std::this_thread::sleep_for(std::chrono::milliseconds(50)); // 模拟计算时间
std::cout << "[OpsCV] 图像缩放 Kernel 已提交到流。" << std::endl;
return OK;
}
// 概念性:颜色空间转换算子
// Status CvtColor(const ImageTensor& input_img, ImageTensor& output_img, ColorConversionCode code, const AscDevKit::Stream& stream);
} // namespace OpsCV
int main() {
std::cout << "--- ops-cv 概念性 C++ API 交互演示 ---" << std::endl;
int target_device_id = 0; // 目标 AI 处理器设备 ID
AscDevKit::Status dev_status;
OpsCV::Status cv_status;
// 使用智能指针管理 AscDevKit 资源
std::unique_ptr<AscDevKit::Context> context_ptr;
std::unique_ptr<AscDevKit::Stream> stream_ptr;
// 定义图像尺寸
const int input_width = 1920;
const int input_height = 1080;
const int input_channels = 3; // RGB
const int output_width = 640;
const int output_height = 480;
// 主机端数据 (用于模拟图像输入和接收结果)
size_t input_host_data_size = input_width * input_height * input_channels * sizeof(float);
std::vector<float> input_host_data(input_host_data_size / sizeof(float), 128.0f); // 模拟图像数据
size_t output_host_data_size = output_width * output_height * input_channels * sizeof(float);
std::vector<float> output_host_data(output_host_data_size / sizeof(float), 0.0f);
// AI 处理器设备内存指针
void* input_dev_ptr = nullptr;
void* output_dev_ptr = nullptr;
try {
// 1. 初始化 AI 处理器设备(通过 AscDevKit)
dev_status = AscDevKit::InitDevice(target_device_id);
if (dev_status != AscDevKit::OK) throw std::runtime_error("AscDevKit 设备初始化失败");
// 2. 创建设备上下文和流(通过 AscDevKit)
dev_status = AscDevKit::CreateContext(target_device_id, context_ptr);
if (dev_status != AscDevKit::OK || !context_ptr) throw std::runtime_error("AscDevKit 上下文创建失败");
dev_status = AscDevKit::CreateStream(*context_ptr, stream_ptr);
if (dev_status != AscDevKit::OK || !stream_ptr) throw std::runtime_error("AscDevKit 流创建失败");
// 3. 在 AI 处理器上分配输入和输出图像的内存
dev_status = AscDevKit::MallocDeviceMemory(&input_dev_ptr, input_host_data_size);
if (dev_status != AscDevKit::OK) throw std::runtime_error("输入设备内存分配失败");
dev_status = AscDevKit::MallocDeviceMemory(&output_dev_ptr, output_host_data_size);
if (dev_status != AscDevKit::OK) throw std::runtime_error("输出设备内存分配失败");
// 4. 将主机图像数据异步拷贝到 AI 处理器设备
dev_status = AscDevKit::MemcpyHostToDeviceAsync(
input_dev_ptr, input_host_data.data(), input_host_data_size, *stream_ptr);
if (dev_status != AscDevKit::OK) throw std::runtime_error("Host到Device拷贝失败");
// 5. 准备 ops-cv 的 ImageTensor 结构
OpsCV::ImageTensor input_img_tensor = {input_dev_ptr, input_width, input_height, input_channels};
OpsCV::ImageTensor output_img_tensor = {output_dev_ptr, output_width, output_height, input_channels};
// 6. 调用 ops-cv 的 Resize 算子,在 AI 处理器上执行图像缩放
cv_status = OpsCV::Resize(input_img_tensor, output_img_tensor, OpsCV::BILINEAR, *stream_ptr);
if (cv_status != OpsCV::OK) throw std::runtime_error("ops-cv Resize 算子执行失败");
// 7. 将 AI 处理器上的结果异步拷贝回主机内存
dev_status = AscDevKit::MemcpyDeviceToHostAsync(
output_host_data.data(), output_dev_ptr, output_host_data_size, *stream_ptr);
if (dev_status != AscDevKit::OK) throw std::runtime_error("Device到Host拷贝失败");
// 8. 同步流,等待所有操作完成
dev_status = AscDevKit::StreamSynchronize(*stream_ptr);
if (dev_status != AscDevKit::OK) throw std::runtime_error("AscDevKit 流同步失败");
std::cout << "\n图像处理流水线概念性完成。主机输出数据示例 (前10个像素值):" << std::endl;
for (int i = 0; i < 10 && i < output_host_data.size(); ++i) {
std::cout << output_host_data[i] << " ";
}
std::cout << std::endl;
} catch (const std::runtime_error& e) {
std::cerr << "程序遇到错误: " << e.what() << std::endl;
}
// 9. 清理 AI 处理器设备内存(通过 AscDevKit)
std::cout << "\n--- 清理 ops-cv 及 AscDevKit 资源 ---" << std::endl;
AscDevKit::FreeDeviceMemory(input_dev_ptr);
AscDevKit::FreeDeviceMemory(output_dev_ptr);
// 智能指针会在作用域结束时自动销毁对象
AscDevKit::DestroyStream(stream_ptr);
AscDevKit::DestroyContext(context_ptr);
// AscDevKit::UninitDevice(target_device_id); // 概念性反初始化设备函数
std::cout << "--- 概念性 ops-cv API 演示结束 ---" << std::endl;
return 0;
}
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)