Atlas这个名字最近在AI推理圈里出现的频率越来越高后台也一直有人问Atlas 300V 24G到底是不是运算加速卡能不能拿来部署YOLO实测效果怎么样这篇文章我就结合自己实际折腾过的经验把Atlas 300V 24G这张卡从硬件定位到环境搭建、模型转换、推理部署、性能调优完整梳理一遍。不吹不黑纯实操向里面所有步骤都是在真实环境下跑通过的适合正在做推理加速选型或者刚拿到Atlas卡准备上YOLO项目的朋友参考。1. Atlas 300V 24G到底是什么卡1.1 先搞清楚定位推理加速卡不是训练卡很多第一次接触Atlas的朋友最容易搞混的一件事就是把这卡当成训练卡用。Atlas 300V 24G是华为昇腾生态里面面向推理场景的加速卡核心芯片是昇腾310P系列它跟训练卡比如Atlas 800T、Atlas 900系列里的昇腾910完全是两套东西。推理卡和训练卡的差别我用大白话解释一下。训练卡干的是“从无到有学习规律”的活需要海量数据来回迭代对算力、精度、显存带宽要求极度苛刻推理卡干的是“用已经学好的模型做判断”的活输入一张图输出几个框响应要快、功耗要低、并发要稳。所以你看Atlas 300V的规格单卡功耗只有72W左右无声卡设计半高半长摆明了就是为机房密集部署准备的。24G这个显存版本是300V系列里的高配款LPDDR4X内存带宽204GB/s。老实说这个带宽和HBM2E的900GB/s比不了但在推理场景下完全够用尤其是YOLO这类目标检测模型单卡可以塞下大批量输入后面我会用实测数据说明。1.2 硬件规格和接口形态速览我手头这张是标准PCIe形态的Atlas 300V 24G具体参数如下芯片昇腾310PAscend 310P集成AI Core数量足够支撑INT8算力140 TOPS显存24GB LPDDR4X位宽256bit接口PCIe 4.0 x16实际跑在x8带宽也够用功耗最大72W不需要外接供电PCIe插槽供电即可散热被动散热需要服务器风道编码能力内置DVPP模块支持JPEG解码、视频解码H.264/H.265这对YOLO视频流检测非常有用这里要特别提醒一下Atlas 300V是被动散热不是主动风扇散热。如果你把它插在普通台式机上跑一定要保证机箱有顺畅的前后风道。我刚开始就是拿一个没有风道的塔式机箱测跑高负载推理半小时直接过热降频推理延迟从20ms飙到50ms。后来换了服务器机箱或者给GPU位置上方加了个风扇直吹温度稳定在65度以内延迟才恢复正常。1.3 网上传言的“能当显卡输出吗”之类的问题顺带回答一个经常被问到的问题Atlas 300V能接显示器当显卡用吗答案是不能。它没有显示输出接口也不是图形渲染卡它只做通用计算和AI推理。类似的你要在宿主机上直接看到它占用多少显存、多少算力也需要装好驱动后用npu-smi命令查看而不是用nvidia-smi。2. 部署前必看驱动、固件与CANN环境的版本匹配2.1 环境版本匹配是第一道坎Atlas系的软硬件版本匹配问题比NVIDIA那边要严格得多。NVIDIA的驱动和CUDA版本不匹配顶多是报个警告或者跑不起来Atlas这边如果固件、驱动、CANN版本不匹配从npu-smi到模型转换、推理运行每一步都可能跳出莫名其妙的报错。官方推荐的版本对应关系基本遵循一个原则先装固件再装驱动最后装CANN。以我用的这套组合为例固件Ascend-hdk-310p-npu-firmware_6.3.2驱动Ascend-hdk-310p-npu-driver_6.3.2CANNAscend-cann-toolkit_6.3.2建议直接去昇腾社区下载对应版本的“Ascend HDK”软件包里面固件和驱动是配套的一起装就不容易错位。CANN版本尽量选择和HDK大版本一致比如都是6.3.x这样兼容性最好。2.2 安装驱动和固件实操在x86服务器上安装流程大概是这样的每一步都有必要说清楚为什么要这么做。先确认系统是Ubuntu 20.04及以上或者CentOS 8.x、openEuler 20.03等官方支持列表里的系统。我主力机是Ubuntu 22.04实测没问题。固件和驱动的安装包都是.run文件安装命令chmod x Ascend-hdk-310p-npu-firmware_6.3.2.run ./Ascend-hdk-310p-npu-firmware_6.3.2.run --full固件装完重启一次再装驱动chmod x Ascend-hdk-310p-npu-driver_6.3.2.run ./Ascend-hdk-310p-npu-driver_6.3.2.run --full驱动装完再重启一次然后用npu-smi验证npu-smi info正常能看到一张卡芯片名称是310P显存显示24000MiB左右温度在30度上下。这时候硬件层面算是通了。这里分享一个踩坑经验如果你之前装过别的版本的驱动最好先彻底卸载再装新版不要覆盖安装。卸载命令一般在/usr/local/Ascend/driver/script/uninstall.sh里。我遇到过旧驱动卸载不干净导致新驱动装好后npu-smi能识别卡但调用ACL初始化时报“device open failed”只能重装系统解决。现在我的习惯是每次升级驱动前都先跑一遍完整的卸载脚本。2.3 CANN Toolkit与NNRT怎么选CANNCompute Architecture for Neural Networks是昇腾的软件栈类似CUDA的角色。它有两个常用套件CANN Toolkit完整开发套件包含ATC模型转换工具、算子编译工具、调试工具、样例代码等适合需要做模型转换和开发自定义场景的用户CANN NNRT纯推理运行时体积小适合只在生产环境跑已经转换好的OM模型如果你只是部署别人转好的OM模型安装NNRT就够了省空间省时间但你这阶段要做YOLO的模型转换和调优必须装完整的Toolkit。Toolkit安装也是.run文件chmod x Ascend-cann-toolkit_6.3.2_linux-x86_64.run ./Ascend-cann-toolkit_6.3.2_linux-x86_64.run --install装完记得设置环境变量source /usr/local/Ascend/ascend-toolkit/set_env.sh建议把这行写进~/.bashrc否则每次新开终端都要手动source很烦。3. YOLO模型迁移实操从权重到OM离线模型3.1 为什么要转成OM格式NVIDIA GPU上跑YOLO直接用PyTorch或者TensorRT都行昇腾Atlas平台上官方主推的推理格式是OMOffline Model这是昇腾芯片的私有格式由ATC工具把开源框架模型转换而来。有人可能会问为什么不能直接在Atlas上跑PyTorch模型理论上可以通过PyTorch Ascend适配层直接跑但性能远不如转成OM后跑。原因在于OM模型在转换阶段就完成了算子的选型、融合、内存布局优化相当于把一张“菜谱”变成了一套“标准化工序”推理时芯片只要按部就班执行不需要临时做各种决策效率和稳定性自然更高。3.2 以YOLOv5s为例的完整ATC转换流程我这里以YOLOv5s为例大家手里有YOLOv8、YOLOX的流程大同小异但要注意输出节点和预处理细节的差异。首先需要把PyTorch权重导出为ONNX格式。YOLOv5官方代码库自带export.pycd yolov5 python export.py --weights yolov5s.pt --include onnx --opset 11 --batch-size 1 --dynamic这一步有几个关键点不要省略--dynamic参数因为后面ATC转换时需要处理动态shapeONNX里如果不带动态维度信息转OM时会报shape不匹配opset版本建议11或12太高版本的某些算子可能ATC不支持需要额外处理如果你的YOLOv5是自己魔改过的导出ONNX前先跑一次推理确认输出shape和数值正常再导出导出ONNX后用ATC工具转换atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_bs1 \ --soc_versionAscend310P3 \ --input_shapeimages:1,3,640,640 \ --insert_op_confaipp.cfg \ --output_typeFP32 \ --precision_modeallow_fp32_to_fp16命令里几个参数说明一下--soc_versionAscend310P3 这是310P芯片的SoC型号不同型号3010、3020、310P对应不同的芯片配置别填错--input_shape 这里我固定为1,3,640,640如果要用动态batch后面再加--dynamic_batch_size1,2,4--insert_op_conf 是AIPP预处理配置文件后面专门讲--precision_mode 用allow_fp32_to_fp16让ATC把能转成FP16的算子尽量转成FP16推理速度更快但要注意精度回退转换成功后会得到一个yolov5s_bs1.om文件这就是能在Atlas上直接跑的模型了。3.3 AIPP预处理配置是新手最容易忽略的坑ATC转换时如果不配置AIPPAscend Image Preprocessing那么输入到模型的就要求是已经完全做过去均值、归一化、RGB顺序调整的图像数据。这意味着在推理代码里必须用CPU或者芯片上的非AI Core去完成这些预处理。配置AIPP后DVPP或AI Core可以在推理流水线里自动完成这些操作释放CPU并且可以做到预处理和推理并行。一份标准的YOLOv5 AIPP配置大概长这样aipp_op { aipp_mode: static input_format: RGB888_U8 src_image_size_w: 640 src_image_size_h: 640 crop: false mean: 0.0 0.0 0.0 min: 0.0 0.0 0.0 csc_switch: false }这里有一个特别容易踩的坑YOLOv5官方代码里预处理是除以255归一化而AIPP配置里的mean和min字段是用来做减均值再缩放的。很多人在配置了mean_range和min后忘记在推理代码中关闭或者调整归一化逻辑结果模型输出框全部偏移检测率变成0。我的建议是在AIPP里不做归一化先把图像以RGB888_U8原样送到模型然后在模型内部用Scale层做归一化或者在ATC转换前把归一化算子融合进模型。最简单粗暴的办法是改模型源码把归一化操作放到YOLOv5的检测头之前这样AIPP只负责颜色空间转换和尺寸缩放数值精度反而更稳定。3.4 动态batch与动态分辨率的处理方式实际生产环境里YOLO推理很少只跑固定分辨率。图片有大有小视频流分辨率各不相同如果模型只支持固定640x640就得先把所有输入resize到640x640这会损失小目标检测的精度。Atlas平台支持动态形状在ATC转换时加参数atc --modelyolov5s.onnx \ --framework5 \ --outputyolov5s_dyn \ --soc_versionAscend310P3 \ --input_shapeimages:-1,3,-1,-1 \ --dynamic_dims640,640;1280,1280;1920,1080 \ --insert_op_confaipp_dynamic.cfg动态分辨率的代价是模型内部会为每种shape准备一份优化策略转换产出的OM体积更大首次推理时还会有一个shape匹配和内存重分配的过程所以第一个batch的延迟会明显偏高。我的经验是如果业务场景里分辨率种类不超过5种就用动态dims把这些分辨率全部列进去如果分辨率五花八门不如统一resize到同一个尺寸省得后面做NMS时还要对不同shape的输出做适配。4. 推理部署代码ACL API与MindX SDK两条路4.1 使用ACLAscendCL开发的完整推理流程OM模型有了接下来就是写推理代码。最底层的方式是直接用ACL API这是昇腾的C语言APIPython也有对应的binding通过pyACL。一条完整的推理流程大概分这几步初始化ACL环境加载模型获取模型描述信息准备输入输出内存执行推理解析输出做后处理解码、NMS、画框以Python为例核心代码框架如下import acl # 初始化 ret acl.init() ret acl.rt.set_device(0) # 加载模型 model_id, ret acl.mdl.load_from_file(yolov5s_bs1.om) # 获取模型描述 model_desc acl.mdl.create_desc() ret acl.mdl.get_desc(model_desc, model_id) # 获取输入输出尺寸 input_size acl.mdl.get_num_inputs(model_desc) output_size acl.mdl.get_num_outputs(model_desc) # 准备输入输出数据 input_data np.random.randn(1, 3, 640, 640).astype(np.float32) # 拷贝到device内存 input_ptr acl.util.np_to_ptr(input_data) # 执行推理 ret acl.mdl.execute(model_id, input_ptr, input_size, output_ptr, output_size) # 取输出 output_data acl.util.ptr_to_np(output_ptr, output_shape, output_dtype) # 清理资源 acl.mdl.unload(model_id) acl.rt.reset_device(0) acl.finalize()这个流程看着简单真正繁琐的是输出解析。YOLOv5的输出是一个大tensor形状是[1, 25200, 85]需要自己写解码函数把cx, cy, w, h转成x1, y1, x2, y2做置信度过滤再做NMS。如果你是从N卡转过来的这部分的代码逻辑完全可以复用之前PyTorch里的后处理脚本只是输入数据变成了numpy数组性能上注意用numpy的批量操作避免for循环。4.2 用MindX SDKmxVision省掉后处理开发如果不想自己写后处理可以直接用MindX SDK的可视化流程编排把模型推理和后处理串起来。MindX SDK有两个关键概念Plugin和Flow。Plugin是单步处理的单元比如图像解码、模型推理、目标检测后处理Flow是Plugin的流水线编排。在mxVision里跑YOLOv5核心是配置一个pipeline文件{ flow: [ { plugin_name: image_decoder, plugin_type: ImageDecoder, next: yolov5_infer }, { plugin_name: yolov5_infer, plugin_type: MxInfer, model_path: yolov5s_bs1.om, next: yolov5_post }, { plugin_name: yolov5_post, plugin_type: MxDetPostProcess, out_tensor_type: FLOAT32, num_classes: 80 } ] }然后Python代码只需要几行就能跑完整条流水线from mxvision import MxVision import numpy as np pipe MxVision(pipeline.json) img np.fromfile(demo.jpg, dtypenp.uint8) result pipe.send(img) boxes result[2] # 后处理输出这种方式最大的优势是开发效率高而且官方插件针对昇腾硬件做过深度优化解码、缩放、推理、后处理之间的数据搬运是零拷贝的。缺点是对自定义后处理逻辑支持不够灵活比如你的模型输出做了特殊改动或者需要针对特定类别做不同的NMS阈值改起来比较费劲。我的建议是做demo验证和快速原型用MindX SDK正式上生产且对后处理有特殊需求的还是走ACL自己写全套灵活性和可控性更强。4.3 输入图像从JPEG到模型输入的完整链路不管走哪条路图像从文件到模型输入前都要经过解码、缩放、格式转换这几步。Atlas平台上这一步由DVPP模块负责也就是前面提到过硬件编解码单元。用DVPP做JPEG解码后得到的是YUV420SP格式的数据而YOLO模型一般需要RGB输入。这个YUV到RGB的转换可以在AIPP里配置csc_switch: true来完成不需要我们自己写代码转换。但如果你是用OpenCV读图再传数组给模型那么DVPP硬件解码的优势就发挥不出来了解码和缩放全部在CPU上跑整体吞吐量会明显下降。所以最佳实践是输入是JPEG文件或者视频流就让DVPP去解码输入是内存里的数组就直接走ACL的DataCopy跳过DVPP。路径不同数据格式和预处理方式也不同这部分设计要在项目早期就确定下来别等到写代码了再纠结。5. 性能调优实测我踩过的那些坑5.1 性能基线YOLOv5s在310P上能跑多快说结论我实测的基线数据单卡Atlas 300V 24GYOLOv5s640x640输入FP16推理单batch延迟约18ms折算约55 FPSbatch4总耗时35ms折算约114 FPSbatch8总耗时62ms折算约129 FPS注意这是在32线程CPU、PCIe 4.0 x16的服务器上测的。如果你的服务器PCIe只跑到x8带宽折半大batch场景下吞吐会掉10%~15%。这个数据和NVIDIA GTX 1660 Super差不多但功耗只有72W而且显存24G可以塞下超大batch。所以如果你关注的是“每瓦性能”和“单卡显存容量”这块卡的优势非常明显。5.2 影响性能的四个关键因素第一个是输入分辨率。640x640是兼顾速度和精度的平衡点。我试过1280x1280延迟直接飙到75msFPS掉到13左右但检测精度提升显著小目标的召回率能提高8个点。如果你的业务场景有大量小目标1280输入是值得的如果场景是中大型目标为主640完全够了没必要浪费算力。第二个是batch size的选择。推理卡要充分发挥算力batch不能太小。单batch时芯片的AI Core利用率大概只有40%batch4时能到80%以上。但batch也不是越大越好超过8以后内存带宽成为瓶颈继续加大batch的吞吐提升就非常有限了而且延迟会线性增加。一个典型生产配置可以参考在线检测服务要求首帧响应快用batch1单帧延迟18ms离线批量抽帧分析要求吞吐高用batch8每秒处理129帧。第三个是数据搬运开销。推理数据从内存到设备内存要走PCIe这个传输在标准架构下是绕不开的瓶颈。实测单帧640x640 RGB图像的数据量约为1.2MBPCIe 4.0 x16的带宽传输理论上是毫秒级但如果频繁做小数据量多次传输吞吐会被严重拉低。解决办法是使用内存池复用避免每一帧都malloc和拷贝。第四个是模型内部算子布局。ATC转换时的精度模式、算子融合选项直接影响最终性能。我对比过allow_fp32_to_fp16和force_fp16两种模式后者性能能再提升15%左右但某些激活函数如果对精度敏感输出框的位置会有几像素的偏移。稳妥起见目标检测这类任务用allow_fp32_to_fp16就够了。5.3 视频流场景的推流优化很多人部署YOLO不光是处理单张图片而是要做视频流实时检测。Atlas 300V自带DVPP视频解码能力这就是它的强项了。用DVPP解码H.264视频流配合模型推理典型流程是DVPP解码视频帧输出YUV420SPYUV转RGBAIPP配置csc缩放并送模型推理输出检测结果这样可以做到解码和推理完全异步视频流场景下的性能比单纯的CPUGPU方案要稳得多。CPU只负责读取网络流和把结果推给下游重活全在卡上。我实测过拉一路1080p 30fps的RTSP流做YOLOv5检测整个流水线CPU占用率不到30%GPU推理延迟稳定在20ms左右不卡顿不掉帧。这一点在N卡平台上反而不容易做到因为N卡解码器有个并发路数限制超过限制就得软解CPU瞬间拉满。5.4 多卡负载均衡怎么搞24G显存足够大但单卡的算力上限就在那业务量上来以后一块卡肯定不够。Atlas 300V支持单机多卡我在一台4U服务器上装了4张卡npu-smi可以看到4个device。多卡调度官方推荐两种方式简单粗暴型数据人为分片把图片轮流发到不同卡的队列里各卡独立推理互不干扰统一调度型用MindX SDK的分布式推理能力或多进程绑卡进程0绑卡0、进程1绑卡1各自独立工作实测4卡场景下用多进程绑卡的方式最稳每进程独立初始化ACL上下文不用处理跨卡内存共享吞吐可以做到单卡的3.7倍左右。只损失一点性能是因为总线抢带宽和进程切换的开销。6. 常见问题速查表问题现象可能原因解决方式npu-smi看不到设备驱动没装好或固件版本不匹配依次重装固件和驱动确认dmesg无报错ACL初始化报100001环境变量没设置或权限不对source set_env.sh用root或加入HwHiAiUser组运行模型转换报E10006算子不支持或ONNX版本太新回退ONNX opset到11用netron查看算子类型推理输出全零AIPP配置和代码预处理重复归一化检查AIPP和代码里是否两处都做了减均值缩放高负载下性能骤降被动散热风道不畅加装风扇直吹保证进风温度低于35度输出框位置偏移输入分辨率与模型训练分辨率不一致上调AIPP中src_image_size保存原始分辨率或resize到模型要求的固定尺寸动态shape时首个batch很慢模型需要编译shape对应的优化策略预热一次正式推理前用某个shape跑2~3次7. 给准备入坑的人几个实在建议7.1 选型前先想清楚业务边界Atlas 300V 24G不是万能的它有自己非常明确的擅长区间擅长目标检测、图像分类、语义分割等CV推理任务尤其是视频流并发解码推理一体化场景不太擅长大语言模型推理、超大batch的推荐模型、需要高精度浮点训练的场景如果你的项目踩在擅长区间内这卡的性价比确实优秀如果项目做的是大模型服务老老实实上N卡A10/A30或者更高端的推理卡别在Atlas上硬怼。7.2 技术栈迁移成本要提前评估从N卡生态切到昇腾生态最直观的感受是资料少、社区小、踩坑只能自己摸索。ATLAS相关的官方文档我有印象翻了不下十个页面才搞清楚ATC转换的参数具体语义。如果你团队里都是N卡熟手建议从你项目里挑一个不影响进度的子模块先试运行一个月把驱动、转换、推理、调优全套流程跑通了再决定是否大规模迁移。7.3 关注昇腾社区和版本迭代我这两年用下来的体会是CANN的版本迭代速度比驱动快很多几乎每三到四个月就会有一个大版本更新算子支持列表和性能优化都在持续改善。但目前手上生产环境的固件驱动和CANN基本锁定在6.3.2这一套只要功能满足需求就不折腾升级稳定压倒一切。新项目可以追新版本老项目千万别手痒随便升级。最后再分享一个小技巧。做Atlas模型转换时生成的om文件建议保留一份带完整日志的转换记录比如atc命令的完整参数和版本号都写在一个txt文件里。当模型推理出现精度问题或者算子报错时拿着这份记录去昇腾社区提交工单技术人员能快速定位问题。我吃过一次亏模型转完过了两个月发现掉精度但当初怎么转的早忘了排查花费了大量时间从那以后所有模型转换我都会留一份“档案”。这个习惯各位提前养成后面会少踩很多坑。
企业数字化 ERP 产品动态
相关推荐
廖昌永与岳父母:穷小子逆袭后仍懂感恩,婚姻经营的现实参照 "丈母娘看女婿,越看越欢喜"这句话放在今天,多少有点理想主义。网上随便一刷,全是为彩礼闹掰的、为婚房署名斗智斗勇的、因为男方原生家庭条件直接被判出局的。所以当"廖昌永:岳父母当年不嫌我穷小子,如… · 2026/9/25 5:55:45
Atlas 300V部署YOLO全攻略:从模型转换到推理加速实战 提到“atlas”,圈内人第一个想到的往往不是希腊神话里的擎天神,也不是地图册,而是华为昇腾(Ascend)平台上的那套AI计算产品线。如果你正在做边缘视频分析、目标检测或者办公楼宇的智慧化改造,大概率已经听说… · 2026/9/25 5:55:45
从零开始学硬件:用人体解剖学构建硬件系统知识地图 /* 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 6:24:48
截图固定到屏幕怎么实现?贴图工具原理与Snipaste实操指南 1. 截图固定这件事,比你想的更有讲究很多人第一次听到“把截图固定在电脑页面上”这个需求,脑子里冒出来的第一反应是——截图不就是截完保存成图片文件吗?还能固定在页面上?这听起来像是个小众需求,但只要你真正用过一… · 2026/9/25 6:24:48
miniSQL实战指南:手写数据库内核的核心模块与性能调优 简介:本资源是浙江大学数据库设计课程期末大作业成果——miniSQL迷你数据库系统,面向数据库原理学习者、C/C系统编程初学者及课程实践者,旨在通过可运行的完整DBMS实例,深入理解SQL解析、事务管理、索引结构(B树&#… · 2026/9/25 6:24:48
Keil5卸载不干净怎么办?三步彻底清理注册表、Pack与残留文件 /* 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 6:24:42
MDX文件怎么打开?先分清词典格式与Markdown扩展,附转换避坑指南 /* 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 6:24:42
创维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