news 2026/9/15 23:05:41

本地大模型推理实战:消费级GPU部署与量化压缩全链路解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
本地大模型推理实战:消费级GPU部署与量化压缩全链路解析

1. 项目概述:这不是“跑个模型”那么简单,而是一场硬件、软件与认知的三重校准

把大模型跑在个人电脑上是怎样一种体验?——这句话在2024年还带着点极客玩笑的意味,到了2025年底,它已经成了技术从业者日常决策的现实命题。我从去年9月开始系统性地搭建本地大模型推理环境,从最初用3090跑7B模型卡顿到怀疑人生,到今年用双4090+384GB内存稳定服务6个并发的14B-32B多模态Agent,中间经历了17次系统重装、9块SSD报废、3次主板BIOS刷崩、以及一次因散热设计失误导致显卡供电模块冒烟(所幸没起火)。这不是一篇“教你怎么装Ollama”的入门指南,也不是罗列参数的硬件评测,而是一个真实使用者在2026年初回看整条技术路径时,对“本地运行大模型”这件事最诚实的剖解:它既不是玄学,也不是坦途;它不依赖魔法,但极度依赖经验;它不排斥小白,但会立刻惩罚想当然。

核心关键词——本地大模型推理、消费级GPU部署、量化压缩、内存带宽瓶颈、Windows/Linux双栈适配、RAG工程化落地、低功耗长时服务——全部来自我过去14个月每天实操的日志记录。这篇文章覆盖的不是“能不能跑”,而是“跑得稳不稳、快不快、省不省、扩不扩、安不安全、好不好维护”。比如你买了一张RTX 4090,它标称24GB显存,但实际能喂给Llama-3-70B-Instruct的可用显存可能只有20.3GB——这0.7GB差额,就是你加载LoRA权重失败、KV Cache分配报错、甚至整个推理进程静默退出的根源。再比如,很多人以为“用CPU跑小模型很慢”,但实测发现:在处理单次<512token的结构化指令(如JSON Schema校验)时,AMD Ryzen 9 7950X3D + 128GB DDR5-6000的纯CPU推理延迟反而比4090低18%,因为避免了PCIe拷贝和CUDA上下文切换开销。这些反直觉的细节,才是决定你能否把“本地大模型”从玩具变成生产工具的关键分水岭。

适合谁读?第一类是技术决策者:CTO、AI Infra负责人、独立开发者,你需要判断“本地部署是否值得投入”,这篇文章会给你一套可量化的ROI评估框架(含电费、折旧、人力运维成本建模);第二类是工程师:后端、全栈、MLOps,你想把模型集成进现有系统,这里提供从API网关设计、流式响应封装、到错误熔断策略的完整链路;第三类是硬核爱好者:你攒机多年,想让新买的5090不只是打游戏,那我会告诉你如何用NVLink桥接双卡、如何绕过Windows WDDM限制启用TCC模式、如何用Linux内核参数榨干PCIe 5.0 x16的4GB/s带宽。全文没有一行代码是“复制粘贴就能跑”的,但每一行配置、每一个参数、每一次重启,都标注了它背后的物理意义和失效边界——因为真正的本地AI,从来不在pip install里,而在你按下电源键之后的每一秒系统反馈中。

2. 硬件选型逻辑:不是算力越高越好,而是“带宽-容量-延迟-功耗”四维平衡

2.1 GPU:显存带宽才是真正的“模型吞吐天花板”

很多人一上来就盯着FP16算力TFLOPS,这是最大的认知陷阱。2026年本地大模型推理的瓶颈,90%以上发生在显存带宽而非计算单元。以Llama-3-70B为例,其完整权重约140GB(FP16),即使经过AWQ 4-bit量化压缩至35GB,推理时仍需维持完整的KV Cache(每个token约占用1.2MB显存),当batch_size=4、max_length=2048时,仅Cache就吃掉近10GB显存,剩余25GB要承载模型权重、LoRA适配器、Tokenizer缓存、CUDA Graph内存池——此时显存带宽直接决定生成速度。

我们实测了6款主流消费级GPU在相同模型(Qwen2-14B-AWQ)下的token/s:

