Skip to content

第 5 章 存储:分层、作用域与无损压缩 ​

2026 年 4 月,一家做医疗随访的团队在灰度里踩了一个不会报错的问题:他们用同一张 memories 表存所有患者与医生的记忆,靠 WHERE user_id = ? 区分。一次重构把分页查询的条件拼装从手写改成构造器,构造器在 filters 为空时不追加任何条件。于是「列出最近记忆」这个管理页接口,在特定参数下返回了全部租户的记录。接口返回 200,日志干净,直到一位医生在页面上看见了别的患者的随访记录。

这不是 SQL 注入,也不是权限模型缺失,是隔离方式的选择问题。共享表加过滤条件,把隔离正确性寄托在「每一个查询都记得加条件」上;而条件有几百处,漏一处就跨租户。物理隔离把这件事从「每次都要做对」变成「默认就对」。

存储层的设计有四个互相牵连的决策,本章按它们被追问的顺序展开。5.1 讲隔离放在哪里;5.2 讲为什么一个单文件数据库够用,以及它什么时候不够;5.3 讲库一直长的时候怎么办;5.4 讲压缩怎么做到不丢信息;5.5 讲图记忆为什么可以内建;5.6 讲这套存储形态的硬边界在哪里。

四个决策不能独立选。选物理隔离,就失去了跨作用域的一次聚合查询;选单文件,就把备份简化成复制文件,同时把并发上限锁在单机;选压缩而不删,就必须承担解压与校验的成本;选内建图,就放弃了大规模图遍历的专用索引。这些交换在本章逐条交代。

还有一层更基础的约束贯穿全章:写入是单调的(第 4 章),所以存储层必须自己承担「增长」这件事。检索层可以把噪声压下去,但它压不掉磁盘占用;写入层可以保证不丢信息,但它保证不了成本可控。这两件事只有在存储层才有解。

5.1 物理隔离 ​

5.1.1 四维作用域如何派生命名空间 ​

Mnemosyne OS 的作用域有四个维度:user_id、agent_id、run_id、app_id。四个维度可以任意组合,每一种组合派生出一个命名空间,每个命名空间对应磁盘上一个独立目录和一个独立的 SQLite 文件。

实现路径很短:_namespace_for() 把作用域字典派生成命名空间名,_build_brain() 按这个名字建一个引擎上下文,每个上下文持有自己的库连接。同一个进程里访问三个用户,就有三个上下文、三个文件,彼此不知道对方存在。源码位置在 mnemosyne/api/memory.py。

这个设计最直接的后果是:隔离不依赖任何一处代码记得加条件。上面那家医疗团队的事故在这个模型下不会发生,因为「列出最近记忆」在某个作用域下只能看见那一个文件里的内容,没有条件可漏。

隔离的边界要说得更准确一些:正文、全文索引、向量集合都随命名空间走;而审计账本、图边、统计在某些部署下是根目录级的共享结构。这意味着「记忆内容隔离」与「审计与图完全隔离」不是同一件事,多 Agent 部署时如果按内容隔离去推断审计也隔离,会得出错误结论。判断依据应该看具体部署的目录结构,而不是看作用域有四个维度。

代价出现在三个地方。

第一,跨作用域的聚合查询需要多次读取。运营要看「全平台今天新增多少条记忆」,在这个模型下必须遍历所有命名空间目录逐个统计,没有一条 SQL 能做完。规模小时这无所谓,命名空间上千之后它会变成一个需要单独设计的离线任务。

第二,命名空间的键一旦不稳定,就会静默产生空库。这是接入阶段最常见的错误:某次调用把 run_id 传成了一个每次请求新生成的 UUID,于是每次写入都落进一个全新的、空的库。系统不报错,写入返回成功,读取返回空——看起来像「记忆没生效」,实际是「记忆被分散到了一千个各只有一条记录的库里」。所以作用域键必须是「在这段记忆应当存在的整个期间内保持不变」的标识,而不是请求级标识。

第三,文件数量本身成为运维对象。备份不再是一个文件,而是一棵目录树;迁移不再是一次拷贝,而是按命名空间分批。

应用形态推荐作用域常见错误
单用户桌面工具user_id="local"用机器名做键,换机后记忆全丢
多用户 Web 应用user_id=<账户 id>用邮箱做键,用户改邮箱后分叉
多租户 SaaSuser_id=<账户 id> + app_id=<租户标识>只传租户,同租户内用户共享记忆
一客户一 Agentagent_id=<客户 id>同时传 user_id,两个维度组合出双倍库
按会话隔离run_id=<会话 id> + user_id只传 run_id,跨会话记忆无法继承

表 5-1 作用域键的选择与常见错误

表里第二行和第四行是同一类错误的两个方向:键选得比需要的更细,会产生大量碎片库;键选得比需要的更粗,会让本应隔离的记忆互相可见。两者都不报错。

5.1.2 与共享表加过滤的对比 ​

共享表加 WHERE 过滤不是坏设计,它在两种条件下更好:需要跨租户聚合分析时,以及租户数量极大、每个租户数据极少时——后者用物理隔离会产生成千上万个几乎为空的文件,文件系统的元数据开销超过数据本身。

所以这里的判据是规模与查询形状,不是抽象的正确性。

维度共享表 + 过滤物理隔离(一作用域一文件)
隔离正确性依赖每处查询都加条件结构保证,无条件可漏
跨作用域聚合一条 SQL需多次读取后合并
单租户数据量小无额外开销文件元数据开销占比高
单库损坏影响面全部租户单个作用域
备份粒度整库按作用域
迁移与删除按行操作按目录操作

表 5-2 两种隔离方式的取舍

最后一行常被忽略,但它决定了合规场景的选择。用户行使删除权时,物理隔离下是「删掉这个目录」,可验证、可复核;共享表下是「删掉这些行」,需要证明没有遗漏任何一张关联表。前者的举证成本低一个量级。

反过来,物理隔离在「租户数据极少」时是一种浪费。假设一个 B2B 产品有五千个客户,每个客户平均二十条记忆,总量十万条——这个量级放一张表里毫无压力,而拆成五千个文件之后,备份要遍历五千个目录,一次全量统计要打开五千个连接。此时的正确做法不是放弃隔离,而是把「隔离」和「分文件」拆开:逻辑上按租户分区,物理上共享一个文件,靠表分区或行级约束保证正确性,同时把条件拼装收敛到一个统一的查询构造器里,让它不可能被绕过。

这条折中的代价是把「结构保证」退回成「收敛的单一入口保证」——它比散落几百处的条件强,但仍然不是结构性保证。

5.2 为什么 SQLite 够用 ​

5.2.1 四个被低估的能力 ​

把 SQLite 当成「玩具数据库」是一种过时的印象。对记忆系统这个负载形状,它恰好提供了四样必需的东西,而且都不需要额外进程。

ACID 与事务。 一次写入要同时改主表、全文索引、图边表。三者必须原子生效,否则会出现「记忆存在但搜不到」或「边指向不存在的节点」。SQLite 的事务覆盖这三者,因为它们在同一个文件里。

WAL 并发。 预写日志模式下,读不阻塞写、写不阻塞读。记忆系统的负载是「写频中等、读频高」——每个回合写一次,每次组装上下文读若干次。这个形状正好落在 WAL 的优势区间。

FTS5 全文索引。 关键词检索不需要外部搜索引擎。中文场景下用 bigram 预分词后写入 FTS5 的内容列,查询侧同样切 bigram 后 MATCH,就能在无分词词典的情况下工作。

JSON1 扩展。 元数据、标签、实体列表都存在 JSON 列里,可以直接用 SQL 函数查询与更新,不必为每个可变字段加列。

这四样能力合起来的意义是:一个进程、一个文件、零外部服务就能同时满足事务一致性、并发读、全文检索与半结构化字段。对一个要嵌入到别人应用里的记忆层,这个组合消掉了整个运维面——不需要让用户先装一个数据库,才谈得上用你的记忆功能。

5.2.2 瓶颈在哪里 ​

一个常见的误判是把「检索慢」归因为「数据库不行」。实测的经验是:瓶颈通常在检索融合,不在存储引擎。一次召回要在候选集上做多路打分、归一化、融合、相关性下限过滤,这些是纯计算;而按主键取一条记录、按 FTS5 取候选,都是毫秒级。

所以优化顺序应该是:先看候选集大小(candidate_n 是否过大)、再看融合的打分轮次、最后才考虑换存储引擎。换引擎的收益通常远小于预期,而它带来的运维复杂度是立刻的。

一个具体的观察有助于判断方向:如果提高 candidate_n 之后延迟上升但召回质量不升,问题在打分与阈值,不在存储;如果按主键单条读取也开始变慢,才是存储层出了问题。前者远多于后者。

SQLite 真正不够用的情况有三种,且都不在「数据量」这一维上。

第一,多进程同时写同一个文件。WAL 支持多读单写,多个写者会串行化并可能返回忙错误。多进程写入需要外部协调,或者把写集中到一个进程。

第二,需要跨机水平扩展。单文件不能分片到多台机器,只能整体复制或按命名空间拆分到不同机器。

第三,需要在写入的同时做大规模分析查询。分析型查询会长时间持有读快照,与高频写争抢 WAL 的检查点。

三种情况都有对应的架构调整,但调整的方向不是「换一个更强的数据库」,而是「把负载拆开」——写集中、按作用域分片、分析走副本。

5.2.3 单写者带来的架构约束 ​

第二与第三种情况的根源都是「单写者」。这条约束比它听起来更强,因为它不只影响吞吐,还影响进程模型。

一旦接受单写者,写入就必须经过一个进程。这个进程可能是应用本身,也可能是一个独立的写入服务。前者简单,但意味着应用重启时写入中断;后者稳定,但引入一次进程间通信。

异步写入是绕开这个约束的常见做法:写请求先入队,热路径只负责入队并立刻返回,真正的落盘由后台线程按批处理。这样做的好处是写入延迟从「一次完整落盘」降到「一次入队」,代价是读一致性被打破——刚写完立刻读,可能读不到。

所以异步写入必须配一个显式的屏障:一个「等待队列落盘」的操作,在写完立刻要读之前调用。没有这个屏障,异步写入会表现为随机丢失,而它无法复现,因为是否丢取决于队列排空的速度。

这条约束在本章只点到为止,第 9 章会把状态恢复、异步与一致性作为主题展开。

5.3 分层记忆 ​

5.3.1 热、温、冷三层 ​

只增不改的存储会一直长,这是第 4 章留下的账。分层是还这笔账的方式:不是删掉旧数据,而是把它移到更便宜的层。

三层的行为不同。

热层是最近写入或最近被访问的记忆,参与全部检索通道,是全量打分的对象。温层是价值下降但仍在库里的记忆,参与检索但优先级被压低。冷层是低价值记忆,从主表移出、压缩归档,主表只留一个可判定的索引,命中后再取回。

从热到温的迁移只改一个字段,成本几乎为零。从温到冷要真正搬数据:主表移除、gzip 归档、建一个布隆过滤器式的索引用于判断「这条是否可能在冷层」。每次迁移都往哈希链账本追加一条记录,记录从哪层到哪层——因为「一条记忆什么时候被降级」本身就是需要可审计的事件。

把降级写进审计账本不是形式主义。当用户问「你们是不是把我的数据弄丢了」时,账本能回答的是「这条在 3 月 12 日从温层迁到冷层,仍在库中,命中后可恢复」,而不是「我们没删过」。前一句可核对,后一句只是承诺。

5.3.2 降级而不是删除 ​

降级策略的核心判断是:价值低的记忆不等于无价值的记忆。一条两年前的「用户当时用的是 Python 3.9」,对当下的技术选型没有指导意义,但它解释了为什么某段代码用了某个已废弃的写法。删掉它,历史就断了。

所以默认动作是降级与压缩,而不是静默删除。所谓「遗忘经济学」指的就是这件事:把存储成本按价值分摊,让低价值记忆占用更便宜的介质,而不是让它消失。

价值的排序由几个信号合成:重要性、访问次数、置信度、新旧程度、命中率。每次降级周期取价值最低的一部分迁走,比例默认是 20%,至少一条。如果给了字节预算,则持续降级直到活跃数据量落进预算。

层参与检索存储形态取回成本适用信号
热全通道、全量打分主表行 + 向量最低近期写入、近期访问
温参与但降权主表行低中等价值、长期未访问
冷命中后取回gzip 归档 + 索引高低价值、极久未访问

表 5-3 三层的检索参与度与取回成本

代价有三个。第一,冷层召回有额外延迟,一次命中要解压再取回。第二,降级没有自动回迁机制,一条被降到冷层的记忆即使后来被频繁访问,也不会自动升回热层,除非有显式的访问触发热路径。第三,降级判据本身可能错:把一条将来会关键的记忆降走,代价是它在需要时出现得更慢,而不是不出现。这个错误的代价被设计成「慢」而不是「丢」,这是整个分层方案成立的前提。

第三个代价值得再压一层:为什么宁可「慢」也不「丢」?因为慢是可观测的——延迟指标会上升,用户会感觉到卡顿;丢是不可观测的——召回为空,系统认为「本来就没有这条记忆」。可观测的失败能被修复,不可观测的失败会被误判成「从来没记过」。整个分层设计围绕这条不对称性展开。

5.3.3 什么时候不该分层 ​

分层不是无条件正确。它在两种情况下是负担。

第一种,库本身就很小。几千条记忆的单文件只有几十 MB,分层带来的调度周期、审计记录、冷层索引都是纯粹的额外复杂度,而它省下的磁盘空间可以忽略。此时应该关掉降级,让全部记忆留在热层。

第二种,访问模式没有冷热之分。有些系统的记忆是全量均匀访问的——例如一个产品知识库,每条都可能被问到,不存在「最近常用」的偏斜。这种情况下按访问频率分层会把一半的有用记忆推到冷层,召回延迟整体上升,而存储几乎没省。

判据是访问分布的偏斜程度:如果前 20% 的记忆承担了 80% 以上的召回命中,分层有效;如果命中分布接近均匀,分层只是在给所有查询加一层间接。

5.4 无损压缩 ​

5.4.1 指针加结构化事实加内容原子 ​

压缩记忆最直接的做法是摘要,而摘要会丢信息。丢掉的往往正是最需要精确的部分:金额、日期、型号、编号。一句「用户买了一台笔记本,大约一万多」把 ASUS VivoBook Pro 14、¥10,499、2026-03-12 全丢了,而这三个值恰恰是后面所有追问的答案。

AIC 的做法是把一条记忆压成三段:指针、结构化事实、内容原子。

指针是一个可校验的引用,形如 m:<id>#<hash>,可选带压缩层级。它是稳定且可解析的,任何时候都能指回原始正文。

结构化事实是从正文里抽出的类型、时间、实体、关系,用于让压缩后的表示仍然可被检索与排序。

内容原子是这条记忆里所有「取值类」片段——数字、日期、金额、型号、URL、邮箱、引号内原话。关键约束是:原子在所有压缩层级下都完整保留。层级只压缩叙述性文字,不压缩原子。

层级从宽到紧有五档:全文、要点、事实、指针、原子。极端情况下只剩「指针 + 原子」,此时正文没了,但「多少钱、什么时候、哪个型号」仍然答得出。

python
from mnemosyne import MemoryBrain

brain = MemoryBrain("./memories", enable_embeddings=True)
brain.ensure_init()
mid = brain.retain("His laptop is an ASUS VivoBook Pro 14", fast=True)

cap = brain.capsule(mid, budget_tokens=60)   # 指针 + 事实 + 原子
print(cap["level"], cap["atoms"], cap["ref"])

capsule() 的第二个参数是 token 预算。预算越小,层级越紧,但返回里的 atoms 一定包含全部取值片段;atoms_preserved 字段恒为真,它是这个函数自己的断言,不是调用方的期望。

渲染出的压缩文本还有一条可读性上的取舍:指针本身要占 token。一条 id16 + hash8 的指针在极短预算里可能占到一半以上,所以渲染给模型看的那份指针会截短,而返回给程序的结构化字段仍保留全量。同一个对象有两种表示,一种给模型省 token,一种给程序保校验能力。

5.4.2 字节级恢复与哈希校验 ​

压缩可逆才算无损。expand() 接收指针,返回原始正文,并校验内容哈希。

python
rec = brain.expand(cap["ref"])
print(rec["verified"], rec["content_chars"])   # True 386
print(rec["content"])

verified 为 True 表示取回的正文与指针声明的哈希一致。不一致时返回 verified=False 并给出实际哈希与一段说明,而不是静默交付可疑内容。这个区分很重要:哈希不符可能意味着记录被改写、id 被复用、或指针抄错,三种情况的处置完全不同,但它们都表现为「内容不对」。

保留哈希的另一个用途是让压缩本身可验证。渲染出的压缩文本必须满足「原始正文的原子集合是压缩文本原子集合的子集」,这条断言在每次构建胶囊时被检查。一旦某次渲染漏掉一个原子,它会被判为缺陷而不是「压缩得更好」。

代价必须说清楚。第一,压缩省的是叙述性文字的 token,不是原子的 token;一条记忆里原子占比越高,可压缩的空间越小——满是数字的日志型记忆几乎压不动。第二,每次 expand() 都是一次额外的存储读取,压缩把「一次读取」变成「两次读取加一次校验」,在批量引用场景里这个成本会累积。第三,压缩后的表示依赖指针有效;一旦原记录被物理删除,指针就永久失效,压缩文本里剩下的只有原子和一段无法核对的摘要。这也是为什么删除默认是软删——硬删会一次性废掉所有指向它的压缩表示。

5.4.3 压缩与预算的配合 ​

压缩在单独使用时收益有限,它真正的价值在于和预算机制配合。

一次召回的预算是固定的。不做压缩时,预算用尽的表现是「后面的记忆进不来」,调用方看到的结果条数比预期少,但无法区分「库里只有这么多」与「预算被砍掉了」。做了压缩之后,装不下正文的条目降级成胶囊,装不下胶囊的再降级成引用索引(不计入正文预算,由调用方决定是否展开)。

这个阶梯把「预算不够」从一个二值事件变成了分层事件。代价是结果的可解释性下降:调用方拿到一段文本,里面混着全文、胶囊、引用三种形态,如果不读返回里的成本报告,无法知道哪一条是完整的。

所以预算路径必须同时返回账本:预算上限、实际用量、被丢弃条数、是否发生截断。没有这四个字段,分层降级就是不可观测的,而不可观测的降级和静默丢信息差别不大。

5.5 图记忆内建 ​

5.5.1 同一文件里的实体链接与多跳 ​

图记忆通常意味着引入一个图数据库。Mnemosyne 的选择是把三元组存进同一个 SQLite 文件:实体作为节点,关系作为边,边带时间与来源。实体链接在写入路径上完成——抽取出的实体在同一次写入里落边,所以图信号从第一条记忆起就可用,不需要事后回填。

多跳遍历用递归查询实现,两跳以内的邻居查询在单文件上是毫秒级。这让「这个决定影响了哪些模块」这类问题可以在召回时顺带回答,而不必额外发一次图查询。

内建图的价值在于零依赖:不需要部署 Neo4j,不需要维护连接池,备份还是复制那个文件。对一个单机部署的记忆层,这消掉了整个运维面。

代价是规模上限。递归查询的复杂度随跳数与度数增长,三跳以上、节点数上百万时,单文件上的遍历会明显慢于专用图数据库。所以内建图适合两跳以内的邻域查询,不适合全图分析。

这里有一个容易忽略的失败模式:实体抽取的质量直接决定图的价值。中文实体抽取靠规则兜底,长串会被硬切,同一个对象可能被抽成两三个不同名字的节点。此时图是「有边但连不起来」,而查询返回空邻居——看起来像「这个实体没有关系」,实际是「节点被拆碎了」。判断依据是节点度数的分布:如果绝大多数节点度数为 1,图基本没在工作。

需要更大规模时,可以挂外部图存储适配器。当前支持六种:内建 SQLite 三元组、Neo4j、Memgraph、Neptune、Kuzu、以及任意 SPARQL 1.1 端点。适配器接口一致,切换只改配置。

5.5.2 内建与外部图库的取舍 ​

维度内建 SQLite 三元组外部图数据库
部署依赖无需独立服务与运维
两跳邻域查询毫秒级毫秒级
三跳以上大规模遍历明显变慢专用索引,优势明显
事务一致性与主表同一事务跨系统,需补偿
备份复制文件独立备份流程
迁移成本零需导出导入

表 5-4 内建图与外部图库的取舍

第四行是最容易被忽略的。内建图与记忆正文在同一个事务里,所以「记忆写入成功但边没写」这种状态不存在。换成外部图库之后,两者分属不同系统,要么接受最终一致,要么引入两阶段提交,两条路都有代价。

判据是查询形状而不是数据量:如果主要查询是「取这条记忆的邻居」「两跳内有哪些相关实体」,内建图够用且更省;如果主要查询是「找出所有经过某类节点的路径」「统计子图密度」,外部图库的专用索引不可替代。

5.6 存储形态的边界 ​

把上面几个决策合起来,这套存储形态的能力边界可以描述得比较具体。

容量。 单文件在数十 GB 量级上是可用的,超过之后备份、检查点与全库扫描的时间都会变成问题。注意这个数字受记录大小影响很大:一条带 1024 维浮点向量的记录比一条纯文本记录大一个数量级,同样 GB 数下的记录条数可以差十倍。判断自己的库什么时候到边界,看的是记录条数与平均大小,不是磁盘占用百分比。

备份。 备份就是复制文件。这是这套形态最大的运维优势:不需要导出工具,不需要停服,WAL 模式下可以在线复制。恢复同理,把文件放回去即可。代价是没有时间点恢复——复制出来的就是一个时刻的快照,两次快照之间的写入无法重建。要更细的恢复粒度,得靠审计账本重放,而那是另一条路径。

迁移。 迁移是按命名空间目录搬。优势是可以只搬一个租户,劣势是没有跨租户的一致性快照——搬的过程中不同命名空间处于不同时间点。

跨作用域查询。 需要多次读取后合并。这是物理隔离的直接代价,也是最常被抱怨的一点。缓解办法是在应用层维护一个轻量的跨作用域索引(只存 id、作用域、时间、类型),用于「有哪些」这类问题,正文仍按需从各自文件读取。

并发写入。 单写者。多个进程同时写同一个文件会串行化并可能返回忙错误。需要高吞吐写入时,把写集中到一个进程,或者按作用域分片到不同进程。

这些边界不是缺陷清单,是这套形态的定义。选它的理由是零依赖、单文件、备份简单、隔离结构正确;代价是跨作用域聚合、水平扩展与细粒度恢复都需要额外设计。当这些代价中的任何一项成为主要矛盾时,正确的动作不是在这套形态上打补丁,而是重新评估是否该换成客户端-服务器架构——那是第 8 章要展开的部署形态问题。

5.7 本章小结 ​

本章确立了三条判断。

第一,隔离正确性应该由结构保证,而不是由纪律保证。 共享表加过滤条件在两种条件下更好——需要跨租户聚合、租户极多而每个租户数据极少——但它的正确性依赖「每一处查询都记得加条件」,而条件有几百处。物理隔离把这件事变成默认就对,代价是跨作用域聚合要多次读取。这条判断成立的条件是作用域键本身稳定;键不稳定时,物理隔离会静默产生大量空库,失效方式比共享表更隐蔽。

第二,单文件数据库够用的前提是负载形状匹配,不是数据量小。 WAL 的多读单写正好对上「写频中等、读频高」的记忆负载,FTS5 与 JSON1 消掉了对外部搜索与文档库的依赖。它在三种情况下不够用:多进程同写、需要跨机扩展、需要在写入同时做大规模分析。这三种都不是「数据变多」导致的,所以按数据量预估是否需要换引擎,通常会估错方向。真正的瓶颈更可能在检索融合的计算上,而不是存储引擎。这条判断在写入吞吐要求高时失效——单写者会成为硬约束,此时要拆的是负载而不是换引擎。

第三,无损压缩的价值在于把「预算不够」从信息丢失降级为精度分层。 原子守恒让最需要精确的取值在任何层级都完整保留,字节级恢复让压缩可逆。这条判断的成立条件是被压缩的记忆里叙述性文字占多数;一条满是数字的记忆几乎压不动,此时压缩的收益会低于它的读取与校验成本。它失效的场景是原记录被物理删除——指针一旦失效,压缩表示就只剩下无法核对的碎片。

三个判断共享一个前提:库会一直长,而读取成本不能跟着长。分层把旧数据移到更便宜的层,压缩把单条记忆的上下文成本压到更低,物理隔离把增长的边界限制在单个作用域内。三者叠加之后,这套形态能撑到的规模是可描述的——单机、单写者、数十 GB、作用域数量可控。

剩下的问题是:当作用域数量本身成为规模变量时——比如一个多租户 SaaS,每个租户一个命名空间——上面这套「按目录管理」的方式会开始吃力。第 10 章处理作用域治理,第 11 章处理规模化运营的成本与容量。而在那之前,第 6 章要先回答一个更靠前的问题:这么多层、这么多信号、这么多压缩层级,一次召回到底该怎么在预算内挑出该给模型的那几条。


Copyright © 2026 胡景堃(Hu Jingkun). 采用 CC BY-NC-ND 4.0 许可。未经授权禁止传播、复制或用于商业化。