1. 先把“吃满”的定义校准了nvidia-smi 的百分比会骗人1.1 GPU-Util 到底在衡量什么很多同学发现显存降下来了GPU 利用率却只有 30%第一反应就是“是不是显卡坏了”。别急先搞清楚 nvidia-smi 里那个 GPU-Util 到底是什么。它指的是在过去某个采样周期内GPU 上至少有一个 SM 在执行任务的时间占比。注意是“至少有一个”而不是“有多少个 SM 在忙”。拿餐厅打比方这个指标只告诉你服务员有没有在走动不告诉你后厨同时在炒多少道菜。哪怕一整个采样窗口里只有一个很小的 kernel 在跑利用率也能显示 100%而这跟“算力吃饱”完全是两码事。反过来如果 GPU 在等数据、等同步、等 host 下发指令SM 就是空的利用率自然往下掉。所以看到 30%~40%不代表显卡不行而是需要找出哪些时间段里 SM 真正空闲空闲的原因又是什么。1.2 显存占用率不是性能指标做 LLM 训练优化久了你会发现一个反直觉的现象显存占用低并不等于训练快显存占用高也不等于训练慢。显存是一个资源约束超过阈值就崩低于阈值就安全如此而已。真正要盯的指标是每秒处理多少样本、多少 token也就是有效吞吐。我在帮别人排查“省显存后反而变慢”的问题时第一个要纠正的就是目标。如果你把“把显存压到多少 G 以下”当成优化目标那你很可能会为了让显存数字好看开一堆副作用很大的技巧结果 step time 从 1.2s 涨到 2.0s得不偿失。省显存是手段不是目的这句话值得在每一期反复强调。2. 显存每省一分都有对应的计算或通信代价2.1 梯度检查点把激活存量的账换成计算量的账Gradient checkpointing 是省显存的第一大热门手段。它的机制是前向传播时不全量保存每一层的激活值而是每隔几层存一个检查点反向传播时遇到缺的激活值就临时再做一次前向把它算回来。账算一下就很清楚原本反向需要约 2 次前向的计算量打开 checkpointing 后反向中多了一次重算前向总计算量从 3 次前向变成 4 次前向等于多出 30% 左右的 FLOPs。这笔额外计算在 GPU 已经满载时会直接体现为吞吐下降。但很多框架默认开启梯度检查点大家也无脑开着完全不管自己的显存是不是真的紧张。如果显存占用很宽松、batch 本来就可以开很大那 checkpointing 就是纯粹的负优化。反过来如果显存卡着上限不 checkpoint 就没法增大 batch那多出的计算量换来了更大的 batch 和更高的算术强度整体吞吐反而可能上升。所以它到底值不值取决于你的显存是不是真的“就差那几十个 G 的激活值”。一个很低成本的验证方法关掉 checkpointing看显存峰值会不会爆不爆就果断关掉。2.2 混合精度与 4-bit 量化省下的显存多出的转换和反量化混合精度训练的显存收益很大权重的半精度存储让显存占用直接腰斩。但要意识到LayerNorm、Softmax 这些数值敏感的算子通常仍用 fp32 执行每一步都隐含着精度转换和归一化操作这些额外指令同样要占用 SM 周期。好在混合精度的整体账通常是赚的所以它是训练赛道上的默认配置问题不大。真正容易踩坑的是 4-bit 量化微调也就是 QLoRA、NF4 这条路。权重以 4bit 存在显存里确实很省但矩阵乘法真正执行时kernel 必须先把 4bit 权重按块反量化成 16bit 再做计算。这个解包、反量化、重打包的动作会吃掉不少 SM 周期。结果就是GPU 好像一直没闲着但大量算力花在了“给数据重新穿上外套”这种无效工作上有效浮点吞吐并不高。这正是“显存省下来了但 GPU 吃不满”的一个典型暗坑你看到的利用率高可能只是反量化内核的火柴烧得旺产出的有效计算很少。跑量化微调或 GGUF 类推理时如果发现 SM 占用不低但端到端吞吐感人优先怀疑这类访存密集和反量化开销。2.3 参数/激活卸载与 ZeRO空间换时间的账未必能兑另一招是把优化器状态、梯度甚至参数卸载到 CPU需要计算时再从 PCIe 拷回来。听起来很灵活但它掩盖了一个数量级的差距PCIe 带宽通常只有几十 GB/s而显存带宽少说也有数百 GB/s高性能卡能到 TB 级。一个训练 step 中几十次参数来回搬运很容易把 GPU 的空闲时间拉得老长。DeepSpeed ZeRO-Offload 也会有类似问题。它把优化器状态和梯度放到 CPU显存确实降下来了但每步的通信和拷贝开销并没有消失而是从显存带宽转移到了 PCIe、甚至内存带宽上。在低显存消费级显卡上由于整机内存带宽本就不宽裕这些搬运开销可能把省下的资源吞得干干净净。我在代码层面通常会做一个快速估算把模型的参数量、梯度量、每 step 搬运次数列成表对比 PCIe 实际带宽就能算出一个 step 至少要耽误多少毫秒。如果这个数字和 profiling 出来的 GPU 空闲时间对得上那就基本坐实了 offload 是罪魁祸首。3. 真正拖住 GPU 的瓶颈往往不在显存账上3.1 GPU 在等数据是利用率低最常见的原因LLM 训练的数据管线没有图像增强那么重但 tokenizer 和 padding 相当吃 CPU。如果你的 DataLoader 在每次迭代都实时裁 raw text、实时做 tokenize、再动态 padding这一整套操作如果没开多进程GPU 很可能在每个 step 开头都要干等着 CPU 把 batch 搓出来。一个很容易观察到的现象是GPU 利用率曲线像心电图高一下低一下而不是平稳的一个高平台。这种节奏感基本说明瓶颈在 host 侧。我曾经见过一个微调脚本语料每次迭代都临时分词动态 padding 到当前 batch 最大长度还不开 num_workers。这种情况下 GPU 利用率能过 50% 都算稀奇。解决办法很朴素能提前算的都不要在训练循环里算。把语料一次性 tokenize 好存成紧凑的二进制格式训练时直接按 offset 切块DataLoader 开多 worker 并行预取每个 worker 的进程里只做切片和物化不要碰 tokenizer。3.2 Kernel 启动开销与算子碎片PyTorch 在 GPU 上执行一个算子背后要经历 Python 调度、C 分发、CUDA kernel launch 这些环节每个 kernel 的启动开销在微秒级。听起来不贵但如果模型里全是细碎的小算子——逐元素乘、mask 操作、padding 变换、dropout——累积起来就是几千个 kernel 排队启动GPU 真正干活的时间被严重稀释。在 profiling 图上看这会表现为一连串很短的 kernel中间夹着大量空白间隙。空白间隙就是 GPU 的闲置时间而它的来源既不是数据也不是显存而是 CPU 端启动 kernel 的速度跟不上 GPU 执行速度。优化方向自然是算子融合。FlashAttention 把注意力里的一长串操作融合成一个大 kernelFusedAdamW 把参数更新、weight decay、学习率调度合并成一轮访问torch.compile能在底层做图融合和 CUDA Graph 捕获对启动开销高的模型收益非常明显。代价是首次编译慢、某些动态模型可能编译失败让你的显存优化前功尽弃。3.3 显存碎片、分配器和同步点除了数据和 kernel还有一种等待来自显存分配器。PyTorch 的 CUDA caching allocator 会把已释放的显存块缓存起来复用但频繁申请不同形状的中间变量会让碎片越来越多。最典型的表现是torch.cuda.memory_summary()显示空闲显存还有 2G但实际分配一个大 tensor 却失败因为那一大块地址不连续。分配器不得不触发同步回收老块这种同步本身就是 GPU 空转。隐藏的同步点也常常被忽略。训练循环里打印一行loss.item()就会触发一次 GPU 到 CPU 的同步拷贝GPU 要停下来等这个值返回。torch.cuda.synchronize()、反复调.cpu()、在热路径里做 Python 端断言都是同类问题。排查时先看看热路径里有没有这种“顺手写下的同步操作”把它们挪出循环经常能白捡 5%~10% 的耗时改善。4. 排查链路先看时间线再钻内核别被一个百分比带偏4.1 从 nvidia-smi 开始但别停在 nvidia-smi排查的第一步我会用nvidia-smi dmon看连续采样的明细命令大概是nvidia-smi dmon -c 100 -s pucm它的输出里有 sm、mem、pcie 这几列分别表示 SM 活动率、显存带宽利用率、PCIe 读写状况。组合起来能初步判断方向如果 sm 低但 pcie 很高多半是数据搬运或 offload 卡住如果 sm 高但端到端吞吐低多半是 kernel 内部不干正事如果 sm 和 pcie 都低大概率是 CPU 数据供给或 kernel 启动间隙。但这只是粗筛。我见过太多人拿着 nvidia-smi 截图问半天其实 dmon 只能看到聚合指标看不到等待发生的具体位置。要看真实时间线必须上 profiler。4.2 PyTorch Profiler 与 Nsight 工具的组合用法PyTorch Profiler 是最快上手的核心代码很简单from torch.profiler import profile, ProfilerActivity with profile(activities[ProfilerActivity.CPU, ProfilerActivity.CUDA], record_shapesTrue) as prof: train_step(batch) print(prof.key_averages().table(sort_bycuda_time_total, row_limit30))用cuda_time_total排序你能快速找到最耗 GPU 时间的 kernel对照 CPU 侧耗时又能判断瓶颈到底在 CPU 调度还是 GPU 执行。如果 CPU 侧的算子耗时远大于对应 GPU kernel 的执行时间说明 CPU 在追着 GPU 跑启动开销和调度是大头。再往下深挖就要上 Nsight Systems 和 Nsight Compute。Nsight Systems 负责全局时间线把 CPU、GPU、内存、通信画在一条时间轴上你能直接看到 GPU 的空闲段集中在哪、持续多久。Nsight Compute 则深入某个 kernel 内部看 SM busy、Tensor Core 利用率、访存吞吐、warp stall 原因。LLM 训练里像 GEMM、FlashAttention 反向这种热点 kernelSM busy 正常应该在 90% 以上如果只有 60%多半是数据布局或 kernel 配置有问题。4.3 把“利用率”拆成“执行密度”和“计算密度”排查时我会在心里始终分开两个问题。一是执行密度SM 是不是一直在被任务填满二是计算密度任务填满的时间里做的是有效浮点运算还是解包、搬数、等待低执行密度靠数据管线重叠、CUDA Graph、算子融合来救高执行密度但低计算密度要换运算引擎或优化数据布局。这两个思路方向完全不同混在一起就容易出现“我加了半天的 DataLoader worker结果 kernel 内部反量化占用才是主因”之类的南辕北辙。5. 把这套方法用在 LLM 训练上几条最有效的调整5.1 数据供给端必须永远比 GPU 快一步对 LLM 训练来说我的推荐配置是把 tokenize 全部提前做掉样本存成 compact 的二进制格式直接用mmap或 WebDataset 按 offset 读取。这样训练循环里的 collate 只做切片和 stack完全不碰 tokenizer。DataLoader 的参数上可以参考这个起点DataLoader( dataset, batch_size4, num_workers8, pin_memoryTrue, prefetch_factor4, persistent_workersTrue, )pin_memory能让主机到设备的数据拷贝变成异步不阻塞 GPU 等待persistent_workers避免每个 epoch 重复 fork 进程的开销。注意 worker 数不是越大越好worker 太多会让 CPU 调度器和页锁内存的竞争加剧实测下来在 4 到 8 个左右往往就到平台期了再多反而掉速。5.2 算子端融合与 CUDA GraphFlashAttention 是训练 LLM 必须优先考虑的 kernel 级优化。它通过分块和重计算减少了 HBM 往返既省显存又能把注意力计算里的算子融合成大 kernel对 SM 利用率和吞吐都是正向。只要你的 GPU 支持 Ampere 及以上架构就值得换。fused 优化器也一样AdamW 这类优化器在 PyTorch 朴素实现里是两步走先算梯度更新再处理正则化。fused 版本把整个更新合并成一个 kernel减少启动场景和显存读写。torch.compile则能自动做图融合和 CUDA Graph 捕获对 kernel 启动开销明显的模型收益很直接。唯一要小心的是 MoE 这类动态路由模型torch.compile有可能退化成每次都要重捕图实际效果反而差需要单独验证。5.3 用大 batch 和 sequence packing 提高计算密度显存不够时很多人本能地把 batch 调小于是每个 step 的计算量小得可怜GPU 每跑完一轮就不得不停下来等下一轮启动。替代思路是 sequence packing把多个短样本拼接进同一个序列用 attention mask 把跨样本的注意力屏蔽掉。这样做的核心收益是原来 8 个 pad 到 2048 的小 batch可能变成 2 个真正装满 4096 的大序列。矩阵乘法的计算密度和 Tensor Core 利用效率会明显提升。虽然 mask 会让一部分算力白费但通常还是比一堆 pad 的短序列划算得多。如果显存实在紧张小 batch gradient accumulation 是兜底方案。它能减少整体同步和优化器更新的次数但注意它并不会提高每个 mini-step 的 SM 利用率。它解决的是“不能让 GPU 频繁停下来等同步”的问题不是“让每个 step 更饱和”的问题。5.4 MoE 的显存与利用率矛盾是怎么回事MoE 架构最近被反复问到一个问题所有专家参数都要常驻显存吗答案是训练框架一般默认全部常驻因为 MoE 只在计算层面稀疏激活了部分专家但参数本身都占着显存位置。如果你为了省显存把不活跃的专家卸载到 CPU等 router 选到它时再搬回来就会引入动态 offload。这类操作的问题在于专家参数从 CPU 到 GPU 的搬运是几百 MB 到 GB 级的数据量PCIe 带宽会成为硬瓶颈。route 分布一偏斜GPU 就会频繁等着某些热门 expert 从远处被拉回来SM 利用率自然被拖住。这也是为什么很多 MoE 方案宁可老老实实把专家切分到多卡上常驻也不太鼓励频繁动态卸载。微调 MoE 时如果发现“显存没爆、GPU 吃不慢”先检查代码里是不是对专家做了动态迁移或按 token 分组的通信这类隐形的等待远比想象中常见。5.5 什么时候该停止“本地省显存”去租卡跑这句话可能不太好听但很现实有一部分“GPU 吃不慢”的问题本质是用一块 8G 卡硬跑 7B 级别模型。QLoRA 虽然能塞进去但计算密度被小 batch 卡死每步的时间大量花在等 CPU、等 PCIe、等 kernel 启动上。你再怎么调 DataLoader 和算子融合天花板也在那里。这种时候如果项目真有性能要求我的建议是直接租个 24G 或 40G 的 GPU把 batch 和序列长度提上去。很多时候你会发现代码一行没改吞吐直接翻倍之前的“优化难题”直接消失了。省显存技术的本质是用计算或通信换显存当显存缺口过大时换来的开销比例也会指数级放大。让资源回到够用范围本身就是性价比最高的“优化”。6. 复盘一块 8G 卡上做 QLoRA 微调SM 占用从 35% 拉到 75%6.1 现场环境和最初现象拿我实际处理过的一个场景复盘吧。一台笔记本 GPU 8G微调一个 7B 模型显存占用压到 6.2G很安全但 step time 稳定在 2.6 秒左右nvidia-smi经常掉到 30%。代码用的是 HF Trainer 默认配置加 bitsandbytes 4bit 和 gradient checkpointing序列长度 2048batch_size 只有 1。我先用nvidia-smi dmon看了一下sm 字段普遍只有 20%~40%pcie 字段时不时冲到 30%。这个组合很典型GPU 没有被计算喂饱和而是大量时间在等数据从某个方向搬进来。但搬进来的路有两条一条是 CPU 到 GPU 的数据管线另一条是 CPU offload 的优化器状态来回搬运。需要用时间线区分。6.2 时间线上找到三个根因上了 Nsight Systems 之后现象很清楚每个 step 开头都有一段很长的 host gap然后 GPU kernel 跑一段接着又一段 host gap。逐一拆解后发现三个问题同时存在。第一DataLoader 每次迭代都实时做 tokenize 和 paddingCPU 处理单个 batch 要花掉约 300msGPU 在前半程基本是空的。第二优化器用了 CPU offload每个 step 都要把梯度拷到 CPU、把更新后的参数拷回 GPUPCIe 来回加起来约 200ms。第三QLoRA 路径下torch.compile没编译成功算子没有融合kernel 启动开销异常高。另外训练循环里还频繁调用loss.item()用于打印引入了额外同步点。6.3 改动清单与最终效果实际改动并不多。首先把语料全部提前 tokenize存成固定长度的 numpy 数组训练时用 mmap 按位置读取再关闭 CPU offload改成纯 4bit 加 gradient checkpointing显存峰值大约涨到 6.8G依然安全接着设置pin_memoryTrue、num_workers4、prefetch_factor4让数据在 GPU 计算时提前准备好最后替换成 FlashAttention顺手把打印 loss 的同步操作挪出热路径。改完之后显存占用大概在 7.1G比之前多了一点但 step time 从 2.6s 降到了 1.1s 左右每秒处理的 token 数提升了两倍多Nsight 里 SM 活动率稳定在 73%~78%。这个结果谈不上极致但方向是对的显存没有压得更高GPU 却真正吃满了。6.4 这个坑让我记住的习惯复盘之后我最大的改变是不要再把“能把显存压到多低”当成第一目标。每次引入一项省显存技术前先算两笔账——它省下多少显存它带来多少额外计算或通信然后做一次 A/B 实验拿 step time 或 token/s 说话。凡是让吞吐不升反降的哪怕显存数字再好看也要直接拿掉。你在自己的项目里遇到“显存省下来了但 GPU 吃不慢”这种困境时第一反应别是去调 batch 或换 kernel而是先跑一轮时间线分析。搞清楚 GPU 到底在等什么是等数据、等搬运、等 kernel 启动还是被反量化之类的低效指令拖住了。定位到根因再做针对性改动省下来的显存才会真正变成能吃的算力。这个排查习惯比任何一条具体优化命令都值钱。
企业数字化 ERP 产品动态
相关推荐
多模型Agent安全防御架构实战解析 1. 这不是科幻,是 Palo Alto Networks 正在部署的防御现实最近在翻 Palo Alto Networks 的最新技术白皮书和开发者博客时,我注意到一个明显变化:他们不再只谈“用 AI 检测威胁”,而是反复强调“用 AI 构建 AI 防御体”。标题里那句… · 2026/11/9 22:44:50
店铺招牌设计:3秒吸引顾客的流量密码 1. 店铺招牌的流量密码走在商业街上,最先吸引顾客注意力的往往不是商品本身,而是店铺的招牌。一块设计得当的店招能在3秒内完成与潜在顾客的第一次"对话"。我见过太多店主在装修时愿意花大价钱买最好的货架,却在招牌上草草了事&… · 2026/11/9 22:44:50
CentOS 7 安装 llvm-toolset-7-clang:SCL 多版本编译工具链实践 在 CentOS 7 上装 llvm-toolset-7-clang,是件看着简单、实际能把人卡半天的事。系统自带 GCC 4.8.5,C14 只支持到一半,C17 基本别想,很多现代项目在 configure 阶段就直接报错退出。你要么升级整个编译环境,要么用 SCL… · 2026/11/7 2:02:38
电工证没复审过期吗?通过率揭秘教你如何应对 电工证没复审过期吗?通过率揭秘教你如何应对 你是不是也遇到过这种情况:证书快到期了,却不知道该怎么做?更糟的是, 电工证没复审过期吗… · 2026/11/9 22:44:31
个人能否报名电工证的良心建议:工地太忙根本没时间复习考试 个人能否报名电工证的良心建议:工地太忙根本没时间复习考试 工地太忙根本没时间复习考试,这种感觉我太懂了,尤其是像你们驻马店中小施工企业负责人,项目一上马,人就绷着弦,哪还有心思顾及电工证考试的事。别急,这不是没辙,今天我就给你一个… · 2026/11/9 22:44:31
电工证过期了不知道怎么复审延期?换电工证要考什么网上查询最全攻略 电工证过期了不知道怎么复审延期?换电工证要考什么网上查询最全攻略 证书过期了不知道怎么复审延期,你不是一个人。我认识不少焦作的朋友,他们因为工作变动、证件过期、换证流程不熟悉,差点丢了饭碗。今天就来聊聊 换电工证要考什么 ,怎么通过… · 2026/11/9 22:44:31
南京电工证哪里有的考在哪考别被中介坑了 南京电工证哪里有的考在哪考别被中介坑了 你是不是也在纠结“南京电工证哪里有的考”,又怕报名被坑?别急,我来给你一针见血地说清楚。现在市面上培训机构五花八门,有的甚至打着“包过”的旗号,结果你花了钱,人也没学好,考试还挂了。今天就从信阳浉河区… · 2026/11/9 22:44:31
考电工证如何考过,网上查询报名流程全攻略 考电工证如何考过,网上查询报名流程全攻略 怕考不过白交培训费?考电工证如何考过是很多想入行或者升职的工友最关心的事。特别是洛阳涧西区房建工程的工友,电工证不光是上岗的硬门槛,更是提升收入和职业发展的关键。很多人交了培训费,担心考试不过白费钱… · 2026/11/9 22:44:31
铜陵电工证选哪家官方回应怕考不过白交培训费 铜陵电工证选哪家官方回应怕考不过白交培训费 你是不是也担心,花了几千块报名电工证培训,结果考试一塌糊涂, 怕考不过白交培训费… · 2026/11/9 22:43:30
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/9 2:05:04
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/9 3:11:26
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/9 5:24:17
电工证不需要考复审延期怎么办理 通过率揭秘 电工证不需要考复审延期怎么办理 通过率揭秘 不知道去哪报名怕被中介坑?电工证不需要考复审延期怎么办理?最近不少学员都在问这个问题,尤其是刚拿到证的新手,对复审流程一知半解,生怕被培训机构套路。别担心,今天咱们就来聊聊电工证复审的真相,尤其是… · 2026/11/9 0:00:54
砀山哪里考电工证的多 电工证报考别踩坑 郑州报考避坑指南 砀山哪里考电工证的多 电工证报考别踩坑 郑州报考避坑指南 工地太忙根本没时间复习考试,这几乎是所有一线施工人员的共同心声。尤其是像电工这样的特种作业,操作证不仅关系到能不能上工,还直接关系到工资高低和职业发展。可偏偏,很多人连怎么报名、去哪… · 2026/11/9 0:00:54
南方电网电工作业含金量高吗?应届生怎么考怎么干? 南方电网电工作业含金量高吗?应届生怎么考怎么干? 工地太忙根本没时间复习考试,这不是个别现象,而是很多刚毕业的工程类学生面临的现实。南方电网电工作业的证书,听起来门槛不低,但 含金量… · 2026/11/9 0:00:54
双极步进电机控制实战: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/9 11:23:35
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/9 11:23:58
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/9 11:25:08