你盯过Transformer论文里那张经典结构图吗反正我盯了很多遍每次都以为自己懂了回头写代码时还是在多头注意力那一块卡到怀疑人生。结构图把所有模块画得清清楚楚——Embedding、位置编码、多头注意力、残差连接、LayerNorm、Feed Forward——但它唯独不告诉你数据究竟是怎么一层层流过去、每一步张量变成了什么形状、注意力权重具体落在哪里。我后来意识到问题不在我笨而是静态结构图这种表达方式本身就缺了最关键的信息维度过程和时间。于是我一咬牙自己动手做了一个Transformer可视化工具让模型的前向计算变成可以一步一步往前推演的动画每一步都标注形状、数值、注意力分布。这篇文章就记录一下这个工具从想法到落地的完整过程包括设计思路、技术选型、实现细节以及开发中踩过的一堆坑。1. 结构图为什么救不了你可视化工具的设计动机1.1 结构图只讲“有什么”不讲数据怎么流动结构图本质上是模型的“零件爆炸图”。它告诉你系统里有注意力模块、有前馈网络、有归一化层并且用箭头标出了它们的前后关系。但理解一个复杂系统光知道有哪些零件是不够的你得知道它们在运行时如何协作。这就像看一张洗衣机内部结构图你知道滚筒、电机、进水阀的位置但完全不知道一次洗衣流程里水是怎么循环的、电机在哪个阶段发力、排水口什么时候打开。只有把动态过程放出来你才算真正理解了这台机器。Transformer也是这样。它的核心是一个数据流过程一串token经过Embedding变成向量加上位置信息然后在每一层里完成“注意力混合信息”和“前馈网络逐位置变换”这两个基本操作中间依靠残差连接保住原始信息依靠LayerNorm稳住数值分布。这个流动过程在静态结构图上完全体现不出来。我的工具最基本的想法就是把前向传播拆成一步步可暂停、可回放的动作让读者亲眼看到句子从“词索引”变成“带语义的向量”再到“层层加工后的表示”的全过程。1.2 真正的入门拦路虎张量形状与维度变换如果说数据流动是主线那维度变换就是这条主线上最折磨人的关卡。Transformer中几乎每隔几步就发生一次张量形状变化而且很多形状变换还不是简单的reshape而是带维度调换的permute。我见过太多人——包括当时的我——被下面这个流程绕晕过输入x从 [batch, seq_len, d_model] 出发被三套权重矩阵分别投影成Q、K、V然后为了做多头注意力要把最后一维拆成 num_heads 和 head_dim 两个维度变成 [batch, num_heads, seq_len, head_dim]接着Q和K做点积得到分数这时的形状是 [batch, num_heads, seq_len, seq_len]再softmax、再与V加权求和恢复到 [batch, num_heads, seq_len, head_dim]最后合并多头转回 [batch, seq_len, d_model]过输出投影。结构图上这整个过程被简化成一个写着“Multi-Head Attention”的圆角方块。你完全看不到内部维度在几个不同的形状之间来回切换。新手在阅读代码时最常犯的错误就是混淆view和permute而本质原因就是脑子里没有建立起“这一步之前是什么形状、这一步之后是什么形状、为什么需要这个形状”的连续图像。所以我的工具把“形状追踪”做成了一个核心面板每走一步都用彩色标签把当前所有活跃张量的形状标注出来并且解释这一步为什么要做这个变换。1.3 “注意力”这个名字反而让新手更糊涂“注意力机制”这个名字非常有迷惑性。它让人下意识觉得模型像人一样知道该“看”哪里。但实际操作是一系列矩阵乘法用查询Query去和所有键Key做点积然后缩放出分数softmax转成权重再拿这个权重去加权求和值Value。所谓注意力从数值上看就是一组和为1的权重。不亲手看一下这些权重长什么样你只会停留在“感觉懂了”的阶段而无法真正产生直觉。我记得自己第一次认真观察一个训练好的小模型的注意力矩阵时才真正明白什么叫“学到了模式”有的head会持续关注前一个词有的head会集中关注句法上的核心动词。这种洞察必须建立在看到具体数值图像的基础上光靠公式推演是得不出来的。所以我在工具里专门做了一个注意力放大镜用热力图和连线图两种方式展示每个attention head的内部权重并且按token标注清楚让用户直观地看到“注意力”到底是什么意思。2. 工具整体设计与技术选型把静态图变成可交互的数据流演练场2.1 为什么做成网页应用而不是本地脚本一开始我其实想得很简单写个Python脚本把每一步的中间结果print出来不就行了但我很快发现print一堆数组只会让问题更糟。我们需要的是视觉化的、可以交互的、可以来回拖动的体验而不是刷屏的数字。于是我决定做一个网页应用。选型时我主要考虑几点。第一交互能力。浏览器天生适合做这种逐步播放、滑块拖动、悬浮查看的操作。第二分享成本。做成网页后发给朋友或者学生只需要一个链接不需要安装Python环境和依赖。第三可视化生态。前端有D3.js、Canvas、WebGL这一整套成熟的绘图工具做热力图和连线图非常顺手。后端方面我选择了Python FastAPI。原因很朴素Transformer的实现和推理用Python最顺手FastAPI写起来轻量又可以方便地通过WebSocket把中间状态推给前端。整个工具的前后端分工很清晰后端负责跑模型、采集中间状态、计算统计量前端负责把状态渲染成界面、控制播放流程、处理用户交互。2.2 四大核心功能逐步推演、形状追踪、注意力放大、数值面板在设计功能时我没有一上来就堆功能而是先问自己一个问题一个初学者拿着这个工具学习Transformer他最需要看到什么我最终确定了四个核心模块。逐步推演是整个工具的主干。用户在输入框里输入一句英文我的第一版只支持英文中文分词是另一套复杂的事情点击运行后数据就开始了在模型中的旅程。每走一步界面右侧的推演步进器就会高亮当前到达的模块从“Tokenization与Embedding”、“位置编码叠加”、“Q/K/V投影”到“注意力分数计算与缩放”再到“Softmax权重生成”、“加权求和”、“多头合并”、“残差连接”、“LayerNorm”、“Feed Forward”……每一步都可以暂停观察也可以回退重看。张量形状追踪是解决“维度恐惧”的专门设计。界面左侧有一个实时更新的张量形状面板用卡片形式列出当前步骤涉及的所有张量名称、当前形状、实际数值样例。比如做Q/K/V投影那一步你会清楚地看到 [1, 6, 4]batch16个tokenembedding4被三个权重矩阵投影成三份 [1, 6, 4]然后如何被拆成 [1, 2, 6, 2]2个head每个head维度2。这个面板一定要和推演步进器联动因为形状变化只有放在数据流动的语境里才真正有意义。注意力放大镜负责把“注意力”这个抽象概念变成图像。当推演到注意力计算环节时用户可以点击任意一个head看到原本 [num_heads, seq_len, seq_len] 的注意力矩阵被单独提取出来以热力图样式渲染。我同时提供两种视图矩阵热力图和token连线图。矩阵热力图适合整体观察注意力分布格局连线图则能直接展现出“当前token与哪些token关系更强”的关系结构对解释自注意力机制更加直观。数值面板是给“想看门道不想只看热闹”的用户准备的。每一步都会显示当前输出张量的均值、方差、最小值、最大值并对关键位置做特殊标注——比如在softmax前后面板会对比原始分数和归一化分数的数值范围解释为什么Transformer要缩放点积、为什么要用softmax。这个面板是四个功能里最不炫酷但知识密度最高的一个不少朋友反馈说靠着这个面板补上了以前忽略的很多细节。3. 核心功能实现从模型埋点到注意力热力图3.1 如何在模型推理时拿到每一步的中间状态工具的核心难点之一是怎么在不破坏模型原有逻辑的前提下把每一步的中间状态采集出来。我一开始想用PyTorch的forward hooks因为这样不需要改动模型代码。但很快发现了一个致命问题hook拿不到模块内部的中间量。具体来说PyTorch自带的register_forward_hook只能拿到模块的输入和输出。对于一个nn.MultiheadAttention模块来说你能拿到输入x、输出attention结果但拿不到里面的Q、K、V矩阵更拿不到softmax之前的注意力分数。而这些恰恰是最需要可视化的东西。所以hooks在这个场景下并不够用。我的最终方案是把模型中需要可视化的模块替换成自己写的、带状态导出的版本。不依赖黑魔法直接重写forward函数在关键位置把中间量保存到一个专用的状态字典里。代码大致长这样class VisualizableSelfAttention(nn.Module): def __init__(self, d_model, num_heads, state_tracker): super().__init__() self.d_model d_model self.num_heads num_heads self.head_dim d_model // num_heads self.state_tracker state_tracker # 实际项目里这里用nn.Linear做投影 self.w_q nn.Linear(d_model, d_model) self.w_k nn.Linear(d_model, d_model) self.w_v nn.Linear(d_model, d_model) def forward(self, x): # x: [batch, seq_len, d_model] batch, seq_len, d_model x.shape q self.w_q(x) # [batch, seq_len, d_model] k self.w_k(x) v self.w_v(x) # 拆多头 q q.view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2) k k.view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2) v v.view(batch, seq_len, self.num_heads, self.head_dim).transpose(1, 2) # 缩放点积注意力 scores torch.matmul(q, k.transpose(-2, -1)) / (self.head_dim ** 0.5) attn_weights torch.softmax(scores, dim-1) # 把内部状态交给tracker self.state_tracker.capture( qq, kk, vv, scoresscores, attn_weightsattn_weights ) out torch.matmul(attn_weights, v) # 合并多头转置 reshape out out.transpose(1, 2).contiguous().view(batch, seq_len, d_model) return out在这段代码里state_tracker是一个负责状态收集的组件每次前向传播都会把当前的Q、K、V、原始分数、归一化权重记录下来并且附带上详细的形状信息和数值统计。这个方案看起来朴实但它让我意识到一个很重要的点做可视化工具时“侵入式改造”往往是比“优雅解耦”更务实的路线为了拿到内部状态该改forward就得改。3.2 注意力矩阵的可视化热力图、连线图与对比度处理拿到attention weights之后怎么把它画出来又是一个值得琢磨的问题。我的工具里选了两种互补的表现形式。矩阵热力图最简单本质上就是把 [num_heads, seq_len, seq_len] 的数据画成一个个格子行代表查询token列代表被关注的token颜色越深表示权重越大。这个方式的优点是全局视图清晰一眼可以看出某个head是关注前一个词还是聚焦特殊token。连线图则更符合“注意力”的隐喻。以当前token为中心用弧线连接到它关注的其它token弧线的粗细和透明度与权重正相关。这个视图非常适合用来解释自注意力的直觉含义。不过在实现时有个细节要注意如果同时画出所有token之间的所有连线图形会变成一个无法辨认的毛线球。我在实践中选择了“聚焦模式”——用户点击某个token后只显示这个token对其他token的注意力连线。还有一个非常实用的处理对比度增强。原始的attention weights经过softmax之后分布往往很不均匀大量权重集中在少数位置剩下的接近0。直接渲染这个原始值热力图大部分格子颜色都很浅视觉上几乎看不到信息。我的处理方式是提供两个可切换的视图raw视图展示原始权重normalized视图对每行的权重做一次额外的标准化让哪怕很小的差异也能通过颜色体现出来。这个经验非常重要因为很多第一次做注意力可视化的开发者都会栽在这里——不是没拿到数据而是画出来的图根本没法看。3.3 形状与数值面板让每个变化都对应到公式“形状追踪”听起来简单做起来难在如何让形状变化和公式形成一一对应关系。我在工具里的做法是每一条形状记录都绑定一个“解释节点”当一个张量发生形状变化时界面上会同时弹出对应的公式片段和文字描述。举个例子当执行q.view(batch, seq_len, num_heads, head_dim).transpose(1, 2)这一步时形状面板会显示一行动态变化[batch, seq_len, d_model] → [batch, seq_len, num_heads, head_dim] → [batch, num_heads, seq_len, head_dim]并同步给出中文解释先把最后一维拆成多头再把“头数”这个维度提到序列长度之前因为后面要按头独立做矩阵乘法。这个设计的价值在于它把新手最容易卡住的“维度即语义”这个概念落到了实处。很多读者反映以前只知道代码里要写transpose但不知道为什么看了这个面板之后终于把公式和代码对应起来了。数值面板则承担了“为什么”的深层解释。shapes告诉你形状发生了什么变化数值统计告诉你这个变化带来的后果。比如在缩放点积注意力这一步面板会显示原始分数矩阵的数值范围可能动辄几十甚至上百然后显示除以 sqrt(d_k) 之后的数值范围再显示softmax之后的分布。配合文字说明用户能直观理解缩放的意义如果不缩放过大的分数会让softmax进入饱和区梯度消失模型训练不稳定。为了让数值面板更友好我把所有统计量都用柱状分布图示意而不是丢一堆小数让用户自己看。4. 开发踩坑实录与排查技巧4.1 别迷信hooks该改forward就改forward前面提到过我第一版天真地试图用PyTorch的forward hooks完成状态采集省去改动模型的麻烦。结果折腾了半天最后发现hook体系在设计上就没打算让你获取模块内部张量。你可以在hook里拿到模块的输入输出但模块中间计算出来的、你真正关心的张量根本不会经过hook。后来我换成了重写forward的方案代码看起来不那么“优雅”但问题立刻解决了。这个事让我明白做工具开发时往往“直接用”比“做得漂亮”更重要。如果你的目标是拿到attention weights那就坦坦荡荡地改模型的前向逻辑而不是在外围打转。我身边也有人用“注册临时hook 在forward里塞全局变量”之类的hack但维护起来只会更痛苦。4.2 深层注意力热力图一片模糊怎么办第二个让我头疼的坑出现在测试阶段。我用一个12层的模型跑测试发现前几层的注意力热力图还挺好看的能明显看出集中关注的模式但到了深层网络热力图几乎变成了一片平滑的色块所有地方都差不多。起初我以为是自己代码有bug查了很久后来才知道这是深度Transformer的一个常见现象深层注意力的分布往往更均匀信息在高层已经被充分混合注意力权重变得比较平滑。这本身是模型运作的真实状态但如果直接展示给用户大家会觉得“好像也没什么规律啊”甚至产生误解。我的解决办法是对可视化做分层处理。工具里提供了“原始权重视图”和“差异化视图”两种模式。差异化视图会对每一行的注意力权重做rank归一化把最受关注的token排在最前面用序号和颜色深浅共同标注。这样即使用户看到的是平滑分布的注意力也能清晰地看到权重排序上的细微差异。同时我在界面上加了一句说明文案提醒用户深层注意力分布平滑是正常现象不代表模型没有学到信息。4.3 长序列渲染性能优化与数据瘦身可视化工具的性能问题藏得比较深输入短句时完全没感觉但一旦输入超过几十个token各种性能问题就冒出来了。第一版我用SVG来画注意力连线图token一多DOM节点数量剧增浏览器直接卡成PPT。后来我果断换成了Canvas绘制在Canvas上一次性画出所有连线和节点性能明显提升。如果你也要做类似的可视化工具建议在渲染大量图形元素时优先考虑Canvas或WebGL不要迷信SVG的便利性。另一个隐藏的性能杀手是前后端数据传输量。前向传播过程中采集到的中间状态非常占空间每个token的embedding向量、每一对token之间的注意力权重、每一层的输出……如果全部塞进JSON通过HTTP传给前端数据量会膨胀到几百万条数字。我的处理方式是前后端各退一步后端仍然采集完整状态但在传输时做了降采样和分页前端按需拉取当前步骤所需的数据而不是一次性把整个模型的状态全部加载进来。比如注意力热力图只在用户点击查看某个特定head时才去取对应的矩阵。5. 使用建议与后续扩展5.1 这套工具在学习和教学中的正确打开方式工具做完了我也陆续让一些朋友和学生试用慢慢摸索出了一套比较高效的使用方式。如果你是初学者我不建议你从第一层开始一层层看。更好的路径是从单层注意力开始先用短句5到6个token即可跑一遍推演把“输入-Embedding-位置编码-注意力-QK点积-softmax-加权求和-残差-LayerNorm-FFN”这条主干流程吃透然后再逐步增加层数观察不同层之间注意力模式的差异。每一步都打开数值面板对比softmax前后的变化这会帮你建立很强的数值直觉。如果你是用来辅助阅读论文最有效的做法是交叉对应看到论文中某个公式时立刻在工具里找到对应的可视化步骤。比如读Attention(Q,K,V)softmax(QK^T/sqrt(d_k))V时我建议你多花几分钟观察工具展示的Q、K、V形状变化以及缩放前后的分数差异这比默念十遍公式都管用。教学场景下我更推荐用连线图配合具体例句做演示。选一句有明显句法关系的句子比如“I put the book on the table”让学员点击token“book”观察它和put、table之间的注意力权重分布很快就能理解自注意力机制是如何编码“语义关联”的。工具最宝贵的点就在这里把抽象概念变成可以操作的直观经验。5.2 接入预训练权重和训练过程后的更多玩法目前的工具默认使用随机初始化的小型Transformer这样做的好处是计算快、结构清晰适合学原理。但只停在随机权重上工具的潜力完全没有发挥出来。我接下来计划做两个扩展方向。第一个是接入预训练权重比如BERT-base或者GPT-2的小号变体让用户直接观察真实模型在语言数据上学到的注意力模式。这能帮很多做模型分析的人直观理解“head specialization”现象。第二个是接入训练过程可视化在模型训练时实时记录每一轮的loss变化和注意力模式的演化让用户看到模型如何从“均匀关注”逐渐形成有结构的关注模式。这一步比静态权重演示更有教育意义也更有意思。另外我还想把工具扩展成支持更多Transformer变体的通用框架比如Vision TransformerViT。把图像切成patch再送入Transformer的流程可视化之后理解起来会直观很多特别适合正在学习视觉Transformer的同学。如果你也在做类似的学习工具我的建议是先选择一个窄场景做深做透再考虑横向扩展不要一开始就想着支持所有模型变体。最后分享一个小体会做这个可视化工具的过程本质上等于把Transformer从零到尾重新实现了一遍。为了弄清楚每一步应该展示什么内容、什么形状、什么数值我不得不把之前模棱两可的概念逐个搞清楚。这个“为了教别人而学习”的过程对理解模型的帮助其实远超我的预期。如果你也处在“结构图看了很多遍代码却写不出来”的阶段我很推荐你也试试“做工具”这种学习方法用输出倒逼输入可能比闷头刷论文要高效得多。
企业数字化 ERP 产品动态
相关推荐
Transformer异常层排查:5个模型的数值稳定性实战 1. 测试起因与测评对象:五个 Transformer 的选型逻辑这几天我泡在一个相当折磨的问题里:手头的一个序列建模任务在换到 Transformer 之后,训练 loss 怎么调都不对劲,曲线看着在下降,但验证集指标就是上不去。顺手把每一… · 2026/11/4 16:15:27
大模型分布式训练:集合通信与模型并行策略调优指南 1. 从一次扩展效率腰斩说起:集合通信究竟卡在哪半夜两点,某实验室的几十张加速卡一起跑一个百亿参数级别的训练任务,单卡吞吐看着挺漂亮,扩到八卡也还行,扩到三十二卡的时候每秒处理的样本数只涨了不到一半,… · 2026/11/5 18:58:39
集合通信与模型并行策略:多卡训练通信代价与显存估算 把模型摊到几十张卡上跑训练,头一两周基本都在和两件事较劲:显存炸了,以及卡在等卡。前者要靠模型并行策略把参数和激活切开,后者要靠集合通信把切碎的东西传起来,而这两件事本质上是同一枚硬币的两面——你怎么切&… · 2026/11/7 11:03:41
新吴区低压电工证怎么转?上岗必备的避坑指南来了 新吴区低压电工证怎么转?上岗必备的避坑指南来了 之前考的证在外省能不能转过来?这个问题我听得太多了,尤其是在新吴区做低压电工的兄弟姐妹们,不少人在外地干了几年,现在想回本地发展,最担心的就是证书能不能转。今天我就用一个真实的案例,带你看看… · 2026/11/10 11:52:11
宿迁高压电工证培训费用保姆级教程:外省证怎么转过来? 宿迁高压电工证培训费用保姆级教程:外省证怎么转过来? 之前考的证在外省能不能转过来?这是很多准备报考或已经持证的高压电工最关心的问题之一,特别是像在宿迁这样的城市,外来务工人员较多,跨省证件转换成了一个绕不开的现实问题。别急,今天这篇保姆级… · 2026/11/10 11:52:11
金华哪里复审电工证内部消息全知道!实操考试心里没底怕挂科?别慌! 金华哪里复审电工证内部消息全知道!实操考试心里没底怕挂科?别慌! 实操考试心里没底怕挂科?其实你不是一个人。金华地区不少电工在复审时都遇到过类似问题,担心自己操作不规范,担心考试不过,甚至担心错过复审时间。但你知道吗?其实… · 2026/11/10 11:52:11
电工证一般什么考题怎么报名跨省转证全攻略 电工证一般什么考题怎么报名跨省转证全攻略 之前考的证在外省能不能转过来?这是很多电工朋友问得最多的问题。尤其是像漯河这样的城市,很多人在外地打工或上学时考了电工证,回本地后却因为证件归属地不同而遇到不少麻烦。如果你也在纠结“… · 2026/11/10 11:52:11
莱芜低压电工证办理怎么考?证书过期了不知道怎么复审延期?网上查询全攻略 莱芜低压电工证办理怎么考?证书过期了不知道怎么复审延期?网上查询全攻略 证书过期了不知道怎么复审延期?你不是一个人。很多人在拿到低压电工证后,因为工作变动、政策调整、遗忘时间等原因,导致证书到期未及时处理,结果不仅影响了工作,还可能面临违法… · 2026/11/10 11:52:11
应急厅特种工安全员多少钱?新手报名全知道 应急厅特种工安全员多少钱?新手报名全知道 急需特种作业证才能上岗找工作,现在连工地、工厂、企业都要求持证上岗, 应急厅特种工安全员… · 2026/11/10 11:51:11
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/10 2:16:10
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/9 3:11:26
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/9 5:24:17
丹寨电工证怎么考?内部消息透露报考条件和流程 丹寨电工证怎么考?内部消息透露报考条件和流程 急需特种作业证才能上岗找工作,这是很多电工朋友最真实的焦虑。尤其是像丹寨电工证这样的证书,直接影响你的职业发展和薪资待遇。但你知道吗?很多人都在报考过程中踩坑,有的甚至花了几千块最后白忙一场。今… · 2026/11/10 0:00:13
成都建筑电工证办理郑州报考避坑指南 成都建筑电工证办理郑州报考避坑指南 证书过期了不知道怎么复审延期?别慌!这篇文章手把手带你解决成都建筑电工证办理的难题,顺便避掉郑州报考的那些坑。不管你是刚拿到证的新手,还是准备复审的老电工,这波干货都别错过。… · 2026/11/10 0:00:13
梅州低压电工证怎么考?怕被中介坑?手把手教你全国通用流程 梅州低压电工证怎么考?怕被中介坑?手把手教你全国通用流程 你是不是正为梅州低压电工证发愁?不知道去哪报名,怕被中介骗钱,又担心证书是不是全国通用?其实,这些顾虑都是正常人会有的。今天我就从一个在开封房建工程一线干了几年的电工角度,手把手教你… · 2026/11/10 0:00:13
双极步进电机控制实战: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