docs: 采纳独立复核精化 v6 注释与不变量(reserve 阈值/consume 快照口径)

This commit is contained in:
vibe-code
2026-08-13 05:51:41 +00:00
parent 729b1a3987
commit f86aa59af1
2 changed files with 41 additions and 15 deletions
+17 -7
View File
@@ -259,7 +259,7 @@ limit = balance_at_sync + usage_at_sync - SMALL_BUFFER(5 万) = B0 + L - SMALL_B
真实剩余 = `B0 − usage` = **`SMALL_BUFFER + reserve − L`**。
- L=0(滞后收敛):剩余 = SMALL_BUFFER + reserve > 0 → **永远不扣费** ✓(原论证仅此情形成立)
- L > SMALL_BUFFER:剩余可 < 0 → **已进扣费区** ✗
- L > SMALL_BUFFER + reserve:剩余 < 0 → **已扣费**(超扣下界,取决于 reserve;保守写 L > SMALL_BUFFER)
- **今日为何安全(08-13)**:真实 B0=2,242,178 > 静态 2M → `min(2M, B0+L−S)` 被静态 2M 压住,
撞线发生在 usage=2M,真实剩余 242,178 > 0——是**静态墙**兜底,不是 v6 不变量。
**若某日 B0 < 2M(包变小),静态墙失效,L > SMALL_BUFFER 即可能超扣。**
@@ -357,8 +357,9 @@ reserve = input_tokens + max_out
**为什么中文偏高更安全**:DeepSeek 自有 tokenizer 对中文 ~1.5 token/字,
cl100k_base ~2-3 token/字。估算偏高 = 预留更多 = 提前撞线 = 更保守。
**效果**:输入 200k 的请求,旧 reserve 只有 50k,新 reserve 250k+,
在 usage=1.8M 时就拦住(而非放行后超 2M)。
**效果**:输入 200k 的请求,旧 reserve 只有 50k,新 reserve 250k+,在 usage=1.8M 时拦住
(而非放行后超 2M)。⚠️ 该阈值仅对 `input ≥ 150k` 成立(由 `1.8M + input + 50k ≥ 2M` 反推);
小输入请求(如 126k)仍会在 usage<2M 时放行,封口实际依赖后置 `soft_evict`(见下节)。
#### 后置闸门兜底
@@ -378,8 +379,12 @@ if current_total >= effective_limit:
| **前置** | `reserve = token_counter(messages) + max_tokens` | 预估超限的请求 | 实际消耗 > reserve 的请求 |
| **后置** | `current_total >= effective_limit → soft_evict` | 超限后的后续请求 | 已发出正在执行的请求 |
**遗留**:若 `max_tokens` 未声明且模型输出无上限,前置仍可能预估过低。
此时只有后置 `soft_evict` 兜底(挡后续不挡本次)。
**遗留解决(2026-08-13 短文本实证)**:`max_tokens` 未声明时前置仍可能预估过低;更隐蔽的是
**思考模型** —— deepseek-v4-flash(ga-260731) 默认思考模式,usage.total_tokens 含 reasoning tokens
且不受 `max_tokens` 限制(max_tokens 只约束正文 completion)。实测「彩虹有几种颜色」:
reserve = 18输入 + 16声明 = 34,实际消耗 +167/+150(正文 ~16 + 思考 ~120-130)。
修复:输出侧取 `max(声明 max_tokens, THINKING_RESERVE_FLOOR=4096)` 兜底思考开销。
大输入请求仍偏保守(cl100k_base 中文 2-3 token/字高估),方向安全。
---
@@ -396,6 +401,7 @@ if current_total >= effective_limit:
与消耗扣减区分(如 08-12 余额 1,558,307 → 596,869,其中约 50 万为滞后修正 + 过期)
6. 2M 采集上限可被单请求 + 并发击穿(§7.6):reserve 估算不足时已放行的请求继续消耗,
`soft_evict` 后置兜底只能挡后续。修复:reserve = `litellm.token_counter(messages)` + `max_tokens`
(输出侧另加 `THINKING_RESERVE_FLOOR` 兜底思考模型 reasoning tokens,§7.6)
7. B0 守恒仅在 API 滞后收敛后成立;同步快照期间 `balance + usage = B0 + L`(虚高,§4.2),
不是真实 B0。判扣费用网页真实初始余额或收敛时点反推
@@ -415,5 +421,9 @@ if current_total >= effective_limit:
- [ ] 数据库泄漏: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)
- [x] reserve 精度验证(2026-08-13 短文本实证:估算 34 vs 实际 167 的根因 =
思考模型 reasoning tokens 不计入 reserve,已用 THINKING_RESERVE_FLOOR 兜底;
token_counter cl100k_base 对中文偏高对输入侧仍是安全方向)
- [ ] consume clamp2 余额快照口径不同步:`plan ≤ balance − SMALL_BUFFER` 用拉包时刻余额快照
(volc_consume.py:455),**不含 23:00→00:00 真实用户消耗**;clamp1 用共享 daily_usage 实时精准,
但余额维度两方不同步。深夜真实消耗大时两方合计可超烧余额。建议运行时重取余额或扣减预估
+24 -8
View File
@@ -101,6 +101,14 @@ CHECK_INTERVAL = 10
# 未设置时 fallback 本常量。堵住"并发请求穿过闸门后集体撞线"的窗口。
PREBLOCK_RESERVE_TOKENS = int(os.getenv("PREBLOCK_RESERVE_TOKENS", "50000"))
# 思考模型输出侧兜底下限:deepseek-v4-flash 等默认思考模式,
# usage.total_tokens 含 reasoning tokens,且不受 max_tokens 限制
# (max_tokens 只约束正文 completion,思考 token 另计照常计费)。
# 短文本请求若客户端声明 max_tokens=16,reserve 输出侧仅 16,
# 实际消耗 ~150+(思考占大头),闸门形同虚设。取
# max(声明值, 本下限) 兜住思考开销;2M 限额下 4096 偏差仅 0.2%。
THINKING_RESERVE_FLOOR = int(os.getenv("THINKING_RESERVE_FLOOR", "4096"))
# stable_limit 缺失时(首次同步前/新增模型/TTL 过期)的保守兜底限额。
# 免费模型余额常态 < 2M(昨日采多少返多少),若 fallback 裸奔到采集上限 2M,
# 余额不足的模型会被烧穿扣费。宁可短时收紧,不可裸奔。
@@ -424,7 +432,8 @@ def format_limit_debug_fields(static_limit=None, balance_at_sync=None, usage_at_
def get_effective_limit(clean_model, api_base, current_usage=None):
"""
Calculate effective limit using the Snapshot-Offset strategy.
limit = balance_at_sync + usage_at_sync - SMALL_BUFFER (= B0 - SMALL_BUFFER)
limit = balance_at_sync + usage_at_sync - SMALL_BUFFER
= B0 + L - SMALL_BUFFER # L = 同步时点滞后窗口未结算消耗(滞后收敛时 L=0, 虚高随消费增长, 见 docs/volc-quota-notes.md §4.2/§7.5)
_check_request_block 用 current_usage + reserve >= limit 撞线,语义正确。
current_usage 参数仅用于无同步数据的 fallback 路径,非公式必需。
"""
@@ -435,8 +444,9 @@ def get_effective_limit(clean_model, api_base, current_usage=None):
static_limit = get_static_limit(clean_model, api_base)
# 2. Get Dynamic Limit from Snapshot
# balance_at_sync + usage_at_sync = 当日初始免费池 B0(常数)
# 累计限额 = B0 - SMALL_BUFFER,两次同步间不变
# balance_at_sync + usage_at_sync = B0 + L(B0 当日初始免费池, L 同步时点滞后未结算消耗;
# 仅滞后收敛 L=0 时恰为 B0, 未收敛时虚高 L, 见 docs/volc-quota-notes.md §7.5)
# 累计限额 = B0 + L - SMALL_BUFFER,两次同步间不变
balance_key = f"limit:dynamic:volcengine:balance_at_sync:{clean_model}"
usage_key = f"limit:dynamic:volcengine:usage_at_sync:{clean_model}"
@@ -449,7 +459,7 @@ def get_effective_limit(clean_model, api_base, current_usage=None):
try:
balance_at_sync = float(balance_val)
usage_at_sync = float(usage_val)
# 累计限额 = B0 - SMALL_BUFFER,不依赖 current_usage
# 累计限额 = B0 + L - SMALL_BUFFER(L=同步时点滞后未结算消耗),不依赖 current_usage
dynamic_limit = balance_at_sync + usage_at_sync - SMALL_BUFFER
dynamic_limit = max(0, dynamic_limit)
@@ -827,17 +837,23 @@ class DailyLimitHandler(CustomLogger):
if not data or not data.get("model"):
return None
# 预留本次请求的预估消耗 = 输入 token(精确) + 输出上限(max_tokens)
# 预留本次请求的预估消耗 = 输入 token(精确) + 输出上限(max_tokens)。
# 输入用 litellm.token_counter 精确计数(未知模型 fallback cl100k_base,
# 对中文偏高更安全);输出用请求声明的 max_tokens,未声明时 fallback 常量。
# 对中文偏高更安全)。
# 输出侧:思考模型 usage 含 reasoning tokens 且不受 max_tokens 限制,
# 故取 max(请求声明 max_tokens, THINKING_RESERVE_FLOOR) 兜底思考开销,
# 防短文本 + 小 max_tokens 请求 reserve 低估(实测 34 vs 实际 150+)。
try:
max_out = float(
declared = (
data.get("max_tokens")
or data.get("max_completion_tokens")
or PREBLOCK_RESERVE_TOKENS
)
max_out = max(float(declared), float(THINKING_RESERVE_FLOOR))
except (TypeError, ValueError):
max_out = float(PREBLOCK_RESERVE_TOKENS)
max_out = max(
float(THINKING_RESERVE_FLOOR), float(PREBLOCK_RESERVE_TOKENS)
)
input_tokens = 0
try: