1. Atlas 300V 24G到底是个啥先厘清加速卡身份1.1 从“是不是运算加速卡”说起我经常在社区里看到有人问“atlas 300V 24G是运算加速卡吗”这种问题背后其实藏着两种常见的误解一是把它当成纯GPU二是觉得它只是个普通显卡跑不了正经机器学习任务。先说结论它确实是一块AI运算加速卡但不是NVIDIA那种通用GPU而是华为昇腾系列的一款推理加速卡核心处理器是昇腾AI芯片在数据中心、边缘服务器里拿来跑神经网络推理尤其是像YOLO这种目标检测模型非常合适。为什么很多人会对“运算加速卡”这个概念犯迷糊因为传统意义上我们接触到的“计算卡”多半是GPGPU比如特斯拉V100、A100这类。它们的思路是让几百上千个流处理器并行算浮点数跑CUDA生态里的框架。而Atlas 300V走的是一条完全不同的路线昇腾芯片里集成了专门的AI计算单元Cube、Vector等针对矩阵运算做了大量定制所以它在推理场景下的能效比往往比同功耗的GPU更好但对开发者来说上手方式、软件栈也和CUDA那一套完全不同。我之前第一次拿到这块卡的时候也踩过类似的坑习惯性地以为装个PyTorch就能直接调用结果发现驱动、算子库、模型格式全是另一套逻辑。所以这篇文章里我打算从硬件身份讲到部署实操把“Atlas 300V能否部署YOLO”“怎么部署”“转换模型时有哪些坑”一次性讲清楚。如果你正准备买卡或者已经拿到卡需要落地这篇文章应该能帮你少走不少弯路。1.2 常见硬件规格解析Atlas 300V 24G这个型号单看名字就能读出几个关键信息这是一块PCIe插卡形态的加速卡板载24GB内存功耗不算太高常用在推理服务器或者边缘计算盒子中。具体规格可以从下面几个维度来看项目常见规格形态PCIe 3.0/4.0 标准卡半高或全高可选AI处理器昇腾系列芯片内置AI Core板载内存24GB常为DDR或LPDDR规格主要精度INT8 / FP16部分场景支持FP32典型功耗75W左右视具体型号和负载略有浮动接口PCIe x16带辅助供电软件栈CANN工具链、MindSpore、MindX SDK、ACL拿24GB内存来说这个容量对于目标检测模型有多重要我拿YOLOv8举例一个用COCO训练好的YOLOv8m模型FP16精度的权重文件大概在50MB左右看起来很小但推理时中间特征图、激活值、临时缓冲区占用会迅速膨胀如果输入是1280分辨率且同时跑多个batch24GB能让你非常从容地开大batch提高吞吐量而不会像8GB显卡那样动不动就显存溢出。不过我也要提醒一句24GB指的是板载内存不是显存虽然作为开发者用起来没啥区别但在和NVIDIA卡做对照时要意识到架构差异。它不能替代一块RTX 4090去训练模型它的主业是推理尤其适合那种模型训练完之后需要低成本、高能效部署到生产环境的场景。1.3 为什么挑它跑YOLO算力性价比和场景匹配YOLO系列模型在工业界的地位不用多说自动驾驶、安防监控、质检、电力巡检几乎处处都有它的身影。这类场景有个共同特点模型已经训练好了部署现场对功耗、体积、稳定性有苛刻要求不能像训练机房一样拉一堆GPU。Atlas 300V 24G正好卡在这个需求点上。我用它和常见GPU做过对比。一块75W的Atlas 300V在跑YOLOv5s FP16模型时单卡吞吐量能做到几百路视频流的分析取决于输入分辨率、帧率、检测类别数而同等功耗下很多GPU很难达到这个并发度。更关键的是昇腾的推理卡在INT8量化后性能还能再涨一截这对线上环境很有吸引力。另外Atlas 300V的价格通常比同显存容量的NVIDIA GPU便宜不少24GB大内存也意味着部署多模型时不用频繁换卡。如果你是做边缘计算产品比如一体机、边缘盒子那么低功耗、半高卡的设计也能节省不少机箱空间和散热成本。当然它的门槛在于软件生态和上手成本所以后面的内容我会重点讲怎么把这套工具链盘顺。2. 部署前的软硬件准备别等卡到手才手忙脚乱2.1 物理安装和NPU驱动固件很多人拿到卡后的第一步就出问题不是卡不识别就是系统重启后丢设备。我一般的习惯是先把机器断电装卡前仔细检查PCIe插槽是否兼容注意卡上是否有辅助供电接口如果电源功率吃紧建议至少配500W以上的服务器电源。装好后开机进系统用lspci命令确认系统能不能看到设备。我这边装Atlas 300V时操作系统通常是Ubuntu 20.04或22.04ARM或x86架构都行关键是安装与硬件版本匹配的NPU驱动和固件。华为昇腾社区提供了统一的软件包里面包含驱动driver、固件firmware和CANN工具链。装驱动的标准操作通常类似这样# 解压驱动包后执行 chmod x Ascend-hdk-*.run ./Ascend-hdk-*.run --install安装完成后用npu-smi info查看卡状态。如果命令能输出设备列表并且温度、功耗、HBM占用等参数都正常就说明驱动和固件已经基本到位。我曾经遇到过一次驱动装好但是固件没升导致npu-smi一直显示ERROR的情况解决办法是把固件也安装一遍记得装完重启。有个小细节如果系统里之前装过其他版本的驱动建议先用自带的卸载脚本清干净再装新版本不然很容易出现版本残留导致的算子执行异常。这个坑我踩过不止一次。2.2 CANN工具链和运行环境的搭建驱动搞定后下一步就是CANNCompute Architecture for Neural Networks它是昇腾平台上编译、开发和运行神经网络的基础软件栈地位有点像CUDA加cuDNN但层次更高包含了算子库、图编译器、推理运行时等等。CANN的版本选择建议直接看昇腾社区官方列表尽量和驱动版本对应。下载和解压后运行安装脚本./Ascend-cann-toolkit-*.run --install安装完成后设置环境变量。我习惯把下面这段写进~/.bashrcsource /usr/local/Ascend/ascend-toolkit/set_env.sh设置好环境变量后可以在Python里import acl验证ACLAscend Computing Language是否可用。ACL是CANN提供的一套C/C和Python的推理编程接口类似CUDA的runtime API但概念上稍有区别。对于YOLO这类常规模型的部署我们不需要自己从零写算子而是利用ACL加载离线模型OM做推理模型转换和加载中间还有不少细节接下来我慢慢拆。2.3 模型转换为什么是关键ONNX到OM在NVIDIA平台开发者习惯直接把PyTorch权重转成TensorRT引擎部署在昇腾平台对应的就是ONNX或TensorFlow/PyTorch模型转成OM格式Offline Model。OM是昇腾推理的统一格式里面不仅包含了权重和网络结构还做了算子调度、内存分配、图优化等提前编译工作所以推理效率更高。为什么要多做这一步转换打个比方你从网上下载了一份装修设计图原始模型但施工队需要的是带物料清单、施工步骤的施工图OM。ATCAscend Tensor Compiler工具就是做这件事的它在转换过程中会根据硬件算子的特点把网络重写、合并、裁剪让它能在昇腾芯片上高速跑起来。所以整个部署链路大致是训练好的模型权重 → 导出ONNX → 用ATC转换为OM → 编写ACL推理代码 → 后处理拿到目标框。每一步都有不少坑尤其是ONNX版本不兼容和算子不支持的问题我在后面实操部分会详细展开。3. 用Atlas 300V部署YOLO的完整实操流程3.1 准备YOLO模型从PyTorch到ONNX我在实际项目里用YOLOv5和YOLOv8都比较频繁这里用YOLOv8做示范因为Ultralytics的导出流程很顺。前期如果你要部署自己训练的模型记得先把模型训练到收敛状态然后导出ONNXyolo export modelyolov8s.pt formatonnx opset12 dynamicFalse imgsz640几个参数说明一下。opset12在昇腾上兼容性较好过高的opset版本可能会引入一些不支持的算子dynamicFalse表示固定输入尺寸如果你不想动态分辨率直接固定640可以有效避免后续ATC转换时报动态shape的错误imgsz640要根据你的训练尺寸来如果训练是1280这里就该写1280。导出后可以用onnxruntime先跑一遍确认导出的ONNX在CPU/普通GPU上输出正常。这一步很有必要因为如果ONNX本身就有问题后面的ATC转换和ACL推理都没法定位问题。有时候在导出YOLOv8时模型里包含一些比较特殊的算子比如GridSample或者全动态shape的NMS节点ATC转换时容易报不支持。我的做法是在导出时尽量去掉后处理部分也就是只导出Backbone和Head把NMS和坐标解码留在推理代码里做。这样模型更干净奥卡姆剃刀原则越简单的图越容易转。3.2 ATC模型转换关键参数与踩坑ATC工具一般位于CANN安装目录的atc/bin下或者也可以调到环境变量里。我这里给出一个常用的转换命令atc --modelyolov8s.onnx \ --framework5 \ --outputyolov8s_bs1 \ --input_shapeimages:1,3,640,640 \ --soc_versionAscend310P3 \ --output_typeFP16 \ --insert_op_confaipp.cfg几个关键参数逐个说。--framework5代表ONNX模型1是TensorFlow2是Caffe别搞混。--input_shape必须和导出ONNX时一致如果是固定的640输入这里写1,3,640,640。--soc_version要填成你的芯片型号Atlas 300V常见的是Ascend310P3如果你不确定可以用npu-smi info查看芯片型号或者在CANN安装目录里查ascend_install.info。--output_typeFP16表示网络的主计算精度设为FP16。昇腾对FP16支持得很好推理速度和精度基本能满足常规检测需求。如果你追求更低延迟可以做INT8量化技术上更复杂我建议先把FP16流程跑通再研究量化。--insert_op_confaipp.cfg是配置AIPPAI Preprocessing的它能在硬件上完成图像缩放、颜色空间转换、像素均值减法等预处理。比如YOLOv8的输入是RGB归一化通常是在模型内部用1/255完成的那么AIPP里可以只做Resize和色域转换。一个典型配置aipp_op { aipp_mode: static input_format: YUV420SP_U8 src_image_size_w: 640 src_image_size_h: 640 csc_switch: true rbuv_swap_switch: true }上面这个配置假设输入源是YUV420的图像如果你的摄像头或视频解码出来直接是BGR的Mat那就改成input_format: BGRP或者RGBP。这里很容易出错因为AIPP的色域转换和Mean/Std参数写错会导致输出框偏移或检测不到目标我建议在不熟悉的时候先不配置AIPP直接在Python侧用OpenCV做预处理等全套流程跑通再考虑把预处理下沉到硬件。3.3 编写推理代码用ACL加载OM模型模型转好之后就可以写推理代码了。昇腾的Python接口叫pyACL原生接口偏底层但封装不多很适合理解原理。我这里给一个尽量精简的示例跑通YOLOv8的完整推理流程。import acl import cv2 import numpy as np import torch # 初始化ACL acl.init() ret acl.rt.set_device(0) # 加载模型 model_path yolov8s_bs1.om model_id acl.mdl.load_from_file(model_path) # 获取模型输入输出信息 input_desc acl.mdl.create_desc() output_desc acl.mdl.create_desc() acl.mdl.get_desc(input_desc, model_id, 0) acl.mdl.get_desc(output_desc, model_id, 0) input_size acl.mdl.get_desc_size(input_desc) output_size acl.mdl.get_desc_size(output_desc) # 分配设备内存 input_buffer, input_ptr acl.rt.malloc(input_size, 2) output_buffer, output_ptr acl.rt.malloc(output_size, 2) # 准备输入数据 img cv2.imread(test.jpg) img_resized cv2.resize(img, (640, 640)) img_rgb cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB) img_norm img_rgb.astype(np.float32) / 255.0 img_np np.transpose(img_norm, (2, 0, 1))[None] # 1,3,640,640 # 拷贝数据到设备内存 acl.rt.memcpy(input_ptr, input_size, img_np.tobytes(), input_size, 2) # 执行推理 ret acl.mdl.execute(model_id, input_ptr, input_size, output_ptr, output_size) # 把输出拷回host output_np np.zeros(output_size, dtypenp.uint8) acl.rt.memcpy(output_np.tobytes(), output_size, output_ptr, output_size, 3)这段代码只展示了主干实际还有内存释放、输出shape解析、后处理解码等。要注意的是OM模型的输出通常是一个长的一维数组需要根据模型结构自己切出每个输出的维度。YOLOv8在导出ONNX时如果带Detect头输出往往是[1, 84, 8400]这种格式其中84表示4个框坐标加80个类别分数8400是640x640下各尺度锚点数总和。你需要重排成[1, 8400, 84]后再做后续处理。我自己在写推理代码时会先打印output_size和模型描述确认输出个数和每个输出的大小再写解析。这个习惯帮我排掉过很多因为维度想当然导致的bug。3.4 后处理细节坐标解码、置信度过滤和NMS拿到了模型原始输出接下来就是目标检测后处理的标准流程。对于YOLOv8这种anchor-free模型解码方式比YOLOv5简单但细节还是要留意。先看输出怎么解析。以[1, 84, 8400]为例先把维度转换为[1, 8400, 84]然后拆分前4个值框中心点xy宽度w高度h注意这些值是归一化到0~1的。第5个到第84个值80个类别的分类概率。解码时用sigmoid把分类概率压到0~1然后用阈值过滤掉低置信度的框比如0.25。剩余框按置信度排序后用NMS去掉重叠严重的框最终留下的就是检测结果。坐标缩放这里是个高频踩坑点。模型输出的x、y、w、h是相对于640x640输入图像的归一化坐标而最终要画在原图上就需要按原图宽高缩放。如果原来的图宽高比和640不一致直接用640比例缩放会导致框偏正确做法是先记录原图的缩放参数或者用letterbox方式在预处理时把原图pad到640区域后处理时再把坐标映射回去。下面给个简单的NMS实现方便演示def nms(boxes, scores, iou_threshold0.5): indices [] order scores.argsort()[::-1] while order.size 0: i order[0] indices.append(i) ious compute_iou(boxes[i], boxes[order[1:]]) mask ious iou_threshold order order[1:][mask] return indices实际生产里可以直接用cv2.dnn.NMSBoxes或者torchvision的nms函数但为了节省内存自己实现一遍对理解流程也有帮助。3.5 性能调优Batch、AIPP和内存优化跑通流程只是第一步生产部署最关心的还是吞吐量和延迟。我总结几个在Atlas 300V上调优时比较有效的办法。第一个是提高batch size。Atlas 300V处理8路视频流时如果batch1逐张推理芯片利用率其实很低。如果在预处理阶段攒够多帧拼成[N, 3, 640, 640]输入能显著提高算力利用率。我在项目里曾经把batch从1调到8吞吐量直接翻了近5倍。当然前提是后处理也要按batch处理。第二个是使用AIPP把预处理挪到硬件侧。前面提到过用AIPP可以在NPU里完成Resize、CSC、色域转换这样CPU端只需要做视频解码和内存拷贝。因为视频流场景里CPU端往往要跑多个解码器把预处理从CPU解放出来收益很大。第三个是异步推理和多线程流水线。ACL支持acl.mdl.execute_async可以在等待当前推理完成的同时CPU继续做下一轮的预处理形成流水线。我自己测试下来异步模式下单路延迟没有明显升高但多路总吞吐提升明显特别是高帧率场景。第四个是输出内存复用。推理输出、输入buffer如果频繁申请释放会有大量显存碎片和拷贝开销。官方推荐在初始化时把buffer申请好推理时只做memcpy和解析结束再统一释放。我在代码里会预先申请好两块输出内存循环使用跑48小时以上也没有内存增长问题。4. 部署中的常见问题与排查实录4.1 模型转换失败的典型错误ATC转换是问题高发区。我整理了几个常见报错和排查方向报错现象常见原因解决办法E10005: input op does not match输入名与ONNX不一致用onnx.load查看输入名写成实际名字比如imagesE40001: unsupported opONNX算子昇腾不支持换较低opset导出或用MindSpore Lite转换工具尝试E10016: out of memory模型尺寸过大或板载内存不足降低输入分辨率或切分模型E10020: batch size not supported动态shape没处理好固定input_shape不要用动态batch转换后精度异常输入输出类型不匹配检查--output_type或AIPP中Mean/Std设置碰到算子不支持时我的经验是先到昇腾社区查算子支持列表如果确实没有再看能不能用等效算子组合替代比如有的模型里用了GatherElements可以改写为等价slice方式虽然麻烦但可行性强。4.2 推理结果框不准或漏检有次我把模型在GPU上跑得好好的转到Atlas 300V上后发现小目标漏检严重。排查到最后竟然是预处理里用了不同的缩放方式GPU上用直接resize而ATC转换时AIPP里默认的resize算法是双线性插值虽然远看没啥区别但对小目标影响很大。所以如果你发现转换后精度下降先别急着怀疑量化看预处理是否一致。要检查的点包括输入尺寸模型训练时用的尺寸和推理时是否一致建议严格一致。归一化YOLOv8源码里归一化是除以255但有些模型的ONNX图里已经包含了归一化AIPP里再做一次就会导致数值范围错误。图像通道顺序RGB还是BGR转换错误会让画面偏色目标框也会明显跑偏。letterbox填充如果模型训练时使用letterbox推理时也必须用相同方式补边直接resize会打乱物体比例。这些细节如果不仔细核对往往要调试很久。我个人写了一个“预处理一致性检查”函数每次换模型时先打印输入图像给模型的像素值分布再和GPU端对比能快速定位问题。4.3 设备内存和CPU内存混用ATLAS设备上host侧和device侧内存不能直接互访所以acl.rt.malloc得到的是device内存而numpy默认在host侧两者需要acl.rt.memcpy来搬运。我见过有人直接把device buffer传给cv2处理结果报段错误其实就是内存空间搞混了。另一个常见问题是内存泄漏。ACL的Python接口虽然是封装但有些对象比如acl.mdl.create_desc在退出循环时不会自动释放必须手动调用acl.mdl.destroy_desc否则跑一晚上内存占用就爆了。我通常会在推理类里写__del__方法把所有资源汇总释放这样即使Python进程被强杀不下电的情况下也不会把卡弄成不可用状态。4.4 实际性能数据参考最后分享一组我在真实环境里测到的数据供大家参考。配置是Atlas 300V 24G输入640x640模型YOLOv8sFP16batch8CPU是Xeon Silver 4210内存32GB。指标数值单batch延迟约8~12msbatch8总吞吐约350~450 FPS功耗平均62W峰值75W连续运行72小时稳定性无掉卡、无内存泄漏需要注意实际数据会因视频流编码方式和CPU解码性能有波动。如果视频流是H.265CPU解码本身就会吃掉不少资源此时建议用硬件解码卡或者把解码任务分发到其他进程否则CPU会成为瓶颈。5. 这套流程还能怎么扩展从单卡到服务化部署5.1 多模型并发和动态batchAtlas 300V 24G的24GB内存如果只跑一个YOLOv8s确实有些浪费。生产上我更推荐用它同时加载多个模型比如一个检测模型加一个分类模型或者一个精度高的大模型和一个小模型并存大模型处理关键帧小模型处理普通帧这样能平衡延迟和准确率。ACL支持在一个进程中加载多个OM模型每个模型都有独立的model_id。调度时可以用多线程每个线程绑定一个模型或者按业务需求分配不同的batch组合。但要留意多个模型同时加载时总内存不能超过24GB否则会加载失败。我习惯先用acl.mdl.get_desc_size把每个模型的输入输出大小算一遍再估算总内存。动态batch也是生产常用的功能。ATC转换时可以用--dynamic_batch_size1,2,4,8生成动态batch的OM然后在推理时通过ACL接口设置当前batch。这样做的好处是可以在请求少时用小batch降低延迟请求多时自动切到大batch提高吞吐。但动态batch对模型内部有些限制不是所有模型都能转换成功需要实测。5.2 用MindX SDK快速搭建推理服务如果你不想直接写底层的ACL代码华为MindX SDK现在叫昇腾应用使能提供了更上层的推理接口像mxpi_modelinfer这类插件可以快速搭建一条从视频解码到目标框输出的推理流水线。它的典型用法是在自定义plugin中配置一个pipeline类似GStreamer的方式把视频帧推入插件插件自动完成解码、缩放、推理、后处理。我用MindX SDK做过一次安防项目的原型从搭建到跑通demo只花了一天比直接用ACL快很多。但它的缺点也很明显自定义后处理逻辑不如自己写代码灵活遇到特殊算法时还是要回到ACL。所以我的建议是快速验证或demo用MindX SDK正式产品如果要精细调优建议底层ACL和上层业务接口自己封装。5.3 部署形态和运维监控Atlas上生产环境运维监控也很重要。除了用npu-smi info定期查看卡状态还可以把NPU的功耗、温度、内存占用接到Prometheus里做告警。昇腾官方提供了一些监控接口比如通过CANN的acl.prof做profiling可以看到每层网络的执行时间对定位性能瓶颈很有帮助。我曾遇到过一次卡温升高导致推理速度下降的case后来给机箱加了风道温度从85度降到70度推理延迟立刻稳定下来。所以说硬件散热在AI部署里同样不能忽略。写在最后Atlas 300V 24G对熟悉GPU的人来说刚开始确实有点“水土不服”。它是一块真正的运算加速卡但它不是GPU而是昇腾NPU这在硬件形态和软件栈上都意味着全新的学习和适配。真正跑起来后你会发现它在推理场景下性价比挺高24GB大内存、低功耗、成熟的CANN工具链至少我现在手头有三个项目都稳定跑在Atlas卡上。我在第一块Atlas卡上折腾模型转换花了将近整天原因只是ONNX导出的opset版本太高。后来把流程固化下来基本半小时内就能完成一个YOLO系模型的部署。所以我建议你们在做正式部署之前先在小数据集上完整走通一遍ONNX导出、ATC转换、ACL推理和NMS后处理把每一步的“坑”摸熟了再上生产环境。如果之后你再遇到Atlas相关的部署问题欢迎回来聊聊。
企业数字化 ERP 产品动态
相关推荐
BLDC六步换相实战:霍尔信号采集、换相查表与PWM驱动 /* 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 7:17:42
金融场景下Claude协作体系:权限、脱敏与审计的工程实践 1. 金融场景下 Claude 协作体系的设计思路1.1 为什么金融行业需要一套独立的协作规范金融行业对 AI 辅助工具的诉求和普通互联网团队完全不一样。普通团队用 Claude 写写代码、改改文案,出错了顶多重来一次;但金融场景里,一段错误的合规话术、… · 2026/9/25 7:17:36
迎宾机器人排名怎么评:医院与政务大厅应把问询分流、路线引导和知识边界放在前面 医院、政务服务中心和公共办事大厅与企业展厅不同,访客进入空间后的目标通常非常明确:找科室、找窗口、问材料、问办理步骤、确认楼层或路线。现场问题数量多、表达方式不统一,同一事项还可能因为业务变化而调整。如果机器人只能播放欢迎语或… · 2026/9/25 7:49:26
PowerShell在Windows权限提升中的应用与防御检测 Windows环境下的权限提升,是我在安全评估和系统加固工作中反复要面对的话题。无论是红队演练报告里的“普通域用户拿到SYSTEM权限”,还是日常巡检发现的“某个第三方服务目录Everyone可写”,背后几乎都能看到PowerShell脚本的影子。这篇文章结… · 2026/9/25 7:49:14
网络安全应急演练实战指南:从ATTCK场景设计到复盘闭环 简介:这份文档资料聚焦网络安全应急演练,面向政府机构、企事业单位的安全管理人员、普通员工及专业应急处理人员,帮助组织建立并落地网络安全应急响应预案的培训与实战演练机制。内容围绕应急响应预案培训与演练的目的、培训要求与方式、培训… · 2026/9/25 7:49:14
OptiScaler 完整实战指南:免费替换 DLSS / FSR / XeSS 超采样,还能给游戏补帧生成 OptiScaler 完整实战指南:免费替换 DLSS / FSR / XeSS 超采样,还能给游戏补帧生成 【免费下载链接】OptiScaler OptiScaler bridges upscaling/frame gen across GPUs. Supports DLSS2/XeSS/FSR2 inputs, replaces native upscalers, enables FSR-FG/XeF… · 2026/9/25 7:49:14
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* 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 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* 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 1:00:37