GPU型号显存容量显存类型带宽(GB/s)实测平均token/s单token延迟(ms)
RTX 409024GBGDDR6X1008128.37.8
RTX 4090 D24GBGDDR6X848102.19.8
RTX 5090(预发布版)32GBGDDR71600189.75.3
RX 7900 XTX24GBGDDR6102489.211.2
Radeon PRO W790048GBHBM33200165.46.0
A100 40GB PCIe40GBHBM2e2039152.86.5

关键发现:HBM3显存的带宽密度(GB/s per mm²)是GDDR6X的2.3倍,这意味着W7900在48GB容量下仍能维持3200GB/s带宽,而4090的24GB GDDR6X带宽已逼近物理极限(1008GB/s)。更残酷的是:当模型权重超过显存容量时,必须启用PagedAttention或vLLM的连续批处理,此时PCIe带宽(Gen4 x16=32GB/s, Gen5 x16=64GB/s)成为新瓶颈。我们曾用双4090通过NVLink互联,但发现当启用模型并行切分时,NVLink带宽(112GB/s双向)反而被PCIe 5.0 x16的64GB/s拖累——因为vLLM默认将输入token先经PCIe送入主卡,再由NVLink分发,这个“PCIe→NVLink→PCIe”的三角路径,让有效带宽衰减至42GB/s。解决方案?改用Linux内核5.19+的PCIe ACS override补丁,强制绕过IOMMU重映射,实测提升27%。

提示:不要迷信“显存越大越好”。RTX 4090的24GB是当前消费级最优解——它刚好能容纳Qwen2-72B-AWQ(28GB)的权重+基础KV Cache,而48GB卡(如W7900)因HBM3高延迟,在中小模型(<32B)上反而不如GDDR6X响应快。我的建议:7B/14B模型选4080(16GB),32B选4090(24GB),70B以上必须上双4090或W7900,且必须用Linux+PCIe ACS优化。

2.2 CPU与内存:别让“数据搬运工”拖垮GPU

GPU再强,也得靠CPU喂数据。我们测试发现:当使用llama.cpp的AVX2版本时,Ryzen 7 7700X(8核16线程)在加载tokenizer和prefill阶段,比i9-14900K慢31%,原因在于AMD的Zen4架构对AVX-512指令集支持不完整,而llama.cpp最新版已默认启用AVX-512加速。但换用AVX-512编译的版本后,7700X性能反超14900K 12%,因为其Infinity Fabric总线延迟更低(28ns vs 41ns),在频繁的小包数据搬运(如prompt分词结果传入GPU)时优势明显。

内存方面,误区在于只看容量。实测表明:DDR5-6000 CL30与DDR5-5600 CL28在大模型推理中的差异微乎其微(<3%),但内存通道数与CPU直连拓扑才是关键。AM5平台的双通道内存带宽为89.6GB/s,而LGA1700的四通道(需Xeon W或HEDT)可达140GB/s。当我们把Qwen2-14B的context length从2048拉到8192时,AM5平台出现明显延迟抖动(P99延迟从120ms跳至380ms),而四通道平台保持在135ms内——因为长context需要CPU高频次搬运KV Cache的中间状态,带宽不足直接触发内存页面交换。

注意:Intel平台务必关闭Resizable BAR(在BIOS中设为Disabled),否则Windows WDDM驱动会强制启用GPU显存分页,导致vLLM的PagedAttention机制失效,实测吞吐下降40%。AMD平台则需开启SAMU(Smart Access Memory)并配合ROCm 6.1+,才能让CPU直访GPU显存。

2.3 存储与散热:SSD不是越快越好,而是“随机读写IOPS”决定加载速度

模型权重文件(.safetensors)本质是数十万个16KB~64KB的小文件集合。我们对比了4种存储方案加载Qwen2-72B-AWQ(32GB)的时间:

  • SATA SSD(550MB/s, 80K IOPS):加载耗时 42.3s
  • NVMe Gen3 x4(3500MB/s, 500K IOPS):加载耗时 18.7s
  • NVMe Gen4 x4(7000MB/s, 1.2M IOPS):加载耗时 11.2s
  • 双NVMe Gen4 RAID0(14GB/s, 2.4M IOPS):加载耗时8.9s

但继续堆叠到Gen5(14GB/s单盘)反而升至9.3s——因为Linux内核5.15的NVMe驱动对Gen5的中断合并策略未优化,导致I/O队列深度溢出。真正起效的是IOPS而非带宽:当启用vLLM的模型分片(tensor parallelism)时,每个GPU需独立加载其分片权重,此时高IOPS能让所有卡同时发起读请求,避免排队等待。

散热上,一个反常识结论:风冷比水冷更适合多卡AI工作站。我们测试了360mm一体式水冷(标称TDP 360W)与双塔风冷(Noctua NH-D15S)在双4090满载下的表现:水冷下GPU温度稳定在72℃,但CPU因水泵占用电源轨,VRM温度飙升至105℃触发降频;风冷下GPU升至78℃,但CPU VRM仅89℃,整机持续功率高出11%。根本原因在于:AI负载是GPU长期95%占用+CPU间歇性爆发,水冷的热容优势无法覆盖VRM瞬时尖峰。最终方案:双塔风冷+机箱前部3×140mm进风+顶部2×120mm排风,实测双卡满载12小时无降频。

3. 软件栈深度拆解:从CUDA到vLLM,每一层都在“背叛”你的直觉

3.1 CUDA与驱动:版本锁死是常态,不是Bug

NVIDIA官方宣称“CUDA 12.4兼容所有RTX 40系显卡”,但实测发现:vLLM 0.4.2在CUDA 12.4 + Driver 535.129下,启用FlashAttention-2时会出现10%概率的kernel launch timeout。根源在于:Driver 535.129的CUDA Graph实现存在race condition,而vLLM 0.4.2的batch scheduler恰好触发该路径。解决方案不是升级驱动,而是降级到Driver 535.54.02(2023年11月LTS版),该版本经vLLM团队认证无此问题。更讽刺的是,CUDA 12.3反而在535.129下完全正常——因为12.3未启用新的Graph优化路径。

我们建立了一套版本兼容矩阵(仅列出关键组合):

vLLM版本CUDA版本Driver版本FlashAttention-2PagedAttention备注
0.4.212.4535.54.02官方推荐组合
0.4.212.4535.129❌(timeout)需patch kernel
0.4.112.3535.129旧版稳定,但无MoE支持
0.5.0(beta)12.5550.42新增动态分片,但需禁用CUDA Graph

实操心得:永远用nvidia-smi --query-gpu=name,driver_version,cuda_version确认三者匹配。不要相信“向后兼容”——CUDA 12.x的ABI在每次小版本更新中都有微调,vLLM的C++扩展会直接链接CUDA runtime,版本错配导致段错误(Segmentation Fault)是最高频崩溃原因。

3.2 推理框架选型:vLLM不是万能解药,llama.cpp仍有不可替代场景

vLLM凭借PagedAttention和Continuous Batching,在高并发场景(>8 req/s)下吞吐碾压所有竞品。但它的代价是:内存占用翻倍。vLLM为每个请求预分配最大KV Cache空间,即使实际只生成10个token,也会按max_length=2048预留内存。我们实测Qwen2-14B在vLLM下,单请求内存占用为3.2GB;而llama.cpp(AVX-512+GPU offload)仅需1.8GB,且支持真正的“按需分配”。

何时选vLLM?当你需要:

  • Web API服务(如FastAPI封装),并发请求波动大(1~50 QPS)
  • 需要流式响应(stream=True)且首token延迟敏感(<500ms)
  • 模型支持MoE(如Qwen2-MoE),需动态专家路由

何时选llama.cpp?当你需要:

  • 嵌入式/边缘设备(如Jetson Orin,仅16GB LPDDR5)
  • 低功耗长时运行(笔记本电池供电,vLLM的CUDA Graph常驻显存耗电高23%)
  • 需要细粒度控制(如自定义RoPE频率、手动管理KV Cache生命周期)

我们开发了一个混合调度器:对短prompt(<128token)走llama.cpp(CPU+GPU混合offload),对长context(>1024token)自动切到vLLM。通过共享内存IPC传递tokenized input,实测比纯vLLM方案节省37%显存,且P95延迟降低22%。

3.3 量化技术实战:AWQ不是终点,而是起点

网上教程千篇一律说“用AWQ 4-bit”,但没人告诉你:AWQ的activation-aware校准过程本身就会引入精度损失。我们对比了同一模型(Phi-3-mini)在不同量化方式下的数学推理准确率(GSM8K测试集):

量化方式比特数校准数据集GSM8K准确率显存占用首token延迟
FP1616-68.2%2.1GB120ms
GGUF Q4_K_M462.7%0.58GB98ms
AWQ 4-bit4128 samples59.3%0.52GB85ms
AWQ+GPTQ 4-bit4128+64 samples64.1%0.54GB89ms

关键突破:AWQ负责权重敏感通道校准,GPTQ负责残差补偿。我们用AWQ生成初始量化权重,再用GPTQ的optimal rounding对AWQ的误差项进行二次优化。工具链:awq --w_bit 4 --q_group_size 128gptq --act_order --percdamp 0.01。虽然耗时增加3倍(从8分钟到24分钟),但精度挽回4.8个百分点,且未增加显存。

警告:不要对LoRA权重单独量化!LoRA的A/B矩阵本质是低秩增量,单独量化会导致梯度消失。正确做法:将LoRA权重与base model合并后再量化,或使用QLoRA(但QLoRA在vLLM中需额外patch)。

4. 工程化落地:从“能跑”到“可运维”的七道生死关

4.1 API网关设计:为什么Nginx反向代理会吃掉30%吞吐?

标准做法是用Nginx做vLLM的反向代理,但实测发现:当并发>20时,Nginx worker进程CPU占用率达95%,吞吐不升反降。根本原因在于:vLLM的HTTP接口返回的是chunked transfer encoding流式响应,而Nginx默认的proxy_buffering on会缓存整个响应体再转发,彻底破坏流式特性。

解决方案:

location /v1/completions { proxy_pass http://localhost:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_buffering off; # 关键!禁用缓冲 proxy_cache off; proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k; }

但更优解是绕过Nginx,用uvicorn原生ASGI:vLLM 0.4.2已内置ASGI支持,直接启动vllm-entrypoint --host 0.0.0.0 --port 8000 --enable-sampling,然后用FastAPI写业务逻辑,通过httpx.AsyncClient直连vLLM。实测吞吐提升32%,且首token延迟稳定在85±5ms(Nginx方案为112±28ms)。

4.2 RAG系统集成:向量数据库不是越多越好,而是“查询延迟-召回率”必须建模

本地RAG最常见错误是盲目上Milvus或Qdrant,却忽略其资源开销。我们测试了3种向量库在100万chunk(768-dim)下的表现:

方案内存占用查询P95延迟召回率@10启动时间适用场景
Chroma(in-memory)4.2GB18ms82.3%<1s小规模知识库(<10万chunk)
Qdrant(Docker)3.8GB42ms89.7%8s中等规模(10~100万chunk)
FAISS + mmap1.1GB9ms76.5%<1s超大规模(>1000万chunk),接受稍低召回

关键洞察:FAISS的IVF_PQ索引在本地场景有奇效。我们将1000万chunk构建为IVF10000,PQ32,内存占用仅1.1GB,查询时先粗筛1000个候选,再用CPU计算精确余弦相似度。虽然召回率比Qdrant低3.2%,但延迟降低78%,且无需维护独立数据库进程。我们的RAG pipeline是:用户query → FAISS快速召回 → top50 chunk送入vLLM重排序(cross-encoder)→ 最终取top5生成答案。端到端延迟142ms,远低于Qdrant+rerank的287ms。

4.3 监控与告警:GPU显存泄漏的“幽灵进程”排查法

某天凌晨,我们的双4090工作站显存占用从45%缓慢爬升至98%,但nvidia-smi显示无进程,ps aux | grep python也无异常。最终定位到:vLLM的CUDA Graph在异常中断(如Ctrl+C)后未释放graph handle,残留的graph object持续占用显存。解决方案不是重启,而是用NVIDIA提供的nvidia-cuda-mps-control工具:

# 查看MPS服务状态 sudo nvidia-cuda-mps-control -d # 强制清理所有MPS客户端 echo quit | sudo nvidia-cuda-mps-control # 重启MPS(需先kill所有CUDA进程) sudo systemctl restart nvidia-cuda-mps

我们编写了监控脚本,每30秒检查:

  • nvidia-smi --query-compute-apps=pid,used_memory --format=csv,noheader,nounits
  • /proc/[pid]/status中的VmRSS值
  • 若某PID显存占用>500MB但VmRSS<100MB,则判定为MPS泄漏,自动执行上述清理。

这套机制上线后,月均非计划停机从4.2次降至0.3次。

5. 踩坑复盘:那些让你凌晨三点还在敲命令的真实事故

5.1 “Windows蓝屏:IRQL_NOT_LESS_OR_EQUAL”——显卡驱动与WSL2的战争

在Windows 11 22H2上启用WSL2运行vLLM,第3次重启后必蓝屏。WinDbg分析dump文件指向nvlddmkm.sys(NVIDIA显示驱动),但禁用WSL2后蓝屏消失。根源在于:WSL2的GPU Paravirtualization(GPUPV)驱动与桌面端WDDM驱动共用同一内核模块,当WSL2中CUDA进程异常退出时,会触发WDDM的IRQL同步错误。微软和NVIDIA的联合方案是:禁用WSL2的GPU支持,改用WSL2+Windows原生vLLM。具体操作:

  1. wsl --update --web-download升级到最新内核
  2. 在WSL2中安装CUDA Toolkit 12.3(非12.4)
  3. 但vLLM仍运行在Windows主机,WSL2仅作为API客户端
  4. netsh interface portproxy将WSL2的8000端口映射到Windows

这样既保留WSL2的Linux生态,又规避GPUPV冲突。

5.2 “模型加载成功,但生成全是乱码”——Tokenizer编码的隐式陷阱

Qwen2系列模型要求输入文本必须用<|endoftext|>结尾,否则RoPE位置编码错位。我们曾用HuggingFace的AutoTokenizer加载,但tokenizer.encode("Hello")返回[151643, 151645],而Qwen2实际需要[151643, 151645, 151643](末尾补endoftext)。修复方法:

# 错误:直接encode input_ids = tokenizer.encode(prompt) # 正确:显式添加eos_token input_ids = tokenizer.encode(prompt + tokenizer.eos_token) # 或更安全:用apply_chat_template messages = [{"role": "user", "content": prompt}] input_ids = tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=True)

这个bug导致我们连续3天生成结果全是符号乱码,直到用torch.cuda.memory_summary()发现KV Cache的position_id张量全为0才定位到。

5.3 “电费账单暴涨300%”——你以为的“空闲”,其实是GPU在偷偷挖矿

部署后首月电费激增,nvidia-smi显示GPU利用率常年15%~20%,但htop无高CPU进程。用nvidia-smi dmon -s u监控发现:pwr(功耗)曲线与sm(流式处理器)利用率高度相关,但mem(显存)利用率仅5%。最终抓包发现:vLLM的健康检查端点/health被监控系统每5秒调用一次,而vLLM默认对此端点也执行一次轻量推理(验证CUDA Graph),导致GPU无法进入P8低功耗状态。解决方案:

  • 修改vLLM源码,在/health路由中移除推理调用,仅返回{"status": "ok"}
  • 或更简单:用Nginx拦截/health,直接返回200

实施后,待机功耗从85W降至22W,月电费回归正常。

6. 未来演进:2026年本地AI的三个确定性方向

6.1 “CPU+GPU异构推理”将成为标配,而非特例

Intel即将发布的Arrow Lake CPU集成NPU(10TOPS),AMD Strix Point APU的XDNA2 NPU达16TOPS,而NVIDIA的Blackwell架构GPU首次开放NPU编程接口。这意味着:一个请求进来,tokenizer分词交给CPU AVX-512,短文本理解交给NPU,长文本生成交给GPU——任务按计算特征自动路由。我们已用OpenVINO在i9-14900KS上实测:对128token prompt,NPU推理延迟仅23ms(GPU为85ms),功耗仅3.2W(GPU为45W)。下一步是构建统一调度器,根据实时功耗/温度/延迟预测,动态分配算力单元。

6.2 “模型即服务(MaaS)”的本地化:从单模型到模型联邦

当前本地部署仍是“一个模型一个服务”,但企业需要的是“一个入口,多个模型”。我们正在开发Model Federation Gateway:用户发送请求时,附带model_hint: "math",网关自动路由至Qwen2-Math-7B;若model_hint: "code",则切到DeepSeek-Coder-33B。关键创新是跨模型KV Cache复用:当用户连续提问“写Python函数”→“优化这个函数”,网关将第一个请求的KV Cache摘要(用小型蒸馏模型压缩)传递给第二个模型,避免重复理解上下文。实测在10轮对话中,平均延迟降低38%。

6.3 “绿色AI”不再是口号:功耗感知推理将成为基础设施能力

欧盟已提出《AI能效法案》,要求本地AI设备标注“每百万token生成能耗(kWh)”。我们为此开发了能耗计量模块:

  • 用IPMI读取主板传感器(IT8686E芯片)获取整机功耗
  • nvidia-ml-py3获取GPU功耗
  • psutil获取CPU功耗(基于RAPL接口)
  • 每次推理记录start_time,end_time,input_tokens,output_tokens,total_energy_kwh

现在我们的API响应头中包含:
X-AI-Energy: 0.00012 kWh per 1000 tokens
这不仅是合规需求,更是技术竞争力——客户会为“每生成1000个token少耗0.00003度电”的方案付费。

我在实际部署中越来越确信:本地大模型不是云端AI的廉价替代品,而是开辟了全新的交互范式——它让AI从“需要联网等待”的服务,变成了“像键盘一样即插即用”的生产力组件。当你的笔记本合盖休眠时,它只是暂停;当你打开,它立刻从上次中断处继续思考。这种确定性,是任何云服务都无法提供的。最后分享一个小技巧:在Linux中,用systemctl --user enable --now vllm.service创建用户级服务,并在~/.config/systemd/user/vllm.service中加入RestartSec=10,这样即使vLLM崩溃,10秒内自动恢复,你完全感觉不到中断。真正的本地AI体验,就藏在这些让系统“忘记自己存在”的细节里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 23:04:44

域名放别人网站3招搞定,免费工具助你省钱

域名放别人网站3招搞定,免费工具助你省钱 找建站公司报价上万?别急,域名解析这步其实能自己搞定,还能省下大笔“技术溢价”。很多老板以为域名必须绑死在对方服务器,结果被绑住手脚,换供应商还得加钱。其实,通过 免费工具…

作者头像 李华
网站建设 2026/9/15 23:03:38

新能源车电耗变化分析:从数据记录到驾驶习惯优化

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

作者头像 李华
网站建设 2026/9/15 22:59:44

深圳网站建设公司评论避坑指南:保姆级建站教程解析

深圳网站建设公司评论避坑指南:保姆级建站教程解析 域名注册完卡在服务器配置,SSL证书申请下来却忘了配置,这种“域名服务器搞不懂”的困境,几乎是深圳中小企业主建站时的第一道坎。很多人以为找个深圳网站建设公司就能一劳永逸,结果上线后发现代码烂、SEO差、备案难,悔之晚矣。这篇保姆级建站教程,不聊虚的,…

作者头像 李华
网站建设 2026/9/15 22:59:28

技术专家转型项目负责人的实战经验与敏捷管理

1. 从技术专家到项目负责人的角色转变十年前&#xff0c;当我第一次被任命为软件项目负责人时&#xff0c;我以为这只是个"高级程序员"的头衔变化。直到第一次项目进度会议上&#xff0c;看着十几双期待的眼睛&#xff0c;我才意识到自己需要完全不同的技能树。技术专…

作者头像 李华
网站建设 2026/9/15 22:58:10

无线对讲系统选型实战:盯紧核心参数避开营销陷阱

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

作者头像 李华