长文本处理技术解析:从Transformer到工程落地实践
在人工智能大模型快速迭代的背景下月之暗面Moonshot AI向港交所提交上市申请的消息引发行业关注。招股书披露其核心产品智能助手Kimi在2024年通过K3系列模型的推出带动公司年度经常性收入ARR实现约三倍增长。这一数据背后反映的是长文本处理技术在商业化落地中的关键价值。对于技术团队而言ARR的快速增长不仅依赖模型能力本身更取决于工程化落地的稳定性、可扩展性和成本控制。长上下文窗口的实现需要解决内存占用、推理速度、上下文管理和错误累积等一系列工程挑战。本文将围绕长文本处理系统的核心模块从架构设计、关键实现到生产环境部署提供一个可落地的技术方案。1. 长文本处理的技术挑战与设计原则长文本处理的核心矛盾在于随着上下文长度增加计算复杂度呈平方级增长如Transformer的自注意力机制直接导致显存溢出和响应延迟。月之暗面Kimi能够处理约200万字上下文其技术方案必然在模型结构、推理优化和系统架构层面做了深度适配。1.1 长上下文模型的技术选型传统Transformer的自注意力计算复杂度为O(n²)当序列长度n达到10万token级别时显存占用会超过硬件极限。目前主流的长文本模型通过以下几种方式降低复杂度滑动窗口注意力每个token只关注固定窗口内的邻近token复杂度降至O(n×w)其中w为窗口大小。分层注意力先对文本分段计算局部注意力再在分段表征上计算全局注意力。稀疏注意力让每个token只关注部分关键token如Longformer、BigBird等方案。状态空间模型如Mamba通过线性递归结构实现O(n)复杂度但需要重新训练。在实际项目中如果基座模型已经确定通常采用滑动窗口或分层注意力进行推理优化如果从零训练可以考虑稀疏注意力或状态空间模型。1.2 长文本系统的架构设计要点一个生产级的长文本处理系统需要具备以下能力流式加载支持边加载边处理避免一次性将全文载入内存。分段处理将长文本切分为重叠的片段分别编码后再融合。缓存复用对已处理的片段建立缓存避免重复计算。内存管理动态管理GPU显存支持交换到主机内存或存储。错误隔离单一片段处理失败不应导致整个任务失败。基于这些要点我们可以设计一个分层处理架构。2. 长文本处理系统的核心实现下面以一个支持10万token上下文长度的问答系统为例展示关键模块的实现。系统采用Python作为主要开发语言使用PyTorch进行模型推理。2.1 项目结构与依赖配置项目采用标准的Python包结构核心依赖包括transformers、torch、accelerate等long-text-processor/ ├── requirements.txt ├── src/ │ ├── __init__.py │ ├── preprocessor/ # 文本预处理模块 │ │ ├── __init__.py │ │ ├── chunker.py # 文本分块 │ │ └── tokenizer.py # 分词器封装 │ ├── model/ # 模型推理模块 │ │ ├── __init__.py │ │ ├── window_attention.py # 滑动窗口注意力 │ │ └── model_wrapper.py # 模型封装 │ ├── cache/ # 缓存管理 │ │ ├── __init__.py │ │ └── segment_cache.py # 片段缓存 │ └── pipeline/ # 处理流水线 │ ├── __init__.py │ └── long_text_pipeline.py └── tests/ # 单元测试requirements.txt中明确定义依赖版本torch2.0.0 transformers4.30.0 accelerate0.20.0 numpy1.21.0 tqdm4.60.0生产环境部署时还需要添加性能监控和日志相关依赖。2.2 文本分块与重叠处理长文本需要被切分为多个片段片段之间设置重叠区域以避免信息割裂。以下是一个支持多种分块策略的Chunker实现class TextChunker: def __init__(self, chunk_size2048, overlap_size256, separator\n): self.chunk_size chunk_size self.overlap_size overlap_size self.separator separator def chunk_text(self, text): 将长文本切分为重叠的片段 if len(text) self.chunk_size: return [text] chunks [] start 0 # 按段落切分避免在段落中间切断 paragraphs text.split(self.separator) current_chunk for paragraph in paragraphs: # 如果当前段落加入后不超过chunk_size则继续累积 if len(current_chunk) len(paragraph) self.chunk_size - len(self.separator): current_chunk paragraph self.separator else: # 当前段落无法加入保存已有chunk if current_chunk: chunks.append(current_chunk.strip()) # 如果单个段落就超过chunk_size需要强制切分 if len(paragraph) self.chunk_size: sub_chunks self._split_large_paragraph(paragraph) # 最后一个子段落作为新chunk的开始 current_chunk sub_chunks.pop() self.separator chunks.extend(sub_chunks) else: current_chunk paragraph self.separator if current_chunk: chunks.append(current_chunk.strip()) # 应用重叠策略 if len(chunks) 1 and self.overlap_size 0: chunks self._apply_overlap(chunks) return chunks def _split_large_paragraph(self, paragraph): 处理超长段落按句子切分 # 简单的句子切分逻辑实际项目可用nltk或spacy sentences paragraph.replace(。, 。|).replace(, |).replace(, |).split(|) sentences [s for s in sentences if s.strip()] sub_chunks [] current for sentence in sentences: if len(current) len(sentence) self.chunk_size: current sentence else: if current: sub_chunks.append(current) current sentence if current: sub_chunks.append(current) return sub_chunks def _apply_overlap(self, chunks): 为chunk添加重叠区域 overlapped [] for i in range(len(chunks)): if i 0: # 第一个chunk不需要前重叠 if len(chunks) 1: # 取下一个chunk的前overlap_size字符作为后重叠 next_start chunks[1][:self.overlap_size] overlapped.append(chunks[i] next_start) else: overlapped.append(chunks[i]) elif i len(chunks) - 1: # 最后一个chunk不需要后重叠 prev_end chunks[i-1][-self.overlap_size:] overlapped.append(prev_end chunks[i]) else: # 中间chunk需要前后重叠 prev_end chunks[i-1][-self.overlap_size:] next_start chunks[i1][:self.overlap_size] overlapped.append(prev_end chunks[i] next_start) return overlapped分块策略需要根据具体任务调整问答系统可以按段落切分代码分析可能需要按函数或类切分。2.3 滑动窗口注意力实现对于已有的Transformer模型可以通过修改注意力机制来支持长文本。以下是一个滑动窗口注意力的实现示例import torch import torch.nn as nn from transformers import PreTrainedModel class SlidingWindowAttention(nn.Module): def __init__(self, original_attention, window_size1024): super().__init__() self.original_attention original_attention self.window_size window_size def forward(self, hidden_states, attention_maskNone): batch_size, seq_len, hidden_dim hidden_states.shape if seq_len self.window_size: # 序列长度小于窗口大小使用原始注意力 return self.original_attention(hidden_states, attention_mask) # 分段处理长序列 outputs [] for start_idx in range(0, seq_len, self.window_size): end_idx min(start_idx self.window_size, seq_len) # 提取当前窗口 window_states hidden_states[:, start_idx:end_idx] if attention_mask is not None: window_mask attention_mask[:, start_idx:end_idx] else: window_mask None # 计算窗口内注意力 window_output self.original_attention(window_states, window_mask) outputs.append(window_output[0] if isinstance(window_output, tuple) else window_output) # 拼接所有窗口结果 result torch.cat(outputs, dim1) return result def apply_sliding_window_to_model(model, window_size1024): 将模型中的自注意力层替换为滑动窗口注意力 for name, module in model.named_children(): if isinstance(module, nn.ModuleList): for i, sub_module in enumerate(module): if hasattr(sub_module, attention): # 替换self-attention original_attention sub_module.attention.self sub_module.attention.self SlidingWindowAttention(original_attention, window_size) else: # 递归处理子模块 apply_sliding_window_to_model(sub_module, window_size) else: if hasattr(module, attention): original_attention module.attention.self module.attention.self SlidingWindowAttention(original_attention, window_size) else: # 递归处理子模块 apply_sliding_window_to_model(module, window_size)这种方法可以在不重新训练模型的情况下扩展上下文长度但可能会损失一些长距离依赖信息。2.4 分段缓存与增量处理为了提升长文本多次查询的效率需要实现分段缓存机制import hashlib import pickle from dataclasses import dataclass from typing import Dict, List dataclass class SegmentCacheEntry: segment_text: str segment_embeddings: torch.Tensor segment_tokens: List[int] timestamp: float class SegmentCache: def __init__(self, cache_dir: str, max_size_mb: int 1024): self.cache_dir Path(cache_dir) self.cache_dir.mkdir(exist_okTrue) self.max_size_bytes max_size_mb * 1024 * 1024 self._current_size 0 self._entries: Dict[str, SegmentCacheEntry] {} def _get_segment_hash(self, text: str) - str: return hashlib.md5(text.encode()).hexdigest() def get_segment_embeddings(self, text: str) - torch.Tensor: segment_hash self._get_segment_hash(text) cache_file self.cache_dir / f{segment_hash}.pkl if cache_file.exists(): # 检查文件是否损坏 try: with open(cache_file, rb) as f: entry pickle.load(f) self._entries[segment_hash] entry return entry.segment_embeddings except (pickle.UnpicklingError, EOFError): cache_file.unlink() return None def save_segment_embeddings(self, text: str, embeddings: torch.Tensor, tokens: List[int]): segment_hash self._get_segment_hash(text) entry SegmentCacheEntry( segment_texttext, segment_embeddingsembeddings.clone(), segment_tokenstokens.copy(), timestamptime.time() ) cache_file self.cache_dir / f{segment_hash}.pkl with open(cache_file, wb) as f: pickle.dump(entry, f) self._entries[segment_hash] entry self._current_size cache_file.stat().st_size # 清理过期缓存 self._cleanup_old_entries() def _cleanup_old_entries(self): if self._current_size self.max_size_bytes: return # 按时间排序删除最旧的条目 sorted_entries sorted(self._entries.items(), keylambda x: x[1].timestamp) for segment_hash, entry in sorted_entries: cache_file self.cache_dir / f{segment_hash}.pkl if cache_file.exists(): file_size cache_file.stat().st_size cache_file.unlink() self._current_size - file_size del self._entries[segment_hash] if self._current_size self.max_size_bytes * 0.8: # 清理到80% break缓存机制可以显著提升长文档多次查询的速度特别是在文档库更新不频繁的场景。3. 生产环境部署与优化长文本处理系统在生产环境面临的主要挑战是资源消耗和稳定性。以下关键配置和优化策略需要特别注意。3.1 资源预估与配置建议根据上下文长度和并发需求合理的资源配置如下表所示上下文长度最小GPU显存推荐GPU显存并发支持响应时间预估4K token8GB16GB中(10-20)1-3秒16K token16GB24GB中低(5-10)3-8秒32K token24GB40GB低(2-5)8-15秒100K token40GB80GB极低(1-2)15-30秒实际资源需求还受模型参数量、批处理大小和优化程度影响。对于100K上下文建议使用多卡推理或CPU offloading技术。3.2 关键配置参数调优在config.yaml中定义关键参数model: name: moonshot-ai/kimi-v3 # 模型名称 max_length: 200000 # 最大上下文长度 chunk_size: 2048 # 分块大小 overlap_size: 256 # 重叠大小 window_size: 1024 # 注意力窗口大小 inference: batch_size: 1 # 批处理大小长文本通常为1 max_concurrent: 2 # 最大并发数 timeout: 300 # 超时时间秒 memory_management: use_gpu: true offload_to_cpu: true # 是否卸载到CPU cache_enabled: true # 缓存启用 cache_size_mb: 1024 # 缓存大小 monitoring: log_level: INFO metrics_enabled: true prometheus_port: 8080这些参数需要根据实际硬件资源和性能要求进行调整。3.3 监控与告警配置生产环境必须建立完善的监控体系核心监控指标包括显存使用率接近阈值时触发告警推理延迟P95、P99延迟监控吞吐量每分钟处理的token数量错误率分段处理失败比例缓存命中率衡量缓存效果使用Prometheus和Grafana的示例配置# prometheus.yml scrape_configs: - job_name: long-text-processor static_configs: - targets: [localhost:8080] metrics_path: /metrics scrape_interval: 15s # 告警规则 groups: - name: long_text_alerts rules: - alert: HighMemoryUsage expr: gpu_memory_usage_percent 85 for: 2m labels: severity: warning annotations: summary: GPU memory usage is high - alert: HighInferenceLatency expr: inference_duration_seconds{quantile0.95} 30 for: 5m labels: severity: critical annotations: summary: 95% inference latency exceeds 30 seconds4. 常见问题排查与优化策略长文本处理系统在实际运行中会遇到各种问题以下是典型问题及解决方案。4.1 显存溢出问题排查显存溢出是最常见的问题排查顺序如下检查输入长度确认实际输入是否超过模型支持的最大长度检查分块设置分块大小是否适配当前硬件检查注意力窗口窗口大小是否合理检查批处理大小长文本推理通常批处理大小为1# 监控GPU显存使用 nvidia-smi -l 1 # 每秒刷新一次 # 在代码中添加显存监控 import torch def print_gpu_memory(): if torch.cuda.is_available(): print(fGPU memory allocated: {torch.cuda.memory_allocated() / 1024**3:.2f}GB) print(fGPU memory reserved: {torch.cuda.memory_reserved() / 1024**3:.2f}GB)4.2 响应延迟优化长文本处理天然存在延迟问题优化方向包括预处理优化文本分块和tokenization使用多线程流水线并行重叠计算和IO操作模型量化使用FP16或INT8量化减少计算量缓存策略对重复内容建立多级缓存# 使用异步处理提升吞吐 import asyncio from concurrent.futures import ThreadPoolExecutor class AsyncTextProcessor: def __init__(self, max_workers4): self.executor ThreadPoolExecutor(max_workersmax_workers) async def process_long_text_async(self, text): loop asyncio.get_event_loop() # 异步执行分块 chunks await loop.run_in_executor( self.executor, self.chunker.chunk_text, text ) # 并行处理各个分块 tasks [] for chunk in chunks: task loop.run_in_executor( self.executor, self.model.process_chunk, chunk ) tasks.append(task) results await asyncio.gather(*tasks) return self.merge_results(results)4.3 长文本质量评估长上下文模型容易产生中间丢失问题即模型对中间部分内容的理解较差。评估方法包括位置偏差测试将关键信息放在不同位置测试召回率连贯性检查检查长文档生成的回答是否前后一致事实一致性验证模型是否准确理解全文事实关系建立自动化测试套件def test_position_bias(doc_length50000): 测试模型对不同位置信息的记忆能力 test_fact 关键测试信息北京是中国的首都 # 将测试信息放在文档的不同位置 positions [0, doc_length//4, doc_length//2, 3*doc_length//4, doc_length-100] recall_rates [] for pos in positions: test_doc 背景内容 * pos test_fact 背景内容 * (doc_length - pos - len(test_fact)) question 北京是哪个国家的首都 answer model.answer_question(test_doc, question) recall_rates.append(1 if 中国 in answer else 0) return recall_rates5. 最佳实践与扩展方向基于月之暗面等公司的工程经验长文本处理系统的最佳实践包括以下几个方面。5.1 工程化最佳实践分级处理策略短文本4K token直接使用完整模型处理中长文本4K-32K token使用分块滑动窗口超长文本32K token采用分层摘要关键信息提取混合精度训练与推理# 使用FP16加速推理同时保持精度 from torch.cuda.amp import autocast with autocast(): outputs model(input_ids, attention_maskattention_mask)动态长度适配根据硬件资源动态调整处理策略在资源紧张时自动降低处理质量保证服务可用性。5.2 扩展方向与技术演进模型架构创新状态空间模型Mamba等在长序列上的潜力混合专家模型MoE的长文本适配检索增强生成RAG与长文本模型的结合系统工程优化边缘计算与云端协同的分层处理专用硬件如长文本推理芯片的利用多模态长上下文处理文本图像表格长文本处理技术正在从能处理向处理得好发展。月之暗面Kimi的ARR增长表明在保证技术可靠性的前提下长上下文能力确实能创造商业价值。但对于大多数团队而言更务实的选择是逐步迭代先解决80%常见场景的需求再向更极致的长度和能力演进。实际项目中建议从16K-32K上下文长度起步重点优化分段策略和缓存机制确保系统稳定后再逐步扩展。同时密切关注FlashAttention、Mamba等新技术的发展在适当时机进行架构升级。

相关新闻

大语言模型可解释性研究:从可信解释到可操作解释的实践路径

大语言模型可解释性研究:从可信解释到可操作解释的实践路径

这次我们来看一个关于大语言模型可解释性的重要研究——《From Plausible to Actionable: A Position on LLM Self-Explanations》。这个研究不是教你部署某个具体模型,而是探讨如何让LLM生成的解释从"看似合理"变成"真正有用"。 在AI应用越来…

2026/10/17 2:47:01 阅读更多 →
分布式:数据复制

分布式:数据复制

可以把“数据复制”理解为:同一份数据保存在多台服务器上,一台坏了,还能从其他服务器读取或恢复。但仅仅复制多份还不够,系统还要解决:写入顺序、何时算成功、节点故障后由谁接管,以及不同副本如何重新同步…

2026/10/16 17:08:41 阅读更多 →
C++异常隔离设计:构建健壮接口与资源安全防护

C++异常隔离设计:构建健壮接口与资源安全防护

1. 项目概述:为什么我们需要“异常隔离”?在C的世界里摸爬滚打了十几年,我见过太多因为异常处理不当而导致的“血案”。一个看似功能完善的库,接口设计得花里胡哨,性能指标也相当亮眼,但只要调用方抛出一个…

2026/10/17 2:51:12 阅读更多 →

