DeepGEMM实战:FP8与MoE分组GEMM的CUDA性能优化
1. 从一块显卡的算力焦虑说起如果你最近在折腾大模型推理或者训练大概率会遇到一个很现实的问题明明买的是同一块卡别人跑出来的吞吐量就是比你高一大截。尤其是做MoE架构推理的时候专家路由带来的不规则计算模式让GPU的利用率经常掉到让人心疼的程度。我前段时间帮一个朋友排查他本地部署的推理服务同样的模型、同样的硬件他的token生成速度只有参考实现的一半左右最后定位下来问题就出在矩阵乘法的底层实现上——他用的还是通用库里的默认kernel没有针对这种细粒度、不规则形状的GEMM做专门优化。这就是DeepGEMM这类工作存在的意义。它本质上是一个专门为现代大模型计算特征设计的高性能矩阵乘法库核心聚焦在FP8精度下的GEMM实现同时针对MoE混合专家场景里的分组矩阵乘法做了深度优化。说白了它解决的就是怎么让GPU在跑大模型时少浪费算力这个问题。适合谁来参考如果你在做推理引擎开发、模型部署优化或者单纯对CUDA kernel编写感兴趣想看看工业级的GEMM是怎么一层层抠性能的那这篇内容应该能给你不少可以直接抄作业的东西。我下面会从设计思路、核心细节、实操流程、问题排查几个角度把这个项目的技术脉络拆开讲。需要提前说明的是部分实现细节是基于我对这类高性能计算库的常见工程实践做的合理补充具体以实际代码为准。2. 整体设计思路与方案选型拆解2.1 为什么偏偏盯上FP8和MoE这两个点要理解DeepGEMM的设计取向得先看清楚当前大模型计算的几个现实约束。第一模型参数量还在涨但显存带宽和容量的增长明显跟不上于是低精度计算成了必选项。FP8相比FP16能把显存占用和带宽需求直接砍半在Hopper架构上还有专门的Tensor Core支持理论算力翻倍。但FP8的坑在于动态范围窄量化误差控制不好模型效果会明显掉点。所以一个靠谱的FP8 GEMM实现必须把缩放因子scale factor的管理做到极致。第二MoE架构越来越流行但它带来的计算模式非常碎。传统GEMM处理的是规整的大矩阵而MoE里每个token会被路由到不同的专家导致每个专家实际处理的token数量参差不齐矩阵形状又小又不规则。如果还用普通GEMM去逐个算kernel启动开销和尾效应会把收益吃干净。DeepGEMM针对这个场景做了分组GEMM把多个小矩阵乘法合并到一次kernel调用里用连续的内存布局和统一的调度来摊薄开销。这两个点结合起来就是DeepGEMM的核心定位在FP8精度下同时把规整GEMM和不规整分组GEMM都做到接近硬件理论峰值。这个目标听起来简单做起来要在指令调度、共享内存管理、流水线编排上抠无数细节。2.2 轻量级框架加JIT编译的组合拳DeepGEMM在工程实现上做了一个很有意思的选择它没有搞一个庞大的模板库而是采用轻量级框架加运行时JIT编译的路线。这个决策背后的逻辑值得细说。传统的高性能GEMM库比如某些经典实现往往通过C模板在编译期生成大量特化版本针对不同的矩阵形状、数据类型、转置组合各编译一份。好处是运行时零开销坏处是编译时间爆炸而且二进制体积巨大新增一种配置就要重新编译整个库。DeepGEMM反其道而行核心kernel用CUDA C写成相对精简的形式然后在运行时根据实际的矩阵参数M、N、K、分组数等动态编译出最适合的那一版。这样做的好处很直接编译速度快、代码量小、易于针对新硬件快速适配。你换一张新卡不需要等漫长的模板实例化JIT在首次调用时编译一次后续直接复用缓存。代价是首次调用有编译延迟但对于推理服务这种长期运行的场景这点开销完全可以接受。而且JIT还能根据运行时的实际形状做更精细的调优比编译期拍脑袋定的参数更准。注意JIT编译依赖本地的CUDA工具链部署环境里必须保证nvcc或者对应的运行时编译组件可用否则会退回到未优化路径或者直接报错。这一点在容器化部署时特别容易踩坑。2.3 统一抽象带来的维护优势另一个设计上的取舍是统一抽象。DeepGEMM把规整GEMM和分组GEMM在接口层面做了统一底层共享大量的调度逻辑和内存管理代码。这样做的收益是维护成本低——修一个bug或者加一个优化两个场景都能受益。但挑战在于分组GEMM的形状不规则性会渗透到调度层需要在抽象里留出足够的灵活性。我的理解是这种设计特别适合快速迭代的阶段。大模型硬件和算法都在飞速变化今天优化的形状明天可能就变了一个笨重但完备的库反而跟不上节奏。轻量加JIT的组合让整个项目能保持较小的代码基数同时具备快速响应新需求的能力。这也是为什么我觉得它值得研究——它代表了一种够用就好、快速演进的工程哲学。3. 核心细节解析与实操要点3.1 FP8 GEMM的缩放因子管理FP8计算最核心的难点不是矩阵乘法本身而是如何管理缩放因子。FP8的表示范围有限直接拿原始浮点数转过去要么溢出要么精度损失严重。常见做法是分块量化把矩阵切成若干块每块算一个缩放因子计算时先把FP8数据反量化回高精度再累加。DeepGEMM在这块的处理思路是块级缩放block-wise scaling。具体来说A矩阵和B矩阵都按一定粒度比如128x128或者1x128划分每个块对应一个缩放因子。计算时Tensor Core直接吃FP8数据做乘加累加器保持FP32精度最后再乘上对应的缩放因子组合。这样做既利用了FP8的高吞吐又通过分块把量化误差控制在可接受范围。实操中需要特别注意缩放因子的布局。如果缩放因子在内存里的排布和矩阵块的遍历顺序不匹配会引入大量非合并访问性能直接腰斩。DeepGEMM在这一点上做了专门的内存布局设计保证缩放因子的读取也是连续高效的。缩放粒度精度表现性能开销适用场景逐张量最差最低对精度不敏感的场景1x128中等中等常规推理128x128较好较高精度要求高的推理细粒度分块最好最高训练或敏感任务3.2 分组GEMM的调度策略分组GEMM要解决的核心问题是如何把多个形状不一的小矩阵乘法高效地塞进一次kernel执行。最朴素的做法是循环调用普通GEMM但每次调用的启动开销和尾部空闲会累积成巨大的浪费。DeepGEMM采用的策略是把分组信息编码进一个统一的调度空间。具体来说它会把所有分组的M维度拼成一个大的逻辑维度N和K维度则按分组对齐。kernel内部通过一个映射表把逻辑索引翻译回具体的分组和块坐标。这样做的关键收益是所有分组的计算可以共享同一套流水线Tensor Core的利用率不会因为某个分组太小而掉下来。调度上还有一个细节是负载均衡。不同分组的实际计算量可能差很多如果简单地按顺序分配线程块会出现有的SM忙死、有的SM闲死的情况。DeepGEMM在调度时会把计算量大的分组优先分配或者采用更细粒度的任务切分让每个SM都能拿到相对均衡的工作量。实操心得调试分组GEMM时建议先把分组数设为1确认基础GEMM路径正确再逐步增加分组数。分组数一多索引映射出错的话现象往往是结果部分正确部分错误非常难定位。3.3 共享内存与流水线编排高性能GEMM的本质是让数据搬运和计算重叠起来。Tensor Core算得再快如果数据供不上照样要停下来等。DeepGEMM在这块的编排非常讲究。它采用了多级流水线的设计全局内存到共享内存的搬运通过异步拷贝指令、共享内存到寄存器的加载、Tensor Core的计算这三者尽量重叠。具体实现上会预先分配多块共享内存缓冲区形成生产者-消费者队列。异步拷贝把下一块数据搬进来的时候Tensor Core正在算当前块寄存器加载在为再下一块做准备。共享内存的大小是硬约束。Hopper架构上每个SM有228KB的共享内存要在这有限的池子里放下A块、B块、缩放因子、以及流水线缓冲需要精打细算。DeepGEMM会根据实际的块大小动态计算共享内存需求如果超了就得缩小块尺寸或者减少流水线级数这中间的权衡直接影响最终性能。// 流水线级数配置的示意逻辑非实际代码 // 根据共享内存容量和块大小反推最大流水线级数 int max_smem get_smem_capacity(); int per_stage a_block_bytes b_block_bytes scale_bytes; int stages max_smem / per_stage; stages min(stages, MAX_PIPELINE_STAGES);3.4 指令级优化的几个关键点到了指令层面还有一堆细节决定成败。比如Tensor Core指令的选择Hopper上有不同形状的MMA指令选哪个取决于块大小和数据类型。再比如寄存器压力控制累加器占用的寄存器数量直接决定了能开多少线程、能放多大的块。寄存器用超了会溢出到本地内存性能断崖式下跌。还有一个容易被忽视的点是边界处理。矩阵尺寸往往不是块大小的整数倍最后一行一列需要特殊处理。DeepGEMM在边界处理上尽量用谓词执行而不是分支跳转避免线程束发散。这些细节单看都很小但累积起来对最终性能的影响可能超过20%。4. 实操过程与核心环节实现4.1 环境准备与依赖检查要跑起来DeepGEMM第一步是把环境理顺。它依赖CUDA工具链而且对版本有要求——FP8相关的指令需要较新的架构支持。我建议按下面的顺序检查确认GPU架构。FP8 Tensor Core在较新的数据中心级GPU上才有完整支持消费级卡可能缺指令或者性能打折。检查CUDA版本。太老的版本不支持FP8数据类型和对应的MMA指令编译会直接失败。确认Python环境如果走Python接口。版本不匹配会导致JIT编译出来的扩展加载失败。验证nvcc可用。JIT编译需要本地有完整的编译工具链只有运行时库是不够的。# 检查GPU架构和CUDA版本 nvidia-smi nvcc --version # 确认PyTorch能识别到GPU python -c import torch; print(torch.cuda.get_device_capability())注意容器环境里经常出现宿主机CUDA版本和容器内不一致的情况导致JIT编译时找不到匹配的架构。建议在容器内也装一份完整的CUDA工具链而不是只挂载运行时库。4.2 基础GEMM的调用与验证环境就绪后先跑一个最基础的GEMM验证正确性。这一步的目的是排除环境问题确认kernel能正常编译和执行。调用流程大致是准备FP8格式的输入矩阵和对应的缩放因子指定输出形状调用库的接口然后和参考实现比如用高精度算一遍再量化对比结果。对比时不要只看最终输出的数值还要看缩放因子的计算结果是否正确因为缩放因子错了会导致整体数值偏移。# 伪代码示意展示调用逻辑 import deep_gemm # 准备FP8输入和缩放因子 a_fp8, a_scale quantize_to_fp8(a_fp16, block_size128) b_fp8, b_scale quantize_to_fp8(b_fp16, block_size128) # 调用GEMM c deep_gemm.gemm_fp8_fp8_bf16(a_fp8, b_fp8, a_scale, b_scale) # 与参考实现对比 c_ref a_fp16 b_fp16 assert torch.allclose(c, c_ref, rtol1e-2, atol1e-2)验证通过后再逐步增加矩阵尺寸观察性能曲线的变化。小矩阵看启动开销大矩阵看吞吐是否接近理论峰值。4.3 分组GEMM的配置与调优分组GEMM的调用比普通GEMM复杂一些需要额外提供分组信息。典型流程是根据路由结果统计每个专家实际分到的token数量得到分组大小数组。把token按专家分组重排形成连续的输入布局。准备每个分组的缩放因子。调用分组GEMM接口传入分组大小和对应的数据指针。把输出按原始顺序还原。调优的关键在于分组大小的分布。如果分组之间大小差异极大负载均衡就成了主要矛盾。我实测下来当最大分组和最小分组的比例超过10:1时简单的顺序调度就会明显掉性能这时候需要启用更细粒度的任务切分。分组大小比例推荐调度策略预期效率1:1 到 3:1顺序调度90%以上3:1 到 10:1按大小排序调度80%左右10:1 以上细粒度切分70%左右4.4 性能剖析与瓶颈定位跑通之后下一步是搞清楚性能瓶颈在哪。我常用的手段是用性能分析工具抓取kernel执行时间然后看几个关键指标Tensor Core利用率如果这个值低说明计算单元在等数据问题出在内存搬运或者流水线编排上。共享内存带宽如果共享内存访问成为瓶颈可能需要调整块大小或者数据布局。寄存器溢出情况如果看到本地内存访问量很大说明寄存器压力过大要缩小块尺寸。SM占用率占用率太低可能是共享内存或寄存器限制了并发线程块数量。定位到瓶颈后调整方向就很明确了。内存瓶颈就优化搬运和流水线计算瓶颈就检查指令选择调度瓶颈就调整任务分配。这个过程往往需要反复迭代每次改一个变量观察性能变化。5. 常见问题与排查技巧实录5.1 编译失败与架构不匹配这是最常见的问题现象是首次调用时报编译错误或者编译出来的kernel跑起来结果不对。根因通常是JIT编译时指定的GPU架构和实际运行的不一致。排查思路先确认torch.cuda.get_device_capability()返回的架构号然后检查编译日志里用的架构参数是否匹配。如果是在容器里还要确认容器内的CUDA版本和宿主机驱动兼容。避坑技巧可以在环境变量里显式指定目标架构避免JIT自动探测出错。多卡异构的环境尤其要注意不同架构的卡需要分别编译。5.2 数值精度异常FP8计算出现精度问题表现可能是输出全零、数值爆炸、或者和参考实现偏差过大。按下面的顺序排查检查缩放因子是否计算正确。缩放因子为0或者无穷大会直接导致输出异常。检查输入数据的量化范围。如果原始数据里有超出FP8表示范围的极值量化后会失真。检查累加器精度。累加过程必须用FP32用FP16累加会在大K维度下累积误差。检查缩放因子的应用顺序。是先反量化再累加还是累加后再乘缩放顺序错了结果完全不同。5.3 性能不达预期跑出来的性能远低于预期可能的原因和对应排查方向现象可能原因排查方向小矩阵性能差启动开销占比高看kernel启动时间考虑合并调用大矩阵性能差内存带宽瓶颈看共享内存利用率和全局内存访问模式分组场景性能差负载不均衡看各SM的实际工作量分布首次调用慢JIT编译延迟确认编译缓存是否生效整体都慢架构不匹配确认编译目标架构和实际GPU一致5.4 内存访问模式问题非合并访问是性能杀手但在GEMM里往往很隐蔽。比如缩放因子的读取如果布局没设计好每个线程读一个分散的地址带宽利用率会惨不忍睹。排查方法是用性能分析工具看全局内存的访问效率如果远低于理论带宽就要检查数据布局。常见的优化手段包括调整缩放因子的存储顺序使其和块遍历顺序一致、用向量化加载指令、预取数据到共享内存等。实操心得我踩过最坑的一次是缩放因子用了行优先存储但kernel按列优先遍历结果性能只有预期的一半。改成匹配的布局后直接翻倍。这种问题不看profiler根本发现不了因为结果是对的只是慢。5.5 多卡与分布式场景的注意事项如果要在多卡上跑除了单卡的优化还要考虑卡间的负载均衡和通信开销。分组GEMM在分布式场景下每个卡可能负责不同的专家分组大小的分布会随输入变化。建议在调度层做动态的任务分配而不是静态切分。另外JIT编译的缓存在多进程场景下要注意并发写入的问题。多个进程同时首次调用同一个配置可能同时触发编译导致缓存文件冲突。可以在启动时预热或者用文件锁保护编译过程。6. 我对这类高性能计算库的一些实际体会折腾DeepGEMM这段时间最大的感受是高性能GEMM的优化没有银弹全是细节的累积。一个看似简单的矩阵乘法从算法选择到指令调度从内存布局到流水线编排每一层都有优化空间每一层的收益可能只有几个百分点但叠起来就是成倍的差距。另一个体会是JIT路线在这个快速演进的领域确实有优势。硬件架构一年一变算法需求也在变笨重的模板库很难跟上节奏。轻量加JIT的组合让项目能保持敏捷代价是首次调用的编译延迟和部署环境的额外依赖。这个取舍在推理服务场景下是划算的因为服务是长期运行的编译一次的成本可以忽略。最后分享一个小技巧调优的时候不要一上来就盯着大矩阵先把小矩阵和边界情况跑对跑快因为这些场景的优化往往能暴露出最基础的问题。基础打牢了大矩阵的性能自然就上来了。反过来如果基础有问题大矩阵上做再多花哨的优化也是白搭。

相关新闻

Agent记忆系统设计:用SQLite构建可追溯、可演化的关系型记忆库

Agent记忆系统设计:用SQLite构建可追溯、可演化的关系型记忆库

1. 为什么 Agent 需要的不是“缓存”,而是一套可追溯、可查询、可演化的记忆系统很多人在第一天给 AI Agent 加“记忆”时,下意识就去翻文档找sessionStorage或者localStorage的用法——这就像给一个博士生配了个小学练习册:能记,…

2026/10/10 9:25:04 阅读更多 →
JavaWeb人事管理系统实战:SSM框架选型、数据库设计与论文答辩避坑指南

JavaWeb人事管理系统实战:SSM框架选型、数据库设计与论文答辩避坑指南

简介:这份PDF文档是JavaWeb人事管理系统的毕业论文设计,面向计算机相关专业学生及需要完成类似课程设计、毕业设计的开发者,帮助解决企业人事管理中效率低下、信息孤岛等问题。文档围绕权限控制、员工信息查询与增加、批量增加、工作状态控制…

2026/10/10 9:24:03 阅读更多 →
国家安全学开题报告,哪些 AI 网站真能帮上忙?[特殊字符]

国家安全学开题报告,哪些 AI 网站真能帮上忙?[特殊字符]

先说一个很典型的场景: 你是法学门类下国家安全学专业的学生,马上要提交毕业论文开题报告和任务书。导师只给了一个大方向——“生物安全治理”,你却要在几周内交出一份像样的成果:明确题目、研究背景、国内外研究现状、研究问题…

2026/10/10 9:24:03 阅读更多 →

最新新闻

可视化运维监控实战:让故障可见、可控、可定位

可视化运维监控实战:让故障可见、可控、可定位

做运维的最怕什么?不是半夜被叫醒,而是被叫醒之后面对一墙壁的监控数据,却不知道线上到底哪里出了问题。过去几年我搭建过几套运维监控体系,从最早用开源的监控组件拼拼凑凑,到后来逐步落地可视化运维监控平台&#xf…

2026/10/10 21:48:37 阅读更多 →
分支结构避坑指南:从if-else到switch的编程思维

分支结构避坑指南:从if-else到switch的编程思维

很多人刚学编程的时候,变量和输入输出都还能跟上,一到分支结构就开始犯迷糊:明明语法都认识,代码也能看懂,轮到自己写就总感觉逻辑拧巴。我刚开始带新人的时候,发现十个人里有六七个会栽在这一块。但其实分…

2026/10/10 21:48:37 阅读更多 →
课堂行为检测数据集:VOC与YOLO格式转换及YOLO训练实践

课堂行为检测数据集:VOC与YOLO格式转换及YOLO训练实践

简介:面向学生课堂行为检测场景,这份VOCYOLO双格式数据集包含5622张课堂实景图片,已按7个类别完成目标标注,可直接用于训练课堂行为识别模型、算法评测或教学实验,尤其适合需要现成标注数据的深度学习研究者。压缩包采…

2026/10/10 21:48:37 阅读更多 →
玩过 Stable Diffusion 就会用:从 SD 工作流平移 LTX-2.5 的 10 处关键差异

玩过 Stable Diffusion 就会用:从 SD 工作流平移 LTX-2.5 的 10 处关键差异

玩过 Stable Diffusion 就会用:从 SD 工作流平移 LTX-2.5 的 10 处关键差异 【免费下载链接】LTX-2.5 项目地址: https://ai.gitcode.com/hf_mirrors/Lightricks/LTX-2.5 如果你是 Stable Diffusion 的老玩家,第一次打开 LTX-2.5 的模型目录时大…

2026/10/10 21:48:37 阅读更多 →
别把 Supabase 当神:开源 BaaS 的隐藏成本与免费陷阱

别把 Supabase 当神:开源 BaaS 的隐藏成本与免费陷阱

别把 Supabase 当神:开源 BaaS 的隐藏成本与免费陷阱 【免费下载链接】supabase The Postgres development platform. Supabase gives you a dedicated Postgres database to build your web, mobile, and AI applications. 项目地址: https://gitcode.com/GitHub…

2026/10/10 21:48:37 阅读更多 →
impeccable:一款面向OpenAPI契约的Python自动化校验工具

impeccable:一款面向OpenAPI契约的Python自动化校验工具

我无法基于当前输入生成符合要求的博文。原因如下:输入中仅提供了项目标题"impeccable",以及空置的“相关热搜词”“最新网络热词”和完全空白的搜索内容块(),未提供任何实质性的项目正文、关键词列表或摘要…

2026/10/10 21:47:36 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/10 10:38:42 阅读更多 →