195 lines
6.0 KiB
Markdown
195 lines
6.0 KiB
Markdown
# Warmup Runtime Config 缓存优化
|
||
|
||
**日期**: 2026-03-31
|
||
**类型**: 性能优化
|
||
**影响范围**: 新会话首消息响应(连续创建新会话场景)
|
||
|
||
---
|
||
|
||
## 1. 问题背景
|
||
|
||
### 1.1 现象
|
||
|
||
连续新开会话时,前 1-2 个会话的 warmup 复用始终 miss,日志出现:
|
||
|
||
```
|
||
[EngineWarmup] ⚠️ 运行时配置不兼容,不复用 warmup(model,apiKey,baseUrl,apiProtocol)
|
||
warmupModel: '(none)',
|
||
warmupBaseUrl: '(none)',
|
||
warmupApiKeySet: false,
|
||
warmupApiProtocol: '(none)',
|
||
```
|
||
|
||
### 1.2 实测数据(20:52 ~ 20:56,5 个连续新会话)
|
||
|
||
| # | 时间 | Warmup | Init(ms) | TTFT(ms) | 总响应(ms) |
|
||
|---|------|--------|----------|----------|-----------|
|
||
| 1 | 20:52 | ❌ cold(runtime 不兼容) | 5,705 | 9,507 | 10,389 |
|
||
| 2 | 20:53 | ❌ cold(runtime 不兼容) | 6,097 | 6,236 | 14,107 |
|
||
| 3 | 20:54:15 | ✅ reuse | 1,513 | 6,089 | 20,938 |
|
||
| 4 | 20:54:50 | ✅ reuse | 1,523 | 5,983 | 20,510 |
|
||
| 5 | 20:55:20 | ✅ reuse | 1,701 | 5,964 | 39,269 |
|
||
|
||
前两个会话 warmup miss 导致额外 ~2-4s 冷启动开销。
|
||
|
||
### 1.3 根因
|
||
|
||
**配置构建时机差异**:
|
||
|
||
```
|
||
init() 阶段:
|
||
baseConfig = { workspaceDir, engine, env }
|
||
↓ 缺少 model/apiKey/baseUrl/apiProtocol
|
||
|
||
chat() 阶段:
|
||
effectiveConfig = {
|
||
...baseConfig,
|
||
model: mp?.model, ← 来自 model_provider
|
||
apiKey: mp?.api_key, ← 来自 model_provider
|
||
baseUrl: mp?.base_url, ← 来自 model_provider
|
||
apiProtocol: mp?.api_protocol, ← 来自 model_provider
|
||
mcpServers: freshMcpServers,
|
||
}
|
||
```
|
||
|
||
- `baseConfig` 在 `init()` 时设置,不含运行时配置
|
||
- `effectiveConfig` 在每次 `chat()` 请求时动态构建
|
||
- `warmup.start(baseConfig)` 用的是不含 runtime config 的 baseConfig
|
||
- `tryReuse()` 检测到 runtime config 不一致 → 回退冷启动
|
||
|
||
**影响的三个调用路径**:
|
||
|
||
| 调用点 | 触发场景 | seedConfig | 结果 |
|
||
|--------|---------|-----------|------|
|
||
| `warmup.start(this.baseConfig)` | `init()` | ❌ 无 | warmup 缺 runtime config |
|
||
| `warmup.respawn(this.baseConfig)` | 会话结束 | ❌ 无 | 同上 |
|
||
| `ensureQimingWarmup({reason: "get_or_create_guard"})` | 引擎不存在 | ❌ 无 | 同上 |
|
||
| `ensureQimingWarmup({seedConfig: {...}})` | warmup 被消费后 refill | ✅ 有 | 正常 |
|
||
| `ensureQimingWarmup({seedConfig: {...}})` | 冷启动后 refill | ✅ 有 | 正常 |
|
||
|
||
**结论**:只有 refill 路径能命中,init / respawn / guard 三个路径永远 miss。
|
||
|
||
---
|
||
|
||
## 2. 修复方案
|
||
|
||
### 2.1 核心思路
|
||
|
||
在 `EngineWarmup` 内部缓存最近一次请求的 runtime config(`model/apiKey/baseUrl/apiProtocol`),在 `start()` 创建 warmup 时自动应用缓存值。
|
||
|
||
### 2.2 改动清单
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `engineWarmup.ts` | 新增 `lastRuntimeConfig` 缓存 + `cacheRuntimeConfig()` 方法 |
|
||
| `unifiedAgent.test.ts` | 新增 2 个测试场景 |
|
||
|
||
### 2.3 具体改动
|
||
|
||
#### `engineWarmup.ts`
|
||
|
||
**新增字段**:
|
||
```typescript
|
||
private lastRuntimeConfig: Pick<
|
||
AgentConfig,
|
||
"model" | "apiKey" | "baseUrl" | "apiProtocol"
|
||
> | null = null;
|
||
```
|
||
|
||
**`start()` 应用缓存**:
|
||
```typescript
|
||
const warmupConfig: AgentConfig = {
|
||
...baseConfig,
|
||
engine: WARMUP_ENGINE_TYPE,
|
||
...(this.lastRuntimeConfig
|
||
? {
|
||
model: this.lastRuntimeConfig.model || baseConfig.model,
|
||
apiKey: this.lastRuntimeConfig.apiKey || baseConfig.apiKey,
|
||
baseUrl: this.lastRuntimeConfig.baseUrl || baseConfig.baseUrl,
|
||
apiProtocol: this.lastRuntimeConfig.apiProtocol || baseConfig.apiProtocol,
|
||
}
|
||
: {}),
|
||
// ...
|
||
};
|
||
```
|
||
|
||
**`tryReuse()` 三条路径均缓存**:
|
||
- Runtime mismatch → `cacheRuntimeConfig(effectiveConfig)` → 后续 refill 用新缓存
|
||
- Warmup hit → `cacheRuntimeConfig(effectiveConfig)` → 保持缓存新鲜
|
||
- Warmup not ready → `cacheRuntimeConfig(effectiveConfig)` → 同上
|
||
|
||
**`dispose()` 清理**:
|
||
```typescript
|
||
this.lastRuntimeConfig = null;
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 配置变更时的行为
|
||
|
||
缓存的 runtime config 与新请求配置不一致时:
|
||
|
||
```
|
||
warmup (缓存 model=A, key=X)
|
||
↓
|
||
新请求 (model=B, key=Y)
|
||
↓
|
||
tryReuse() 检测不兼容 → 回退冷启动 (~2s)
|
||
↓
|
||
cacheRuntimeConfig(B, Y) → 更新缓存
|
||
↓
|
||
refill warmup 用 (B, Y) 创建
|
||
↓
|
||
后续请求 (B, Y) → 命中 ✅
|
||
```
|
||
|
||
**最多影响一次会话**(配置变更的那次),后续自动纠正。这与修改前行为一致——修改前是每次都 miss,修改后仅配置变更时 miss 一次。
|
||
|
||
---
|
||
|
||
## 4. 预期效果
|
||
|
||
| 场景 | 修改前 | 修改后 |
|
||
|------|--------|--------|
|
||
| 首次 init → 首次会话 | miss → 冷启动 ~3.7s | miss → 冷启动 ~3.7s(首次无缓存) |
|
||
| 首次会话后 → 第二次会话 | **miss** → 冷启动 ~2.6s | **命中** → ~1.5s(省 ~1.1s) |
|
||
| 连续新会话(配置不变) | 每次前 1-2 个 miss | 仅首次 miss,后续均命中 |
|
||
| 配置变更 | miss → 冷启动 | miss → 冷启动 → 自动纠正缓存 |
|
||
|
||
**关键指标改善**:
|
||
|
||
- 连续新会话 Init 耗时:5,705ms / 6,097ms → **~1,500ms**(reduced ~70%)
|
||
- 首消息端到端(不含 LLM TTFT):~8s → **~6s**(节省 warmup 冷启动开销)
|
||
|
||
---
|
||
|
||
## 5. 测试覆盖
|
||
|
||
### 5.1 新增测试
|
||
|
||
| 测试 | 验证内容 |
|
||
|------|---------|
|
||
| `warmup runtime config 缓存:首次 miss 后 refill 命中后续请求` | 首次 miss → 缓存 → refill 带缓存 → 第二次命中 |
|
||
| `warmup runtime config 缓存:配置变更时回退冷启动并更新缓存` | configA miss → refill(A) → configB miss → refill(B) → B 命中 |
|
||
|
||
### 5.2 回归测试
|
||
|
||
全部 54 个 warmup 相关测试通过,包括:
|
||
- 运行时配置缺失回退冷启动(原有测试不受影响)
|
||
- MCP 配置不兼容回退冷启动
|
||
- qimingcode 连续命中补仓
|
||
- destroy() 清理 warmup
|
||
- persistent MCP 归一化
|
||
|
||
---
|
||
|
||
## 6. 相关文档
|
||
|
||
- [首消息性能优化报告 (2026-03-23)](./FIRST-MESSAGE-PERF-OPT-2026-03-23.md)
|
||
- [macOS 首消息性能测试报告 (2026-03-31)](./MACOS-FIRST-MESSAGE-PERF-REPORT-2026-03-31.md)
|
||
- [Session 复用修复 (2026-03-24)](./SESSION-REUSE-FIX-2026-03-24.md)
|
||
|
||
---
|
||
|
||
*文档生成时间: 2026-03-31*
|