最新新闻

MoE架构解析:万亿参数模型如何以稀疏激活实现高效计算?

MoE架构解析:万亿参数模型如何以稀疏激活实现高效计算?

1. 核心认知:参数规模和计算量,很多人的第一反应都错了1.1 一个让大模型“变轻”的反直觉设计我最初看到“万亿参数”这个词的时候,跟大部分人一样,第一反应是“这得要多大的算力才能跑起来”。但后来实际接触并拆解了混合专家&am…

2026/10/17 11:55:36 阅读更多 →
AI辅助教材编写:低查重与教学工作流设计实战指南

AI辅助教材编写:低查重与教学工作流设计实战指南

这两年我用AI辅助编写了多本校本教材和课程讲义,最大的体会是:AI写教材真正的“秘密武器”,不是让大模型一口气吐出几十万字,而是把“检索、组织、改写、审校”拆开的一套生产流程。很多人栽跟头,是因为对AI有两层误解…

2026/10/17 11:55:36 阅读更多 →
上海租公寓,这三样我现在一定会现场确认

上海租公寓,这三样我现在一定会现场确认

有一套我当时觉得很合适:月租在预算里,离地铁也近,照片里的客厅很亮。住了两周我才明白,亮是因为下午侧光,早上房间是暗的;晚上临着马路,窗户关上仍有车声。那次之后,看公寓整租我固…

