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

基于Hadoop与Spark的大语言模型多维度性能评估系统实践

发布时间:2026/11/4 9:11:00 来源:云帆数科 栏目:资讯中心
基于Hadoop与Spark的大语言模型多维度性能评估系统实践
1. 为什么要把大模型评测从单机搬到Hadoop与Spark上做LLM选型和部署的人应该都有这种体会模型压测这事看着简单真要做得可靠、可对比、可追溯工作量完全不亚于部署一个在线服务本身。我最初遇到的场景是团队里同时评估Qwen、Llama系和DeepSeek几个开源模型每个人拿一张A100跑vLLM再用Python脚本开几十个线程发请求统计完把结果贴在飞书表格里。第一轮测评还能应付第二周问题就全暴露了不同人提交的指标口径不一致有人统计的是处理完成时间有人统计的是流式首Token时间压测数据没地方统一存跑完就丢最麻烦的是想分析并发从32提高到64时P99延迟到底恶化到多少所有人都得重新跑一遍。这时再去考虑基于Hadoop与Spark构建大语言模型多维度性能评估系统逻辑就很自然了。HDFS负责统一纳管海量原始评测日志Spark负责把日志转成可量化的指标结论可视化平台负责让管理者不用看Excel也能看懂趋势。这套系统解决的不是能不能测而是测完的数据如何变成组织资产——同一个模型在不同并发、不同提示词长度、不同推理引擎下的表现全部沉淀下来后续做容量规划、模型选型、成本核算都有据可依。当然也不是所有团队都需要上这套架构。如果你只需要单卡上简单验证一个模型能不能跑通直接用text-generation-benchmark这类工具就够了。但如果你面临多模型对比、多引擎横向评估、定期回归验证、需要向管理层输出资源预算结论那么Hadoop Spark这套组合确实值得投入。整个系统从设计到落地我用到的核心组件就是HDFS做数据底座、Spark做离线聚合计算、ECharts做前端可视化整个技术栈都不小众遇到问题能找到的参考资料也多。2. 系统整体结构HDFS管数据Spark管计算可视化管结论这套系统的架构我拆成了五层每一层边界划得很清楚。第一层是评测任务编排层负责定义评测任务参数比如模型服务地址、并发梯度、请求总条数、提示词数据集第二层是负载注入与采集层并发压测工具向模型服务发请求同时在每个请求周期里采集TTFT、生成延迟、GPU显存占用和吞吐量等原始数据第三层是数据落地层原始日志经过轻量清洗写入HDFS按评测批次和日期分区存储第四层是计算层Spark作业定期扫描新增评测批次计算多维度聚合指标并写出结果表第五层是可视化服务层前端直接查Spark产出的结果表渲染指标趋势图、对比图和资源热力图。2.1 每一层为什么这么选负载注入这个环节我没有自己造轮子直接用了基于Python的locust扩展。原因很简单它天然支持分布式压测从多台压力机同时对模型服务发起请求能模拟出比单机脚本更接近生产环境的并发请求分布。采集到的原始记录是JSON格式每行包含请求ID、批次ID、模型名、推理引擎、并发数、提示词长度、生成长度、TTFT、总延迟、是否成功、GPU显存峰值等字段。数据落地选择HDFS而不是普通数据库核心原因是原始日志量极大。一次完整的并发梯度评测从并发1到128共七档每档发5000条请求一次就是35000条记录如果做多模型多引擎交叉评测原始日志动辄上百万行。这种规模的半结构化日志存PostgreSQL也能扛但后续做大规模聚合分析会越来越吃力。更关键的是HDFS上可以直接被Spark原生的spark.read.parquet读取省去了从数据库导数据到分析平台的环节整条链路没有任何数据搬运成本。2.2 批处理比流处理更适合这个场景有同行问过我评测数据是实时产生的为什么不用Flink或者Spark Streaming我当时的回答是评测任务本质是批任务每轮评测跑完才需要统计结论没有毫秒级实时性诉求。用Spark批处理在凌晨定时跑一次聚合结果表在早上上班前写好可视化页面打开就有最新数据完全够用。引入流处理会白白增加运维复杂度你说对吧。但如果以后要做评测过程中的实时告警——比如某个并发档位下P99延迟突然超过阈值就自动熔断——那再叠加一套流任务是合理的演进方向。下面用一张简表回顾各层的角色层级承担任务关键组件评测编排定义批次、并发梯度、数据集自研调度脚本负载注入分布式并发压测locust 扩展数据落地海量原始日志统一存储HDFS Parquet离线计算多维度指标聚合Spark DataFrame API可视化指标展示与对比分析ECharts / Superset3. 多维度指标体系别只盯着吞吐量一个数字很多评测工具默认给你三个指标总延迟、吞吐量、成功率。这套系统我做的最重要的设计是把指标体系拆成了四个维度来收集和存储这样多维度性能评估才真正落在实处。3.1 能力维度与性能维度的区分能力维度衡量答得好不好包括在标准评测集上的准确率、ROUGE/BLEU分数、passk等。这个维度其实不依赖Hadoop和Spark也能算但多模型多版本频繁迭代时能力评测结果需要和对应版本的性能数据做联动分析所以我把能力评测结果也拍成parquet写入HDFS统一纳入数据治理。性能维度才是Spark大显身手的部分。细分下来包含首Token延迟TTFT用户感知到的第一个字出来有多快、端到端请求延迟、Token生成吞吐量、并发承载上限、失败率与超时率。其中TTFT和P99延迟是用户体感最强的两个指标前者决定对话时打字机的状态后者决定系统在高峰期的稳定性。3.2 资源与成本维度不可省资源和成本维度是很多自建评测脚本完全忽略的。GPU显存峰值、平均利用率、单次评测的服务器能耗折算、按Token数计算推理成本——这些数据单独看意义不大但放到Spark里和性能指标做关联分析就很有说服力。比如模型A比模型B吞吐量高20%但显存占用高了35%单位Token成本反而贵了8%这种结论只有数据关联后才能自然浮现。维度划分清楚了表结构也就顺势定下来了。原始请求日志表按行存每一条请求的明细批次汇总表按批次存聚合后的指标资源快照表存压测过程中周期性采样的GPU信息。三张表都以批次ID为主键或分区键Spark作业做跨表JOIN也有明确的关联线索。4. 评测数据在HDFS上的预处理格式、分区与合并策略数据存储这块看着基础实操中却是最容易返工的地方。我第一版直接用Spark把JSON文件读进来不做任何预处理就落HDFS结果跑了三次就出问题凌晨批量写入时小文件激增NameNode内存吃紧后续Spark读取时任务数据倾斜严重。后来踩坑总结下来三件事必须前置。4.1 用Parquet替代纯JSON文件HDFS上存储的原始数据统一转化为Parquet格式压缩选用Snappy。理由很直白Parquet是列式存储Spark对它做谓词下推效果最好查询时只读取需要的列能省掉大量IO。我做过实测同样一批评测日志JSON格式在HDFS上占用约8.6GB转成ParquetSnappy之后只有2.1GB列裁剪后Spark作业读取的数据量再降一个数量级。数据写入之前会先做一层轻量清洗丢掉字段缺失的记录、统一时间戳格式、把模型名和引擎名映射成枚举编码。清洗规则很简单没必要上复杂的ETL框架。4.2 分区粒度设计关系Run效率分区策略直接决定Spark作业后续跑得快不快。我按batch_date/dt和batch_id两级分区组织数据。两级的考虑是日常查询绝大多数时候按日期过滤最近数据而按批次过滤能一次性把一个评测任务的所有明细取出来不需要额外的索引维护。如果你把分区粒度过细比如再叠加model_name层HDFS会产生大量小目录Spark调度开销飙升过粗则单分区文件过大并发读取仍会碰到热点。我这套两级分区方案在百万级请求记录下实测单批次Spark聚合作业能在3到5分钟内完成属于可接受的范围。4.3 合并小文件是必须做的日常维护小文件问题是HDFS上的经典病。初期每天压测结束后几十个executor并发写parquet文件每个分区下可能产生上百个小文件。解决方案是两道工序写入前用repartition(col(batch_date))或者coalesce(n)控制分区数在分区内尽量写出大文件写入后额外起一个轻量合并作业定期把小于128MB的parquet碎片合并。合并作业没必要天天跑批次数据量小时可以攒三天一跑。做完这套之后Spark作业执行计划的扫描文件数从几千降到几十效率提升非常明显。注意如果你的评测数据量在百万行以内这套处理看上去确实有些重但考虑到系统要长期沉淀多轮评测数据提前建立数据治理规范后面扩容时才不会被迫推翻重建。5. Spark分布式聚合逻辑从原始请求日志到性能结论Spark在这套系统里承担的计算任务本质上可以总结成一句话把散落在HDFS上的海量请求明细按评测批次、模型名、并发档位、推理引擎等维度聚合出可对比的指标。下面给出我最常用的几个核心计算场景的写法。5.1 按批次与并发档位聚合延迟分布分布式环境下计算百分位数不用自己造轮子直接用Spark SQL自带的percentile_approx函数。下面是批次维度聚合的关键代码from pyspark.sql import functions as F raw spark.read.parquet(hdfs://namenode/warehouse/llm_eval/raw) summary ( raw .filter(F.col(batch_id) BATCH_ID) .groupBy(model_name, engine, concurrency) .agg( F.count(*).alias(total_requests), F.avg(ttft).alias(avg_ttft), F.expr(percentile_approx(ttft, 0.5)).alias(ttft_p50), F.expr(percentile_approx(ttft, 0.95)).alias(ttft_p95), F.expr(percentile_approx(total_latency, 0.99)).alias(latency_p99), F.sum(output_tokens).alias(total_output_tokens), F.sum(output_tokens).alias(total_output_tokens), F.mean(gpu_mem_peak).alias(avg_gpu_mem_peak) ) )这里必须提一个容易被坑的点percentile_approx默认相对误差是0.05对P99这种尾部指标来说误差会被放大可能直接影响是否达到SLO的结论。实际项目中我建议显式传入第三参比如percentile_approx(ttft, 0.99, 0.001)把误差控制到千分之一。代价是计算量略有增加但对评测数据量级来说完全可以承受。5.2 吞吐量的两种口径尽量都保留对于吞吐量指标有两种常见口径它们的含义完全不同。第一种是成功请求的Token生成速率用sum(output_tokens) / sum(total_latency)计算第二种是系统层面的有效吞吐考虑并发请求和失败请求用sum(output_tokens) / max(end_time) - min(start_time)计算。建议两个字段都算出来写入结果表因为容量规划场景看系统有效吞吐而模型性能对比场景看Token生成速率。我见过不少评测报告只给一个吞吐量数字导致后端做资源规划时一次又一次返工根源就是口径没对齐。5.3 横向对比分析的Spark读取口径多模型横向对比是另一个高频场景。当年想给管理层出一张三个模型分别在并发32、64、128下的P99延迟对比的报告其实代码逻辑非常简单把同一批次内的多模型指标表做一次透视。compare ( summary .groupBy(concurrency) .pivot(model_name) .agg(F.round(F.first(latency_p99), 2).alias(latency_p99)) .orderBy(concurrency) ) compare.write.mode(overwrite).parquet(hdfs://namenode/warehouse/llm_eval/report)这类作业的数据量不大重点是让可视化服务能快速查到一张已透视好的宽表前端拿到的数据已经不需要二次处理。Spark的计算优势不在于这单次查询有多复杂而是面对一个月几百个评测批次积累下来的几百GB明细任何时候想追溯任何一批历史数据的结论都能在分钟级完成这是单机Pandas脚本完全做不到的。6. 可视化平台不止画几张折线图那么简单可视化这一层最容易做成把表格换个样子但真正有价值的可视化是帮助人快速回答问题。这套系统的可视化平台有四个固定页面对应四类核心角色。6.1 性能总览大屏性能总览大屏服务运维和平台管理员核心是一张气泡图。横轴是吞吐量纵轴是P99延迟气泡大小代表GPU显存峰值颜色代表不同模型。一眼能看出哪个模型是高吞吐低延迟的优等生哪个模型虽然吞吐不错但代价是延迟波动大。气泡图背后就是前面Spark产出的summary表前端每次加载时按日期过滤基本在几百毫秒内出图完全不依赖预计算缓存。6.2 并发压测趋势图并发压测趋势图服务做容量规划的同学展示的是随着并发数从1增长到128TTFT的P50/P95/P99三条曲线如何变化。这类图能直接反映系统拐点并发到64之前延迟平缓超过64后P99直线上升那这个系统承载并发容量就可以定在64以下。为了画这组图Spark作业里专门计算了各并发档位下的延迟分位数可视化层只是普通折线图。6.3 热点字段相关热力图热力图服务模型调优组。把多个模型在不同提示词长度区间、不同生成长度区间下成功率和延迟的表现用热力图呈现能迅速暴露出问题模型A在短提示词下表现优秀但提示词超过2000 token后成功率明显下降TTFT膨胀得厉害。这些问题隐藏在平均数之下只在细粒度聚合时才看得见。所以Spark作业里我保留了prompt_len_bucket和output_len_bucket字段作为聚合维度就是为这类分析留的口子。6.4 评测报告自动生成最后一个页面是评测报告生成器。可视化不用我做复杂设计选择合适技术栈就行——平台整体用的ECharts后端用FastAPI提供查询接口数据源直连HDFS上的Parquet结果表。评测报告页面提供一键导出功能前端把图表转成图片后端把汇总指标拼成Markdown文档自动上传到团队的文档系统。当初设计这个功能时我的想法很简单评测结论不能只留在系统里要能随报告一起流转出去。7. 实测踩坑记录与参数调优这个系统运行了两个月期间踩过的坑比预想的多这里挑最有代表性的几个记录下来。整体来看系统本身复杂度不高真正让人头疼的点全在工程化细节。7.1 客户端压测和Spark作业抢资源导致的OOM踩坑第一次全量评测跑批的时候压测服务和Spark作业同时跑在同一批GPU机器上结果两边一起崩。原因现在看很清晰vLLM服务占满显存Spark executors又申请内存做聚合物理机内存被打穿。解决方案是硬性的资源隔离压测任务在专门的GPU资源池执行Spark作业则在CPU资源池执行两个池子上层用YARN队列做限制。如果你没有条件拆资源池那就错峰执行把Spark聚合全部排在压测完成之后用工作流编排来控制依赖顺序不要再让两个任务并发抢资源。7.2 executor内存与shuffle调参经验Spark作业的参数优化我花了最多时间的是spark.sql.shuffle.partitions和executor内存配置。早期我沿用默认的200个shuffle分区结果中等规模数据量下每个分区的任务只有几十MB数据大量空任务在空转。后来根据评测数据量动态计算分区数公式很简单目标每个shuffle分区处理约200MB数据用计算输入大小总量除以200MB估算分区数。executor内存方面我的经验是不要无脑申请64GB大内存单executor 8GB到16GB、配4个CPU核在这个数据量级下性价比最高容器化调度也更灵活。7.3 schema推断是最隐蔽的负面因素还有一个隐蔽的坑是Spark读取原始日志时的schema推断。日志里有ttft和total_latency这两个时间字段有时候采集端上报时因为四舍五入把值上报成了整数字符串有时候上报成小数Spark推断出的schema就不一致导致聚合时Null值突然增多。后来统一在落地阶段预设schema显式声明字段类型为DoubleType并加一层校验凡是无法转换成Double的字段直接丢弃并记录异常计数。这个细节看起来不起眼但能避免深夜被性能指标全部为Null的告警叫醒。7.4 评测批次元数据管理说一个容易被忽略的设计每个评测批次必须有唯一的batch_id并且批次详情表里写明模型版本、评测集版本、压测参数、引擎参数、涉及节点列表。这样一旦某次评测数据出现异常可以快速定位到是不是那天某台机器的GPU降频了或者某个引擎版本带了会导致延迟异常的配置。批次元数据是不可或缺的审计信息千万别省。8. 这套系统后续还能扩展什么系统跑稳之后我一直在想它的演进方向这里给出两个明确可行的扩展思路。一个是把评测集从静态变成动态——当前评测集大多是预先整理好的固定问题集后续可以接入线上真实流量采样把脱敏后的实际用户请求回放到评测管道里做持续性能回归保证每次模型升级或引擎优化上线前都能自动跑一遍历史回归集。另一个是在Spark作业里增加基于诊断规则的自动化解读——比如模型升级后同一复现集下P99延迟劣化超过20%平台自动生成告警快照并推送给相关人省去每轮都要人工看报告判断是否需要干预。从工程交付的角度看这个基于Hadoop与Spark的大语言模型多维度性能评估系统最大价值不在于用了多新的技术而在于把大模型评测这件本来是临时脚本的工作变成了一个可迭代、可追溯、有产出物沉淀的平台化能力。如果你也在做类似的事情我的建议是先把指标体系定清楚再谈存储和计算最后做可视化——顺序反了的话大概率会在数据口径上反复返工。整个系统复制和改造都不难真正需要花时间的是把你自己业务的评测口径想明白。

相关推荐

SeaweedFS 7.1 Filer元数据架构与优化解析
SeaweedFS 7.1 Filer元数据架构与优化解析

1. SeaweedFS 7.1 Filer元数据架构解析SeaweedFS作为一款高性能分布式文件系统,其7.1版本的Filer组件在元数据管理方面进行了显著优化。Filer本质上是一个专门处理文件元数据的服务,它将文件目录结构、属性信息等元数据与实际文件内容分离存储&#xff0… · 2026/11/4 9:11:00

智能眼镜AIUI应用开发实战:从0到1打造语音交互应用
智能眼镜AIUI应用开发实战:从0到1打造语音交互应用

1. 为什么我把第一个眼镜应用选在"吃"上 先交代一下背景。我拿到 Rokid Glasses 的开发者预览机之后,翻了半天文档,脑子里蹦出来的第一个练手项目不是那种"看起来很有科技感"的复杂玩意儿,而是一个极其朴素的需求&#x… · 2026/11/4 9:11:00

VS Code Remote-SSH连接失败排查与解决方案
VS Code Remote-SSH连接失败排查与解决方案

1. 问题现象与初步排查最近在配置VS Code的Remote-SSH插件连接远程服务器时,遇到了反复连接失败的情况。典型症状表现为:输入正确的主机信息、用户名和密码后,VS Code底部状态栏长时间显示"正在连接...",最终弹出"… · 2026/11/4 9:09:58

Q-Learning入门实战:16格网格世界MATLAB源码详解
Q-Learning入门实战:16格网格世界MATLAB源码详解

简介:这份资源面向强化学习入门者与需要动手复现经典算法的学习者,以一份带详细注释的 MATLAB 源码,解决 Q-Learning 从理论到代码落地的问题。压缩包共 1 个 doc 文件,约 33KB,内容为可直接运行的脚本与逐行中文注释&… · 2026/11/4 18:22:38

Node.js构建高并发私厨服务系统实战
Node.js构建高并发私厨服务系统实战

1. 项目概述:私厨服务系统的市场定位与技术选型私厨服务在近几年呈现爆发式增长,这种介于家庭厨房与专业餐厅之间的餐饮模式,满足了现代人对个性化、健康化饮食的需求。基于Node.js的全栈解决方案,能够快速构建高并发的实时订餐系… · 2026/11/4 18:22:38

MarkItDown实战指南:用Python批量转换PDF/Word/Excel为Markdown
MarkItDown实战指南:用Python批量转换PDF/Word/Excel为Markdown

文档格式转换这件事,几乎每个跟文字打交道的人都绕不开。你手头攒了一堆 PDF 报告、Word 方案、Excel 数据表,想把这些内容喂给大模型做分析,或者整理进自己的知识库,结果发现格式五花八门,复制粘贴出来全是乱码和错位… · 2026/11/4 18:22:38

STM32F103C8T6多传感器采集:DHT11+SGP30+ADC+CH340串口上传实战
STM32F103C8T6多传感器采集:DHT11+SGP30+ADC+CH340串口上传实战

简介:面向嵌入式入门与传感器采集实践者的一套STM32环境监测工程源码,围绕STM32F103C8T6主控,整合DHT11温湿度、SGP30空气质量传感器与CH340 USB转串口,重点演示内置ADC模拟信号读取、USART串口收发及引脚接线与函数功能说明&… · 2026/11/4 18:22:38

KWDB在运维监控中的高效应用与实践
KWDB在运维监控中的高效应用与实践

1. 运维监控的痛点与KWDB的破局之道 在互联网企业的运维战场上,服务器监控系统就像人体的神经系统,任何微小的异常都可能引发连锁反应。我曾亲历过一次数据库主节点宕机事故,由于监控系统未能及时预警,导致业务中断近30分钟&#… · 2026/11/4 18:22:38

Unity 3D游戏开发中的三大AI协作范式解析
Unity 3D游戏开发中的三大AI协作范式解析

1. 这不是“跑个Demo”,而是一场真实开发节奏的极限压力测试最近两周,我把自己关在工作室里,用三套完全不同的大模型开发环境——Step 5 Preview、DeepSeek V4 Pro 和 GLM5.3 ——同步推进一个完整可运行的 3D 游戏原型:一个基于 … · 2026/11/4 18:21:38

双极步进电机控制实战: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/4 6:59:29

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/4 7:02:38

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/4 6:59:29

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/4 7:02:38

了解更多?预约专属演示

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

企业微信二维码