docs: document net generation time and mid-stream stall hedging
This commit is contained in:
@@ -18,12 +18,13 @@
|
||||
| 背压与判死 | 配额满与熔断开路**各自**可选等待或快速失败(`QUOTA_FULL` / `CIRCUIT_OPEN`,两键不可互相替代);等待期按双条件判死(本地非生产性等待与全局无进展**同时**超窗)。stall 窗口只计**非生产性**等待(429 退避/配额轮询/熔断冷却),与 `TIMEOUT_S` 无耦合 |
|
||||
| 调用期限 | 一次逻辑调用可选一条**墙钟硬边界**(`{SCOPE}__CALL_DEADLINE_S` 或 `chat(call_deadline_s=...)`,缺省不启用):治理的是**等待**——重试退避、配额轮询、熔断冷却、结构化重问与 embedding 分批共享同一份期限。三条须知:①**返回时刻 = 期限 + 清理耗时**(遥测/结算/缓存收尾在 `finally` 里跑完,允许超期;实测构造达期限的 5–7 倍),库只承诺切断等待、不给返回上界;②**到期 ≠ 未产出、≠ 未计费**,上游可能已算完并计费;③**不配置即逐字保持 1.3.5 语义**(纯 429 序列仍可能长等、有限大 `Retry-After` 仍照睡)。到期抛 `CallDeadlineExceeded`,**不属四分类、不属 `GatewayUnavailableError` 族** |
|
||||
| 长尾对冲 | **默认关闭**的可选加速(`{SCOPE}__HEDGE__AFTER_S`,仅 chat):一次尝试挂起超阈值时向**异源**并发再发一次,先回者赢、输家取消。流式以「首 token 未至」为触发判据(不误杀慢生成),非流式只有纯总时长阈值;对冲走完整限流/熔断准入,拿不到配额静默放弃。成本须知:开启即用配额换延迟(触发窗口内 in-flight 翻倍),输家取消按 est 保留预扣且**可能已被上游计费**(取消止不住上游);输家不喂熔断,遥测 attempt 行标 `error="hedge_cancelled"` 可按 `logical_call_id` join 对账;两路皆败只计一次重试预算;与 `call_deadline_s` 正交组合(装配守卫强制对冲阈值 < 期限)
|
||||
| 中段沉默对冲(可选,超业界现状能力) | **默认关闭**(`{SCOPE}__HEDGE__STALL_AFTER_S`,仅流式 chat):主路**首 token 之后**相邻增量 chunk 沉默超阈值时向异源并发再发一次,与首 token 对冲 `HEDGE__AFTER_S` **独立生效**、可独设可组合;仅设它且请求非流式 → 逐字走单路(中段沉默无非流式概念)。**诚实标注:中段沉默触发并发副本在主流网关/库中无公开先例**(业界收敛为看门狗掐流重试)。成本须知:下游实测沉默 >20s 占 19.0%,触发率远高于首 token 对冲,开启即用配额换延迟,输家可能已被上游计费;看门狗掐死**喂**熔断而对冲输家取消**不喂**(不对称声明),详见[1.3.8 生成时间口径与中段沉默对冲](#138-生成时间口径与中段沉默对冲) |
|
||||
| 响应缓存 | Redis/内存;key 含 model + messages 摘要 + namespace(缓存隔离单位)+ salt + 采样参数 + 请求级推理档位(同 messages 跑 low 与 max 不互相命中),多模态 content 先摘要再 hash(防毒化);可 per-call 绕过(科研重采样) |
|
||||
| 流式看门狗 | TTFT / inter-token / 总超时三层活性;thinking token 刷活性不计结果;截断流(缺 `[DONE]`)判瞬时不入缓存 |
|
||||
| 推理可观测性 | "这次到底推理没推理"由多信号裁定(推理正文压倒 usage 明细),三态落在 `LLMResponse.thinking_observation`:`observed` / `absent` / `unknown`——**`unknown` 是"本次判不出",不是"没推理"**;本次实发档位与实测观测矛盾时按 `(源, 模型, 生效档位)` 各告警一次(能力表过期、开启未生效、注入了却观测不到;同一模型的 low 与 max 是两个独立的矛盾,不共用节流键);裁定结果随遥测落库 |
|
||||
| 推理档位 | 推理是**八档**(`none`/`auto`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`)而非开关:源级 `REASONING_EFFORT` + 请求级 `chat(reasoning_effort=...)`,`ENABLE_THINKING` 保留为语法糖;库带 24 条能力表(逐条 evidence 自报实测/文档推定),档位打空**默认报错并给出该模型最省的可用档与该配的键**,要静默映射需显式配 `EFFORT_FALLBACK=nearest`;实发档随 `LLMResponse.applied_effort` 与遥测落库 |
|
||||
| 遥测与成本 | 每次调用(含缓存命中与失败)必录 36 字段;三类行(`event_kind` = `attempt` / `cache_hit` / `terminal_failure`)加逐源诊断列(`http_status_code` / `error_type` / `cause_type` / `error_body`);SQLite / Postgres 后端(表已存在时**不需要** schema 建表权限,最小权限账号可直接用);按价格表折算成本落库(注意 `LLMResponse.cost` 本身恒为 `None`,成本只进遥测);多模态内容摘要落库不存原图 |
|
||||
| 逻辑调用统计 | 治理单位是**一次逻辑调用**而非一次尝试:四种响应(chat / embedding / OCR 两种)带 `call_stats`(`logical_call_id` / `attempts` / `total_latency_ms`,及裸生成时间 `generation_ms`——赢家那次 transport 调用的墙钟,排除准入排队/退避/触发前等待,与 `total_latency_ms` 的差值即「在等不在生成」——与对冲计数 `hedges` / `hedge_won`),重试、换源、结构化重问、embedding 分批共享同一逻辑 ID;每次**领域失败**另落一条 `terminal_failure` 行,失败调用数从此是一条 `WHERE event_kind = 'terminal_failure'`,详见[1.3.5 逻辑调用统计与失败诊断](#135-逻辑调用统计与失败诊断) |
|
||||
| 遥测与成本 | 每次调用(含缓存命中与失败)必录 38 字段;三类行(`event_kind` = `attempt` / `cache_hit` / `terminal_failure`)加逐源诊断列(`http_status_code` / `error_type` / `cause_type` / `error_body`);SQLite / Postgres 后端(表已存在时**不需要** schema 建表权限,最小权限账号可直接用);按价格表折算成本落库(注意 `LLMResponse.cost` 本身恒为 `None`,成本只进遥测);多模态内容摘要落库不存原图 |
|
||||
| 逻辑调用统计 | 治理单位是**一次逻辑调用**而非一次尝试:四种响应(chat / embedding / OCR 两种)带 `call_stats`(`logical_call_id` / `attempts` / `total_latency_ms`,及生成时间 `generation_ms`——**1.3.8 起为净口径**:赢家那次 transport 调用墙钟 − Σ超阈值中段沉默,仍含 TTFT 与尾部,排除准入排队/退避/触发前等待;1.3.7 的墙钟值由 `generation_ms + stall_ms` 还原——与沉默归因 `stall_ms` / `stall_count`(成对报告,None = 无沉默观测)、对冲计数 `hedges` / `hedge_won`),重试、换源、结构化重问、embedding 分批共享同一逻辑 ID;每次**领域失败**另落一条 `terminal_failure` 行,失败调用数从此是一条 `WHERE event_kind = 'terminal_failure'`,详见[1.3.5 逻辑调用统计与失败诊断](#135-逻辑调用统计与失败诊断)与[1.3.8 生成时间口径与中段沉默对冲](#138-生成时间口径与中段沉默对冲) |
|
||||
| 遥测的资源与降级 | Postgres 池**闲时占 0 条连接**、忙时上限可配(`PGW_TELEMETRY_PG_POOL_MAX`,缺省 4),每次写入有硬预算(`PGW_TELEMETRY_PG_WRITE_TIMEOUT_S`,缺省 5s);后端不可用是**可恢复的降级**(冷却 60s 后自动重试,DBA 建完表/放开权限即自愈),永久失能只留给 DSN 本身写错;降级状态可编程查询——`client.telemetry_status` 给出 `degraded`/`fatal`/`reason`/`dropped_rows` 等只读快照,不必再靠人工对账。**对账要同时看 `degraded` 与 `dropped_rows`**: 池饱和超预算丢的行走行级丢弃,`degraded` 保持 `False`(后端没挂,是本进程并发超了),只按 `degraded` 告警会看不见这一类丢行——而它恰是 `pool_max` 配小了的唯一信号 |
|
||||
| 调用方维度 | 每次调用可带 `tenant_id`(遥测表的真实列,可挂 RLS、可建复合索引)与 `meta`(≤16 个自定义 KV);四个公共方法全覆盖,校验超限即报错;**库只交付列,不启用 RLS、不建索引** |
|
||||
| 遥测表治理 | `llm_calls` 是**下游的表**:PG 侧缺省**不再自动 `ALTER` 补列**(`PGW_TELEMETRY_SCHEMA_MODE` 三态,不设则 sqlite→auto、postgres→manual),manual 档点名缺列并按现有列裁剪写入;`telemetry_schema_sql(backend)` 自取可粘进迁移文件的建表/补列 SQL;`PGW_TELEMETRY_TEXT_CAP` 限正文长度(**不设 = 存全文**);保留期与访问控制走[生产部署 DDL 模板](#生产部署-ddl-模板postgresql)加 `tools/telemetry_retention.py` |
|
||||
@@ -111,6 +112,33 @@ stats.total_latency_ms # 含缓存 IO、退避、准入等待、重
|
||||
|
||||
自建 `TelemetryRecorder` 的下游请同步补齐这 10 个 keyword-only 无默认值参数(或改成 `**fields` 形态),并同步自己的 schema 与 INSERT 字段。装配期形状闸只能证明形状能被接受,证不了函数体真的落这些列。
|
||||
|
||||
## 1.3.8 生成时间口径与中段沉默对冲
|
||||
|
||||
> [!WARNING]
|
||||
> **`generation_ms` 自 1.3.8 起是净生成时间,不再是墙钟。** 同一字段两版口径不同(放行依据:字段发布仅 2 天、消费者唯一、遥测无账本,人类「窗口期最小代价」裁决);1.3.7 的墙钟值 = 本版的 `generation_ms + stall_ms`。两段口径的存量快照不可直接纵比。
|
||||
|
||||
### `generation_ms` 口径表
|
||||
|
||||
| 项 | 口径 |
|
||||
| --- | --- |
|
||||
| 定义 | **净生成时间** = 赢家那次 transport 调用的墙钟 − Σ(所有 > `STALL_THRESHOLD_MS`(缺省 1000ms)的相邻增量 chunk 沉默);仍**排除**准入排队/退避/对冲触发前等待(同 1.3.7) |
|
||||
| 边界 | 仍含 **TTFT 与尾部**(末 chunk → 流结束归活跃侧,墙钟基底口径的有意选择) |
|
||||
| 还原公式 | **墙钟 = `generation_ms + stall_ms`**(`stall_ms is None` 时墙钟 = `generation_ms`) |
|
||||
| `stall_ms` / `stall_count` | 沉默总额(毫秒)与超阈值相邻间隔个数,**成对报告**(只报净数无法行动);chunk = 任何增量产出(`content` 或 `reasoning_content` 均计),空 chunk / usage 帧 / SSE keepalive 不计时 |
|
||||
| `stall_ms is None` 分支条件 | 非流式 / 增量 chunk <2 → 两字段 None 且 `generation_ms` **不退减**;None = 「无沉默观测」而非 0,下游预算公式以此为分支条件 |
|
||||
| 与基准口径的区隔 | NVIDIA TTFT/ITL/E2EL 与 vLLM 的基准口径**均不扣除**沉默——扣沉默是本库自造口径,与基准数字比较时不得混用 |
|
||||
|
||||
`max_inter_token_ms`(单次最大相邻间隔)原语义不变,与 `generation_ms` 是两个口径、不可互算。沉默归因同批进遥测表 `stall_ms`/`stall_count` 两**可空**列(INSERT 字段 36 → 38,旧行 NULL 不回填);`generation_ms` 本身仍不落库,墙钟用上式还原。对冲场景三值全部取**赢家路**:落选输家的沉默在其 attempt 遥测行(可按 `logical_call_id` join),被取消输家的 `hedge_cancelled` 行 stall 恒 NULL。
|
||||
|
||||
### 配置表(两键,均仅 chat 生效)
|
||||
|
||||
| 键 | 值域 | 缺省 | 守卫与调整接口 |
|
||||
| --- | --- | --- | --- |
|
||||
| `{SCOPE}__STREAM__STALL_THRESHOLD_MS` | 正整数毫秒 | **1000** | 仅改计量口径,不改任何行为;三层调整接口同权(env 键 / `GatewaySettings.stall_threshold_ms` / `GatewayClient(stall_threshold_ms=...)`);无 per-call 覆盖,不做自适应(防与对冲阈值耦出反馈环) |
|
||||
| `{SCOPE}__HEDGE__STALL_AFTER_S` | 有限正数秒 | **未设 = 关闭** | 独立于 `HEDGE__AFTER_S` 生效;守卫: `≥ min(各源 inter_token_timeout_s 或 timeout_s)`(对冲窗永不开)或 `≥ call_deadline_s` → 装配期 **ValueError**,单源 scope → warning;三层调整接口同权(env 键 / `GatewaySettings.hedge_stall_after_s` / `GatewayClient(hedge_stall_after_s=...)`);Embedding/OCR scope 配任一对冲键 → warning 后静默退化 |
|
||||
|
||||
对冲语义(两阶段触发窗/复检防误触发/取消穿透)与成本披露见 [CHANGELOG 未发布段](CHANGELOG.md);运维「挂起率」统计须知不对称:看门狗掐死**喂**熔断失败计数,对冲输家取消**不喂**熔断。
|
||||
|
||||
## 安装
|
||||
|
||||
发布在实验室 Gitea PyPI(公开包,匿名可装):
|
||||
|
||||
Reference in New Issue
Block a user