LLM推理流水线动态换挡:在碎片化GPU集群中突破性能瓶颈
今天想聊一个我们折腾了很久的问题GPU集群里的卡型号杂七杂八既有后补的高算力大显存新卡也有早年买的小显存旧卡混在一起跑LLM推理流水线时静态切分总是让整条链路卡在最弱的那一段上。后来我们做了个叫FlexPipe的内部原型核心思路就一条让LLM推理流水线在碎片化GPU集群里能“动态换挡”——根据实时负载、每一张卡的实时状态动态调整流水线的深度、并行度、微批量大小把以前白白浪费的碎片算力重新用起来。如果你也遇到过“明明还有一堆卡闲着但延迟就是压不下去”的情况这篇应该能给你一些可落地的思路。1. 为什么固定流水线在碎片化集群里总是“卡壳”1.1 流水线并行不是“层数除以卡数”这么简单很多刚接触LLM推理优化的同学第一反应是把模型按层平均切开每张卡放几层请求像工厂流水线一样从第一段流到最后一层。这个想法在完全同构的集群里是成立的每张卡算力差不多显存差不多每个stage耗时接近流水线能跑得比较顺。但一旦进入混卡集群问题立刻暴露。流水线并行有一个很要命的特性每个step的耗时取决于最慢的stage。所有stage是同步推进的快卡跑完了不能继续下一批必须在buffer那里等慢卡。结果就是快卡的实际利用率可能只有50%甚至更低而慢卡永远处于过载状态。我印象很深的一次调优某张旧卡上恰好放了几层attention计算很重的层单层耗时是新卡的三倍。整条流水线的QPS直接掉到理论值的一半左右。你问能不能让快卡多分几层理论可以但显存又不够——旧卡显存小放不下那么多层权重和KV Cache。这是一个算力和显存互相拉扯的问题静态划分很难两边都优化。1.2 碎片化不只是“显存大小不一样”很多人把“碎片化集群”理解为“显存大小不同的卡混在一起”但实际部署中碎片化至少有三个层面我习惯叫它们算力碎片、显存碎片和时间碎片。算力碎片好理解不同代际的计算卡浮点算力可以差好几倍。同样是跑Transformer的FFN层新卡一个step可能只要30毫秒旧卡要90毫秒。显存碎片也不只是总容量差异还有单卡容量不匹配。比如某张卡能放下8层另一张只能放下4层如果机械地平均切分放不下的卡就成了“过不了编译”的约束点。时间碎片则更隐蔽集群里有一些卡本身空闲但它们是那种“当前没任务可下一小时要被其他作业占用”的状态。静态调度器往往不敢用这种时隙不稳定的卡因为流水线一旦建立中途少一张卡整个推理服务就得中断。FlexPipe最初想解决的就是把这三类碎片都纳入调度考虑而不是只盯着“哪张卡显存够大”。1.3 “动态换挡”到底换的是什么“换挡”这个类比不是随便起的。汽车在平路、上坡、高速上需要不同档位让发动机保持在高效区间推理流水线面对不同请求量、不同目标延迟、不同集群可用资源也需要在多种运行形态之间切换。在我这个项目里换挡具体换的是三类东西第一是流水线的stage数量和stage边界也就是模型层怎么切、每张卡负责哪些层第二是并行度组合比如某个stage能不能放多副本用数据并行分摊流量第三是调度参数最常见的就是micro-batch大小和一次往流水线里灌多少个请求。固定配置是一条直线动态换挡是一组曲线。你需要事先准备好几套可切换的“档位”然后由控制逻辑根据实时监控决定当下应该挂哪个档。这个思想比“加机器”“减机器”更贴近LLM推理流水线的实际瓶颈。2. 动态换挡的关键设计档位模型与决策机制2.1 档位不是玄学是几套可量化的流水线配置做动态换挡第一件事不是写调度代码而是先把“档位”定义清楚。我们内部初期定义了三个基础档位延迟优先档、吞吐优先档、均衡档。每个档位不只是一个名字而是对应一组完整可执行的流水线参数。延迟优先档的目标是把P99延迟压到最低。通常采用更浅的流水线避免请求穿过太多stage同时把每个stage的micro-batch调小降低单batch在慢卡上的排队时间。必要的时候还会给热点stage设置多个副本来并行消费。吞吐优先档则相反它会加深流水线把更多小卡充分利用起来同时调大batch size让每一张卡都在接近饱和的状态下工作代价是单个请求的延迟会上升。均衡档在这两者之间折中适合流量波动不大但对服务质量要求也比较高的场景。档位目标典型stage数micro-batch多副本策略适用场景延迟优先档P99低浅例如2-4小1-4热点stage多副本在线交互、RAG问答均衡档延迟/吞吐兼顾中例如4-8中4-8关键stage复制混合流量吞吐优先档QPS高深例如8-12大8-16少副本走深管线离线批量、批处理这个表只是示例实际档位参数必须结合模型层数和集群拓扑去算。比如模型一共32层你不可能切出一个15层的stage再配一个17层的stage除非某张卡特别强否则stage间负载会失衡。更合理的做法是让每个stage包含的层数不同但期望执行时间接近。2.2 换挡决策要看哪些信号动态换挡最怕的就是“频繁乱换”。如果请求量稍微抖一下就去换挡系统会一直处于迁移状态性能反而更差。我们后来用的是滞回触发加平滑信号的组合方案。监控端每两秒采集一次指标包括每个stage的利用率、平均排队长度、最近一分钟的实际QPS、P50和P99延迟。决策端不是直接拿原始值判断而是先做指数移动平均把瞬时毛刺滤掉。然后看两个核心条件第一个是瓶颈倾斜度如果某个stage利用率长期大于90%而其他stage利用率都低于60%说明当前档位的负载分配已经严重不均衡换挡有明确收益第二个是延迟达标率如果P99连续超过目标值一定时间同时总体吞吐还有上升空间就切到更偏向延迟的档位。滞回的意思是不能超过阈值就立刻换要设置一个“进入区间”和“退出区间”。比如利用率失衡只有连续3个采样周期都满足条件才触发切换换完之后除非新档位也连续3次触发另一组阈值否则不允许再换回去。这个缓冲极大减少了“乒乓效应”。2.3 为什么靠“多实例扩缩容”救不了碎片集群有人会说既然吞吐不够多拉几个模型副本前面加负载均衡不就行了这在普通Web服务上是对的但在LLM推理场景里有两个坎迈不过去。第一个坎是显存。一个模型副本完整加载可能就要几十GB显存。碎片化集群里往往找不到一张足够大的卡去放整个副本就算能找到多副本的资源开销也很高而且副本之间如何路由也需要额外管理。第二个坎是尾延迟。多副本模式请求是水平分发的每个副本内部仍然是固定流水线如果某个副本恰好在慢卡上这部分请求延迟就会很糟。负载均衡无法改变单副本内部的瓶颈。FlexPipe的做法更像“在模型副本内部做结构优化”不新起整个副本而是调整现有流水线把负载从慢卡往快卡上挪或者把瓶颈stage拆成两个stage并行处理。这种调整的粒度比autoscaling细得多也更贴合GPU碎片化的现状。3. 实操落地从资源画像到换挡执行的完整链路3.1 第一步给集群做“体检”动态换挡的前提是你必须了解每一张卡的真实能力。这里我强烈建议不要只看显卡标称的“显存容量”一定要做实际探测。我们会在集群空闲窗口执行一轮轻量探测任务对每张卡依次跑几个代表性算子比如大矩阵乘法、注意力计算和全连接层分别记录耗时和显存占用峰值再用两两互发数据的方式粗测任意两张卡之间的互连带宽和延迟。探测结果会汇总成一个资源画像JSON或YAML文件。比如下面这个简化的格式nodes: - node_id: gpu-001 generation: old memory_gb: 24 compute_score: 1.0 max_batch_microbatch: 4 interconnect: to_gpu_002: bandwidth_gbps: 12 latency_us: 5 - node_id: gpu-002 generation: new memory_gb: 48 compute_score: 3.2 max_batch_microbatch: 8 interconnect: to_gpu_001: bandwidth_gbps: 12 latency_us: 5有人会觉得搞这么细是不是过度工程。但我可以负责任地说如果不做资源画像“动态换挡”就只是在几个静态配置之间瞎切。你连每张卡能承受多大的micro-batch都不清楚怎么判断换到吞吐档之后哪张卡先爆显存3.2 第二步根据画像生成候选档位有了资源画像下一步是自动计算候选档位而不是手写死配置。我们用一个简单的“虚拟放置器”——本质上是一个背包搜索加模拟评估器——输入模型层数、每层的权重大小和计算量估算、资源画像输出几套可行的流水线切分方案。比如32层的模型候选方案A可能把层分布分成[4,6,10,12]让算力最强的卡承担最多的层候选方案B则可能为某几个耗时特别大的层单独成一个stage然后放两个副本来并行。每一套方案都要在模拟器里跑一遍估算出理论吞吐和理论P99再把这些指标连同实际运行参数打包成一个档位配置。这一步我们踩过最大的坑是模拟器估算的耗时和真实推理耗时差异很大。主要原因是LLM推理的KV Cache显存分配是动态的重叠的访存和计算有时候会互相掩盖。所以后来模拟器只负责生成“看起来平衡”的候选最终档位是否好用必须靠在线监控反馈来修正。每个档位配置的核心元素如下stage列表每个stage包含哪些层、部署在哪张卡上并行副本数某些stage是否有多个并行实例micro-batch大小单次进入流水线的样本批大小换挡阈值这个档位适合的QPS区间和目标延迟3.3 第三步控制环怎么设计动态换挡的背后是一个经典的控制环监控器采集数据决策器判断是否需要换挡执行器负责把流水线从旧档位切到新档位。下面是我简化后的决策伪代码已经去掉了集群细节但整体逻辑就是这样def maybe_shift_gear(monitor, current_gear, target_latency): stats monitor.sample() smooth_stats ema(stats, alpha0.3) # 1. 判断是否有明显瓶颈倾斜 utilizations smooth_stats.stage_utilization() max_util max(utilizations) min_util min(utilizations) load_imbalance_trigger max_util 0.9 and min_util 0.6 # 2. 判断延迟是否持续超标 latency_over smooth_stats.p99 target_latency * 1.2 latency_under smooth_stats.p99 target_latency * 0.7 # 3. 用滞回区间做切换 if load_imbalance_trigger and current_gear ! throughput: return throughput if latency_over and current_gear ! latency: return latency if latency_under and current_gear ! balanced: return balanced return current_gear判断逻辑本身不复杂复杂的是决策之后执行器怎么安全落地。我们的原则是换挡期间不允许丢失任何正在处理的请求也不允许把P99拉到不可接受的程度。执行器实际上分三步走。第一步是“影子预热”在新目标卡上初始化新stage加载权重和计算图但是不接入主流量先让它跟着旧stage接收同样的请求副本来做预热确保缓存和显存分配都稳定下来。第二步是“排水”旧stage停止接收新的micro-batch但已经在流水线里的请求继续处理完这个过程也叫drain。第三步才是“切换”确认旧stage队列清空、新stage影子状态健康之后把流量入口一次性指向新stage释放旧stage占用的显存。这个“先建后切”的流程说起来简单真正落地要处理很多细节。比如影子预热阶段新stage会多占一份显存如果集群显存本来就很紧可能没有空间做预热。我们后来针对这种情况加了“降级模式”如果找不到足够显存做完整影子预热就先在目标卡上只分配权重不分配KV Cache池等切换完成后再用流式方式逐步扩大KV Cache池。3.4 切换点选择与请求状态迁移LLM推理和普通无状态服务有一个本质区别请求在流水线里流动时每一层产生的KV Cache都放在对应stage的显存里而且这些缓存是连续的、动态增长的。如果你在请求执行到一半的时候把某个stage的层迁移到另一张卡你不仅得迁移模型权重还得迁移那一时刻所有正在生成序列的KV Cache块。这个开销非常大。所以我们明确了一个规则动态换挡只能发生在安全切换点。所谓安全切换点就是流水线里没有正在执行的in-flight请求或者stage边界处没有等待处理的micro-batch。为了制造这样的切换点执行器必须主动限制新请求进入让流水线快速排空。听起来像是牺牲了一部分实时性但实测下来大多数场景可以接受。我们通常选在流量低峰期或请求间隙执行换挡。如果你的业务对延迟特别敏感可以考虑用双缓冲保留旧档位的完整副本切到新档位后旧副本不立刻释放而是保留几百毫秒一旦新档位表现异常就切回去。代价是多占一份显存换来的是一颗“后悔药”。4. 踩坑实录动态换挡的五个常见问题与排查4.1 换挡瞬间延迟毛刺从哪来第一次灰度切换时我们一度觉得换挡成功了平均延迟变化不大但P99却从50毫秒飙到2秒。后来定位发现问题出在影子预热阶段。新stage在预热时虽然不接主流量但它要和旧stage共享GPU的算力和显存带宽导致旧stage执行变慢尾延迟升高。这个问题比想象中隐蔽因为平均延迟可能只涨了10%不知道的人会以为是系统偶然抖动。解决办法有两个一是预热耗时要控制不要让影子stage长时间空转二是优先给影子stage分配与其他stage不同的硬件资源比如利用不同卡上的空闲流处理器或者限制影子stage的并发减少对主路径的影响。我们在配置里给每个影子stage加了一个“预热并发上限”默认只占单个卡算力的20%。4.2 显存碎片和KV Cache池冲突换挡之后stage边界变了KV Cache池也需要重新分配。如果之前那张卡上的显存被碎片化得很严重新stage可能无法申请到一块足够大的连续显存。刚开始我们用的是标准显存分配器结果频繁出现“明明显存总量够但申请大块失败”的情况。后来我们对KV Cache池改用预分配加页表化管理的模式也就是说在集群空闲时先把大块显存切成固定大小的块池stage运行中按页取用。这样即使显存物理上不连续逻辑上也能把KV Cache块组织起来。遇到换挡新stage优先从块池里取页旧stage释放的页也可以立即归还给池子复用。4.3 互连带宽才是隐形瓶颈资源画像阶段我们测了算力和显存忽略了卡之间的互连带宽。结果有一次换挡后某两张卡之间的通信负载大增导致stage间数据传输耗时翻倍。最麻烦的是它不是持续高负载而是随着batch size增大周期性爆发在监控上看就是平均带宽不高、但单次传输延迟很大。后来我们在档位生成时加入了一个约束条件任何相邻stage之间的预计通信量不能超过两张卡互连带宽的70%。计算方式很简单用每token激活张量的大小乘上micro-batch再乘上每秒处理的batch数。这个约束会拦截掉一些看起来很美观但通信不现实的切分方案。4.4 换挡收益为负的情况动态换挡并不是稳赚不赔。如果你切得太频繁或者新档位带来的收益还覆盖不了权重更新和KV Cache迁移的开销整体表现反而更差。我们做过一次统计一次完整换挡在集群内产生的数据移动量可能达到几十GB如果是跨卡迁移还会占掉大量互连带宽。所以决策器不能只看“现在负载失衡”还得算一笔账新档位预估能提升多少吞吐持续运行多久能把迁移开销赚回来。我们内部设置了一个“最短驻留时间”每次换挡成功之后至少要稳定运行10分钟如果10分钟内又触发换挡条件决策器会把阈值自动调高一级。这个机制虽然简单但非常有效换挡频率直接降了一大半。4.5 常见问题速查表现象可能原因排查思路推荐处理换挡后P99明显升高影子预热与主stage争抢算力检查影子stage算力占用限制预热并发上限大块显存申请失败KV Cache池碎片化查看显存分配日志改用块池页式分配换挡后吞吐不升反降通信带宽超阈值监控stage间传输延迟加通信约束重新生成档位频繁来回切换阈值滞回区间太小看触发次数统计提高最短驻留时间新档位一接入就报OOMKV Cache预分配不足检查峰值序列长度动态扩池或降低并发最后一个建议是给每个档位加上健康标记。如果某个档位在上一次运行中触发了OOM或严重超时决策器就把它标记为“不健康”短期内不再切换到它直到模型发布或资源画像刷新。我们之前就是因为没有这个标记反复切同一个问题档位浪费了很多调试时间。做动态换挡这件事真正难的不是实现换挡动作而是知道什么时候不该换。我最大的体会是先让系统具备快速切换的能力再小心翼翼地用阈值和滞回让切换变得“稀少但精准”。FlexPipe到现在还只是一个原型但它让我看清楚了一件事——碎片化集群最缺的不是更高端的卡而是让不同能力的卡各司其职的调度逻辑。如果你也准备动这个方向建议从资源画像和档位模拟开始先把“看得见的跑得不平衡”修掉再考虑高级的预测式换挡。

相关新闻

基于Qt的TCP/UDP/串口通讯源码解析与工程实践

基于Qt的TCP/UDP/串口通讯源码解析与工程实践

简介:面向QT开发者的通信编程参考代码,整合TCP、UDP与串口三类通信,覆盖TCP服务器与客户端搭建、UDP收发、串口参数配置和实时读写等关键环节,适合嵌入式、物联网及设备调试场景。资源包共67个文件,包含20个cpp、15个h…

2026/10/17 16:34:38 阅读更多 →
Vue 项目中让 AI 更稳:AGENTS.md + Prompt 模板实践与 TaoToken 统一接入

Vue 项目中让 AI 更稳:AGENTS.md + Prompt 模板实践与 TaoToken 统一接入

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

2026/10/18 17:33:40 阅读更多 →
TCN时间序列预测实战:用因果卷积替代LSTM,提升精度与训练速度

TCN时间序列预测实战:用因果卷积替代LSTM,提升精度与训练速度

简介:这是一份面向初级及以上数据科学家的 Python 时间序列预测实战资料,聚焦时间卷积神经网络(TCN)的完整建模流程。资料以外汇市场模拟数据为示例,涵盖数据集生成、归一化处理、TCN 与 RNN 模型构建训练、预测效果对…

2026/10/18 19:00:06 阅读更多 →

最新新闻

项目管理系统ROI怎么算?拆解六大关键变量与三年落地模型

项目管理系统ROI怎么算?拆解六大关键变量与三年落地模型

1. 为什么大多数项目管理系统ROI,从一开始就算错了1.1 ROI算不清,多半是把它当成了纯财务题老板在季度会上突然问我:“项目管理系统上了快两年,ROI到底是多少?”当时后台工时数据、任务完成率、甘特图、人员负载表都是…

2026/10/18 21:40:08 阅读更多 →
C#与ROS2桥接实战:基于gRPC搭建工业机器人通信链路

C#与ROS2桥接实战:基于gRPC搭建工业机器人通信链路

1. 为什么C#与ROS2需要“握手”1.1 先看看两边的家底工业机器人集成项目里,最典型的一个组合是:Windows工控机上跑着C#写的上位机程序,负责WPF界面、配方管理、报警弹窗、数据库存储,还得对接MES、OPC UA、Modbus之类的老工业协议…

2026/10/18 21:40:08 阅读更多 →
微博疫情舆情分析实战:基于GraphSAGE异构图的情感分类方案

微博疫情舆情分析实战:基于GraphSAGE异构图的情感分类方案

简介:面向自然语言处理初学者与情感分析方向研究者的微博疫情情感分析实战资料包,项目聚焦NLP文本分类任务,将微博文档构建为图结构,借助图卷积网络学习文档嵌入并完成情感分类,适合希望掌握GCN在文本领域应用的学习者…

