首页/新闻资讯/正文详情

GPU利用率低至20%?显存优化后训练速度不升反降的排查指南

发布时间:2026/11/3 23:11:08 来源:云帆数科 栏目:资讯中心
GPU利用率低至20%?显存优化后训练速度不升反降的排查指南
1. 显存空出来了GPU 利用率却趴在 20%一个被忽视的经典陷阱显存占用从 22GB 降到 9GBnvidia-smi里显存条绿油油一片看着特别舒服。结果nvidia-smi dmon一开GPU 利用率在 15% 到 35% 之间来回晃训练一个 epoch 的时间跟没省显存之前几乎一样。这个场景我在做 LLM Training Lab 系列的时候遇到过不止一次每次排查下来根因都不一样但有一个共同点显存和算力是两条独立的流水线省显存的操作往往会把瓶颈从显存搬到别的地方去。先把结论摆出来GPU 吃不满绝大多数情况下不是显卡不行而是数据供给、算子调度、通信同步、内存搬运这四个环节里至少有一个在拖后腿。显存优化梯度检查点、ZeRO、量化、LoRA解决的是能不能跑起来的问题而 GPU 利用率解决的是跑得快不快的问题两者根本不是同一件事。很多人把这两件事混为一谈以为显存降下来速度自然就上去了结果调了半天发现 GPU 该闲还是闲。这篇内容适合正在做 LLM 微调或预训练、已经跨过OOM 报错这道坎、但发现训练速度上不去的朋友。我会按先定位瓶颈在哪条流水线再针对性拆解的思路把我在实际项目里踩过的坑和验证过的排查方法完整讲一遍。涉及的工具主要是 PyTorch、CUDA、Nsight 系列和 nvidia-smi 自带的一些监控手段不需要额外装什么重型 profiler 也能定位大部分问题。提示GPU 利用率低和显存占用低是两个完全不同的信号。前者说明计算单元在等活干后者说明内存够用。把这两个指标放在一起看才能判断瓶颈到底在哪。2. 先搞清楚 GPU 到底在等什么四条流水线的定位方法2.1 用 nvidia-smi dmon 做第一轮粗筛排查 GPU 吃不满第一步不是上 profiler而是用最轻量的工具做粗筛。nvidia-smi dmon -s pucm -d 1这条命令会每秒刷新一次输出包括功耗p、利用率u、显存控制器利用率c、显存带宽利用率m。重点看两个数SM 利用率和显存控制器利用率。如果 SM 利用率低但显存控制器利用率高说明数据搬运是瓶颈典型场景是 dataloader 的 pinned memory 没开、或者 batch 里序列长度差异太大导致 padding 浪费。如果两个都低那问题多半在 CPU 侧或者通信侧GPU 纯粹在等数据或者等 all-reduce 完成。如果 SM 利用率高但显存控制器利用率也高那才是真正的计算密集这时候 GPU 吃不满反而是正常的因为它在等内存。我一般会连续观察 60 秒把数据复制到表格里算平均值和波动范围。波动范围比平均值更重要——如果利用率在 10% 到 90% 之间剧烈抖动说明有周期性的同步点或者数据供给断流这种抖动比稳定在 40% 更值得警惕。2.2 PyTorch Profiler 抓时间线看 GPU 上的空洞粗筛之后用torch.profiler抓一段 20 到 30 个 step 的时间线。关键不是看哪个 kernel 耗时最长而是看GPU 时间线上有没有空洞。空洞意味着 GPU 在某个时刻没有 kernel 可执行只能干等。import torch from torch.profiler import profile, ProfilerActivity with profile( activities[ProfilerActivity.CPU, ProfilerActivity.CUDA], scheduletorch.profiler.schedule(wait2, warmup3, active10, repeat1), on_trace_readytorch.profiler.tensorboard_trace_handler(./log), record_shapesTrue, profile_memoryTrue, ) as prof: for step, batch in enumerate(dataloader): train_step(batch) prof.step() if step 20: break跑完之后用 TensorBoard 打开 trace重点看三件事空洞出现在 forward 之前还是 backward 之后、空洞的宽度是多少微秒、空洞期间 CPU 在干什么。如果空洞出现在每个 step 开头基本可以确定是 dataloader 供给不足如果出现在 backward 之后多半是优化器 step 或者梯度同步在等。2.3 四条流水线的判断表把常见现象和对应瓶颈整理成一张表方便快速对照现象SM 利用率显存控制器利用率最可能的瓶颈每个 step 开头有空洞低低DataLoader 供给不足backward 后有长空洞低低梯度同步或优化器 step利用率周期性抖动中高序列长度不均、padding 浪费利用率稳定但偏低中中kernel 太小、launch 开销占比高利用率高但吞吐低高高真正的计算密集正常现象这张表不是绝对的但能覆盖八成以上的场景。我自己的经验是DataLoader 问题占了所有 GPU 吃不满案例的一半以上尤其是做 LLM 微调的时候tokenize 和动态 padding 的开销经常被低估。2.4 一个容易被忽略的指标kernel launch 间隔在 profiler 的 trace 里把视角切到 CUDA kernel 那一行看相邻两个 kernel 之间的间隔。如果间隔稳定在几十微秒以上说明 CPU 侧 launch 跟不上 GPU 消费。这种情况在小模型、小 batch 或者大量小算子比如 LayerNorm、激活函数逐个调用的场景特别常见。解决办法有两个方向一是用torch.compile或者 CUDA Graph 把多个小 kernel 合并减少 launch 次数二是把 batch size 调大让每个 kernel 的计算量上去摊薄 launch 开销。我实测下来torch.compile在这种场景下能把 GPU 利用率从 40% 拉到 70% 以上代价是首次编译要等一两分钟。3. DataLoader 这条流水线LLM 微调里最大的隐形杀手3.1 为什么 LLM 的 DataLoader 特别容易拖后腿图像任务里一张图 decode 加 augment 也就几毫秒DataLoader 很容易跟上。但 LLM 不一样一条样本可能要经过 tokenize、拼接、动态 padding、attention mask 生成、label 对齐这一整套流程单条处理时间轻松上到几十毫秒。如果num_workers设得太小或者 tokenizer 是 Python 实现的慢版本GPU 就只能干等。更麻烦的是动态 padding。为了省显存很多人会把一个 batch 内的序列 pad 到该 batch 的最大长度而不是全局最大长度。这个操作本身没问题但它意味着每个 batch 的实际计算量不一样GPU 利用率会随 batch 内最长序列的长度波动。如果 batch 里恰好有一条超长序列整个 batch 的计算时间就被它拉长其他短序列的算力全浪费在 padding 上。3.2 num_workers 和 prefetch_factor 的调法num_workers不是越大越好。设太大反而会因为进程间通信和内存拷贝导致 CPU 侧开销上升。我的经验值是num_workers 设为 CPU 物理核心数的 50% 到 75%比如 16 核机器设 8 到 12。prefetch_factor默认是 2在 LLM 场景下建议调到 4 到 6让 worker 提前多准备几个 batch 放在队列里。from torch.utils.data import DataLoader dataloader DataLoader( dataset, batch_size8, num_workers10, prefetch_factor4, pin_memoryTrue, persistent_workersTrue, shuffleTrue, )pin_memoryTrue这个几乎必开它让数据放在页锁定内存里H2D 拷贝可以用 DMA 直接搬不用经过 CPU 中转。persistent_workersTrue避免每个 epoch 重新创建 worker 进程在 epoch 数多的时候能省下不少启动开销。注意pin_memory会占用额外的系统内存如果主机内存本来就紧张开之前先确认一下余量。一般按 batch 大小的 2 到 3 倍预留比较稳妥。3.3 tokenize 前置把 CPU 活干在训练之前最彻底的优化是把 tokenize 从训练循环里挪出去。做法是训练前先跑一遍预处理把 tokenize 好的 input_ids、attention_mask、labels 存成内存映射文件或者 Arrow 格式训练时 DataLoader 只负责读取和拼接。HuggingFace 的datasets库配合set_format就能做到这一点读取速度比实时 tokenize 快一个数量级。如果数据量太大存不下退而求其次用IterableDataset配合流式读取但要注意IterableDataset和num_workers配合时每个 worker 会拿到完整的数据流副本需要手动做 shard 切分否则会重复训练。这个坑我踩过一次训练 loss 降得特别快后来发现是数据被重复喂了好几遍。3.4 动态 padding 的取舍省显存还是保利用率动态 padding 到 batch 内最大长度能省显存但会牺牲利用率。我的建议是按长度分桶先把数据集按序列长度排序然后每个 batch 从相近长度的样本里采样。这样 batch 内长度差异小padding 浪费少同时显存占用也不会像全局 padding 那样爆掉。实现上用LengthGroupedSampler或者自己写一个 sampler把长度相近的样本 index 分到一组。代价是每个 epoch 的数据顺序不再完全随机但对 LLM 微调来说影响很小因为数据量通常远大于 batch 数一个 epoch 内看到的样本组合还是足够多样的。4. 计算图与算子调度小 kernel 堆出来的低利用率4.1 为什么 backward 之后 GPU 会闲下来backward 算完之后优化器 step 是在 CPU 侧发起的如果用的是 Adam 这类带状态更新的优化器每个参数都要做一次 element-wise 运算。参数量大的时候这些 kernel 虽然单个很快但数量极多launch 开销累加起来很可观。更关键的是如果开了梯度累积或者梯度裁剪中间还会有同步点GPU 在这些点上只能等。我见过一个典型案例模型 7B用 AdamW优化器 step 阶段 GPU 利用率直接掉到 5%持续好几百毫秒。后来换成 fused AdamWtorch.optim.AdamW的fusedTrue参数把多个 element-wise 操作合并成一个 kernel这段耗时直接砍掉一半以上。optimizer torch.optim.AdamW( model.parameters(), lr2e-5, fusedTrue, # 需要 PyTorch 2.0 和 CUDA 环境 )4.2 torch.compile 在 LLM 训练里的实际收益torch.compile对 LLM 训练的帮助主要体现在两个方面一是把相邻的小算子融合成一个大 kernel减少 launch 次数二是对 attention 这类模式固定的计算做图优化。我在 7B 模型上实测torch.compile配合modemax-autotune能把 GPU 利用率从 55% 拉到 78%step 时间缩短约 25%。但torch.compile不是没有代价。首次编译要等一到三分钟而且如果模型里有动态控制流比如根据输入长度走不同分支可能会触发反复编译反而更慢。我的做法是先用modedefault跑通确认没有 graph break再切到max-autotune榨性能。graph break 的日志里会明确告诉你哪一行代码导致断图照着改就行。4.3 FlashAttention 与显存、算力的双重关系FlashAttention 经常被当成省显存的工具但它对 GPU 利用率的提升同样明显。标准 attention 要显式构造 N×N 的注意力矩阵显存占用随序列长度平方增长而且这个矩阵的读写会占用大量显存带宽。FlashAttention 用分块计算避免了这个矩阵的物化显存降下来的同时显存带宽压力也小了SM 能把更多时间花在真正的矩阵乘上。PyTorch 2.0 之后torch.nn.functional.scaled_dot_product_attention会自动选择 FlashAttention 后端如果硬件支持。但要注意不是所有 GPU 都支持比如一些较老的架构就只能回退到 memory-efficient 实现收益会打折扣。用之前先确认一下自己的卡在不在支持列表里。4.4 梯度检查点省显存但要多算一遍 forward梯度检查点gradient checkpointing是省显存的利器但它的原理是用计算换显存——backward 的时候要重新跑一遍 forward 来重建中间激活。这意味着总计算量增加了大约 30%如果原本 GPU 就没吃满开了检查点之后利用率可能会上去一点但 step 时间反而变长。我的判断标准是如果显存够用就别开检查点。只有在显存确实不够、必须靠检查点才能跑起来的时候才开。开了之后如果发现 step 时间涨得太多可以考虑只对部分层开检查点比如只对 attention 层开FFN 层不开在显存和速度之间找个平衡。5. 通信与同步多卡场景下 GPU 等待的重灾区5.1 DDP 的 all-reduce 为什么会让 GPU 闲下来单卡场景下通信不是问题但一旦上多卡 DDP每个 backward 结束都要做一次梯度 all-reduce。如果梯度量大、网络带宽有限all-reduce 的时间可能比计算还长GPU 就在那儿等着通信完成。这时候nvidia-smi看到的利用率会呈现明显的锯齿状——计算时冲高通信时掉底。判断方法很简单在 profiler 里看 all-reduce kernel 的耗时占比。如果超过 20%说明通信是瓶颈。解决办法有几个一是开gradient_as_bucket_viewTrue减少梯度拷贝二是调大bucket_cap_mb让每次通信传更多数据减少通信次数三是用梯度累积把多个 step 的梯度攒起来一起同步摊薄通信开销。model torch.nn.parallel.DistributedDataParallel( model, device_ids[local_rank], gradient_as_bucket_viewTrue, bucket_cap_mb64, )5.2 梯度累积与通信频率的权衡梯度累积是省显存的常用手段但它对 GPU 利用率的影响是双面的。好处是每个 micro-step 的计算量不变但同步频率降低了坏处是如果累积步数太多有效 batch size 变大可能需要调学习率而且中间步骤的 GPU 利用率不会有明显变化。我的经验是累积步数控制在 4 到 8 之间比较合适。再往上通信开销的节省就不明显了反而增加了调参的复杂度。如果显存实在不够优先考虑用 LoRA 或者量化而不是一味加大累积步数。5.3 数据并行之外的并行策略对利用率的影响张量并行TP和流水线并行PP在 LLM 训练里很常见但它们对 GPU 利用率的影响和 DDP 完全不同。TP 每层都要做 all-reduce通信频率极高对带宽要求苛刻PP 则有流水线气泡如果 micro-batch 数不够GPU 会在气泡期间空闲。选并行策略的时候先看互联带宽。NVLink 环境下 TP 的通信开销可以接受PCIe 环境下 TP 的收益可能还不如老老实实做 DDP。PP 的气泡问题可以通过增加 micro-batch 数缓解但 micro-batch 数受显存限制这就又绕回显存和利用率的权衡了。6. 监控与调优的实操闭环从定位到验证6.1 建立一套可复现的监控脚本排查不能靠感觉得有一套固定的监控流程。我一般会写一个脚本在训练启动的同时后台跑nvidia-smi dmon把日志存下来训练结束后用 pandas 做统计分析。nvidia-smi dmon -s pucm -d 1 -o T gpu_monitor.log MONITOR_PID$! python train.py kill $MONITOR_PID分析的时候重点看三个统计量SM 利用率的中位数、P10 分位数、以及利用率低于 30% 的时间占比。中位数反映整体水平P10 反映最差情况低利用率时间占比反映抖动的严重程度。我一般要求中位数在 70% 以上P10 不低于 40%低利用率时间占比低于 10%。6.2 调优的优先级顺序面对一堆可能的优化点按什么顺序试我的经验顺序是先查 DataLoadernum_workers、prefetch_factor、pin_memory 这三个参数调一遍成本最低收益往往最大。再查优化器换 fused 版本检查有没有不必要的同步点。然后上 torch.compile收益大但有一定风险放在中间试。最后调并行策略涉及多卡改动大放在最后。这个顺序的逻辑是先动成本低、影响面小的参数再动需要改代码结构的优化。很多人一上来就想着上 TP、PP结果发现根本问题是 DataLoader 没配好白折腾一圈。6.3 验证优化效果的正确方法优化做完之后怎么确认真的有效不能只看nvidia-smi的瞬时值那个波动太大。正确做法是固定随机种子跑相同数量的 step对比总耗时和利用率统计量。同时要确认 loss 曲线没有异常避免优化引入了正确性问题。我一般会跑三轮基线一轮、优化后一轮、优化后再跑一轮确认可复现。三轮的 step 时间标准差如果在 5% 以内说明结果可信。如果波动很大先排查是不是有别的进程在抢 GPU或者温度墙导致降频。6.4 几个我踩过的坑和对应的解法第一个坑是开了 pin_memory 但没开 persistent_workers结果每个 epoch 结束都要重新创建 worker启动开销把收益吃掉了。第二个坑是torch.compile 和 gradient checkpointing 同时开编译时间暴涨而且收益不明显后来发现两者在计算图优化上有冲突只留一个就好。第三个坑是num_workers 设成了 CPU 逻辑核心数结果超线程反而拖慢了 tokenize改成物理核心数之后利用率上去了。还有一个比较隐蔽的坑CUDA 版本和 PyTorch 版本不匹配。这种问题不会报错但某些 kernel 会走 fallback 路径性能差一大截。装环境的时候一定要按 PyTorch 官网的对应表来别自己乱配。7. 一些关于显存与利用率关系的个人体会做了这么多轮 LLM 训练调优我最大的体会是显存和利用率是一对需要动态平衡的指标不存在显存越省越好这回事。省显存的每一个操作几乎都有代价——检查点要多算 forward量化要引入反量化开销LoRA 要额外做低秩矩阵乘。这些代价在显存紧张的时候是值得的但在显存够用的时候就是纯粹的浪费。我的做法是先把显存占用压到目标值的 80% 左右留出 20% 的余量应对波动然后把剩下的精力全放在利用率优化上。如果发现某个省显存的操作让利用率掉了超过 10%我就会重新评估这个操作是否必要。毕竟训练的目标是在合理时间内跑完而不是把显存占用压到最低。另外不同规模的模型瓶颈位置完全不一样。小模型1B 以下通常是 launch 开销和 DataLoader 瓶颈大模型7B 以上通常是通信和显存带宽瓶颈。调优之前先搞清楚自己的模型属于哪一档能少走很多弯路。

相关推荐

显存省了GPU却吃不满?LLM训练中显存优化与吞吐提升的冲突与解法
显存省了GPU却吃不满?LLM训练中显存优化与吞吐提升的冲突与解法

1. 显存省下来了,GPU 为什么还是吃不满 显存占用从 22GB 压到 9GB, nvidia-smi 里显存那一栏确实绿油油一片很健康,结果 GPU 利用率死活卡在 30% 上下,风扇都不怎么转,训练一个 epoch 的时间跟没优化前一模一样。这个… · 2026/11/3 22:26:33

大模型训练并行策略详解:DP、TP、PP、CP、EP原理与实战调优
大模型训练并行策略详解:DP、TP、PP、CP、EP原理与实战调优

1. 算法同学为什么要补上分布式计算这一课 做算法的同学,尤其是从传统机器学习、深度学习一路走到大模型方向的,大概率都经历过这样一个阶段:单卡能跑通的模型,参数量一上去就爆显存;好不容易把 batch size 调到 1&… · 2026/11/3 20:03:14

欠驱动AUV轨迹跟踪的Lyapunov控制设计与仿真
欠驱动AUV轨迹跟踪的Lyapunov控制设计与仿真

1. 欠驱动AUV轨迹跟踪问题的本质与挑战 自主水下车辆(AUV)在海洋勘探、管道检测等场景中发挥着关键作用。与全驱动系统不同,欠驱动AUV的独立控制输入数量少于系统自由度(通常只有推进器和舵面控制),这使得其… · 2026/11/3 22:41:57

MoBaiTool系统优化六模块实战:CPU调校、电源与内存清理避坑
MoBaiTool系统优化六模块实战:CPU调校、电源与内存清理避坑

系统优化这事儿,我折腾了快十年,从最早手动改注册表、写批处理,到后来自己攒一套调校脚本,踩过的坑能装满一个移动硬盘。MoBaiTool 这个系统优化工具箱吸引我的地方,不在于它功能多,而在于它把六个最容易互… · 2026/11/4 4:18:39

GitHub周榜怎么读:开源项目评估与技术雷达搭建
GitHub周榜怎么读:开源项目评估与技术雷达搭建

刷周榜这件事,我从最早把它当"每周科技新闻"看,到现在把它当一份需要交叉验证的行业信号源,中间踩过的坑不算少。2026 年 9 月 6 日这一期的 GitHub 周榜出来后,我把整份榜单从头翻了一遍,顺手做了几组数据对… · 2026/11/4 4:18:39

微电网多目标优化系统设计与MOPSO算法实现
微电网多目标优化系统设计与MOPSO算法实现

1. 微电网多目标优化系统概述在分布式能源系统快速发展的今天,如何高效协调风能、太阳能等可再生能源与传统电网、储能系统及负荷之间的关系,成为微电网运行优化的核心挑战。本文介绍的微电网多目标优化系统,正是为解决这一难题而设计的智能决… · 2026/11/4 4:18:39

无人机遥感图像处理链路:去噪、辐射校正与智能裁剪
无人机遥感图像处理链路:去噪、辐射校正与智能裁剪

1. 无人机遥感图像为什么“不能直接用”——从航拍到可交付成果的处理链路拿到无人机落下来的原始影像,绝大多数人第一反应是直接拖进软件里拼图,然后被结果吓一跳:田块边界糊成一团,同一片作物在相邻两张图上颜色一深一浅&#x… · 2026/11/4 4:18:39

ipa转换工具实战:.app打包、重签名与批量出包避坑
ipa转换工具实战:.app打包、重签名与批量出包避坑

简介:这份rar压缩包面向需要了解iOS应用打包与IPA格式转换的开发者、测试人员及越狱/非官方分发场景的学习者,核心解决将.app等软件包整理为可安装IPA、并关联代码签名与配置信息的问题。压缩包共31个文件、约447KB,包含15张png界面或资源图、… · 2026/11/4 4:18:39

pytest框架setup与teardown机制及fixture应用详解
pytest框架setup与teardown机制及fixture应用详解

1. pytest框架的四种级别setup与teardown机制解析在自动化测试领域,环境初始化和清理是保证测试用例独立性的关键。pytest作为Python生态中最主流的测试框架,提供了四种不同粒度的setup/teardown机制:1.1 模块级(module&#xff0… · 2026/11/4 4:17:39

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

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

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

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

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

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

AI辅助技术栈评估:提升研发决策效率60%
AI辅助技术栈评估:提升研发决策效率60%

1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17

从生成Token到直接决策:Jev如何重塑AI Agent工程实践
从生成Token到直接决策:Jev如何重塑AI Agent工程实践

1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17

32G统一内存跑大模型:内存账、TPS实测与端云分流
32G统一内存跑大模型:内存账、TPS实测与端云分流

想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

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

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

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

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

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

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码