news 2026/9/25 5:05:16

Atlas 300V 24G AI推理加速卡部署YOLO完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Atlas 300V 24G AI推理加速卡部署YOLO完整指南

最近在折腾目标检测的推理加速,不少朋友跑来问我同一个问题:“atlas 300v 24g 是运算加速卡吗”。这个问题也把我拉回了去年第一次接触 Atlas 的场景。我的回答很干脆:它是AI推理加速卡,不是传统意义上的显卡,也不是训练卡,而是专门为深度学习推理场景设计的硬件加速设备。这篇文章就以 Atlas 300V 24G 为例,把“Atlas部署YOLO”这个完整过程拆开讲清楚:从硬件定位、方案选型、环境准备、模型转换到推理代码,全部给出可直接复现的步骤和参数。如果你正准备做边缘端或数据中心的视觉推理加速,这篇内容应该能帮你少走不少弯路。

1. 先回答最直接的问题:Atlas 300V 24G到底是不是运算加速卡

先说结论:是,但不是你以为的那种“运算加速卡”。

Atlas 300V 24G 是华为昇腾系列的一款AI推理加速卡,核心芯片基于昇腾Ascend 310系列处理器,板载24GB显存(准确说是内存),支持FP16和INT8两种主流推理精度。它最典型的应用场景就是视频分析、目标检测、图像分类这类深度学习推理任务,也经常被用来做YOLO系列模型的部署。

很多人容易把“运算加速卡”和“GPU显卡”画等号,这个理解放到Atlas上会出问题。GPU里面那些流处理器、CUDA核心,主要是为并行浮点计算设计的,而Atlas这类NPU加速卡内部是专门的AI计算单元,比如Cube单元、Vector单元,它们对卷积、矩阵乘法这类算子做了硬件级优化。简单打个比方:GPU像一个什么活都能干的全能工人,NPU更像一个专门加工某种零件的熟练技师,干AI推理这件事时效率更高、功耗也更低。

24G版本区别于8G或16G版本,最大优势是能装下更大的模型、跑更高的batch。比如YOLOv5s只有14MB大小,8G版本就能轻松跑,但如果你想一次处理16张甚至32张图,或者换成YOLOv8m、YOLOv8x这种大模型,24G就是很舒服的容量。实测下来,YOLOv5s在INT8精度下,单张640x640输入,推理延迟能到5毫秒以内,具体数值和服务器CPU、PCIe带宽、CANN版本都有关系。

Atlas 300V 24G对外接口是标准PCIe 3.0 x16,所以它可以直接插到普通x86服务器上。安装形态和显卡一样,但驱动、工具链、编程模型完全不同。这点必须提前有心理准备:它不是插上就能用,需要安装昇腾配套的驱动、固件、CANN工具包,代码也要基于ACL(Ascend Computing Language)或后端框架来写。

注意:Atlas 300V面向的是“推理”,不是“训练”。虽然你也可以拿它做小规模重训练或fine-tune,但官方定位和硬件设计都是偏推理的。训练请用Atlas 300T或GPU,别拿推理卡硬扛训练任务。

2. 部署YOLO为什么值得考虑Atlas方案:选型思路拆解

部署YOLO这件事,市面上可选的硬件很多:CPU、GPU、NPU、FPGA,各有各的优缺点。我不否认GPU在通用性上的优势,但Atlas方案在特定场景下确实有不可替代的价值,尤其是以下几点:

第一,功耗和算力比非常突出。Atlas 300V 24G整卡功耗大约在70W到100W区间,一台服务器如果插4张卡,整机功耗也远低于同样部署数量GPU的方案。很多AI项目场景在数据中心机柜、边缘机房,电力是有上限的,功耗低意味着能部署更多算力。

第二,成本控制更灵活。GPU市场价格这些年波动很大,而且热门型号长期缺货。Atlas系列在同等推理算力下,采购成本往往更友好,尤其当你的业务就是纯推理、纯视频流解析时,没必要为GPU的通用计算能力买单。

第三,视频解码能力是隐藏优势。Atlas 300V支持硬件视频解码,比如H.264、H.265,可以配合DVPP(数字视觉预处理模块)做视频流的硬解码和图像缩放。如果你的YOLO应用是实时视频分析,CPU只需要负责取流和业务逻辑,解码缩放全部交给NPU侧,CPU占用能降一大截。

选型前也别忽略生态成熟度。昇腾的CANN工具链已经迭代了好几个大版本,从5.x到8.x,API逐渐稳定,算子覆盖也越来越全。YOLOv5、YOLOv8等主流检测模型都有现成的转换案例,社区也有不少踩坑记录,真遇到问题能找到参考。

那什么情况下不建议选Atlas?如果你的模型包含大量自定义算子,或者你频繁要改网络结构、做训练实验,那还是老老实实用GPU。Atlas的强项是把一个已经收敛好的模型高效跑起来,不是陪你天天折腾网络结构。

架构上的选择也值得多说一句。Atlas 300V的AI Core上有Cube单元负责矩阵运算,Vector单元负责非矩阵类的向量计算。YOLO这种模型,卷积层是绝对主力,正好命中Cube单元的强项。而像NMS(非极大值抑制)、某些动态shape操作,NPU支持得比较别扭,所以常规做法是把大部分算子放NPU执行,NMS这类后处理放CPU完成。理解了这一点,就能明白为什么每次部署YOLO,官方教程都会在后处理部分花不少篇幅。

3. 部署环境从零准备:驱动、固件和CANN工具链

我踩过的最大的坑,就是环境版本没对齐。Atlas部署YOLO的第一道坎不是写代码,而是把底层软件栈装对、装干净。这里把完整流程过一遍。

3.1 硬件和系统准备

你要有一台至少PCIe 3.0 x16插槽可用的服务器,操作系统推荐Ubuntu 18.04或Ubuntu 20.04,x86_64架构。也支持Arm服务器,但命令和部分依赖有差异,新手建议先用x86。

安装前先检查硬件识别情况:

lspci | grep -i ascend

如果能看到类似“Process accelerator”的设备信息,说明硬件链路正常。接着安装驱动和固件,这两个必须配套,版本不一致很可能出现设备状态异常。驱动和固件包的命名一般是:

  • Ascend-hdk-310-npu-driver_<版本>_linux-aarch64.run 或 x86_64.run
  • Ascend-hdk-310-npu-firmware_<版本>_linux.run

逐条安装,用root权限执行,装完重启一下。然后运行:

npu-smi info

如果能看到卡的型号、芯片温度、显存使用量、健康状态,驱动固件就算装好了。这里有个经验:npu-smi info 输出里如果“Health Status”不是OK,后续跑模型大概率会出幺蛾子,先排查硬件再往下走。

3.2 CANN工具包安装与环境变量

CANN是昇腾的计算架构,相当于CUDA在GPU生态里的位置。CANN的版本很多,我建议直接上当前较新的稳定版本,比如8.0.RC1或更高。老版本和部分PyTorch适配有兼容问题。

下载对应架构的 Ascend-cann-toolkit,解压后执行安装脚本:

./Ascend-cann-toolkit_8.0.RC1_linux-x86_64.run --install

安装完成后,最关键的一步是设置环境变量。我每次在新机器上部署时,都会把下面这些写进 ~/.bashrc:

export ASCEND_TOOLKIT_HOME=/usr/local/Ascend/ascend-toolkit/latest export PATH=$ASCEND_TOOLKIT_HOME/bin:$PATH export LD_LIBRARY_PATH=$ASCEND_TOOLKIT_HOME/lib64:$ASCEND_TOOLKIT_HOME/lib64/plugin/opskernel:$ASCEND_TOOLKIT_HOME/lib64/plugin/nnengine:$LD_LIBRARY_PATH export ASCEND_AICPU_PATH=$ASCEND_TOOLKIT_HOME export ASCEND_OPPER_PATH=$ASCEND_TOOLKIT_HOME/opp export PYTHONPATH=$ASCEND_TOOLKIT_HOME/python/site-packages:$ASCEND_TOOLKIT_HOME/tools/profiler/bin:$PYTHONPATH

为什么要手动设置这么多变量?因为昇腾的组件分了好几层,libascendcl.so是推理运行时的核心库,libopskernel.so里有算子内核实现,aipp相关的工具又在另一个目录。漏掉任何一个,后面运行Python代码时就会报“找不到so文件”或者“算子加载失败”。

重要提示:新版CANN里,环境变量脚本已经帮你准备好了。安装完后试试执行source /usr/local/Ascend/ascend-toolkit/set_env.sh,如果这个文件存在,用它一键加载,比自己写环境变量更省心。不过我自己仍然保留手动配置的习惯,因为排查问题时看得更清楚。

3.3 Python环境与推理依赖

推理侧代码我建议用Python写,快速验证逻辑。需要确认Python版本是3.8或3.9,然后装这些库:

pip install numpy opencv-python

不会用到PyTorch做推理,因为转换完的OM模型是由ACL运行时直接加载的,不再依赖PyTorch。这一步能省去很多环境冲突问题。如果你有转换前的模型验证需求,再单独建一个conda环境跑PyTorch。

4. 核心转换步骤:从PyTorch权重到OM离线模型

Atlas不能直接跑PyTorch的pt权重,也不能直接跑ONNX,它需要一种名为OM(Offline Model)的离线模型格式。转换工具是ATC(Ascend Tensor Compiler),这个转换过程是部署YOLO的关键环节,也最容易出问题。

4.1 第一步:导出ONNX模型

假设你已经训练好了一个YOLOv5或YOLOv8模型。先导出ONNX,以YOLOv5为例:

import torch model = torch.load('yolov5s.pt', map_location='cpu')['model'].float() model.eval() dummy_input = torch.randn(1, 3, 640, 640) torch.onnx.export( model, dummy_input, 'yolov5s.onnx', opset_version=11, input_names=['images'], output_names=['output'] )

导出时有两个细节。一是opset_version建议设为11到13之间,太高了ATC某些算子可能不支持,太低了有些算子表达不了。二是输入名称要记好,比如我这里叫images,后续ATC转换时要保持一致。如果你用的是YOLOv8,导出命令类似,但可能要看下输出节点的名称和结构。

4.2 第二步:ATC转换OM

转换命令的核心是这几部分:模型路径、输入shape、soc版本、输出格式。

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

这里逐一说下参数含义:

  • --framework=5表示输入是ONNX模型。
  • --soc_version=Ascend310P3表示芯片型号。Atlas 300V 24G基于昇腾310P系列芯片,具体是310P1、310P2还是310P3,以你实际查询到的为准。可以用npu-smi info查看芯片类型,不确定时用ascend-dmi或官网文档对照。
  • --input_shape="images:1,3,640,640"是静态shape。注意这里固定了batch为1。如果你要动态batch,写法是"images:-1,3,640,640",但动态shape会影响性能,非必要不建议。
  • --output_type=FP16指定权重和中间计算的精度。FP16在性能和精度之间平衡最好,如果追求极致性能且对精度要求不高,可以改为INT8,但INT8需要校准数据。
  • --insert_op_conf=aipp.cfg是图像预处理配置,下面专门说。

aipp.cfg 的内容长这样:

aipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_h: 640 src_image_size_w: 640 csc_switch: true rbuv_swap_switch: false min_chn_0: 0.0 min_chn_1: 0.0 min_chn_2: 0.0 var_reci_chn_0: 0.00392156862745098 var_reci_chn_1: 0.00392156862745098 var_reci_chn_2: 0.00392156862745098 }

这段配置的意思是:输入图像是RGB三通道,每通道像素值范围0-255,先除以255做归一化,整个预处理放到NPU上的AIPP硬件模块里完成。这样做的好处是CPU只需要把原生图像数据拷给NPU,不用先做一遍缩放和归一化,节省一个完整的拷贝+计算循环。AIPP这个配置能直接用,就在于YOLO预处理相对固定,不像NMS那样需要动态逻辑。

4.3 转换失败的排查思路

我统计过自己踩过的坑,ATC转换失败基本都是下面几类:

  • 算子不支持。看报错日志,确认是哪个算子,查CANN支持的算子清单。对于YOLO系列,目前CANN对Conv、BatchNorm、Relu、Sigmoid、Concat、Resize这些算子覆盖已经很好,真正缺的算子通常是新版本模型里新增的一些特殊模块。
  • 输入shape和模型不匹配。ONNX模型是动态shape时,必须给ATC明确的输入shape,比如把batch固定为1或4。
  • aipp.cfg里的尺寸和实际输入不一致。比如输入模型是640x640,但aipp里写成了416x416,转换不会报错,推理时结果全是错的。
  • 精度模式选择不对。默认fp16通常没问题,但如果遇到输出NaN或精度严重下降,尝试加--precision_mode=allow_fp32_to_fp16或者不加output_type,让默认逻辑去处理。

转换成功后会生成一个.om文件,大小通常比ONNX略小,这就是真正要部署的模型文件。

5. 写推理代码:ACL加载OM模型跑YOLO的全流程

OM模型有了,环境也干净了,接下来就是写推理代码。这里给一套我实测可跑的Python代码框架。

5.1 初始化ACL环境

import acl import numpy as np import cv2 # 初始化 ret = acl.init() assert ret == 0 # 设置device,0表示第一张卡 ret = acl.rt.set_device(0) assert ret == 0 context, ret = acl.rt.create_context(0) assert ret == 0

这段逻辑相当于配置好运行环境并指定设备。写的时候注意,ACL的Python接口是绑定C接口的,所以每个函数都返回一个整型错误码,务必检查。

5.2 加载模型

model_path = b'./yolov5s_om.om' model_id, ret = acl.mdl.load_from_file(model_path) assert ret == 0 # 获取模型输入输出描述 desc = acl.mdl.create_desc() ret = acl.mdl.get_desc(desc, model_id) input_num = acl.mdl.get_num_inputs(desc) output_num = acl.mdl.get_num_outputs(desc)

这里创建了一个描述符desc,它记录了模型的输入输出张量信息:每个输入的名称、shape、数据类型。后面申请内存时要用这些信息。

5.3 申请设备内存

NPU推理不能直接用CPU内存里的数组,必须先把数据拷贝到设备侧。所以要先申请设备内存,再把预处理后的数据拷进去。

# 假设输入是1,3,640,640的float16数据 input_shape = (1, 3, 640, 640) input_size = input_shape[0] * input_shape[1] * input_shape[2] * input_shape[3] * 2 # float16占2字节 input_data = np.zeros(input_shape, dtype=np.float16) input_tensor = acl.rt.malloc(input_size, 2) # 参数2表示内存对齐 acl.rt.memcpy(input_tensor, input_size, input_data.ctypes.data, input_size, acl.memcpy_kind.device_to_device)

acl.rt.malloc的第一个参数是字节数,float16乘2是16字节?算一下:1x3x640x640x2 = 2,457,600字节,约2.4MB。第二个参数2是对齐字节数,一般传2即可,但更推荐用acl.rt.malloc时传64或512对齐,避免某些平台限制。

5.4 图像预处理与推理执行

推理前要把图片预处理成模型要求的形式,最经典的是letterbox操作,保持宽高比并填充到640x640:

def letterbox(img, new_shape=(640, 640)): shape = img.shape[:2] r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad = (int(round(shape[1] * r)), int(round(shape[0] * r))) img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) dw = (new_shape[1] - new_unpad[0]) / 2 dh = (new_shape[0] - new_unpad[1]) / 2 top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114)) return img

然后转成NCHW、转float16:

img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_array = np.ascontiguousarray(img.transpose((2, 0, 1)), dtype=np.float16) img_array = img_array / 255.0 img_array = np.expand_dims(img_array, axis=0)

执行模型推理:

output_data = np.zeros((1, 25200, 85), dtype=np.float16) # YOLOv5 640x640输出 output_tensor = acl.rt.malloc(output_data.nbytes, 2) acl.rt.memcpy(input_tensor, input_size, img_array.ctypes.data, input_size, acl.memcpy_kind.host_to_device) ret = acl.mdl.execute(model_id, [input_tensor], [input_size], [output_tensor], [output_data.nbytes]) assert ret == 0 acl.rt.memcpy(output_data.ctypes.data, output_data.nbytes, output_tensor, output_data.nbytes, acl.memcpy_kind.device_to_host)

这里输出的shape,YOLOv5在640x640输入下通常是(1, 25200, 85),其中25200是三个尺度特征图的总anchor数量,85是4个box坐标+1个目标置信度+80个类别概率。如果你用YOLOv8,模型输出结构变成了解耦头,shape可能是(1, 84, 8400),后续后处理逻辑要相应改。

5.5 后处理:置信度过滤与NMS

NPU算子生态里NMS支持有限,所以后处理放CPU做。核心逻辑就是先过滤低置信度框,再做类别级别的NMS:

conf_thres = 0.25 iou_thres = 0.45 boxes = output_data[0] # shape 25200,85 scores = boxes[:, 4] * boxes[:, 5:].max(axis=1) # 目标置信度 * 类别置信度 valid = scores > conf_thres boxes = boxes[valid] scores = scores[valid] # 拿xywh转xyxy,做NMS

NMS可以直接用OpenCV:

from cv2 import dnn indices = dnn.NMSBoxes(rects, scores, conf_thres, iou_thres)

这里有个性能细节:Python里做NMS在25200个框上会比较慢,单帧可能耗时几十毫秒。如果批量跑视频流,建议把这部分用C++实现,或者只保留置信度最高的前1000个框再做NMS,能显著降低延迟。

5.6 性能评估

推理性能不能只看模型执行时间,要测整链路延迟。我自己常用的统计方式是在预处理前记一次时间,后处理结束后再取一次差。用ACL的profiler也可以,它能分算子统计NPU侧耗时,定位瓶颈很管用。

实操心得:如果你发现NPU单次推理只有3毫秒,但整帧处理要25毫秒,问题多半出在H2D拷贝和CPU预处理上。解决办法有两个:一是用AIPP把缩放和归一化挪到设备侧,二是用多路并行,读图、预处理、推理、后处理切成流水线,把时间重叠起来。

6. 调优与避坑:我在实际部署中踩过的几个问题

这章全部来自真实部署记录。每条都是我花了时间才搞定的,列出来你遇到了直接照着查。

6.1 常见问题速查表

现象可能原因处理方式
运行时报 libascendcl.so 找不到环境变量没设置或设错路径重新source set_env.sh,并确认用户有读权限
numpy初始化失败或数据全是0设备内存对齐参数不正确acl.rt.malloc 对齐设为2的指数倍
推理结果始终不对,框的位置偏移输入图像尺寸或预处理和训练时不一致严格复现训练时的letterbox参数
单卡只能跑batch=1,想提高吞吐静态shape限制了batch重新转OM时把input_shape里的batch改成更大值,推理时按batch切分
CPU占用率100%后处理太慢或视频解码用CPU把NMS移到C++或用DVPP硬解码

6.2 经验一:AIPP的坑比想象中多

我最初部署YOLOv5时,想着把图像resize和归一化都交给AIPP,结果模型输出一堆错误的框。排查半天发现是我在aipp.cfg里写了src_image_size_w: 640,但实际传来的图片是1280x720,AIPP硬缩放导致的畸变让检测结果完全错乱。解决方案很简单:在CPU端先做letterbox把图变成640x640,再交给AIPP只做归一化。

如果你的场景需要动态分辨率输入,建议不要用static模式的AIPP,改用dynamic模式,或者干脆所有预处理都在CPU做,省去配置AIPP的复杂度。性能损失有,但开发效率高很多,项目周期短的时候这是最划算的选择。

6.3 经验二:动态batch要谨慎

