Deepseek新架构Engram全面解析:从MoE到N-gram的论文代码详解与TaoToken实践
1. 从 MoE 的“偏科”说起为什么需要 Engram 这条稀疏轴如果你最近在追 DeepSeek 的架构演进大概率会注意到一个词反复出现Engram。它被描述成 MoE 之外的第二条稀疏轴专门负责“静态知识检索”这件事。我第一次读到论文里那句“语言建模包含两个完全不同的子任务”时其实有点被点醒——MoE 擅长的是动态计算比如数学推导、逻辑链分析但像“亚历山大帝”“张仲景”“四大发明”这种高度固定的命名实体和短语用多层注意力去一层层拼特征本质上是在“用计算模拟查表”把宝贵的深度浪费在了本该 O(1) 完成的检索上。Engram 想做的事很直接给 Transformer 装一个原生的“查找组件”。它不改输入嵌入、不改输出层只在中间某几层插入一个条件记忆模块通过哈希 N-gram 做 O(1) 检索再用上下文门控决定这份静态记忆要不要注入主干。论文给出的数字也很有说服力Engram-27B 在 MMLU 上比同参数同计算量的 MoE 基线高 3.4BBH 高 5.0HumanEval 高 3.0而推理开销控制在 3% 以内。更关键的是那条 U 形缩放定律——固定参数和计算量下把 20%–25% 的稀疏参数分给 Engram 效果最优。这篇文章不打算只做论文复述。我会带你从零把 Engram 的核心模块跑起来先配好本地环境再用 TaoToken 统一 Key/API 通道完成模型侧验证最后对照论文里的哈希检索、门控、压缩分词器逐段拆代码。适合谁看如果你已经能跑通 PyTorch 小模型想搞明白“N-gram 哈希 门控”到底怎么落地或者你正在做长上下文检索优化这篇可以直接跟着敲。我试过把 Engram 的 demo 拆成三块来理解压缩分词器负责“瘦身”NgramHashMapping 负责“定位”Engram 主类负责“融合”。下面按这个顺序来。2. TaoToken 前置统一 Key/API 通道把模型侧验证跑通在动 Engram 代码之前有个现实问题论文里的 tokenizer 用的是 DeepSeek-V3 的 128k 词表本地直接from_pretrained拉取有时会因为网络或缓存问题卡住。更稳的做法是先把模型调用通道统一好这样后面无论是验证 tokenizer 行为还是对比不同模型对同一段文本的 token 化结果都不用反复改配置。TaoToken 在这里的角色是一个统一的 API 入口把不同模型的 Key 和 Base URL 收敛成一套。你只需要在控制台生成一个 Key后面所有请求都走同一个地址。具体操作路径先到官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册并登录然后进控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建 API Key。Key 生成后在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 可以随时查看和轮换。拿到 Key 之后Base URL 统一填https://taotoken.net/api注意这个地址不加 UTM 参数直接用于代码里的base_url。模型 ID 按你实际要验证的填比如deepseek-v3或deepseek-chat。如果你只是想先确认通道通不通可以直接用模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发一条消息看返回是否正常。这里有个容易踩的坑很多人把 Base URL 写成带/v1的完整路径结果 SDK 又自动拼了一次导致 404。正确做法是 Base URL 只写到/api让 SDK 自己补/v1/chat/completions。另外Key 不要硬编码在脚本里用环境变量TAOTOKEN_API_KEY读取后面跑 Engram demo 时也方便复用。如果你后面要长期跑编码类任务或 Agent 流程可以顺手了解下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite它更适合高频调用场景。但本篇的重点还是 Engram 本地复现模型通道只是辅助验证 tokenizer 和推理结果。配置完成后建议先跑一个最小请求确认通道import os from openai import OpenAI client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlhttps://taotoken.net/api ) resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: 用一句话解释什么是 N-gram}] ) print(resp.choices[0].message.content)如果这段能正常打印说明 Key 和 Base URL 都没问题。接下来就可以把注意力放回 Engram 本身了。3. 可复制配置EngramConfig 与 NgramHashMapping 的完整参数Engram 的代码结构其实很清晰核心配置集中在两个 dataclass 里。先看EngramConfig它决定了哈希表大小、N-gram 阶数、嵌入维度、插入层位置这些关键参数from dataclasses import dataclass, field from typing import List dataclass class EngramConfig: tokenizer_name_or_path: str deepseek-ai/DeepSeek-V3 engram_vocab_size: List[int] field(default_factorylambda: [129280 * 5, 129280 * 5]) max_ngram_size: int 3 n_embed_per_ngram: int 512 n_head_per_ngram: int 8 layer_ids: List[int] field(default_factorylambda: [1, 15]) pad_id: int 2 seed: int 0 kernel_size: int 4 dataclass class BackBoneConfig: hidden_size: int 1024 hc_mult: int 4 vocab_size: int 129280 num_layers: int 30这里有几个参数值得单独说。engram_vocab_size是一个长度为max_ngram_size - 1的列表对应 2-gram 和 3-gram 各自的哈希表大小。默认值129280 * 5意味着每个 N-gram 阶数的哈希空间是词表的 5 倍用空间换冲突率。n_head_per_ngram 8表示每个 N-gram 用 8 个独立哈希头每个头用不同的素数取模进一步降低冲突。layer_ids [1, 15]是 Engram 插入的位置论文里也提到早期层插入效果最好因为静态知识重建本来就发生在浅层。BackBoneConfig里的hc_mult 4对应论文里的多分支架构mHCM4。Engram 的门控是分支专属的每个分支有自己的 key projection 和 RMSNorm这样“科技分支”和“生活分支”可以对同一份静态记忆做出不同评估。接下来是NgramHashMapping的初始化部分它负责生成每层的乘法因子和素数模数import numpy as np class NgramHashMapping: def __init__(self, engram_vocab_size, max_ngram_size, n_embed_per_ngram, n_head_per_ngram, layer_ids, tokenizer_name_or_path, pad_id, seed): self.vocab_size_per_ngram engram_vocab_size self.max_ngram_size max_ngram_size self.n_embed_per_ngram n_embed_per_ngram self.n_head_per_ngram n_head_per_ngram self.pad_id pad_id self.layer_ids layer_ids self.compressed_tokenizer CompressedTokenizer( tokenizer_name_or_pathtokenizer_name_or_path ) self.tokenizer_vocab_size len(self.compressed_tokenizer) if self.pad_id is not None: self.pad_id int(self.compressed_tokenizer.lookup_table[self.pad_id]) max_long np.iinfo(np.int64).max M_max int(max_long // self.tokenizer_vocab_size) half_bound max(1, M_max // 2) PRIME_1 10007 self.layer_multipliers {} for layer_id in self.layer_ids: base_seed int(seed PRIME_1 * int(layer_id)) g np.random.default_rng(base_seed) r g.integers(low0, highhalf_bound, size(self.max_ngram_size,), dtypenp.int64) multipliers r * 2 1 self.layer_multipliers[layer_id] multipliers self.vocab_size_across_layers self.calculate_vocab_size_across_layers()multipliers r * 2 1保证乘法因子是奇数这样和 token ID 相乘后再异或顺序敏感性更强。calculate_vocab_size_across_layers会为每个 N-gram 阶数的每个哈希头分配一个唯一的素数作为模数避免不同头之间共享模数导致冲突叠加。如果你要把这些配置写进项目建议单独放一个engram_config.json方便版本管理{ engram: { tokenizer_name_or_path: deepseek-ai/DeepSeek-V3, engram_vocab_size: [646400, 646400], max_ngram_size: 3, n_embed_per_ngram: 512, n_head_per_ngram: 8, layer_ids: [1, 15], pad_id: 2, seed: 0, kernel_size: 4 }, backbone: { hidden_size: 1024, hc_mult: 4, vocab_size: 129280, num_layers: 30 } }这样后面无论是调max_ngram_size还是换layer_ids都不用改代码只改 JSON 就行。4. 验证请求与成功结果跑通 Engram 前向传播并对比输出配置就绪后下一步是跑通完整的前向传播。Engram 的 demo 入口在__main__里构造了一个简化版 LLM词嵌入层 30 个 TransformerBlock 输出线性层。其中只有layer_id在engram_cfg.layer_ids里的层才会挂载 Engram 模块。先看主流程import torch import torch.nn as nn from transformers import AutoTokenizer if __name__ __main__: LLM [ nn.Embedding(backbone_config.vocab_size, backbone_config.hidden_size), *[TransformerBlock(layer_idlayer_id) for layer_id in range(backbone_config.num_layers)], nn.Linear(backbone_config.hidden_size, backbone_config.vocab_size) ] text Only Alexander the Great could tame the horse Bucephalus. tokenizer AutoTokenizer.from_pretrained( engram_cfg.tokenizer_name_or_path, trust_remote_codeTrue ) input_ids tokenizer(text, return_tensorspt).input_ids B, L input_ids.shape for idx, layer in enumerate(LLM): if idx 0: hidden_states LLM[0](input_ids) hidden_states hidden_states.unsqueeze(2).expand(-1, -1, backbone_config.hc_mult, -1) elif idx len(LLM) - 1: hidden_states hidden_states[:, :, 0, :] output layer(hidden_states) else: hidden_states layer(input_idsinput_ids, hidden_stateshidden_states) print(Forward Complete!) print(f{input_ids.shape}\n{output.shape})这段代码里有个细节hidden_states在进入 TransformerBlock 之前被扩展成[B, L, hc_mult, D]模拟多分支架构。Engram 的门控就是在这个维度上逐分支计算的。跑通后你会看到类似输出Forward Complete! input_ids.shapetorch.Size([1, 14]) output.shapetorch.Size([1, 14, 129280])接下来验证 Engram 的核心——哈希检索。你可以单独调用NgramHashMapping.hash()看同一段文本在不同层的哈希 ID 分布hash_mapping NgramHashMapping( engram_vocab_sizeengram_cfg.engram_vocab_size, max_ngram_sizeengram_cfg.max_ngram_size, n_embed_per_ngramengram_cfg.n_embed_per_ngram, n_head_per_ngramengram_cfg.n_head_per_ngram, layer_idsengram_cfg.layer_ids, tokenizer_name_or_pathengram_cfg.tokenizer_name_or_path, pad_idengram_cfg.pad_id, seedengram_cfg.seed, ) hash_ids hash_mapping.hash(input_ids.numpy()) print(Layer 1 hash shape:, hash_ids[1].shape) print(Layer 15 hash shape:, hash_ids[15].shape)预期输出是[1, 14, 16]——14 个 token每个 token 有 16 个哈希头2-gram 8 头 3-gram 8 头。这个 16 就是后面MultiHeadEmbedding的num_heads。再进一步验证门控值。门控是 Engram 的“智能筛选器”它用当前隐藏状态和静态记忆的 key 做点积再经过 sigmoid 得到 0~1 的权重。你可以在Engram.forward里加一行打印gates torch.stack(gates, dim2) print(Gate stats:, gates.min().item(), gates.max().item(), gates.mean().item())在实体密集的句子里你会看到门控值在 “Alexander the Great” 和 “Bucephalus” 这些位置明显偏高而在普通虚词位置偏低。这正好对应论文里图 7 的可视化结论门控只在静态、固定、高频重复的语言模式上激活。如果你想用 TaoToken 的模型对话接口做交叉验证可以把同一段文本发给deepseek-chat让它解释 “Alexander the Great” 和 “Bucephalus” 的关系再对比 Engram 门控激活的位置。两者结合能更直观地理解“静态检索”和“动态推理”的分工。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth跑 Engram demo 和配置 TaoToken 通道时有几个报错出现频率特别高。我按实际遇到的顺序列一下方便你对照。401 Unauthorized这个最常见基本是 Key 没读到或写错了。先确认环境变量TAOTOKEN_API_KEY是否在当前 shell 生效echo $TAOTOKEN_API_KEY看有没有值。如果用的是.env文件注意load_dotenv()要在OpenAI()之前调用。还有一种情况是 Key 复制时带了空格strip 一下再试。local proxy failed / connection error这个通常不是 TaoToken 侧的问题而是本地网络环境或代理配置干扰。检查你的HTTP_PROXY/HTTPS_PROXY环境变量如果指向了一个不可用的地址SDK 会直接连不上。临时清掉这两个变量再跑一次。另外Base URL 一定要写https://taotoken.net/api不要自己拼/v1否则会走到一个不存在的路径。reading choices of undefined这个报错说明返回体里没有choices字段通常是请求根本没成功但 SDK 没抛异常。打印完整resp看error字段。常见原因是模型 ID 写错了比如把deepseek-chat写成了deepseek-v3-chat。对照模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 里可选的模型名确认拼写。OAuth / token expired如果你用的是某些 CLI 工具比如 Claude Code 或 Codex 类客户端可能会遇到 OAuth 流程失败。这类工具通常需要你在配置里同时填 Base URL、Key 和 Model ID 三件套。以 Claude Code 为例配置文件里要写清楚{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: deepseek-chat }三件套缺一不可。只填 Key 不填 Base URL工具会默认走官方地址导致 401 或超时。如果你用的是 Cline MCP 或 CC Switch 这类插件同样检查这三项是否对齐。接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里有各客户端的完整配置示例照着填基本不会错。还有一个 Engram 特有的坑CompressedTokenizer在构建 lookup table 时如果 tokenizer 的trust_remote_codeTrue没加会直接报ValueError。另外pad_id在压缩后会被重新映射如果你手动传了一个原始 ID 进去记得先过一遍lookup_table。6. 语义一致 CTA把 Engram 接进你的日常编码流Engram 的代码跑通之后你会发现它其实不只是一个论文复现项目。哈希 N-gram 检索 上下文门控这套组合可以直接迁移到很多场景比如给现有模型加一个轻量级的外部记忆模块或者在长上下文任务里用 Engram 的思路做局部依赖卸载把注意力资源省下来处理全局逻辑。如果你想把这条通道固化到日常开发里建议把 TaoToken 的 Key 和 Base URL 统一配到环境变量或项目级.env这样无论是跑 Engram demo、调模型对话还是后面接 Coding Plan 做 Agent 流程都不用反复改配置。API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 可以管理多个 Key按项目隔离。最后留一个我踩过的坑Engram 的layer_ids不要设得太深。论文里消融实验显示Layer 2 插入效果最好Layer 6 次之再深就接近普通 MoE 的表现了。原因是静态知识重建本来就发生在浅层Engram 插在浅层才能把主干深度“省”出来给推理。如果你把layer_ids改成[10, 20]验证损失反而会上升。这个细节在论文 2.6.2 节有完整对照表值得回头细看。

相关新闻

3分钟看懂 Agent、Skills、MCP:把 MCP endpoint 改到 TaoToken 的实操拆解

3分钟看懂 Agent、Skills、MCP:把 MCP endpoint 改到 TaoToken 的实操拆解

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

2026/10/17 15:06:43 阅读更多 →
从图灵测试到GPT-3:大模型演进与下一个词预测技术解析

从图灵测试到GPT-3:大模型演进与下一个词预测技术解析

简介:这份PPT由清华大学人工智能研究院常务副院长孙茂松教授主讲,面向希望系统了解人工智能与大模型技术脉络的高校学生、研究人员及技术从业者,帮助建立从基础理论到前沿应用的完整认知框架。内容围绕“迈向通用智能的大模型技术”展开&…

2026/10/17 15:06:43 阅读更多 →
工业大模型落地白皮书:产线可嵌入、工艺可解释、数据可生长

工业大模型落地白皮书:产线可嵌入、工艺可解释、数据可生长

简介:《2025工业大模型白皮书》是一份面向高校新工科师生、智能制造从业者及AI工程技术人员的权威行业研究报告,系统解答工业大模型与通用大模型的本质差异、技术体系构建路径、重点应用场景(如高端装备状态监控、故障预测与维护优化&#xf…

2026/10/17 15:06:43 阅读更多 →

最新新闻

PCB软硬结合设计降本指南:叠层、弯折半径与工艺验证

PCB软硬结合设计降本指南:叠层、弯折半径与工艺验证

简介:一项关于印制电路板软硬结合设计的技术资料,面向硬件工程师、电路板布局设计师及电子制造相关人群,主要解决移动设备小型化、低成本高可靠性要求下,传统刚性板配接电缆方案带来的连接器成本高、虚焊风险大等问题。文档从成本…

2026/10/17 20:16:22 阅读更多 →
低代码开发平台入门:JNPF拖拽式构建业务系统全攻略

低代码开发平台入门:JNPF拖拽式构建业务系统全攻略

简介:JNPF软件开发平台V3.4.2操作手册由福建引迈信息技术有限公司编制,是一份面向JNPF产品使用者的功能指南,适合低代码开发人员、项目实施与系统管理人员参考学习。压缩包内为单个PDF文件,大小40.01MB,目录涵盖在线开…

2026/10/17 20:16:22 阅读更多 →
Flutter移动端大文件分片上传实战:断点续传与内存优化

Flutter移动端大文件分片上传实战:断点续传与内存优化

1. 移动端大文件上传的痛点与整体思路拆解做过移动端文件上传的朋友应该都有体会,一个几百兆的视频直接往服务端扔,结果基本只有两种:要么请求超时,要么内存直接爆掉。尤其是现在短视频、在线教育、社交分享这类场景,用…

2026/10/17 20:16:22 阅读更多 →
单图去雨网络设计:SE、上下文聚合与循环结构解析

单图去雨网络设计:SE、上下文聚合与循环结构解析

1. 从"雨丝糊脸"说起:为什么单图去雨值得单独写一篇论文笔记第一次在真实场景里拍雨景的人,大概率都经历过这种崩溃:肉眼看着雨不大,拍出来整张照片像蒙了一层灰纱,远处的建筑轮廓糊成一团,近处的…

2026/10/17 20:16:22 阅读更多 →
构建可生长的AI编程工作台:VS Code+Ollama+Cursor深度集成指南

构建可生长的AI编程工作台:VS Code+Ollama+Cursor深度集成指南

1. 这不是“AI编程工具合集”,而是一套可生长的个人工作台系统我第一次把 VS Code、Ollama、Cursor 和本地知识库全打通跑通时,没急着截图发朋友圈,而是盯着终端里滚动的日志看了三分钟——不是因为炫技,而是突然意识到&#xff1…

2026/10/17 20:16:22 阅读更多 →
Agent Harness工程商业化指南:B端定制与标准化产品的战略选择

Agent Harness工程商业化指南:B端定制与标准化产品的战略选择

最近接了几个AI Agent落地项目的咨询,聊下来发现大家都卡在同一个问题上:Agent的原理、Demo、论文看了无数,POC也跑通了,但到了商业化这个环节,突然不知道该怎么卖了。到底是按客户需求一个个定制,还是抽象…

2026/10/17 20:15:21 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/16 14:23:36 阅读更多 →