2026/10/17 11:55:36 阅读更多 →
开源Agent网关OpenClaw:架构设计、部署实操与踩坑记录

开源Agent网关OpenClaw:架构设计、部署实操与踩坑记录

做AI Agent也有一段时间了,前后折腾过不少框架和项目,但一直有个问题像鞋里的沙子一样硌脚:AI助手和我的各种终端之间,总是隔着一层说不清的别扭。直到某天我实在忍不住,动手写了个叫OpenClaw的东西——一个开源的AI A…

2026/10/17 11:55:36 阅读更多 →
Hadoop+MapReduce+HBase离线日志分析集群搭建实战

Hadoop+MapReduce+HBase离线日志分析集群搭建实战

最近有个项目要做离线日志分析和实时查询,数据量不大但增长很快,我们最后选择了CentOS 8.1 Hadoop MapReduce HBase这套组合。这套方案最直接的优势在于:Hadoop负责海量文件的分布式存储,MapReduce处理批量计算,HBa…

2026/10/17 11:55:36 阅读更多 →
深入理解 DOM ProcessingInstruction:用法与实战

深入理解 DOM ProcessingInstruction:用法与实战

1. 引言在 DOM(文档对象模型)中,除了常见的元素节点、文本节点和注释节点之外,还存在一类相对冷门但功能明确的节点类型——ProcessingInstruction(处理指令)。它通常出现在 XML 文档中,用于向解…

2026/10/17 11:54:35 阅读更多 →

日新闻

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/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →