From b246393be4acfed1f5fc33bdedea88f20f8d2bd9 Mon Sep 17 00:00:00 2001 From: vibe-code Date: Thu, 13 Aug 2026 04:26:44 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20=E9=80=9A=E8=AF=BB=E6=95=B4=E7=90=86=20?= =?UTF-8?q?volc-quota-notes.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §2 扣费案例标注已被 §5 推翻,消除误导 - §5 日志示例标注为 v2/v5 历史,补充 v6 当前格式 - §7 重编号: v6 Snapshot-Offset 从 §7.6→§7.5, 2M 击穿从 §7.7→§7.6 - §7.1 标题修正(不再写 'v5 已恢复') - 删除过时的 §7.4 待观察项(BUFFER 已不存在) - 修正 §7.5 SMALL_BUFFER(5k) 笔误为 5 万 - 修正全章节交叉引用编号 - 统一半角标点风格 --- docs/volc-quota-notes.md | 340 +++++++++++++++++++-------------------- 1 file changed, 167 insertions(+), 173 deletions(-) diff --git a/docs/volc-quota-notes.md b/docs/volc-quota-notes.md index 8fb9c08..b6e3400 100644 --- a/docs/volc-quota-notes.md +++ b/docs/volc-quota-notes.md @@ -1,20 +1,20 @@ # 火山免费额度机制速查(VolcEngine Quota Notes) -> 日期:2026-08-12(2026-08-13 增补:网页口径校准 + v6 滞后击穿实测 §4.2/§7.6 + 2M 击穿事件 §7.7) +> 日期:2026-08-12 ~ 2026-08-13 > 用途:后续限额/防扣费设计、排障、消耗任务的事实基础,避免重复推导 > 来源: > - `merge-auto-consume.md` §1(2026-08-11 用户确认的业务细则) -> - 2026-08-12 对话澄清(扣费口径、余额 API 滞后、限额方案演进) -> - 2026-08-12 生产日志观察(deepseek-v4-flash-ga-260731) -> - 2026-08-13 生产日志 + 网页口径(真实初始余额 B0=2,242,178;API 结算滞后 Δ≈14 分钟) +> - 2026-08-12 ~ 13 对话澄清 + 生产日志观察(deepseek-v4-flash-ga-260731) +> - 2026-08-13 网页口径校准(真实初始余额 B0=2,242,178;API 结算滞后 Δ≈14 分钟) > -> 代码现状:v6 = Snapshot-Offset(`limit = balance_at_sync + usage_at_sync − SMALL_BUFFER`), -> 2026-08-12 修改;BUFFER 盲猜法已废弃,见 §7.6。 -> ⚠️ 2026-08-13:§7.6 的「B0 常数/永远不扣费」论证有前提缺陷,见 §4.2 与 §7.6 修正。 +> 代码现状:v6 = Snapshot-Offset(`limit = balance_at_sync + usage_at_sync − SMALL_BUFFER`),2026-08-12 实施。 +> v2/v5 (`usage + balance − BUFFER`) 已废弃,见 §7.4/§7.5。 +> v6 reserve 改进(`token_counter + max_tokens`)2026-08-13 实施,见 §7.6。 +> ⚠️ v6 的「B0 常数/永远不扣费」论证有前提缺陷,见 §4.2 与 §7.5 修正。 --- -## 1. 业务模型(细则,用户确认) +## 1. 业务模型(用户确认) ### 1.1 日返还循环 @@ -28,13 +28,13 @@ | 规则 | 值 | 影响 | |---|---|---| -| 采集上限 | 2M token/模型/天 | 消耗超过 2M 的部分**不返还**(白烧);所有模型统一 | -| 发放额度 | = 昨日采集量(≤2M) | 消耗 X → 次日返还 X,用多少补多少,延迟一天到账 | +| 采集上限 | 2M token/模型/天 | 消耗超过 2M 的部分**不返还**(白烧);所有模型统一 | +| 发放额度 | = 昨日采集量(≤2M) | 消耗 X → 次日返还 X,用多少补多少,延迟一天到账 | | 发放时间 | 不定时 | 次日 0:00 至发放时点存在余额空窗 | | 资源包有效期 | 30 天 | 过期额度实打实消失,失去"启动资金" | -| 消耗顺序 | FIFO(先消耗获得时间早的包) | 队列头部 = 最早获得 = 最早过期 | +| 消耗顺序 | FIFO(先消耗获得时间早的包) | 队列头部 = 最早获得 = 最早过期 | | 余额耗尽 | 请求不失败,按真实价格扣费 | 全部防护的终极目标:避免扣费 | -| 首次开通 | 每模型赠送 2M | 启动资金的种子(无对应前日采集的免费午餐) | +| 首次开通 | 每模型赠送 2M | 启动资金的种子(无对应前日采集的免费午餐) | ### 1.3 账目守恒 @@ -44,28 +44,27 @@ = 余额_N (消耗即采集,≤2M 内全额返还) ``` -推论: -- **使用不损失额度**(用多少补多少,仅延迟一天) -- **过期才损失额度**(额度蒸发 = 启动资金永久减少) +推论: +- **使用不损失额度**(用多少补多少,仅延迟一天) +- **过期才损失额度**(额度蒸发 = 启动资金永久减少) - 消耗任务的价值 = 防止"启动资金"被 30 天过期侵蚀 -- 真实用量 ≥ 66.7k/天(= 2M/30 天)时,赠送包被自然消耗;低于此值必须靠消耗任务在最后 9 天窗口洗掉 +- 真实用量 ≥ 66.7k/天(= 2M/30 天)时,赠送包被自然消耗;低于此值必须靠消耗任务在最后 9 天窗口洗掉 --- -## 2. 扣费判定(2026-08-12 用户确认口径,最重要) +## 2. 扣费判定(用户确认口径,最重要) > 用户原话:**是否扣费只取决于当下有多少免费额度。超出免费额度的请求,都会被扣费。采集量只决定明日发放,与今日是否扣费无关。** ``` -扣费判定:消耗 ≤ 当下余额(免费额度)→ 免费,余额扣减 - 消耗 > 当下余额 → 超出部分按真实价格扣费(无返还、不退) -今日采集量(≤2M)→ 只决定明日返还的包大小,与今日是否扣费无关 +扣费判定:消耗 ≤ 当下余额(免费额度) → 免费,余额扣减 + 消耗 > 当下余额 → 超出部分按真实价格扣费(无返还、不退) +今日采集量(≤2M) → 只决定明日返还的包大小,与今日是否扣费无关 ``` -推论(2026-08-12 案例): -- 当日余额 1,558,307,今日已采 1,696,135 → 超出 **137,828 tokens 已扣费**,不可挽回 -- 明日返还 = 今日采集量 1,696,135 的包(免费额度回正),但今天扣的钱不退 -- 因此「先采后返」**不构成透支许可**:消耗超余额的部分当场计费 +> 注:2026-08-12 §5 曾基于滞后快照推断"当日超余额 137,828 tokens 已扣费", +> 后经 §4.2 网页口径校准推翻(滞后快照高估真实剩余约 50 万,实际无扣费)。 +> 判扣费必须用**网页真实初始余额 B0** 或滞后收敛时点的 U+B 反推,不能用任意时点快照。 --- @@ -73,13 +72,13 @@ | 概念 | 含义 | 量级特征 | 与扣费的关系 | |---|---|---|---| -| 采集量(今日) | 今日实时消耗统计(火山网页"今日采集量") | 单日流量,0~2M | 只决定明日返还 | -| 余额(免费额度) | 资源包 AvailableAmount 存量之和 | 累计存量,守恒 ≈2M(种子) | **扣费唯一依据** | -| 采集配额 2M | 今日最大采集量 | 每模型固定 | 超了白烧(不返还),未必当场扣费 | -| 返还(T+1) | 昨日采集量 → 新资源包 | 单日流量延迟一天 | 补回余额,不抵今日扣费 | +| 采集量(今日) | 今日实时消耗统计(火山网页"今日采集量") | 单日流量,0~2M | 只决定明日返还 | +| 余额(免费额度) | 资源包 AvailableAmount 存量之和 | 累计存量,守恒 ≈2M(种子) | **扣费唯一依据** | +| 采集配额 2M | 今日最大采集量 | 每模型固定 | 超了白烧(不返还),未必当场扣费 | +| 返还(T+1) | 昨日采集量 → 新资源包 | 单日流量延迟一天 | 补回余额,不抵今日扣费 | -**核心:余额是累计存量(守恒),采集量是单日流量。限额设计必须用存量(余额), -绝不能用单日流量(昨日采集量)当上限——那会把可用性锁死在水位以下(见 §7.2 v3)。** +**核心:余额是累计存量(守恒),采集量是单日流量。限额设计必须用存量(余额), +绝不能用单日流量(昨日采集量)当上限——那会把可用性锁死在水位以下(见 §7.2 v3)。** --- @@ -87,48 +86,55 @@ ### 4.1 2026-08-12 观测 -- **非实时,延迟未知**(用户明确)。实测:2026-08-12 13:36:01 同步快照 Bal=1,558,307, - 此后 usage 从 1,241,821 涨到 1,696,135(13:36→13:38)期间 Bal 纹丝不动。 -- 观察结论:AvailableAmount 不随今日消耗实时递减(至少滞后 20 万级)。 +- **非实时,延迟未知**(用户明确)。实测:2026-08-12 13:36:01 同步快照 Bal=1,558,307, + 此后 usage 从 1,241,821 涨到 1,696,135(13:36→13:38)期间 Bal 纹丝不动。 +- 观察结论:AvailableAmount 不随今日消耗实时递减(至少滞后 20 万级)。 -### 4.2 2026-08-13 网页口径校准(Δ≈14 分钟,本次谬误的根源) +### 4.2 2026-08-13 网页口径校准(Δ≈14 分钟) -- 网页确认今日真实初始余额 **B0 = 2,242,178**(采集量 0 时点读数,可信;次日 0:00 前无新包入账) -- 11:08 同步:Bal = 2,242,063 ≈ B0(API 尚未扣减当日用量,差 115 为残差) -- 11:22 同步:Bal = 1,726,526 = **B0 − 515,652**(= 11:08 时点累计用量,**精确吻合**) +- 网页确认今日真实初始余额 **B0 = 2,242,178**(采集量 0 时点读数,可信;次日 0:00 前无新包入账) +- 11:08 同步:Bal = 2,242,063 ≈ B0(API 尚未扣减当日用量,差 115 为残差) +- 11:22 同步:Bal = 1,726,526 = **B0 − 515,652**(= 11:08 时点累计用量,**精确吻合**) - 结论:**结算滞后 Δ ≈ 14 分钟**,即 `Bal(t) ≈ B0 − Usage(t−Δ)`;滞后是**周期收敛**的 - (2026-08-12 14:59:51 曾收敛:B=596,869 = 2,293,004 − 1,696,135 分毫不差) -- 滞后量(未结算消耗)随消费速度增长:实测 149,616(14 分钟突发消费),全天平均口径仅 ~1 万 + (2026-08-12 14:59:51 曾收敛:B=596,869 = 2,293,004 − 1,696,135 分毫不差) +- 滞后量(未结算消耗)随消费速度增长:实测 149,616(14 分钟突发消费),全天平均口径仅 ~1 万 #### 核心修正:`balance_at_sync + usage_at_sync ≠ B0 常数` ``` -设 Δ_settled = 同步时点 API 已结算的今日消耗(= B0 − Bal_sync) +设 Δ_settled = 同步时点 API 已结算的今日消耗(= B0 − Bal_sync) L = 滞后窗口内未结算消耗 = Usage_at_sync − Δ_settled ≥ 0 limit(v6) = Bal_sync + Usage_at_sync − SMALL_BUFFER = B0 + L − SMALL_BUFFER ``` -- 滞后收敛时(L=0):反推值 = B0 ✓(08-12 14:59:51 场景) -- 滞后未收敛时(L>0):反推值 = B0 + L,**虚高随消费增长**(08-13 11:22:2,391,794 vs - 真实 2,242,178,虚高 149,616) +- 滞后收敛时(L=0):反推值 = B0 ✓(08-12 14:59:51 场景) +- 滞后未收敛时(L>0):反推值 = B0 + L,**虚高随消费增长**(08-13 11:22:2,391,794 vs + 真实 2,242,178,虚高 149,616) - 「守恒池」只在滞后收敛时成立;日常撞墙/恢复判定用的全是未收敛快照,故 v6 限额常态虚高 L --- -## 5. 症状对照:Today > Rem(2026-08-12 案例) +## 5. 症状对照:Today > BalAtSync -日志示例(13:36-13:38,deepseek-v4-flash-ga-260731): +> 以下日志为 v2/v5 时代历史示例(字段 Rem/StableLimit 已不存在于 v6 代码), +> 保留用于说明「Today > 余额」这一常见误判。 + +历史日志示例(13:36-13:38,deepseek-v4-flash-ga-260731): ``` DEBUG: Checking deepseek-v4-flash-ga-260731 (Bal: 1,558,307, Rem: 1,258,307) (余额/剩余) -DEBUG: deepseek-v4-flash-ga-260731 Pool=2,800,128 | Usage=1,241,821 | StaticLimit=2,000,000 | StableLimit=2,500,128 | EffectiveLimit=2,000,000 -Set Redis limit:dynamic:volcengine:remaining:deepseek-v4-flash-ga-260731 = 1,258,307 📊 [Usage] deepseek-v4-flash-ga-260731:ba0383d3: +38083 (Today: 1,279,904 | Rem: 1,558,307 | Limit: 2,000,000) ``` -字段口径: +v6 当前日志格式: + +``` +📊 [Usage] deepseek-v4-flash-ga-260731:ba0383d3: +98 (Today: 1,696,233 | BalAtSync: 596,869 | Limit: 2,000,000) +``` + +字段口径(v6): | 字段 | 含义 | 计算 | |---|---|---| @@ -138,21 +144,16 @@ Set Redis limit:dynamic:volcengine:remaining:deepseek-v4-flash-ga-260731 = 1,258 | Today | 本地今日采集量 | Redis `daily_usage` 逐请求累加 | | Limit | 生效限额 | min(2M, balance_at_sync + usage_at_sync − SMALL_BUFFER) | -**Today > BalAtSync 的原因**:Today 是单日流量,BalAtSync 是存量余额快照,两套口径本就不该比较。 -火山网页「今日采集量 1,696,135/2,000,000」对应 Today 与 2M 配额,与余额无关。 +**Today > BalAtSync 的原因**: Today 是单日流量,BalAtSync 是存量余额快照,两套口径本就不该比较。 +火山网页「今日采集量 1,696,135/2,000,000」对应 Today 与 2M 配额,与余额无关。 -**扣费结论(2026-08-12 15:09 实测修正)**:14:59:51 同步 U=1,696,135 + B=596,869, -守恒反推今日初始免费池 B0 = 2,293,004 → 今日消耗 1,696,135 < B0 → **当日无扣费**。 -此前「超余额 13.8 万已扣费」的推断基于 13:36 滞后快照(B=1,558,307 高估真实剩余 -约 50 万),不成立。判定扣费要用「守恒初始池」(U+B 反推)而非任意时点快照。 - -⚠️ **2026-08-13 修正(§4.2)**:U+B 反推的「守恒池」仅在**滞后收敛时点**成立—— -该例 14:59:51 恰好收敛(B0=2,293,004 可信,差 135)。滞后未收敛时 U+B = B0 + L 虚高, -不能当 B0 用。判扣费正确口径 = **网页真实初始余额 B0**(或收敛时点的 U+B 反推)。 +**扣费结论(2026-08-13 修正)**: 判扣费必须用**网页真实初始余额 B0** 或滞后收敛时点的 U+B 反推。 +13:36 滞后快照(B=1,558,307)高估真实剩余约 50 万,不能用于判扣费。 +14:59:51 恰好收敛(B0=2,293,004 可信),今日消耗 1,696,135 < B0 → 当日无扣费。 --- -## 6. Redis key 与日志口径(速查) +## 6. Redis key 与日志口径(速查) ### 6.1 Redis key @@ -166,110 +167,102 @@ Set Redis limit:dynamic:volcengine:remaining:deepseek-v4-flash-ga-260731 = 1,258 ### 6.2 关键日志 - `🚫 [PRE-BLOCK] {model} daily limit reached ({usage}+{reserve}/{limit})`:请求前闸门 429 -- `📊 [Usage] {model}:{hash}: +{tokens} (Today: X | BalAtSync: Y | Limit: Z)`:成功记账 +- `📊 [Usage] {model}:{hash}: +{tokens} (Today: X | BalAtSync: Y | Limit: Z)`:成功记账 +- `🚫 [BLOCK] {model} Limit Hit: {current}/{limit}`:后置闸门触发,soft_evict --- ## 7. 限额方案演进 -### 7.1 v2(HEAD c6c1224 原式,v5 已恢复):`stable_limit = usage + balance − BUFFER` +### 7.1 v2(原式,已被 v5/v6 继承和替代):`stable_limit = usage + balance − BUFFER` -- 意图:`usage + balance` = 当日初始免费池(守恒 B0),消耗中恒定 → 限额恒定 -- **数学正确性(2026-08-12 15:09 实测确认)**:B 滞后收敛后确实满足 B = B0 − U - (14:59:51:U=1,696,135 + B=596,869 = B0=2,293,004,与理论剩余 597,004 几乎一致)。 +- 意图:`usage + balance` = 当日初始免费池(守恒 B0),消耗中恒定 → 限额恒定 +- **数学正确性**:B 滞后收敛后确实满足 B = B0 − U + (14:59:51:U=1,696,135 + B=596,869 = B0=2,293,004,与理论剩余 597,004 几乎一致)。 同步时 stable = B0 − BUFFER,usage 累计撞线 = 用到剩 BUFFER 停止 —— 语义正确 -- **残留风险(非公式错误,是快照时点问题)**:同步时 B 若滞后(13:36 高估真实剩余 - 约 50 万)→ stable 虚高 → 多放行滞后窗口内消耗。由 BUFFER + 低余额加密同步 - (600s/300s)+ burst 触发覆盖;收敛后池回到守恒值 -- 案例:13:36 快照 B=1,558,307 为滞后值(真实剩余 ≈1,051,183),并非「余额不足」; - 今日无扣费(见 §5) +- **残留风险(非公式错误,是快照时点问题)**:同步时 B 若滞后(13:36 高估真实剩余 + 约 50 万)→ stable 虚高 → 多放行滞后窗口内消耗。由 BUFFER + 低余额加密同步 + (600s/300s) + burst 触发覆盖;收敛后池回到守恒值 +- **已废弃**:BUFFER 盲猜无法解决高强度击穿/低强度浪费(§7.5) -### 7.2 v3(尝试后否决,已回滚):昨日锚 `effective = min(2M, 昨日采集量 × 系数)` +### 7.2 v3(尝试后否决,已回滚):昨日锚 `effective = min(2M, 昨日采集量 × 系数)` - 思路:把「返还 = 昨日采集量」当作「今日可采上限」 -- **否决理由(用户反例)**:余额 2M、昨日用量 10K → 今日最多 10K?荒谬。 - 错误在于把**单日流量(昨日采集量)当存量(余额)** 用;昨日少用不损余额(守恒), +- **否决理由(用户反例)**:余额 2M、昨日用量 10K → 今日最多 10K?荒谬。 + 错误在于把**单日流量(昨日采集量)当存量(余额)** 用;昨日少用不损余额(守恒), 今日可用 = min(2M, 余额) ≈ 2M,与昨日无关 -- 附:系数 <1.0 还会引发「今日少采 → 明日返还更少」的日复一日递减陷阱(0.9 系数 5 日降至 66%) +- 附:系数 <1.0 还会引发「今日少采 → 明日返还更少」的日复一日递减陷阱(0.9 系数 5 日降至 66%) -### 7.3 v4(2026-08-12 实施后当天发现错误,已回退):冻结快照 +### 7.3 v4(2026-08-12 实施后当天发现错误,已回退):冻结快照 ``` stable_limit = max(0, balance − BUFFER) # ❌ 错误:把「剩余余额」当「从 0 累计的上限」 ``` - 设计初衷:余额 API 滞后 → 冻结快照防池膨胀 -- **错误实证(15:09:51 生产日志)**:Bal=596,869(真实剩余 59.7 万), +- **错误实证(15:09:51 生产日志)**:Bal=596,869(真实剩余 59.7 万), stable = 296,869,usage=1,696,135 ≥ 296,869 → `[PRE-BLOCK] … (1,696,135+16/296,869)` 误拦。 - 账上有真实免费额度却全拦 —— 因为 stable 应是「今日累计上限」(= B0 − BUFFER), - 而 balance 是「剩余」(= B0 − U);U>0 时两者相差 U,直接替换导致低估、误拦 -- 仅在同步时点 U=0(日初/刚返还)巧合正确(doubao 场景 stable=2.2M 无碍) -- 教训:**「剩余额度」不能当「累计限额」用**;两者通过 `usage + balance` 桥接(v5) + 账上有真实免费额度却全拦 —— 因为 stable 应是「今日累计上限」(= B0 − BUFFER), + 而 balance 是「剩余」(= B0 − U);U>0 时两者相差 U,直接替换导致低估、误拦 +- 仅在同步时点 U=0(日初/刚返还)巧合正确 +- 教训:**「剩余额度」不能当「累计限额」用**;两者通过 `usage + balance` 桥接 -### 7.4 待观察项(2026-08-12) - -- **BUFFER(现 30 万)**:防「同步时点 B 滞后窗口内的消耗」。13:36 实测高估约 50 万 - > 30 万 BUFFER → 若滞后窗口内流量大,v2 会多放行到虚高 stable;可调大(如 50 万,env) -- **部署**:v5 已改(工作区),需重启 litellm 容器加载;重启在生产主机由用户执行 - -### 7.5 v5(2026-08-12 修正,已废弃):恢复 v2 骨架 +### 7.4 v5(2026-08-12,已废弃):恢复 v2 骨架 ``` stable_limit = max(0, usage + balance − BUFFER) ``` -- 与 v2(§7.1)公式相同,区别在**论证与认识**:v2 数学正确(B 收敛后 = B0 − U), - 不需要冻结快照;v4 的「剩余当上限」误拦由「usage + balance 桥接」天然避免 +- 与 v2(§7.1)公式相同,区别在**论证与认识**:v2 数学正确(B 收敛后 = B0 − U), + 不需要冻结快照;v4 的「剩余当上限」误拦由「usage + balance 桥接」天然避免 - 实测验证(14:59:51 同步 + 15:09:51 生产日志):U=1,696,135、B=596,869 → - stable=1,993,004 → 今日剩余可用 296,869(账上 59.7 万用到剩 30 万 BUFFER)✓ -- 防扣费语义不变:usage 撞 stable = 剩余恰为 BUFFER 时停,扣费结构性杜绝; + stable=1,993,004 → 今日剩余可用 296,869(账上 59.7 万用到剩 30 万 BUFFER) ✓ +- 防扣费语义不变:usage 撞 stable = 剩余恰为 BUFFER 时停,扣费结构性杜绝; balance < BUFFER(U=0)→ stable=0 → 全拦 -- **已废弃原因**:BUFFER 盲猜滞后窗口消耗,高强度击穿低强度浪费。被 §7.6 Snapshot-Offset 替代。 +- **已废弃原因**:BUFFER 盲猜滞后窗口消耗,高强度击穿低强度浪费。被 §7.5 v6 Snapshot-Offset 替代。 -### 7.6 v6(Snapshot-Offset,2026-08-12 实施;2026-08-13 修正论证缺陷):宝塔防扣费 +### 7.5 v6(Snapshot-Offset,当前方案) ``` -limit = balance_at_sync + usage_at_sync − SMALL_BUFFER(5万) +limit = balance_at_sync + usage_at_sync − SMALL_BUFFER(5 万) = B0 + L − SMALL_BUFFER # L = 同步时点滞后窗口未结算消耗(§4.2) ``` -> ⚠️ 2026-08-13 修正:原论证称 `= B0 − SMALL_BUFFER`(B0 常数、精确抵消),前提「同步时点 +> ⚠️ 2026-08-13 修正:原论证称 `= B0 − SMALL_BUFFER`(B0 常数、精确抵消),前提「同步时点 > balance + usage = B0」在滞后未收敛时**不成立**——实际 = B0 + L(§4.2 实测 L=149,616)。 #### 核心思路 -用同步时点快照 `balance_at_sync + usage_at_sync` 计算累计限额:**滞后收敛时(L=0)恰为 -当日初始免费池 B0**;滞后未收敛时虚高 L。两次同步间快照不变,实时 `current_usage` 撞线 -只抵消同步**之后**窗口内的增量消耗,**不抵消同步时点已存在的滞后 L**。 +用同步时点快照 `balance_at_sync + usage_at_sync` 计算累计限额:**滞后收敛时(L=0)恰为 +当日初始免费池 B0**;滞后未收敛时虚高 L。两次同步间快照不变,实时 `current_usage` 撞线 +只抵消同步**之后**窗口内的增量消耗,**不抵消同步时点已存在的滞后 L**。 -**limit 是累计限额(从 0 到上限),不是剩余余额**。`_check_request_block` 用 `current_usage + reserve >= limit` 撞线。 +**limit 是累计限额(从 0 到上限),不是剩余余额**。`_check_request_block` 用 `current_usage + reserve >= limit` 撞线。 -#### 为什么 SMALL_BUFFER 不能覆盖全部滞后 - -旧方案用 BUFFER(30 万)猜「滞后窗口内消耗了多少」——猜不准。新方案改为快照 + 实时 usage: +#### 与旧方案的核心区别 ``` -# 旧:盲猜 -limit = usage + balance - BUFFER(300k) +# 旧 v5: 盲猜滞后窗口消耗 +limit = usage + balance - BUFFER(30 万) -# 新:快照 -limit = balance_at_sync + usage_at_sync - SMALL_BUFFER(5k) = B0 + L - SMALL_BUFFER +# 新 v6: 快照 + 实时 usage 撞固定线 +limit = balance_at_sync + usage_at_sync - SMALL_BUFFER(5 万) = B0 + L - SMALL_BUFFER ``` -**SMALL_BUFFER(5 万)覆盖「非消耗性余额跳降」(资源包过期) + 少量未结算残差**—— -滞后未结算消耗 L 只兜 5 万:**L > 5 万时限额虚高进入扣费区**(08-13 实测 L=149,616 > 5 万, -击穿条件成立;当日幸被静态 2M 兜底,见「不变量」)。 +- v5 的 BUFFER 猜的是「两次同步之间 + 滞后窗口内的消耗」——猜不准,高强度击穿低强度浪费 +- v6 的 SMALL_BUFFER(5 万)只覆盖**非消耗性余额跳降**(资源包过期) + 少量未结算残差, + 不覆盖滞后窗口消耗 L。**L > 5 万时限额虚高进入扣费区**(08-13 实测 L=149,616 > 5 万)。 #### 不变量(带 L 的精确形式) -撞线 `usage + reserve >= B0 + L - SMALL_BUFFER` 时:usage = `B0 + L − SMALL_BUFFER − reserve`, +撞线 `usage + reserve >= B0 + L - SMALL_BUFFER` 时:usage = `B0 + L − SMALL_BUFFER − reserve`, 真实剩余 = `B0 − usage` = **`SMALL_BUFFER + reserve − L`**。 -- L=0(滞后收敛):剩余 = SMALL_BUFFER + reserve > 0 → **永远不扣费** ✓(原论证仅此情形成立) -- L > SMALL_BUFFER:剩余可 < 0 → **已进扣费区** ✗ -- **今日为何安全(08-13)**:真实 B0=2,242,178 > 静态 2M → `min(2M, B0+L−S)` 被静态 2M 压住, - 撞线发生在 usage=2M,真实剩余 242,178 > 0——是**静态墙**兜底,不是 v6 不变量。 - **若某日 B0 < 2M(包变小),静态墙失效,L > SMALL_BUFFER 即可能超扣。** +- L=0(滞后收敛):剩余 = SMALL_BUFFER + reserve > 0 → **永远不扣费** ✓(原论证仅此情形成立) +- L > SMALL_BUFFER:剩余可 < 0 → **已进扣费区** ✗ +- **今日为何安全(08-13)**:真实 B0=2,242,178 > 静态 2M → `min(2M, B0+L−S)` 被静态 2M 压住, + 撞线发生在 usage=2M,真实剩余 242,178 > 0——是**静态墙**兜底,不是 v6 不变量。 + **若某日 B0 < 2M(包变小),静态墙失效,L > SMALL_BUFFER 即可能超扣。** #### 执行流程 @@ -286,47 +279,48 @@ limit = balance_at_sync + usage_at_sync - SMALL_BUFFER(5k) = B0 + L - SMALL_BUFF #### 与旧方案对比 -| 场景 | 旧(BUFFER 30万) | 新(Snapshot-Offset) | +| 场景 | 旧 v5(BUFFER 30 万) | 新 v6(Snapshot-Offset) | |---|---|---| -| 高强度消耗 | 滞后窗口内消耗 > 30 万 → 击穿扣费 | 撞 B0+L 线:L ≤ 5 万安全,L > 5 万虚高 | +| 高强度消耗 | 滞后窗口内消耗 > 30 万 → 击穿扣费 | 撞 B0+L 线:L ≤ 5 万安全,L > 5 万虚高 | | 低强度消耗 | 浪费 30 万额度(实际只用了 5 万) | 只留 5 万 SMALL_BUFFER | -| 新包到账 | 下次同步 stable limit 跳升 | 下次同步 B0 跳升,一致 | -| 包过期 | 余额跳降,limit 偏高至下次同步 | 同左,但 SMALL_BUFFER=5 万风险窗口小 | +| 新包到账 | 下次同步 stable limit 跳升 | 下次同步 B0 跳升,一致 | +| 包过期 | 余额跳降,limit 偏高至下次同步 | 同左,但 SMALL_BUFFER=5 万风险窗口小 | #### 遗留风险 -1. **同步时点滞后 L 无法抵消(08-13 实测,当前首要风险)**:API 结算滞后 Δ≈14 分钟, - L = 窗口内消耗,随消费速度增长(实测 149,616 > SMALL_BUFFER 5 万)。v6 限额常态虚高 L, - 仅被静态 2M 墙 + `min()` 兜住(今日 B0=2,242,178 > 2M)。若 B0 < 2M 或 L 放大,有 - 放行进扣费区风险。缓解方向:SMALL_BUFFER 与最近消费联动 / 缩短低余额同步+突发检测 / +1. **同步时点滞后 L 无法抵消(当前首要风险)**:API 结算滞后 Δ≈14 分钟, + L = 窗口内消耗,随消费速度增长(实测 149,616 > SMALL_BUFFER 5 万)。v6 限额常态虚高 L, + 仅被静态 2M 墙 + `min()` 兜住(今日 B0=2,242,178 > 2M)。若 B0 < 2M 或 L 放大,有 + 放行进扣费区风险。缓解方向:SMALL_BUFFER 与最近消费联动 / 缩短低余额同步 + 突发检测 / 直接以网页口径 B0 校验限额。 -2. **多包同时过期 > SMALL_BUFFER**:概率低,无法绝对防御。可调大 SMALL_BUFFER 或监控包过期时间。 +2. **多包同时过期 > SMALL_BUFFER**:概率低,无法绝对防御。可调大 SMALL_BUFFER 或监控包过期时间。 +### 7.6 2M 采集上限击穿事件与 reserve 改进 -### 7.7 2M 采集上限击穿事件与 reserve 改进(2026-08-12 20:53 实测) - -#### 事件回顾 +#### 事件回顾(2026-08-12 20:53) ``` 20:38:26 Today: 1,696,233 (delta +98) 20:53:13 Today: 1,872,804 (delta +176,571) ← 单请求消耗 176k 20:53:18 突发触发 (+176,669 > 100,000) 20:53:35 同步完成: Usage=2,050,826 ← 已超 2M! -23:18:59 下次同步: Bal=242,178 (API 滞后修正,B0 回归 2,293,004) +23:18:59 下次同步: Bal=242,178 (API 滞后修正,B0 回归 2,293,004) 火山网页: 今日采集量 2,000,000/2,000,000 ← 2M 撑满 ``` -**2M 采集上限确实撞了**,usage 超出 50,826 token。但 B0=2,293,004 > usage,未扣费。 +**2M 采集上限确实撞了**,usage 超出 50,826 token。但 B0=2,293,004 > usage,未扣费。 -#### 根因:reserve 估算不足 + 并发击穿 +#### 根因:reserve 估算不足 + 并发击穿 + +旧 reserve 逻辑: -旧 reserve 逻辑: ```python reserve = data.get("max_tokens") or PREBLOCK_RESERVE_TOKENS(50000) # 闸门: usage + reserve >= effective_limit → 429 ``` -deepseek-v4-flash 单请求实际消耗 176,571 token(远超 reserve 50k): +deepseek-v4-flash 单请求实际消耗 176,571 token(远超 reserve 50k): + ``` 请求放行时: usage=1,800,000, reserve=50,000 → 1,850,000 < 2M → PASS 请求完成后: usage=1,976,571 (单请求吃了 176k) @@ -334,8 +328,8 @@ deepseek-v4-flash 单请求实际消耗 176,571 token(远超 reserve 50k): # 但此时已经超了 2M ``` -**并发请求放大问题**:多个请求同时通过闸门(都读到 usage < 2M),各自实际消耗叠加, -放行时 usage 相加不超限,完成后集体超限。 +**并发请求放大问题**:多个请求同时通过闸门(都读到 usage < 2M),各自实际消耗叠加, +放行时 usage 相加不超限,完成后集体超限。 #### B0 守恒假设的再次验证 @@ -345,37 +339,37 @@ deepseek-v4-flash 单请求实际消耗 176,571 token(远超 reserve 50k): 23:18 B0 = 242,178 + 2,050,826 = 2,293,004 ← API 修正后回归初值 ``` -B0 守恒 **在 API 滞后收敛后成立**,但同步快照期间虚高(§4.2 的 L 量)。 +B0 守恒 **在 API 滞后收敛后成立**,但同步快照期间虚高(§4.2 的 L 量)。 -#### reserve 改进:token_counter 精确估算输入 +#### reserve 改进:token_counter 精确估算输入 ```python -# 旧:盲猜 +# 旧: 盲猜 reserve = data.get("max_tokens") or 50000 -# 新:精确输入 + 声明输出 +# 新: 精确输入 + 声明输出 input_tokens = litellm.token_counter(model=data["model"], messages=data["messages"]) -# 未知模型 fallback cl100k_base,对中文偏高(更安全) +# 未知模型 fallback cl100k_base,对中文偏高(更安全) max_out = data.get("max_tokens") or PREBLOCK_RESERVE_TOKENS reserve = input_tokens + max_out ``` -**为什么中文偏高更安全**:DeepSeek 自有 tokenizer 对中文 ~1.5 token/字, +**为什么中文偏高更安全**:DeepSeek 自有 tokenizer 对中文 ~1.5 token/字, cl100k_base ~2-3 token/字。估算偏高 = 预留更多 = 提前撞线 = 更保守。 -**效果**:输入 200k 的请求,旧 reserve 只有 50k,新 reserve 250k+, +**效果**:输入 200k 的请求,旧 reserve 只有 50k,新 reserve 250k+, 在 usage=1.8M 时就拦住(而非放行后超 2M)。 #### 后置闸门兜底 ```python -# _record_usage 里,写完 usage 后: +# _record_usage 里,写完 usage 后: if current_total >= effective_limit: - soft_evict_from_router(raw_model, api_base) # 改名踢出,后续全 429 + soft_evict_from_router(raw_model, api_base) # 改名踢出,后续全 429 ``` -前置闸门挡不住已放行的请求,但 `_record_usage` 完成记账后检查超限, -立即 `soft_evict`,后续请求全部 429。 +前置闸门挡不住已放行的请求,但 `_record_usage` 完成记账后检查超限, +立即 `soft_evict`,后续请求全部 429。 #### 双重防线总结 @@ -384,42 +378,42 @@ if current_total >= effective_limit: | **前置** | `reserve = token_counter(messages) + max_tokens` | 预估超限的请求 | 实际消耗 > reserve 的请求 | | **后置** | `current_total >= effective_limit → soft_evict` | 超限后的后续请求 | 已发出正在执行的请求 | -**遗留**:若 `max_tokens` 未声明且模型输出无上限,前置仍可能预估过低。 +**遗留**:若 `max_tokens` 未声明且模型输出无上限,前置仍可能预估过低。 此时只有后置 `soft_evict` 兜底(挡后续不挡本次)。 + --- -## 8. 排障速查(今日结论汇总) +## 8. 排障速查 -1. 看到 Today > BalAtSync 不是异常,先分清口径(§5);判扣费优先看**网页真实初始余额 B0**(或滞后 - 收敛时点的 U+B 反推,§4.2),不要用任意时点快照(13:36 滞后快照曾误判「超余额扣费」,实际无扣费) -2. 余额显示不动 ≠ 余额充足,是 API 滞后(Δ≈14 分钟,§4.2);v6 的 `balance_at_sync + usage_at_sync` - 在滞后未收敛时 = B0 + L(虚高),「实时抵消」仅覆盖同步后窗口,L 不抵消(§7.6) -3. 判断「还能免费采多少」:保守用 `网页 B0 − 今日已采`;v6 公式在滞后收敛时等价,未收敛时偏高 L -4. 消耗任务 23:00 直连 Ark 绕过 LiteLLM 闸门(防自杀),其自身 clamp:`plan ≤ 2M − usage`、 - `plan ≤ 余额快照 − SMALL_BUFFER`,停止条件三闭环 -5. 余额滞后不改变 B0 守恒;30 天过期蒸发是余额下跌的另一来源, - 与消耗扣减区分(如 08-12 余额 1,558,307 → 596,869,其中约 50 万为滞后修正 + 过期) -6. 2M 采集上限可被单请求 + 并发击穿(§7.7):reserve 估算不足时已放行的请求继续消耗, - `soft_evict` 后置兜底只能挡后续。修复:reserve = `litellm.token_counter(messages)` + `max_tokens` -7. B0 守恒仅在 API 滞后收敛后成立;同步快照期间 `balance + usage = B0 + L`(虚高,§4.2), +1. 看到 Today > BalAtSync 不是异常,先分清口径(§5);判扣费优先看**网页真实初始余额 B0**(或滞后 + 收敛时点的 U+B 反推,§4.2),不要用任意时点快照(13:36 滞后快照曾误判「超余额扣费」,实际无扣费) +2. 余额显示不动 ≠ 余额充足,是 API 滞后(Δ≈14 分钟,§4.2);v6 的 `balance_at_sync + usage_at_sync` + 在滞后未收敛时 = B0 + L(虚高),「实时抵消」仅覆盖同步后窗口,L 不抵消(§7.5) +3. 判断「还能免费采多少」:保守用 `网页 B0 − 今日已采`;v6 公式在滞后收敛时等价,未收敛时偏高 L +4. 消耗任务 23:00 直连 Ark 绕过 LiteLLM 闸门(防自杀),其自身 clamp:`plan ≤ 2M − usage`、 + `plan ≤ 余额快照 − SMALL_BUFFER`,停止条件三闭环 +5. 余额滞后不改变 B0 守恒;30 天过期蒸发是余额下跌的另一来源, + 与消耗扣减区分(如 08-12 余额 1,558,307 → 596,869,其中约 50 万为滞后修正 + 过期) +6. 2M 采集上限可被单请求 + 并发击穿(§7.6):reserve 估算不足时已放行的请求继续消耗, + `soft_evict` 后置兜底只能挡后续。修复:reserve = `litellm.token_counter(messages)` + `max_tokens` +7. B0 守恒仅在 API 滞后收敛后成立;同步快照期间 `balance + usage = B0 + L`(虚高,§4.2), 不是真实 B0。判扣费用网页真实初始余额或收敛时点反推 --- ## 9. 待确认/风险清单 -- [~] 余额 API 滞后实测(2026-08-13:Δ≈14 分钟,滞后量 = 窗口内消费,实测 L=149,616 > SMALL_BUFFER - 5 万;上限随消费速度变化,建议 SMALL_BUFFER 与消费联动或改用网页 B0 校验,§7.6 遗留风险 1) -- [x] v4 误拦发现与回退(2026-08-12 15:09 生产日志实证,§7.3) -- [x] v5 废弃(2026-08-12,§7.5;被 v6 Snapshot-Offset 替代) -- [x] v6 实施(2026-08-12,§7.6;待生产主机重启生效) -- [x] 2M 采集上限击穿修复(2026-08-12 20:53 实测 §7.7:reserve 改用 token_counter + max_tokens, - 后置 soft_evict 兜底;23:18 重启后 PRE-BLOCK 生效) -- [ ] 消耗任务口径同步收紧(已改为 vls.SMALL_BUFFER,确认与闸门一致) -- [ ] 空窗期(0:00–返还到账)行为观察:v6 下 B0 自动收紧,确认无异常误杀 -- [ ] 数据库泄漏:08-12 一天 deepseek-v4 消耗记录量级约 5 万条(此处曾误记「512 亿」,从未烧过该量级, - 为口径笔误)导致 Redis 内存膨胀;触发监控/清理机制 -- [ ] v6 无静态 2M 兜底时的安全:08-13 幸由 B0(2,242,178) > 2M 静态墙兜底;若某日 B0 < 2M, - 滞后 L > SMALL_BUFFER 时 v6 限额虚高 → 可能放行到余额 0 → 扣费(§7.6 遗留风险 1) -- [ ] reserve 精度验证:token_counter fallback cl100k_base 对中文偏高,需实测确认偏差范围 - (DeepSeek 自有 tokenizer ~1.5 token/字 vs cl100k_base ~2-3 token/字,§7.7) +- [~] 余额 API 滞后实测(2026-08-13:Δ≈14 分钟,滞后量 = 窗口内消费,实测 L=149,616 > SMALL_BUFFER + 5 万;上限随消费速度变化,建议 SMALL_BUFFER 与消费联动或改用网页 B0 校验,§7.5 遗留风险 1) +- [x] v4 误拦发现与回退(2026-08-12 15:09 生产日志实证,§7.3) +- [x] v5 废弃(2026-08-12,§7.4;被 v6 Snapshot-Offset 替代) +- [x] v6 实施(2026-08-12,§7.5;2026-08-13 生产验证) +- [x] 2M 采集上限击穿修复(2026-08-12 20:53 实测 §7.6:reserve 改用 token_counter + max_tokens, + 后置 soft_evict 兜底;23:18 重启后 PRE-BLOCK 生效) +- [ ] 消耗任务口径同步收紧(已改为 vls.SMALL_BUFFER,确认与闸门一致) +- [ ] 空窗期(0:00~返还到账)行为观察:v6 下 B0 自动收紧,确认无异常误杀 +- [ ] 数据库泄漏:08-12 一天 deepseek-v4 消耗记录量级约 5 万条导致 Redis 内存膨胀;触发监控/清理机制 +- [ ] v6 无静态 2M 兜底时的安全:08-13 幸由 B0(2,242,178) > 2M 静态墙兜底;若某日 B0 < 2M, + 滞后 L > SMALL_BUFFER 时 v6 限额虚高 → 可能放行到余额 0 → 扣费(§7.5 遗留风险 1) +- [ ] reserve 精度验证:token_counter fallback cl100k_base 对中文偏高,需实测确认偏差范围 + (DeepSeek 自有 tokenizer ~1.5 token/字 vs cl100k_base ~2-3 token/字,§7.6)