2026/10/18 21:40:08 阅读更多 →
智能体在车联网落地48天:从决策闭环到多车协同的实践记录

智能体在车联网落地48天:从决策闭环到多车协同的实践记录

智能体在车联网里跑了48天,今天正好是一个阶段性的节点。不是那种demo验证、跑通就发朋友圈的48天,而是每天顶着数据、时延、通信断连、边缘资源这些硬约束,一遍遍调参、压测、复盘,才走到了这一步。这十天半月里,我们…

2026/10/18 21:40:08 阅读更多 →
vmtk血管中心线提取:微分几何驱动的医学图像建模范式

vmtk血管中心线提取:微分几何驱动的医学图像建模范式

简介:vmtk(血管建模工具包)是一套面向医学图像处理领域的开源C/Python混合架构工具集,专为临床医生、生物医学研究人员及医学影像算法开发者设计,用于解决CT/MR影像中血管结构的3D重建、几何分析、表面数据量化与网格生…

2026/10/18 21:40:08 阅读更多 →
SSM框架广告管理系统:架构解析、部署运行与答辩改造

SSM框架广告管理系统:架构解析、部署运行与答辩改造

简介:面向Java毕业设计或课程设计场景,这是一套基于SSM框架与Vue前后端分离的广告管理系统项目。系统按业务角色划分为管理员、广告客户、广告商、广告制作商四大端,覆盖广告投放渠道管理、广告需求与提案流转、设计预览与调整、签署管理及结…

2026/10/18 21:39:07 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 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/18 0:55:02 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →