news 2026/9/20 9:29:43

昇腾Atlas 300V推理卡YOLO部署全攻略:硬件识别、环境搭建与模型转换

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
昇腾Atlas 300V推理卡YOLO部署全攻略:硬件识别、环境搭建与模型转换

最近后台有朋友连着问我两个问题:Atlas 300V 24G到底算不算运算加速卡?用atlas部署yolo到底怎么搞?这两个问题其实指向同一件事——昇腾推理卡从硬件选型到模型落地的完整链路。作为一个在安防视频分析项目里把YOLO系列反反复复部署过多次的人,我想把这条路上的关键点好好捋一遍,从硬件身份确认、环境搭建、模型转换,到推理代码和常见坑位,一次性说清楚。

这篇文章适合谁看?刚拿到Atlas 300V卡片、准备在边缘设备上跑YOLO的工程师,以及正在做技术选型、纠结"到底要不要买这张卡"的方案评估人员。如果你已经在这套环境里折腾过一段时间,后面几节的问题速查表应该也能帮你省下不少查文档的时间。内容全部基于实际部署经验,涉及的命令和参数都以当下主流的CANN版本为准,细节上我会标注哪些是版本相关、哪些是通用逻辑。

1. 动手之前先弄清:Atlas 300V 24G到底是张什么卡

很多人在选型阶段就犯了第一个错误——看到"300V"几个字,想当然地以为这是一张跟GPU类似的通用运算卡,结果硬件买回来,软件栈完全不一样,项目进度直接卡住。

1.1 Atlas家族盘点:I卡、V卡、Pro卡别买错

Atlas是昇腾计算产品线的总称,下面分了几个大系列,用途差别非常大。Atlas 200系列是模组和开发套件,适合做嵌入式原型验证;Atlas 300系列是标准PCIe插卡,这也是咱们这篇文章的主角;Atlas 500系列是智能小站,自带机箱和散热;Atlas 800/900系列是训练服务器,面向数据中心场景。

在300系列内部,又分成300I、300V、300I Pro三个常见型号,很多人搞混就是在这里。

300I是通用推理卡,主打图像分类、目标检测这类推理负载,适合当纯推理加速器用。300V的"V"是Video的意思,专门为视频分析场景设计,最明显的区别是把视频解码能力做进了硬件里,一张卡能同时解码多路1080P视频流,解码后的帧直接在卡内完成缩放和推理,不需要CPU反复搬运图像数据。300I Pro是后续升级版,算力更高,支持的特性更多,但定位仍然是通用推理。

Atlas 300V 24G的"24G"指的是板载24GB内存,而不是像GPU那样的"显存"概念。这个容量在同代推理卡里属于比较充裕的,跑YOLOv5s、YOLOv8s这类轻量检测模型毫无压力,跑YOLOv5m甚至YOLOv5l的量化版本也能承受。

1.2 为什么说它算运算加速卡,又为什么不能拿它当GPU用

从功能上讲,Atlas 300V 24G当然算运算加速卡,它能承接神经网络推理计算,能并行处理大量矩阵运算。但把它当GPU来看待是完全错的,下面三条差异非常关键:

第一,软件栈不同。GPU生态用CUDA,Atlas系列用的是CANN(Compute Architecture for Neural Networks),从驱动、算子库到推理框架全部是另一套体系。你在GPU上写好的TensorRT推理代码,不能直接拿到这张卡上跑。

第二,训练和推理能力不均衡。这张卡是纯推理卡,不支持训练反向传播。你可以在上面部署调优好的模型,但不能用它从头训练YOLO。想训模型只能另找训练设备,或者用MindSpore在云端训练再导出。

第三,通用计算能力有限。除了AI算子,它不提供CUDA core那种通用并行计算能力,不能用来做C++通用并行加速、不能跑CUDA生态的第三方库。它是一张"专用加速卡",不是"通用并行计算卡"。

打个比方,GPU像是多功能的瑞士军刀,能做推理、渲染、通用计算;Atlas 300V更像一台专业咖啡机,做咖啡又快又好,但你不会拿它去拧螺丝。所以选型之前一定要想清楚,如果你的业务是纯视频分析、纯深度学习推理,这张卡性价比很高;如果还想顺带做点别的并行计算任务,请仔细评估兼容性。

1.3 一张表看懂300V 24G在选型中的位置

参数层面,我可以给一个参考维度表,方便你在选型会上直接对照。注意具体算力数字以官方规格书为准,不同批次和固件版本会有一点浮动,但定位差异不会变。

维度Atlas 300V 24GAtlas 300I Pro普通GPU推理卡(如T4)
产品定位视频分析专用推理卡通用AI推理卡通用GPU推理卡
核心优势硬解码+推理一体算力均衡生态成熟,CUDA通用
内存规格24GB板载内存16GB/24GB可选16GB GDDR6
INT8算力140 TOPS级别140 TOPS级别65 TOPS级别
视频解码能力支持多路1080P硬解码较弱,依赖CPU需要额外购买授权
软件生态CANNCANNCUDA/TensorRT
适合场景智能安防、视频结构化图像分类、检测推理多业务混合负载

选型的时候先回答一个问题:你的瓶颈到底在"视频解码"还是"模型算力"?如果做的是摄像头视频流的实时检测,Atlas 300V这种带硬解码的卡优势非常明显,CPU占用率能被压得很低;如果只是对一批图片做离线推理,选300I Pro或者普通推理卡可能更灵活。

2. 环境搭建:驱动、固件、CANN一个都不能少

拿到卡之后第一件事不是急着写代码,而是把环境一次装对。昇腾这套软件栈有个特点:驱动、固件、CANN Toolkit三者版本必须严格对应,随便混装很容易出现"卡插上了但npu-smi info查不到设备"的情况。

2.1 装机前的硬件确认与系统准备

先确认两件事。第一,你的服务器或工作站要有空闲的PCIe x16插槽,Atlas 300V是标准PCIe卡,供电走PCIe接口,不需要外接电源线,但要注意机箱内部风道,卡上散热器不小,风道不好的话满载温度会很难看。第二,操作系统版本要在支持列表里,常见的有Ubuntu 20.04/22.04 x86_64、CentOS 7.6、openEuler等,也可以用ARM版本,区别只是下载的安装包后缀不同。

系统准备阶段建议干净系统。如果你之前装过别的AI框架、别的GPU驱动,倒不影响,但如果之前装过老版本的CANN,请先卸载干净,否则新版装上后会有一堆环境变量冲突,查起来非常痛苦。

有一个小操作可以快速确认服务器有没有识别到卡:用lspci命令过滤昇腾设备信息。看到类似"Processing accelerators"或"Huawei"相关的条目,就说明PCIe链路正常,硬件层面已经被系统看到了。如果这里什么都看不到,先查插槽接触和BIOS设置,别急着装软件。

2.2 从下载到验证:环境安装的完整命令

昇腾的软件包统一在昇腾社区下载,你需要拿三个东西:NPU驱动包、NPU固件包、CANN Toolkit包。以x86_64的Ubuntu 20.04为例,文件名大致长这样:Ascend-hdk-310p-npu-driver_版本_linux-x86_64.run、Ascend-hdk-310p-npu-firmware_版本_linux-x86_64.run、Ascend-cann-toolkit_版本_linux-x86_64.run。注意Atlas 300V对应的是310p系列的驱动,别下错成910系列的。

安装顺序有讲究:先驱动,再固件,最后CANN Toolkit。建议用root执行,非root用户后面会有一堆权限问题。给安装包加执行权限后直接运行:

chmod +x Ascend-hdk-310p-npu-driver_*.run ./Ascend-hdk-310p-npu-driver_*.run --full --install chmod +x Ascend-hdk-310p-npu-firmware_*.run ./Ascend-hdk-310p-npu-firmware_*.run --full --install

驱动和固件装完,把当前用户加入昇腾用户组:

usermod -a -G HwHiAiUser your_username

然后安装CANN Toolkit,这里建议只装toolkit,不需要装nnrt或者推理包,因为咱们是直接在开发环境上做模型转换和推理验证:

chmod +x Ascend-cann-toolkit_*.run ./Ascend-cann-toolkit_*.run --install

安装完成后,source一下环境变量脚本,这个操作每次开新终端都要做,建议写进~/.bashrc:

source /usr/local/Ascend/ascend-toolkit/set_env.sh

最后用npu-smi info命令验证,正常会输出设备号、芯片温度、内存使用量、当前算力状态等信息。如果能看到Device count为1以上,环境就算通了。

2.3 一次装不上的常见原因

我遇到过最多的问题是装完驱动后npu-smi info报"no device"或者直接命令不存在。排查看三个方向:

一是版本匹配。驱动、固件、CANN Toolkit三个包的版本号必须对应,昇腾社区每个版本会列出配套关系表,下载时对照一下。我见过有人拿310P的驱动配910的固件,结果反复重启都识别不了卡,最后重新下载配套包才解决。

二是内核版本和驱动模块冲突。如果系统内核更新过,驱动模块可能加载失败,重启后不能正常识别。此时先确认OS内核版本在支持列表里,再检查驱动的dkms状态。

三是用户权限。非root用户执行npu-smi info会提示权限不足,解决办法是把用户加进HwHiAiUser组后重新登录,或者直接sudo执行。

给一个小建议:环境配置阶段每一步都做验证,不要憋到最后一起验证。装完驱动立刻npu-smi info,装完CANN立刻跑一个官方样例的python脚本,这样问题定位范围小很多。

3. 模型转换:把YOLO的pt权重变成OM离线模型

环境通了之后,下一个核心环节是把YOLO权重转成昇腾设备能识别的OM离线模型。这一步是新手最容易困惑的,因为PyTorch的.pt文件在这个生态里根本跑不起来。

3.1 为什么非要过一道ONNX再到OM

昇腾芯片不能直接加载PyTorch权重,也不能直接加载TensorFlow模型,它有自己的模型格式,叫OM(Offline Model)。OM是离线模型,推理时不需要框架参与,调度开销小,性能更稳定。

从PyTorch到OM,公认的做法是先把.pt导出为ONNX,再用ATC工具转成OM。为什么中间要塞一个ONNX?因为ONNX是开放格式,PyTorch、TensorFlow等框架都能转出,昇腾的ATC对ONNX的支持也最成熟。直接写脚本解析PyTorch权重再转OM理论上可行,但算子映射复杂,完全没有必要自己造轮子。

以YOLOv5s为例,导出ONNX这一步用官方仓库的export.py:

python export.py --weights yolov5s.pt --include onnx --opset 11

导出后最好用onnxruntime或者Netron看一眼模型结构,确认输出节点的名称和shape。YOLOv5的输出通常是三个尺度的检测头,shape类似[batch, 25200, 85]或者是[batch, 3, 80, 80, 85]这样的锚框形式。看清楚了,后面写后处理的时候才不会蒙圈。

3.2 ATC转换参数逐个拆解

拿到ONNX文件后,设置好CANN环境变量,执行ATC转换。我先给一条完整的命令,再拆开讲每个参数:

atc --model=yolov5s.onnx \ --framework=5 \ --output=yolov5s_bs1 \ --input_shape="images:1,3,640,640" \ --soc_version=Ascend310P3 \ --insert_op_conf=aipp.cfg \ --output_type=FP16

--model指定输入的ONNX文件路径。--framework=5表示输入是ONNX格式,在ATC的框架编号里ONNX对应5,Caffe是0,TensorFlow是3。--output指定输出的OM文件名。--input_shape用来锁定输入维度,YOLOv5的输入节点名通常是images,我们固定batch为1、3通道、640x640分辨率。

--soc_version是最容易填错的参数,它告诉ATC编译器目标芯片的架构版本。Atlas 300V对应的昇腾芯片是Ascend310P3,所以这里填Ascend310P3。如果你用的是Atlas 300I Pro或者别的型号,先用npu-smi info查看芯片型号再填,填错会导致编译出来的OM无法加载。

--insert_op_conf指定AIPP配置文件,这个很重要,我单独开一节讲。--output_type=FP16是把模型权重和计算精度转换为半精度浮点,对推理性能有明显提升,大部分网络在FP16精度下精度损失小到可以忽略。新手容易纠结要不要用INT8,我的建议是先跑通FP16,稳定之后再考虑量化。

3.3 AIPP配置:把预处理塞进模型里

AIPP(Artificial Intelligence Pre-Processing)是昇腾提供的一套硬件预处理模块,可以在芯片内部完成图像缩放、格式转换、减均值、归一化等操作,相当于把预处理从CPU搬到了设备端。这样做的好处有两个:CPU占用更低,端到端延迟更小。

下面是一个适用于YOLOv5的AIPP配置示例:

aipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_w: 640 src_image_size_h: 640 mean_chn_0: 0 mean_chn_1: 0 mean_chn_2: 0 min_chn_0: 0.0 min_chn_1: 0.0 min_chn_2: 0.0 var_reci_chn_0: 0.003921569 var_reci_chn_1: 0.003921569 var_reci_chn_2: 0.003921569 }

input_format填RGB888_U8告诉硬件输入图片是RGB排列、每个通道8bit。var_reci_chn的值是1/255,也就是0.003921569,这样硬件会直接把像素值从0到255归一化到0到1,对应YOLOv5训练时的归一化逻辑。

要注意的是,如果AIPP里开了resize,输入图片的尺寸和目标尺寸不一致时会由硬件直接缩放。但YOLOv5推理一般不是直接resize,而是先把长边缩放到640再做letterbox填充,这个逻辑放在AIPP里实现比较麻烦,我通常选择在CPU侧做好letterbox,把处理完的640x640图像送给设备,AIPP只负责格式转换和归一化。这样代码逻辑清晰,也不容易踩坑。

3.4 静态shape与动态维度如何选择

ATC转换时shape可以固定成静态,也可以留成动态。静态shape就是--input_shape里写死1,3,640,640,编译出来的OM只能接受这个尺寸的输入。动态shape则要加--dynamic_shape_range之类的参数,允许推理时改变batch或分辨率。

对YOLO部署,我的经验是:能用静态就用静态。静态shape的优点是显存分配固定、算子调度优化更激进,实测吞吐比动态高不少。如果你的应用场景是固定分辨率视频流,输入尺寸稳定,根本没有必要用动态。只有在业务必须多分辨率输入的情况下才引入动态shape,但要做好性能下降的心理准备,同时代码里也要处理动态shape带来的额外复杂度。

还有一个容易被忽略的点:导出的ONNX里如果包含了过多的自定义算子或者版本过高,ATC可能会报不支持。遇到这种情况,先尝试降低opset版本重新导出,再不行就检查是否用了不常见算子,比如某些后处理算子能省就省,放到CPU侧做反而更灵活。

4. 推理部署:Python快速跑通,C++上生产

模型转换成OM文件之后,终于到了推理环节。昇腾官方主推的语言有两种:Python的ACLLite封装和C++的AscendCL原生接口。我的建议很清楚:验证阶段用Python,产出阶段用C++。

4.1 Python方案:ACLLite三步搞定

ACLLite是昇腾社区封装的一套Python推理库,把设备初始化、模型加载、推理执行等操作简化成了几个类,非常适合快速验证模型。安装方式一般是pip安装或从昇腾社区拉取源码:

pip install acllite

核心代码思路如下:

from acllite.acl import ACL from acllite.model import Model from acllite.image import AclImage # 第一步:初始化ACL acl = ACL() acl.init() # 第二步:加载OM模型 model = Model("yolov5s_bs1.om") # 第三步:读取图片并推理 image = AclImage("test.jpg") resized = image.resize((640, 640)) # 实际要按letterbox逻辑处理 out = model.execute([resized]) # 拿到三个尺度的输出,CPU侧做NMS后处理

注意execute的返回结果是模型输出,对YOLO来说就是原始的检测框预测值,NMS和坐标解算还是得自己在CPU侧实现。千万不要指望模型直接输出画好框的结果,除非你导出ONNX时把后处理也写进了网络图里,但那样做灵活度太差,不推荐。

Python方案的优点是真的快,从加载环境到出检测框可能半小时就够;缺点也很明显,性能上限不高,多路视频流并发时Python的GIL和内存管理会成为瓶颈,不适合做生产服务。

4.2 C++方案:用AscendCL写一套可复用的推理接口

上生产环境,我强烈建议用C++。AscendCL的API设计比较清晰,核心流程可以规整为四步:初始化设备、加载模型、准备输入输出、执行推理。

先看初始化和模型加载部分:

#include "acl/acl.h" #include "acl/acl_mdl.h" // 1. 初始化ACL aclInit(nullptr); // 2. 设置并打开设备 aclrtSetDevice(0); // 3. 创建推理流Stream aclrtStream stream; aclrtCreateStream(&stream); // 4. 加载OM模型文件 uint32_t modelId; aclmdlLoadFromFile("yolov5s_bs1.om", &modelId); // 5. 获取模型描述信息,包括输入输出Tensor信息 aclmdlDesc *modelDesc = aclmdlCreateDesc(); aclmdlGetDesc(modelDesc, modelId);

推理执行时,要先准备好输入数据的Device内存。做法是:先用aclDataBuffer创建数据缓冲,用aclrtMemcpy把Host端图像数据拷贝到Device端,然后创建输入数据集和输出数据集,调用aclmdlExecute异步接口。

// 6. 准备推理输入 aclDataBuffer *inputBuf = aclCreateDataBuffer(deviceInputPtr, inputSize); aclmdlDataset *inputSet = aclmdlCreateDataset(); aclmdlAddDatasetBuffer(inputSet, inputBuf); // 7. 执行推理 aclmdlExecuteAsync(modelId, inputSet, outputSet, stream); aclrtSynchronizeStream(stream); // 8. outputSet 里取得推理结果,在CPU侧做后处理

写C++接口时有个建议:把这套调用封装成一个推理类,对外只暴露一个LoadModel和Infer的接口,上层业务不用关心昇腾细节。我做过一个项目,模块内部用模型ID做索引,支持同时加载多个OM模型,上层只要传模型序号和输入图像就能拿到结果,维护起来非常舒服。

4.3 视频流场景:DVPP硬解码与多路并发

Atlas 300V最大的价值体现在视频场景。拿安防项目举例,摄像头推流过来是H.264或H.265,传统做法是CPU用FFmpeg软解,非常消耗资源。300V自带DVPP硬件解码模块,可以在设备端直接拉流解码,再把解码帧交给AI Core推理。

具体操作上,先用FFmpeg做拉流,把视频帧送入DVPP的VPC模块完成解码和缩放,缩放后的RGB数据直接作为推理输入。这样CPU只需要做码流分发和结果后处理,整个流水线的CPU占用能降到很低。实测中,单张Atlas 300V 24G跑YOLOv5s的端到端处理能力,在720P视频流下可以做到十几路并发,具体数字取决于码流参数和后处理复杂度,但相比纯CPU方案提升是数量级的。

多路并发时的另一个关键点是batch。如果你希望把多路的图像合并成同一个batch推理,需要在预处理阶段把多张图拼成一个Tensor,同时记录每张图对应的流ID,推理完成后再按顺序拆解。这种做法能显著提升吞吐。如果代码复杂度不允许,也可以一路一个推理流,牺牲一点器件利用率但逻辑简单,小规模项目完全够用。

5. 部署问题速查与排坑实录

最后这部分是真正的干货。我把自己和团队在实际部署中踩过的坑整理出来,按问题现象、可能原因、解决思路三个维度列了一个速查表,后面再针对高频问题详细展开。

5.1 问题速查表

现象可能原因排查与解决
npu-smi info看不到设备驱动固件版本不匹配或未安装成功检查驱动固件包版本,卸载重装,重启后重新验证
ATC转换报错 E10003ONNX模型版本过高或算子不支持降低opset版本重新导出,检查是否用了特殊算子
推理报错 device memory not enough动态shape导致显存分配过大改静态shape,或减小输入分辨率
检测框偏移、位置不准输入预处理和训练时不一致检查letterbox、归一化参数是否和训练一致
性能远低于预期使用了动态shape或未开启AIPP改静态shape,把预处理塞进AIPP
进程崩溃在aclInit阶段设备权限不足或设备被占用sudo执行或加入HwHiAiUser组,检查是否有残留进程

5.2 性能上不去的几个主要原因

很多人反映模型能跑,但帧率就是上不去。我排查这类问题时通常会按顺序检查四个地方:

第一,AIPP有没有用。如果预处理在CPU完成,图片从CPU拷贝到设备再进网络,耗时翻倍。放到AIPP后,一次拷贝直接进设备完成预处理,延迟明显下降。

第二,shape是不是静态。动态shape下算子编译和显存分配都会变慢,我见过同一个模型动态比静态性能差30%以上的案例。

第三,batch和stream有没有用起来。单batch单stream只能发挥芯片一小部分算力,适当增加batch或者并发stream可以提高吞吐,但不要盲目开大,芯片占用率到80%以后继续加并发收益就不明显了。

第四,输出后处理是否拖后腿。YOLO的输出后处理如果写成层层循环,CPU会直接被打满。建议用向量化操作代替for循环,或者用多线程把多路结果并行处理。

5.3 我的一点实际体会

做这套环境部署,最折磨人的往往不是AI知识,而是版本适配的琐碎问题。我的习惯是:每次部署都在文档里记下驱动、固件、CANN的精确版本号,以及系统内核版本,这样出了问题能快速回溯。这个习惯已经帮我省下过好几个通宵。

另外,尽量少用网上流传的兼容不同版本的通用安装脚本,自己手动执行三步安装其实更可控。遇到报错先看日志,CANN的日志输出位置通常在~/ascend/log/,日志里的报错信息远比"AI加速卡初始化失败"这种提示有效得多。

最后想说的是,Atlas 300V这种专用推理卡,用好了确实是视频分析场景的一把利器。它需要你接受它的生态边界,但一旦环境打通,稳定性、功耗、算力性价比都会让你觉得前期折腾是值得的。希望这篇文章能帮你把最陡的一段路走平。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 9:29:41

Flutter CustomScrollView:打造高性能滑动视图的终极指南

1. Flutter滑动视图的革命性解决方案在Flutter应用开发中,CustomScrollView就像一把瑞士军刀,它能让你突破常规ScrollView的限制,创造出令人惊艳的滚动效果。我曾在电商APP开发中,用CustomScrollView实现了商品详情页的"悬浮…

作者头像 李华
网站建设 2026/9/20 9:28:45

ESP32外接SD卡读写实战:从硬件接线到数据可靠存储

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 9:26:58

调 Claude Code 时 API 请求失败?TaoToken 这边先查 Base URL 填没填对

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 9:26:45

Atlas 300V 24G部署YOLOv5s:AI推理加速卡全流程指南

前阵子朋友扔过来一块Atlas 300V 24G,让我赶紧把YOLOv5s的检测服务跑起来,结果他第一句话就是问我:“这卡是运算加速卡吗?”这个问题其实问到根子上了:它确实是加速卡,但不是大多数人熟悉的GPU,…

作者头像 李华
网站建设 2026/9/20 9:26:39

FX Configurator-EN-L:三菱PLC以太网通信参数配置与IP在线修改实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 9:25:24

Claude Mods实战指南:从安装到定制你的AI编程助手

1. Claude Mods到底是什么?——社区为什么集体盯上「魔改」最近在AI编程工具圈子里,"Claude Mods"这个词的出现频率突然高了起来。如果你跟我一样长期关注Claude Code的更新动态,会发现自从官方放开了扩展能力之后,GitH…

作者头像 李华