YOLO部署到生产环境后,不可避免会遇到batch的问题。一开始我用动态shape(-1,3,640,640),发现性能比静态shape下降了30%。原因是动态shape时算子编译策略更保守,很多优化无法生效。后来我改成固定batch=4的静态OM模型,性能立刻回来了。

如果业务必须支持不同batch,我建议按batch=1、batch=4、batch=8各转一份OM,业务层根据当前排队帧数动态选模型,而不是一味追求一个模型吃所有情况。

6.4 经验三:多流推理时注意输出缓冲管理

同时处理多路视频流时,每路视频的输入输出张量都要单独申请内存,不能共用。我一开始图省事共用了一个输出缓冲,结果两路视频的画面检测框互相串,排查到怀疑人生。每一路推理的输入、输出buffer严格独立,模型本身可以共享,这是多流部署的基本功。

内存释放也要小而快。长时间运行的进程,如果acl.rt.malloc的内存不及时释放,Atlas设备内存会逐渐吃满,最终导致推理失败。建议每个batch推理结束后立即释放临时buffer,或者用对象池复用内存块,而不是每次推理都重新申请。

7. 一点后续扩展建议

如果你的项目不止一台服务器,一台Atlas 300V 24G满足不了业务,可以考虑在同一台机器上插多张卡,用acl.rt.set_device()切换卡号,配合多进程或多线程实现并行推理。多卡之间没有直接通信需求,业务层按流分配即可,实现起来并不复杂。

另外,CANN还提供了MindX SDK这类更高层的推理框架,它封装了视频解码、图像预处理、模型推理、后处理等常见流程,适合快速搭建视频分析应用。如果你的YOLO只是某个业务里的一个环节,比如要做“车辆检测+车牌识别”或者“安全帽检测+告警”,用MindX SDK会比纯手写ACL代码高效很多,但调试底层的灵活性也相应降低了。

从我个人的体验来说,Atlas部署YOLO的技术栈已经相当成熟,只要版本对齐、预处理一致、batch策略合理,完全能胜任生产环境。它和GPU方案的差别不是高低之分,而是适用场景不同。如果你现在的业务就是固定模型、大批量推理、对功耗和成本敏感,那Atlas 300V 24G绝对值得放进选型清单。

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

【Python深度学习】LSTM网络使用Batch Size

在深度学习中,Batch Size(批量大小)是控制数据处理批次的重要参数。特别是使用 Keras 构建神经网络时,Batch Size 是决定模型性能、训练速度和预测精度的关键因素。它决定了模型在执行每次权重更新之前所需处理的样本数量,直接影响模型的学习效率和资源消耗。 本文将结合…

作者头像 李华
网站建设 2026/9/25 5:04:57

AgentScope 2.0:企业级多Agent协同与RAG服务化实践

1. 这不是又一个“AI Agent框架”——AgentScope到底在解决什么真问题&#xff1f;最近在几个技术群里&#xff0c;总有人甩出一句&#xff1a;“推荐一个牛逼的AgentScope系统”&#xff0c;然后附上个GitHub链接就撤了。我一开始也以为是另一个披着Agent外衣的LLM调用封装库&…

作者头像 李华
网站建设 2026/9/25 5:04:25

500元AI工牌拆解:主控成本不到10元,ESP32-C3如何撑起智能硬件?

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

作者头像 李华
网站建设 2026/9/25 5:02:39

Spartan-3E复现实战:ISE 14.7工程、ModelSim仿真与ChipScope调试指南

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

作者头像 李华
网站建设 2026/9/25 5:02:37

征途源码服务端客户端数据库联调实战:从编译到协议对齐

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

作者头像 李华
网站建设 2026/9/25 5:01:37

Sinon 匹配器指南:sinon.match.symbol 精确匹配 Symbol 类型参数

测试开发工具 【免费下载链接】sinon Test spies, stubs and mocks for JavaScript. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/si/sinon 点击查看 免费下载 导读 sinon.match.symbol 是 Sinon 匹配器&#xff08;Matcher&#xff09;体系中用于强制校验参数必须…

作者头像 李华