别被"十亿级"唬住:billion-context 落地时的资源边界排查
【免费下载链接】billion-contextA context-compression plugin for small context windows (a 100K context is enough), token savings (5x fewer tokens), and month-long single sessions (billions of tokens).上下文压缩插件,兼顾小窗口(100k上下文足矣)省token(省5倍token)和超长会话(数月级别几十亿token单会话)。billion-context is all you need项目地址: https://gitcode.com/gh_mirrors/bil/billion-context
"billions of tokens" 是 billion-context 项目宣传里最扎眼的数字——月级单会话、数十亿 token 吞吐。但先泼一盆冷水:这个数字指的是累计流经代理的输入 token 总量,不是单次请求的上下文大小,更不是它自己需要吃掉的内存。项目自带论文的纵向生产数据显示,4.5 个月、174,327 次模型调用共处理 18.76B 累计输入 token(三宿主合计约 24.7B),而每次调用的上下文均值只有 88K、中位数 72K,峰值 257,721——全程跑在不变的硬件和窗口上,204,800-token 窗口零违规。
也就是说,"十亿级"靠的是有界上下文 + 增量折叠这套机制把窗口从硬上限变成有界区间,代理本身只是一个跑在 localhost 上的 Node 进程。但"代理自身很轻"不等于"落地零成本"——真正会让你踩坑的,是它周围一圈默认值:内存里的会话池、磁盘上的 dump 目录、Windows 上的杀软锁文件。这篇文章把仓库里能挖到的每一条真实资源边界都列出来,给出一份落地前的排查清单。
一、"十亿"的口径:先拆掉误读
社区里对这类项目最常见的误读是把名字里的量级当硬件需求。billion-context 的架构决定了它与硬件配置的关系比想象中简单:
Agent (Claude Code / Codex / pi / opencode ...) │ base URL 指向代理 ▼ ┌─────────────────┐ │ billion-context│ 解析请求 → acp-kernel 压缩 → 注入 compress 工具 │ proxy │ → 转发真实模型 API → 重写流式响应 └─────────────────┘ ▼ 真实模型 API它不放大上下文窗口,不占额外 KV-cache 显存,不部署任何检索服务(论文结论 C2 原话:"no longer context, no extra KV-cache memory (which instead shrinks proportionally), no retrieval service")。"28.6B token / 106 天"是论文对单会话容量的仿真外推(拟合自 57 个 tier-2 事件,约为最大观测会话的 19 倍,属于标注为 [A: 仿真] 的数字而非实测),对应硬件需求恰恰是"不变":你原来用多大机器跑 agent,接上代理后还是多大机器。
所以排查的第一问不是"我要加多少显存",而是:代理进程的内存上限、磁盘配额、并发预算,在默认配置下分别卡在哪里?
二、内存:四条写死的边界
2.1 请求体预算:100 MB 硬顶,超大请求只放行 2 个
src/request-body-budget.ts是最直接的内存闸门:
export const MAX_REQUEST_BYTES = 100 * 1024 * 1024; export const MAX_DECODED_REQUEST_BYTES = 2 * MAX_REQUEST_BYTES; const MAX_LARGE_DECODED_REQUESTS = 2;两条规则值得注意:
- 出站重建后的请求体超过 100 MB 直接拒绝,错误信息是
rebuilt request exceeds 100 MB bytes (...); reduce historical images or content before retrying——注意它点名的原因是"减少历史图片或内容",图片大户会先撞这堵墙; - 解压后超过旧上限的大请求,全局只允许同时存在 2 个(
DecodedRequestBusyError: "large decoded request budget is busy; retry after an active request finishes")。第 3 个并发大请求会被拒,这是防内存放大攻击式的自我保护。
落地含义:单用户单 agent 的日常场景几乎碰不到这条线;但如果你在同一个代理上跑多路并发 agent(README 明确支持并发会话,显式 id 客户端可安全并行),且每路都背着大图片历史,这是最先报 4xx 的地方。
2.2 会话池:256 个会话封顶,LRU 逐出
CONFIGURATION.md中sessions块给了三个内存参数,全部有磁盘兜底:
| 参数 | 默认 | 语义 |
|---|---|---|
sessions.max(BILI_MAX_SESSIONS) | 256 | 内存会话表上限,超出 LRU 逐出空闲会话,磁盘仍是 source of truth |
sessions.warmMaxBytes(BILI_SESSION_WARM_MAX_BYTES) | 67108864(64 MiB) | 启动预热集的解码字节预算——按解码后大小计费而非压缩文件大小,zstd 归档绕不过这个 RAM 上限;单个超预算的文件永不预热 |
sessions.residentMaxBytes(BILI_SESSION_RESIDENT_MAX_BYTES) | 536870912(512 MiB) | 常驻会话字节的软目标:活跃/in-flight 状态钉在其上不被逐出;超出部分在请求完成后把空闲状态落盘释放 |
这是 #2803 "bounded startup" 的设计:启动只水化最近的预热集,其余按需重载,活跃状态永不逐出。仓库里tests/bounded-storage-scale.test.ts直接验证了这个设计目标:
320 MB archive boots under a 128 MB heap and restores an older original
——用--max-old-space-size=128起一个 Node 进程,让它加载 320 个会话文件组成的归档,断言预热集有界且旧会话可透明重载。128 MB 堆能启动 320 MB 状态目录,这就是项目对"代理自身内存"给出的工程承诺:常驻内存被压在几百 MB 量级,而不是随历史状态线性增长。
2.3 解压临时块:50 个
顶层标量decompressTmpCap(twinBILI_DECOMPRESS_TMP_CAP)默认50,限的是"in-session 临时持有的解压内容块"数量——decompress工具按需还原压缩区间时,临时内容不是无限驻留的。多路 agent 频繁 decompress 的极端场景(比如一个服务机同时挂多个子代理回查历史)才需要关心它,普通单会话用不到。
2.4 量级参数与硬件的对应关系
把上面四条拼起来,落地时的"量级—配置"映射其实是:
- 上下文窗口(100K / 200K / 1M)→ 决定模型侧行为,与代理内存无关。窗口只影响"何时触发压缩"(首次软压缩默认在 boot 内容后 50K token 增长处触发,
compress.nudgeGrowthTokens),不影响代理占多少 RAM; - 会话数量→ 每增加一个活跃会话,增量受
residentMaxBytes(默认 512 MiB 软目标)约束;256 个会话封顶; - 单请求体大小→ 100 MB 硬顶,解码 200 MB,大请求并发 2;
- 累计 token 量("十亿"所在的那个量级)→ 与代理内存无函数关系,只随磁盘会话文件线性增长(见下节)。
这是它与"把窗口扩到十亿"类方案(LongNet 那一路)的本质区别:后者的成本曲线是窗口函数,KV-cache 随长度爆炸;前者的成本曲线近似常数,历史被折叠成逐层衰减的摘要塔(tier-1 中位保留约 1/8,每层再蒸馏约 1/4),累计吞吐可以无限涨而上下文占用不涨。
三、磁盘:默认配置下的两个"无界增长"
内存有硬边界,磁盘方向恰恰相反——两个大目录默认无上限,这是整篇排查清单里最要命的部分。
3.1 日志:10 MB 轮转,安全
日志边界在src/logger.ts里写得明明白白:
const MAX_BYTES = 10 * 1024 * 1024; // 10 MB → rotate~/.local/state/billion-context/bili.log到 10 MB 自动轮转为bili.log.old。这条不用操心,除非你开了--debug长期跑且日志消费极慢——即便如此,单文件也被钉在 10 MB。
3.2 dump 目录:实测 63 GB/天,默认不删
CONFIGURATION.md的diagnostics段留了一句触目惊心的话:
Retention bounds (#2412): dump dirs are write-only with no rotation by default —active body/SSE capture measured at ~63 GB/day can fill a disk in days.
src/state-gc.ts的注释记录了事故现场:debug dump 目录(dumps/、raw/、SSE dump 目录)"write-only and grew without bound —70 GB in 26 hmeasured under active forensics"。这是 #2412 修复前在取证场景下的实测值。修复方式是有界的,但默认关闭:
{ "diagnostics": { "maxTotalBytes": 2147483648, "maxAgeDays": 3 } }不配这两个值,开了ACP_DUMP_BODY/ACP_DUMP_REQ之类开关的机器会在几天内吃满磁盘;删文件走 FIFO-by-mtime,err-*/summary-err-*事故转储受保护不删。排查动作:grep 你的配置里有没有打开任何 dump 开关;打开了,就必须配maxTotalBytes/maxAgeDays。
3.3 会话状态:opt-in 清理,压缩过的永不删
会话状态落在~/.local/share/billion-context/(XDG 可改),每轮重写状态文件,这是"累计数十亿 token"唯一会物理增长的地方,而且是按 token 而非按请求数增长的:
- 默认不删任何东西——session 文件被定义为用户数据,没有静默删除策略;
- 开启
BILI_SESSION_GC=1后,启动时 + 每小时扫描一次,删除必须同时满足两条:年龄超过BILI_SESSION_GC_MAX_AGE_DAYS(默认 7 天)且从未压缩过、最新请求体 ≤BILI_SESSION_GC_MAX_TOKENS(默认 1M token); - 带折叠块的会话无论多大永不删除——摘要无法从原始历史无损重建,删了就真丢;
- 每次删除都写审计日志。
另外两个磁盘相关的量级参数:persist.zstd默认关(纯 JSON 可 grep 可恢复),开启后启用BILI_ENCRYPTION_KEY的会话文件可缩小约 5–10×;persist.tailTokens默认 16384,预算被折叠快照中持久化的尾部 token 数。
四、哪些场景会触发边界问题
按仓库里真实 issue 和测试覆盖归纳,四类场景:
1. 图片大户 → 出站 100 MB 拒发。OutboundBodyTooLargeError的报错文本直接告诉你要"reduce historical images"。多模态任务(大量截图进历史)在长会话中 base64 图片累积,重建请求体时最先撞顶。项目有独立的图片压缩/恢复管线(src/image-compress.ts、image-restore.ts),但边界本身仍是 100 MB。
2. 取证/排障时打开 dump 开关 → 磁盘几天打满。63 GB/天的实测数据就是活教材。这不是配置错误而是默认行为,属于"开了开关没配上限"的经典事故。
3. Windows 上杀软/同步盘锁文件 → 持久化 EPERM。README 单列了一节(#362):长会话每轮都重写状态文件,Defender 实时扫描、搜索索引器、OneDrive 可能在写入中途锁住目录,持久化以EPERM失败直到锁释放。仓库对应src/persist-eperm.ts专门处理这个错误路径并带告警阈值(persist.epermAlertThreshold)。修复方案是加排除项:%USERPROFILE%\.local\share\billion-context\进杀软白名单,并让同步工具远离该路径。Windows 落地这一步不做,长会话会间歇性丢状态刷新。
4. 首次压缩为什么等到 ~200K → 被误以为"没工作"。这不算资源故障但常被当成 bug 上报:压缩不按窗口绝对位置触发,而按增长区间——默认 boot 内容后再长 50K 才触发首次软压缩。boot 内容本身 ~100K(大AGENTS.md、全量工具 schema)时,首次压缩要到 ~200K。缓解三件套:裁 system prompt/工具/skills;把compress.nudgeGrowthTokens降到 ~50K;开 lean mode。硬兜底是 preflight:输入本身超窗口时强制触发(#470)。
5. 多路并发 + 插件模式下的"僵尸代理"。严格说不是资源边界,但它是长时部署的隐性成本:插件模式下代理嵌在宿主进程里,auto-update 装了新版本但宿主不重启就永远跑旧代码(#1603);而自更新失败退避机制(三次失败后 5 min→10→20…封顶 6 h 的指数冷却,#1603/#2192)的存在本身就说明"更新风暴打满网络与磁盘"是被真实观测到过的行为——修复前 dsh 桌面布局损坏时一台机器一天最多 ~480 次 tarball 下载。
五、落地前资源预估清单
按上面的源码证据,给一份可以直接执行的排查表:
| # | 查什么 | 命令/位置 | 默认值 / 判定 |
|---|---|---|---|
| 1 | 代理进程堆上限 | Node 启动参数 | 测试基准是 128 MB 堆启动 320 MB 状态目录;常规机器 512 MB 堆富余 |
| 2 | 会话数规模 | GET /__bili/sessions、web UI | 内存表封顶 256;超过走 LRU + 磁盘重载,不会 OOM |
| 3 | 常驻内存目标 | sessions.residentMaxBytes | 默认 512 MiB;跑很多长活会话可调大,但不改变磁盘为事实源的语义 |
| 4 | 大请求并发 | 无需配置 | 100 MB 出站硬顶 / 2 个超大解码请求;多路大图片 agent 才需留意 |
| 5 | dump 目录配额 | diagnostics.maxTotalBytes/maxAgeDays | 默认不限;开 dump 开关就必须配,参考事故值 63 GB/天 |
| 6 | 日志盘 | 自动 | 10 MB 轮转,忽略 |
| 7 | 会话目录清理 | BILI_SESSION_GC=1+BILI_SESSION_GC_MAX_AGE_DAYS(7)+BILI_SESSION_GC_MAX_TOKENS(1M) | 默认不删;压缩过的会话永不删,规划磁盘时按"压缩历史只增不减"估 |
| 8 | Windows 杀软排除项 | %USERPROFILE%\.local\share\billion-context\ | 必做,否则 EPERM 间歇故障 |
| 9 | 磁盘压缩 | persist.zstd | 默认关;海量短会话机器可开,加密场景体积再降 5–10× |
| 10 | 首压缩时机 | compress.nudgeGrowthTokens | 默认增长 50K 触发;boot 内容大时提前调小,避免 ~200K 才首压的观感 |
| 11 | 监听面 | bili --host | 默认 127.0.0.1;开0.0.0.0无认证,仅限可信 LAN(#409 起盲隧道仍只接受 loopback,管理端点仅 loopback 可达) |
最后回到选题的问题:被"十亿级"唬住的两种人——一种以为它需要十亿级硬件(错:它是 localhost 上的有界 Node 进程,内存以 MB/百 MB 计,历史成本只落在磁盘上),另一种以为"有界"等于"不用管"(错:dump 目录默认无界、会话文件默认不清、Windows 杀软默认不排)。仓库的态度其实很直白:内存边界用测试钉死(128 MB 堆 / 320 MB 状态目录)、磁盘边界把删除权交回用户(opt-in、双门槛、审计日志、压缩历史永不删)。落地前把上面 11 项过一遍,"十亿 token"就只是一个累计吞吐的计数,而不是你资源账单上的数字。
【免费下载链接】billion-contextA context-compression plugin for small context windows (a 100K context is enough), token savings (5x fewer tokens), and month-long single sessions (billions of tokens).上下文压缩插件,兼顾小窗口(100k上下文足矣)省token(省5倍token)和超长会话(数月级别几十亿token单会话)。billion-context is all you need项目地址: https://gitcode.com/gh_mirrors/bil/billion-context
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考