1286 lines
43 KiB
Markdown
1286 lines
43 KiB
Markdown
# 边缘 AI 算力机统一 AI 通讯层 — 产品需求文档(PRD)
|
||
|
||
> 文档定位:基于 `0-req.md` 需求规格,定义产品功能细节、用户交互流程、API 规格和交付标准,用于指导设计与研发。
|
||
>
|
||
> 版本:1.0 | 状态:初始草案 | 关联需求:`0-req.md`
|
||
|
||
---
|
||
|
||
## 1. 产品概述
|
||
|
||
### 1.1 产品定位
|
||
|
||
**Edge AI Gateway** 是部署在边缘 AI 算力机上的统一 AI 通讯层软件。它作为业务应用与底层推理服务之间的唯一入口,对每次 AI 调用进行准入、上下文编排、排队调度、模型路由、连接管理和资源治理。
|
||
|
||
### 1.2 核心价值主张
|
||
|
||
| 角色 | 痛点 | 价值 |
|
||
|---|---|---|
|
||
| 业务应用开发者 | 多种推理引擎协议不同,模型替换需改代码 | 一套 OpenAI 兼容 API,逻辑模型名解耦底层 |
|
||
| 系统管理员 | 并发请求导致 OOM,高优先级任务被阻塞 | 优先级队列 + 显存准入 + 公平调度 |
|
||
| 运维人员 | 无法统计 Token、延迟、排队和错误 | 统一指标、调用链和告警 |
|
||
| 安全合规 | 敏感数据可能泄露到云端 | 边缘优先 + 数据分级 + 出域审计 |
|
||
|
||
### 1.3 产品边界
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 业务应用 / Agent │
|
||
└──────────────────────┬──────────────────────────────┘
|
||
│ HTTP / SSE / WebSocket
|
||
┌──────────────────────▼──────────────────────────────┐
|
||
│ Edge AI Gateway(本产品) │
|
||
│ ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │
|
||
│ │ 网关 │ 认证 │ 上下文│ 调度 │ 路由 │ 连接 │ 监控 │ │
|
||
│ └──────┴──────┴──────┴──────┴──────┴──────┴──────┘ │
|
||
└──────────────────────┬──────────────────────────────┘
|
||
│ 模型适配器
|
||
┌──────────────────────▼──────────────────────────────┐
|
||
│ Ollama │ vLLM │ llama.cpp │ Triton │ 云端 │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**本产品不包含:** 模型训练/微调、数据标注、知识库构建、推理引擎本身的实现。
|
||
|
||
---
|
||
|
||
## 2. 目标用户与典型场景
|
||
|
||
### 2.1 用户画像
|
||
|
||
#### 画像 A:业务应用开发者(主要用户)
|
||
|
||
- **身份**:在边缘算力机上部署 AI 应用的开发者。
|
||
- **目标**:快速接入 AI 能力,不关心底层是 Ollama 还是 vLLM。
|
||
- **行为**:通过 HTTP/SSE 调用 OpenAI 兼容 API,使用逻辑模型名,管理会话 ID。
|
||
- **痛点**:不同引擎协议不同;模型替换需改代码;历史消息过长导致报错。
|
||
|
||
#### 画像 B:系统管理员
|
||
|
||
- **身份**:负责边缘算力机配置和运营的管理员。
|
||
- **目标**:合理分配算力,保证高优先级任务不被阻塞,敏感数据不出域。
|
||
- **行为**:配置模型映射、并发配额、优先级权限、安全策略和降级链。
|
||
- **痛点**:并发请求导致 OOM;无法控制哪个应用能用哪个模型;缺少降级机制。
|
||
|
||
#### 画像 C:运维人员
|
||
|
||
- **身份**:负责系统监控和故障排查的运维工程师。
|
||
- **目标**:实时掌握系统健康状态,快速定位问题。
|
||
- **行为**:查看 Grafana 大盘,分析调用链日志,配置告警规则。
|
||
- **痛点**:无法统计首 Token 延迟、排队时长和 Token 消耗;故障时缺少链路追踪。
|
||
|
||
### 2.2 典型场景
|
||
|
||
#### 场景 1:安防告警实时分析(P0 优先级)
|
||
|
||
```
|
||
触发:设备传感器检测到异常
|
||
应用:安防服务
|
||
流程:
|
||
1. 安防应用提交 AI 请求,priority=P0,local_only=true
|
||
2. 网关鉴权通过,检查配额
|
||
3. 上下文管理器组装设备日志 + 历史告警摘要
|
||
4. 调度器将 P0 任务插入队首,资源管理器预留专用槽位
|
||
5. 模型路由器选择本地 general-chat 模型
|
||
6. 推理结果通过 SSE 流式返回
|
||
7. 通讯层记录调用链和审计信息
|
||
预期:首 Token < 1s,不受后台批处理任务影响
|
||
```
|
||
|
||
#### 场景 2:办公助手多轮对话(P2 优先级)
|
||
|
||
```
|
||
触发:用户在办公助手应用中提问
|
||
应用:办公助手
|
||
流程:
|
||
1. 应用提交请求,session_id=session-001,priority=P2
|
||
2. 上下文管理器加载会话历史,计算 Token 预算
|
||
3. 历史消息 + 知识检索 + 当前请求 = 18,000 Token,在预算内
|
||
4. 调度器将任务放入 P2 队列
|
||
5. 资源管理器检查显存,允许执行
|
||
6. 推理结果流式返回,更新会话历史
|
||
预期:正常排队 < 5s,Token 自动管理不超限
|
||
```
|
||
|
||
#### 场景 3:文档批量分析(P3 优先级,资源紧张时降级)
|
||
|
||
```
|
||
触发:用户上传文档请求分析
|
||
应用:文档分析
|
||
流程:
|
||
1. 应用提交请求,priority=P3,allow_smaller_model=true
|
||
2. 调度器检查资源,发现显存不足
|
||
3. 模型路由器按降级链切换到量化小模型
|
||
4. 响应元数据中 degraded=true,actual_model 记录实际使用模型
|
||
5. 审计日志记录降级原因
|
||
预期:功能完整但质量可能降低,降级过程对应用透明
|
||
```
|
||
|
||
#### 场景 4:客户端断开后自动取消
|
||
|
||
```
|
||
触发:用户关闭浏览器,SSE 连接断开
|
||
流程:
|
||
1. 连接管理器检测到 SSE 连接断开
|
||
2. 触发取消信号,传播到调度器和模型适配器
|
||
3. 模型适配器向推理引擎发送取消请求
|
||
4. 执行槽位和 KV Cache 在 cancel_grace_period 内释放
|
||
5. 任务状态变为 CANCELLED,记录取消原因
|
||
预期:GPU 算力和显存不浪费,资源在 3s 内释放
|
||
```
|
||
|
||
#### 场景 5:敏感数据强制本地处理
|
||
|
||
```
|
||
触发:处理包含个人隐私信息的请求
|
||
应用:医疗信息助手
|
||
流程:
|
||
1. 应用提交请求,routing.local_only=true
|
||
2. 网关识别数据策略级别为"敏感"
|
||
3. 模型路由器强制选择本地模型,跳过所有云端降级选项
|
||
4. 即使本地资源紧张,也排队等待而非路由到云端
|
||
5. 审计日志记录"local_only enforced"
|
||
预期:敏感数据永不出域,合规可审计
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 功能规格
|
||
|
||
### 3.1 API 网关
|
||
|
||
#### 3.1.1 接口清单
|
||
|
||
| 接口 | 方法 | 阶段 | 说明 |
|
||
|---|---|---|---|
|
||
| `/v1/chat/completions` | POST | P1 | 文本生成,支持流式(SSE)和非流式 |
|
||
| `/v1/embeddings` | POST | P1 | 向量嵌入 |
|
||
| `/v1/models` | GET | P1 | 可用模型列表 |
|
||
| `/v1/sessions` | POST | P1 | 创建会话 |
|
||
| `/v1/sessions/{id}` | GET | P1 | 查询会话 |
|
||
| `/v1/sessions/{id}` | DELETE | P1 | 删除会话 |
|
||
| `/v1/tasks` | POST | P2 | 提交异步任务 |
|
||
| `/v1/tasks/{id}` | GET | P2 | 查询任务状态 |
|
||
| `/v1/tasks/{id}` | DELETE | P2 | 取消任务 |
|
||
| `/v1/responses` | POST | P2 | 响应式接口 |
|
||
| `/v1/audio/transcriptions` | POST | P3 | 语音转文字 |
|
||
| `/v1/audio/speech` | POST | P3 | 文字转语音 |
|
||
| `/v1/images/analyze` | POST | P3 | 图像理解 |
|
||
| `/health` | GET | P1 | 健康检查(进程存活) |
|
||
| `/ready` | GET | P1 | 就绪检查(依赖就绪) |
|
||
|
||
#### 3.1.2 请求格式
|
||
|
||
**`POST /v1/chat/completions` 请求体:**
|
||
|
||
```json
|
||
{
|
||
"model": "general-chat",
|
||
"messages": [
|
||
{
|
||
"role": "system",
|
||
"content": "你是一名设备维护专家"
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": "请分析设备异常日志并给出处理建议"
|
||
}
|
||
],
|
||
"stream": true,
|
||
"session_id": "session-001",
|
||
"idempotency_key": "app01-20260803-00001234",
|
||
"priority": "P1",
|
||
"max_output_tokens": 1200,
|
||
"context_policy": "summary_and_recent",
|
||
"timeouts": {
|
||
"queue_ms": 5000,
|
||
"first_token_ms": 10000,
|
||
"inference_ms": 60000,
|
||
"total_ms": 90000
|
||
},
|
||
"routing": {
|
||
"local_only": true,
|
||
"allow_smaller_model": true
|
||
},
|
||
"metadata": {
|
||
"application": "device-maintenance",
|
||
"user_id": "user-1001",
|
||
"trace_id": "trace-abc123"
|
||
}
|
||
}
|
||
```
|
||
|
||
**字段说明:**
|
||
|
||
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
||
|---|---|---|---|---|
|
||
| `model` | string | 是 | — | 逻辑模型名 |
|
||
| `messages` | array | 是 | — | OpenAI 格式消息数组 |
|
||
| `stream` | bool | 否 | false | 是否流式返回 |
|
||
| `session_id` | string | 否 | — | 会话 ID,用于上下文关联 |
|
||
| `idempotency_key` | string | 否 | — | 幂等键,防止重复推理 |
|
||
| `priority` | string | 否 | P2 | 优先级 P0–P4 |
|
||
| `max_output_tokens` | int | 否 | 模型配置 | 最大输出 Token 数 |
|
||
| `context_policy` | string | 否 | `summary_and_recent` | 上下文组装策略 |
|
||
| `timeouts` | object | 否 | 全局配置 | 分层超时覆盖 |
|
||
| `routing` | object | 否 | 全局策略 | 路由控制 |
|
||
| `metadata` | object | 否 | — | 应用元数据 |
|
||
|
||
#### 3.1.3 响应格式
|
||
|
||
**非流式响应:**
|
||
|
||
```json
|
||
{
|
||
"request_id": "req-20260803-000001",
|
||
"task_id": "task-20260803-000001",
|
||
"session_id": "session-001",
|
||
"status": "succeeded",
|
||
"model": "general-chat",
|
||
"choices": [
|
||
{
|
||
"index": 0,
|
||
"message": {
|
||
"role": "assistant",
|
||
"content": "根据日志分析,建议..."
|
||
},
|
||
"finish_reason": "stop"
|
||
}
|
||
],
|
||
"logical_model": "general-chat",
|
||
"actual_model": "qwen3-8b-int4",
|
||
"node_id": "edge-node-01",
|
||
"usage": {
|
||
"input_tokens": 2380,
|
||
"output_tokens": 615,
|
||
"total_tokens": 2995
|
||
},
|
||
"timing": {
|
||
"queue_ms": 86,
|
||
"first_token_ms": 724,
|
||
"inference_ms": 4380,
|
||
"total_ms": 4588
|
||
},
|
||
"degraded": false
|
||
}
|
||
```
|
||
|
||
**流式响应(SSE):**
|
||
|
||
```
|
||
data: {"request_id":"req-20260803-000001","task_id":"task-20260803-000001","status":"streaming","logical_model":"general-chat","actual_model":"qwen3-8b-int4"}
|
||
|
||
data: {"choices":[{"index":0,"delta":{"role":"assistant","content":"根据"},"finish_reason":null}]}
|
||
|
||
data: {"choices":[{"index":0,"delta":{"content":"日志分析"},"finish_reason":null}]}
|
||
|
||
data: {"choices":[{"index":0,"delta":{"content":",建议..."},"finish_reason":"stop"}],"usage":{"input_tokens":2380,"output_tokens":615,"total_tokens":2995},"timing":{"queue_ms":86,"first_token_ms":724,"inference_ms":4380,"total_ms":4588},"finish_reason":"stop","degraded":false}
|
||
|
||
data: [DONE]
|
||
```
|
||
|
||
**首帧** 包含 `request_id`、`task_id`、`status`、`logical_model`、`actual_model`,让客户端尽早知道任务已接受和实际使用的模型。
|
||
|
||
**末帧** 包含 `usage`、`timing`、`finish_reason`、`degraded`,让客户端获取完整统计。
|
||
|
||
**`[DONE]`** 标记流结束。
|
||
|
||
#### 3.1.4 错误响应
|
||
|
||
```json
|
||
{
|
||
"error": {
|
||
"code": "QUEUE_TIMEOUT",
|
||
"message": "请求排队等待超时,当前队列长度: 47",
|
||
"request_id": "req-20260803-000042",
|
||
"retry_after_ms": 3000
|
||
}
|
||
}
|
||
```
|
||
|
||
| HTTP 状态码 | 错误码 | 说明 |
|
||
|---|---|---|
|
||
| 401 | `AUTH_FAILED` | 认证失败 |
|
||
| 403 | `PERMISSION_DENIED` / `POLICY_BLOCKED` | 权限不足或策略拦截 |
|
||
| 429 | `RATE_LIMITED` / `QUOTA_EXCEEDED` / `QUEUE_FULL` | 限流或配额不足 |
|
||
| 400 | `INVALID_REQUEST` / `CONTEXT_TOO_LARGE` | 参数错误或上下文超限 |
|
||
| 408 | `QUEUE_TIMEOUT` / `FIRST_TOKEN_TIMEOUT` / `INFERENCE_TIMEOUT` | 超时 |
|
||
| 409 | `REQUEST_CANCELLED` | 请求已取消 |
|
||
| 503 | `MODEL_UNAVAILABLE` / `RESOURCE_EXHAUSTED` | 模型不可用或资源不足 |
|
||
| 500 | `INTERNAL_ERROR` | 内部错误 |
|
||
|
||
`RATE_LIMITED` 和 `QUEUE_FULL` 响应应附带 `Retry-After` 头。
|
||
|
||
---
|
||
|
||
### 3.2 认证与权限
|
||
|
||
#### 3.2.1 认证流程
|
||
|
||
```
|
||
客户端请求
|
||
│
|
||
├─ Authorization: Bearer <api_key> ──→ API Key 校验
|
||
├─ Authorization: Bearer <jwt> ──→ JWT 校验
|
||
├─ mTLS 证书 ──→ 证书校验
|
||
│
|
||
▼
|
||
认证通过 → 提取 application_id, tenant_id, user_id
|
||
认证失败 → 返回 401 AUTH_FAILED
|
||
```
|
||
|
||
#### 3.2.2 权限模型
|
||
|
||
```
|
||
Application
|
||
├── allowed_models: [general-chat, fast-chat]
|
||
├── allowed_priorities: [P1, P2, P3]
|
||
├── max_running_tasks: 4
|
||
├── max_queued_tasks: 100
|
||
└── data_policy:
|
||
├── allow_cloud: false
|
||
└── sensitive_level: standard
|
||
|
||
User (within Application)
|
||
├── max_running_tasks: 1
|
||
└── requests_per_minute: 20
|
||
```
|
||
|
||
#### 3.2.3 管理接口
|
||
|
||
管理接口与业务接口分离,使用独立端口和独立认证:
|
||
|
||
| 接口 | 方法 | 说明 |
|
||
|---|---|---|
|
||
| `/admin/applications` | GET/POST/PUT/DELETE | 应用管理 |
|
||
| `/admin/models` | GET/POST/PUT/DELETE | 模型配置 |
|
||
| `/admin/policies` | GET/PUT | 安全策略配置 |
|
||
| `/admin/quotas` | GET/PUT | 配额配置 |
|
||
| `/admin/tasks` | GET | 任务监控 |
|
||
| `/admin/metrics` | GET | 内部指标 |
|
||
|
||
---
|
||
|
||
### 3.3 会话与上下文
|
||
|
||
#### 3.3.1 会话生命周期
|
||
|
||
```
|
||
创建会话 (POST /v1/sessions)
|
||
│
|
||
├── 配置: max_messages, max_tokens, ttl, idle_timeout
|
||
│
|
||
▼
|
||
活跃 ──── 新消息写入 ──→ 更新 last_active
|
||
│
|
||
├── idle_timeout 到期 ──→ 过期清理
|
||
├── max_messages 达到 ──→ 触发摘要压缩
|
||
├── 用户删除 ──→ 立即清除
|
||
└── ttl 到期 ──→ 过期清理
|
||
```
|
||
|
||
#### 3.3.2 上下文组装流程
|
||
|
||
```
|
||
输入: session_id + 当前请求 + context_policy
|
||
│
|
||
├─ 1. 加载平台安全规则 (最高优先级)
|
||
├─ 2. 加载应用系统提示词
|
||
├─ 3. 注入用户身份与权限信息
|
||
├─ 4. 加载会话长期摘要
|
||
├─ 5. 加载最近 N 轮原始对话
|
||
├─ 6. [可选] 加载知识库检索结果
|
||
├─ 7. [可选] 加载工具调用结果
|
||
├─ 8. 追加当前用户请求
|
||
├─ 9. 追加输出格式与长度约束
|
||
│
|
||
├─ 计算 Token 总量
|
||
│ ├─ ≤ 预算 → 直接使用
|
||
│ └─ > 预算 → 执行裁剪流程
|
||
│
|
||
└─ 输出: 组装后的 messages + token_count + budget_report
|
||
```
|
||
|
||
#### 3.3.3 Token 预算分配示例
|
||
|
||
模型窗口 32,768 Token,安全系数 0.9,可用预算 29,491 Token:
|
||
|
||
| 上下文部分 | 分配比例 | Token 数 |
|
||
|---|---:|---:|
|
||
| 平台与应用系统指令 | 7% | 2,000 |
|
||
| 会话摘要 | 14% | 4,000 |
|
||
| 最近对话 | 31% | 9,000 |
|
||
| 知识检索结果 | 27% | 8,000 |
|
||
| 当前请求与工具结果 | 10% | 3,000 |
|
||
| 模型输出预留 | 21% | 6,000 |
|
||
| **合计** | **100%** | **~29,000** |
|
||
|
||
#### 3.3.4 上下文裁剪策略
|
||
|
||
```
|
||
超限?
|
||
│
|
||
├─ Step 1: 删除重复/低相关度知识片段 → 重新计算
|
||
├─ Step 2: 压缩工具返回结果 → 重新计算
|
||
├─ Step 3: 删除最早无关键状态对话 → 重新计算
|
||
├─ Step 4: 早期对话转为摘要 → 重新计算
|
||
├─ Step 5: 减少检索结果数量/长度 → 重新计算
|
||
├─ Step 6: [策略允许] 切换更大上下文模型 → 重新计算
|
||
└─ Step 7: 仍超限 → 返回 CONTEXT_TOO_LARGE
|
||
```
|
||
|
||
**保护项(不可裁剪):** 系统指令、权限信息、当前用户请求、输出约束。
|
||
|
||
---
|
||
|
||
### 3.4 队列与调度
|
||
|
||
#### 3.4.1 调度流程
|
||
|
||
```
|
||
请求准入
|
||
│
|
||
├─ 鉴权 → 配额检查 → 限流检查 → 参数校验 → 幂等检查
|
||
│
|
||
▼
|
||
创建任务 (状态: QUEUED)
|
||
│
|
||
├─ 按优先级放入对应队列
|
||
│ ├── P0 队列 (reserved_realtime_slots)
|
||
│ ├── P1 队列
|
||
│ ├── P2 队列 (默认)
|
||
│ ├── P3 队列
|
||
│ └─ P4 队列 (仅空闲时)
|
||
│
|
||
▼
|
||
调度循环 (每个 tick)
|
||
│
|
||
├─ 1. 检查资源管理器: 显存、执行槽位、模型状态
|
||
├─ 2. 按加权公平队列选择下一个任务
|
||
│ weight = base_priority_weight + aging_bonus(wait_time)
|
||
├─ 3. 检查任务是否超时 (queue_timeout)
|
||
│ ├─ 超时 → 状态变为 TIMED_OUT
|
||
│ └─ 未超时 → 分派执行
|
||
└─ 4. 提交到模型适配器 (状态: DISPATCHING → RUNNING)
|
||
```
|
||
|
||
#### 3.4.2 优先级与公平性配置
|
||
|
||
```yaml
|
||
scheduler:
|
||
max_running_tasks: 8
|
||
max_queued_tasks: 500
|
||
fairness: weighted_fair_queue
|
||
priority_aging_seconds: 30
|
||
reserved_realtime_slots: 2
|
||
|
||
priorities:
|
||
P0:
|
||
weight: 100
|
||
reserved_slots: 2
|
||
P1:
|
||
weight: 50
|
||
P2:
|
||
weight: 25
|
||
P3:
|
||
weight: 10
|
||
P4:
|
||
weight: 1
|
||
run_only_when_idle: true
|
||
|
||
applications:
|
||
security_service:
|
||
max_running_tasks: 4
|
||
max_queued_tasks: 100
|
||
allowed_priorities: [P0, P1]
|
||
office_assistant:
|
||
max_running_tasks: 2
|
||
max_queued_tasks: 50
|
||
allowed_priorities: [P2, P3]
|
||
doc_analyzer:
|
||
max_running_tasks: 1
|
||
max_queued_tasks: 30
|
||
allowed_priorities: [P3, P4]
|
||
|
||
users:
|
||
default_max_running_tasks: 1
|
||
default_requests_per_minute: 20
|
||
```
|
||
|
||
#### 3.4.3 显存准入估算
|
||
|
||
```
|
||
输入: model, input_tokens, max_output_tokens, concurrency
|
||
│
|
||
├─ model_weight_mem = 模型权重大小 (查表)
|
||
├─ kv_cache_input = input_tokens × per_token_kv_size
|
||
├─ kv_cache_output = max_output_tokens × per_token_kv_size
|
||
├─ batch_temp = 估算并发批次临时显存
|
||
├─ multimodal_mem = 图像/音频编码占用 (如适用)
|
||
├─ safety_margin = total × safety_margin_ratio (默认 0.08)
|
||
│
|
||
├─ estimated_total = model_weight + kv_cache_input + kv_cache_output
|
||
│ + batch_temp + multimodal_mem + safety_margin
|
||
│
|
||
├─ estimated_total ≤ available_vram?
|
||
│ ├─ 是 → 准入,状态变为 DISPATCHING
|
||
│ └─ 否 → 按降级链处理
|
||
│ ├─ 减少输出长度 → 重新估算
|
||
│ ├─ 切换量化模型 → 重新估算
|
||
│ ├─ 切换小模型 → 重新估算
|
||
│ ├─ 排队等待 → 状态保持 QUEUED
|
||
│ └─ 拒绝 → 返回 RESOURCE_EXHAUSTED
|
||
└─
|
||
```
|
||
|
||
---
|
||
|
||
### 3.5 连接与超时管理
|
||
|
||
#### 3.5.1 分层超时时间线
|
||
|
||
```
|
||
请求到达
|
||
│
|
||
├── connect_timeout (建立连接)
|
||
│ └── 超时 → 连接失败,不创建任务
|
||
│
|
||
├── queue_timeout (排队等待)
|
||
│ └── 超时 → 返回 QUEUE_TIMEOUT
|
||
│
|
||
├── first_token_timeout (首 Token 等待)
|
||
│ └── 超时 → 取消任务或切换模型
|
||
│
|
||
├── inference_timeout (推理执行)
|
||
│ └── 超时 → 向推理引擎发送取消信号
|
||
│
|
||
├── idle_timeout (流式空闲)
|
||
│ └── 超时 → 检查模型状态,终止异常连接
|
||
│
|
||
├── total_timeout (总时间)
|
||
│ └── 超时 → 强制结束整个生命周期
|
||
│
|
||
└── cancel_grace_period (取消后等待)
|
||
└── 超时 → 隔离或重启异常实例
|
||
```
|
||
|
||
#### 3.5.2 默认超时配置
|
||
|
||
```yaml
|
||
timeouts:
|
||
default_connect_ms: 5000
|
||
default_queue_ms: 5000
|
||
default_first_token_ms: 10000
|
||
default_inference_ms: 60000
|
||
default_idle_ms: 15000
|
||
default_total_ms: 90000
|
||
cancel_grace_period_ms: 3000
|
||
```
|
||
|
||
#### 3.5.3 取消传播链路
|
||
|
||
```
|
||
触发源:
|
||
├─ 客户端主动取消 (DELETE /v1/tasks/{id})
|
||
├─ SSE/WebSocket 连接断开 (TCP FIN/RST)
|
||
├─ 队列超时
|
||
├─ 首 Token 超时
|
||
├─ 推理超时
|
||
├─ 总超时
|
||
├─ 管理员终止
|
||
├─ 权限撤销
|
||
└─ 设备危险状态 (温度/显存/负载)
|
||
│
|
||
▼
|
||
网关标记任务为 CANCELLED
|
||
│
|
||
▼
|
||
调度器从队列移除 (如仍在排队)
|
||
│
|
||
▼
|
||
连接管理器向模型适配器发送取消信号
|
||
│
|
||
▼
|
||
模型适配器调用推理引擎取消 API
|
||
│
|
||
├─ 取消成功 → 释放执行槽位 + KV Cache
|
||
└─ cancel_grace_period 内未响应 → 隔离实例
|
||
│
|
||
▼
|
||
记录取消原因和资源释放时间
|
||
```
|
||
|
||
---
|
||
|
||
### 3.6 任务状态机
|
||
|
||
```
|
||
┌──────────┐
|
||
│ RECEIVED │
|
||
└────┬─────┘
|
||
▼
|
||
┌───────────┐
|
||
│ VALIDATING│
|
||
└──┬────┬──┘
|
||
┌────────┘ └────────┐
|
||
▼ ▼
|
||
┌──────────┐ ┌───────────┐
|
||
│ REJECTED │ │ QUEUED │
|
||
└────┬─────┘ └─┬──┬──┬───┘
|
||
│ ┌─────┘ │ └──────┐
|
||
│ ▼ ▼ ▼
|
||
│ ┌──────────┐ ┌────────┐ ┌───────────┐
|
||
│ │TIMED_OUT │ │CANCELLED│ │DISPATCHING│
|
||
│ └────┬─────┘ └───┬────┘ └──┬───┬───┘
|
||
│ │ │ │ │
|
||
│ │ │ ▼ ▼
|
||
│ │ │ ┌──────┐ ┌──────┐
|
||
│ │ │ │RUNNING│ │FAILED│
|
||
│ │ │ └─┬──┬─┘ └──┬───┘
|
||
│ │ │ │ │ │
|
||
│ │ │ ▼ ▼ │
|
||
│ │ │ ┌─────────┐ │
|
||
│ │ │ │STREAMING│ │
|
||
│ │ │ └─┬─┬─┬─┬─┘ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ │ ▼ ▼ ▼ ▼ │
|
||
│ │ │ ┌────┐┌────┐ │
|
||
│ │ │ │SUCC││TIME│ │
|
||
│ │ │ │EED ││OUT │ │
|
||
│ │ │ └─┬──┘└─┬──┘ │
|
||
│ │ │ │ │ │
|
||
▼ ▼ ▼ ▼ ▼ ▼
|
||
┌────────┐ ┌────────┐ ┌────────┐ ┌──────┐ ┌──────┐
|
||
│REJECTED│ │TIMED_ │ │CANCEL │ │SUCCE │ │FAILED│
|
||
│ (终态) │ │OUT(终态)│ │LED(终态)│ │ED(终态)│ │(终态)│
|
||
└────────┘ └────────┘ └────────┘ └──────┘ └──────┘
|
||
```
|
||
|
||
每次状态变化记录:
|
||
|
||
```json
|
||
{
|
||
"task_id": "task-20260803-000001",
|
||
"from_state": "QUEUED",
|
||
"to_state": "DISPATCHING",
|
||
"timestamp": "2026-08-03T06:56:00.123Z",
|
||
"reason": "resource_available",
|
||
"node_id": "edge-node-01",
|
||
"model_instance": "qwen3-8b-int4#0",
|
||
"operator": "scheduler"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3.7 模型路由
|
||
|
||
#### 3.7.1 逻辑模型映射
|
||
|
||
```yaml
|
||
models:
|
||
general-chat:
|
||
provider: vllm
|
||
actual_model: qwen3-8b-int4
|
||
endpoint: http://127.0.0.1:8001
|
||
context_window: 32768
|
||
max_output_tokens: 4096
|
||
max_concurrency: 4
|
||
residency: always
|
||
cancel_supported: true
|
||
|
||
fast-chat:
|
||
provider: ollama
|
||
actual_model: qwen3:4b
|
||
endpoint: http://127.0.0.1:11434
|
||
context_window: 16384
|
||
max_output_tokens: 2048
|
||
max_concurrency: 2
|
||
residency: on_demand
|
||
idle_unload_seconds: 600
|
||
|
||
vision-analysis:
|
||
provider: ollama
|
||
actual_model: llama3.2-vision:11b
|
||
endpoint: http://127.0.0.1:11434
|
||
context_window: 8192
|
||
max_output_tokens: 2048
|
||
max_concurrency: 1
|
||
residency: on_demand
|
||
idle_unload_seconds: 300
|
||
```
|
||
|
||
#### 3.7.2 路由决策矩阵
|
||
|
||
| 决策因素 | 数据来源 | 权重 |
|
||
|---|---|---|
|
||
| 任务类型/模态 | 请求参数 | 必须 match |
|
||
| 数据隐私级别 | 应用策略 + 请求 routing | 硬约束 |
|
||
| 模型已加载 | 资源管理器 | 高 |
|
||
| 队列长度 | 调度器 | 高 |
|
||
| 显存余量 | 资源管理器 | 高 |
|
||
| 模型错误率 | 可观测模块 | 中 |
|
||
| 设备温度/功耗 | 系统监控 | 中 |
|
||
| 延迟要求 | 请求 priority | 中 |
|
||
| 成本 | 配置 | 低 |
|
||
|
||
#### 3.7.3 降级链执行
|
||
|
||
```
|
||
请求需要 general-chat 模型
|
||
│
|
||
├─ 1. 检查 general-chat 本地实例是否可用
|
||
│ ├─ 可用 → 使用
|
||
│ └─ 不可用 ↓
|
||
│
|
||
├─ 2. 检查同设备上的小模型 (fast-chat)
|
||
│ ├─ 可用且 allow_smaller_model=true → 使用,degraded=true
|
||
│ └─ 不可用 ↓
|
||
│
|
||
├─ 3. 检查其他边缘节点 (第三阶段)
|
||
│ ├─ 可用 → 转发,degraded=true
|
||
│ └─ 不可用 ↓
|
||
│
|
||
├─ 4. 返回缓存/规则化结果 (如适用)
|
||
│ ├─ 命中 → 返回,degraded=true
|
||
│ └─ 未命中 ↓
|
||
│
|
||
├─ 5. 检查云端模型 (如 allow_cloud=true 且数据策略允许)
|
||
│ ├─ 可用 → 路由云端,degraded=true,审计记录
|
||
│ └─ 不可用 ↓
|
||
│
|
||
└─ 6. 返回 MODEL_UNAVAILABLE 或系统繁忙
|
||
```
|
||
|
||
---
|
||
|
||
### 3.8 可靠性机制
|
||
|
||
#### 3.8.1 幂等控制
|
||
|
||
```
|
||
请求到达,携带 idempotency_key
|
||
│
|
||
├─ 查询幂等存储 (Redis / SQLite)
|
||
│ ├─ 存在且未过期 → 返回原任务状态/结果
|
||
│ ├─ 存在且已过期 → 创建新任务,更新幂等记录
|
||
│ └─ 不存在 → 创建新任务,写入幂等记录
|
||
│
|
||
└─ 幂等记录结构:
|
||
{
|
||
"idempotency_key": "app01-20260803-00001234",
|
||
"tenant_id": "tenant-01",
|
||
"application_id": "app-01",
|
||
"task_id": "task-20260803-000001",
|
||
"created_at": "2026-08-03T06:56:00Z",
|
||
"expires_at": "2026-08-03T07:06:00Z",
|
||
"status": "succeeded"
|
||
}
|
||
```
|
||
|
||
#### 3.8.2 熔断器状态机
|
||
|
||
```
|
||
错误率 < 阈值
|
||
┌──────────────────────┐
|
||
│ │
|
||
▼ │
|
||
┌──────────┐ ┌──────────┐
|
||
│ CLOSED │◄────────│ HALF │
|
||
│ (正常路由) │ │ OPEN │
|
||
└────┬─────┘ └────┬─────┘
|
||
│ │
|
||
│ 错误率 ≥ 阈值 │ 探测请求成功
|
||
│ 或连续错误 │
|
||
▼ │
|
||
┌──────────┐ │
|
||
│ OPEN │───────────────┘
|
||
│ (熔断中) │ 探测间隔后
|
||
└──────────┘ 放行单个请求
|
||
```
|
||
|
||
熔断范围:模型实例、设备节点、云端供应商、具体 API。
|
||
|
||
#### 3.8.3 背压执行
|
||
|
||
```
|
||
系统负载评估 (每个 tick)
|
||
│
|
||
├─ 负载 < 70% → 正常运行
|
||
│
|
||
├─ 负载 70%-85% → Level 1 背压
|
||
│ ├─ 限制 P4 新请求
|
||
│ └─ 缩短 P3/P4 队列等待时间
|
||
│
|
||
├─ 负载 85%-95% → Level 2 背压
|
||
│ ├─ 限制 P3/P4 新请求
|
||
│ ├─ 降低单请求 max_output_tokens 上限
|
||
│ └─ 将批处理任务延后
|
||
│
|
||
└─ 负载 > 95% → Level 3 背压
|
||
├─ 限制 P2 及以下新请求
|
||
├─ 路由至备用节点/小模型
|
||
└─ 返回 503 + Retry-After
|
||
```
|
||
|
||
---
|
||
|
||
### 3.9 可观测性
|
||
|
||
#### 3.9.1 指标暴露
|
||
|
||
通过 Prometheus `/metrics` 端点暴露指标:
|
||
|
||
```
|
||
# 请求指标
|
||
edgeai_requests_total{application,model,priority,status} # Counter
|
||
edgeai_request_duration_seconds{application,model} # Histogram
|
||
edgeai_queue_time_seconds{application,model} # Histogram
|
||
edgeai_first_token_latency_seconds{application,model} # Histogram
|
||
edgeai_tokens_total{application,model,direction} # Counter (input/output)
|
||
edgeai_tokens_per_second{model} # Gauge
|
||
edgeai_active_tasks{application,model} # Gauge
|
||
edgeai_queue_length{priority} # Gauge
|
||
|
||
# 资源指标
|
||
edgeai_gpu_utilization{device} # Gauge
|
||
edgeai_gpu_memory_used_bytes{device} # Gauge
|
||
edgeai_gpu_memory_total_bytes{device} # Gauge
|
||
edgeai_kv_cache_usage_ratio{model} # Gauge
|
||
edgeai_model_loaded{model} # Gauge (0/1)
|
||
edgeai_model_load_time_seconds{model} # Histogram
|
||
edgeai_device_temperature_celsius{device} # Gauge
|
||
edgeai_device_power_watts{device} # Gauge
|
||
|
||
# 质量指标
|
||
edgeai_degradation_total{application,from_model,to_model} # Counter
|
||
edgeai_context_trimming_total{application} # Counter
|
||
edgeai_policy_blocked_total{application,reason} # Counter
|
||
edgeai_cancellation_total{application,reason} # Counter
|
||
```
|
||
|
||
#### 3.9.2 调用链日志结构
|
||
|
||
每次调用生成一条结构化 JSON 日志,通过 `request_id`、`task_id`、`session_id`、`trace_id` 串联:
|
||
|
||
```json
|
||
{
|
||
"timestamp": "2026-08-03T06:56:00.123Z",
|
||
"level": "INFO",
|
||
"event": "task_state_change",
|
||
"request_id": "req-20260803-000001",
|
||
"task_id": "task-20260803-000001",
|
||
"session_id": "session-001",
|
||
"trace_id": "trace-abc123",
|
||
"application": "device-maintenance",
|
||
"tenant_id": "tenant-01",
|
||
"user_id": "user-1001",
|
||
"from_state": "QUEUED",
|
||
"to_state": "DISPATCHING",
|
||
"logical_model": "general-chat",
|
||
"actual_model": "qwen3-8b-int4",
|
||
"node_id": "edge-node-01",
|
||
"reason": "resource_available",
|
||
"queue_time_ms": 86,
|
||
"input_tokens": 2380,
|
||
"output_tokens": 0,
|
||
"prompt_hash": "sha256:abc123...",
|
||
"context_version": "v3",
|
||
"degraded": false
|
||
}
|
||
```
|
||
|
||
日志策略:
|
||
- 默认 `metadata_only`:不记录完整 Prompt 内容,只记录哈希。
|
||
- 排障模式 `full`:通过管理接口临时开启,记录完整内容,自动过期关闭。
|
||
- 敏感字段自动脱敏(API Key、密钥、个人信息)。
|
||
|
||
#### 3.9.3 告警规则
|
||
|
||
| 告警名称 | 条件 | 严重级别 |
|
||
|---|---|---|
|
||
| HighFirstTokenLatency | P95 首 Token 延迟 > 阈值持续 5min | Warning |
|
||
| QueueNearFull | 队列使用率 > 80% 持续 2min | Warning |
|
||
| ModelOOM | 检测到 OOM 或模型进程重启 | Critical |
|
||
| ModelErrorRateHigh | 某模型错误率 > 10% 持续 5min | Warning |
|
||
| GpuTemperatureDanger | GPU 温度 > 阈值 | Critical |
|
||
| ResourceLeakDetected | 任务取消后资源未释放 | Critical |
|
||
| CloudFallbackSpike | 云端降级比例异常增加 | Warning |
|
||
| AuthFailureSpike | 认证失败或策略拦截异常增加 | Warning |
|
||
|
||
---
|
||
|
||
## 4. 配置规格
|
||
|
||
### 4.1 全局配置文件
|
||
|
||
```yaml
|
||
server:
|
||
host: 0.0.0.0
|
||
port: 8080
|
||
admin_port: 8081
|
||
max_request_body_mb: 20
|
||
|
||
auth:
|
||
enabled: true
|
||
methods: [api_key, jwt]
|
||
jwt_issuer: edge-ai-gateway
|
||
jwt_secret_env: EDGEAI_JWT_SECRET
|
||
|
||
scheduler:
|
||
max_running_tasks: 8
|
||
max_queued_tasks: 500
|
||
fairness: weighted_fair_queue
|
||
priority_aging_seconds: 30
|
||
reserved_realtime_slots: 2
|
||
|
||
timeouts:
|
||
default_connect_ms: 5000
|
||
default_queue_ms: 5000
|
||
default_first_token_ms: 10000
|
||
default_inference_ms: 60000
|
||
default_idle_ms: 15000
|
||
default_total_ms: 90000
|
||
cancel_grace_period_ms: 3000
|
||
|
||
context:
|
||
safety_margin_ratio: 0.08
|
||
default_policy: summary_and_recent
|
||
max_session_messages: 200
|
||
session_idle_ttl_minutes: 60
|
||
enable_prompt_persistence: false
|
||
|
||
models:
|
||
general-chat:
|
||
provider: vllm
|
||
actual_model: qwen3-8b-int4
|
||
endpoint: http://127.0.0.1:8001
|
||
context_window: 32768
|
||
max_output_tokens: 4096
|
||
max_concurrency: 4
|
||
residency: always
|
||
cancel_supported: true
|
||
|
||
fast-chat:
|
||
provider: ollama
|
||
actual_model: qwen3:4b
|
||
endpoint: http://127.0.0.1:11434
|
||
context_window: 16384
|
||
max_output_tokens: 2048
|
||
max_concurrency: 2
|
||
residency: on_demand
|
||
idle_unload_seconds: 600
|
||
|
||
routing:
|
||
sensitive_data_local_only: true
|
||
allow_cloud_fallback_by_default: false
|
||
overload_strategy:
|
||
- same_model_other_instance
|
||
- smaller_local_model
|
||
- backup_edge_node
|
||
- reject
|
||
|
||
circuit_breaker:
|
||
error_rate_threshold: 0.1
|
||
min_requests: 10
|
||
window_seconds: 60
|
||
open_duration_seconds: 30
|
||
half_open_max_requests: 1
|
||
|
||
backpressure:
|
||
level1_threshold: 0.70
|
||
level2_threshold: 0.85
|
||
level3_threshold: 0.95
|
||
|
||
observability:
|
||
metrics_enabled: true
|
||
metrics_path: /metrics
|
||
tracing_enabled: true
|
||
prompt_logging: metadata_only
|
||
audit_retention_days: 180
|
||
log_level: info
|
||
|
||
storage:
|
||
session_db: sqlite:///var/lib/edgeai/sessions.db
|
||
task_state: sqlite:///var/lib/edgeai/tasks.db
|
||
redis:
|
||
enabled: false
|
||
endpoint: redis://127.0.0.1:6379
|
||
```
|
||
|
||
### 4.2 环境变量
|
||
|
||
| 变量名 | 说明 | 示例 |
|
||
|---|---|---|
|
||
| `EDGEAI_JWT_SECRET` | JWT 签名密钥 | — |
|
||
| `EDGEAI_ADMIN_KEY` | 管理接口认证密钥 | — |
|
||
| `EDGEAI_CLOUD_API_KEY` | 云端模型 API 密钥 | — |
|
||
| `EDGEAI_DB_PATH` | 数据库文件路径 | `/var/lib/edgeai` |
|
||
| `EDGEAI_LOG_LEVEL` | 日志级别 | `info` |
|
||
| `EDGEAI_CONFIG_PATH` | 配置文件路径 | `/etc/edgeai/config.yaml` |
|
||
|
||
---
|
||
|
||
## 5. 产品指标与成功标准
|
||
|
||
### 5.1 北极星指标
|
||
|
||
**单台边缘算力机同时服务的应用数 × 平均每应用日调用成功率**
|
||
|
||
### 5.2 核心产品指标
|
||
|
||
| 指标 | MVP 目标 | 生产目标 |
|
||
|---|---|---|
|
||
| 通讯层附加延迟 | ≤ 50ms | ≤ 20ms |
|
||
| 首 Token P95 延迟 | ≤ 3s | ≤ 1s |
|
||
| 请求成功率 | ≥ 95% | ≥ 99% |
|
||
| 客户端断开后资源释放时间 | ≤ 5s | ≤ 3s |
|
||
| 模型替换后 API 兼容性 | 100% | 100% |
|
||
| 并发上限下 OOM 次数 | 0 | 0 |
|
||
| 跨租户数据泄露事件 | 0 | 0 |
|
||
|
||
### 5.3 用户满意度指标
|
||
|
||
| 指标 | 目标 |
|
||
|---|---|
|
||
| 应用接入耗时(从零到首次成功调用) | ≤ 30 分钟 |
|
||
| 模型替换对应用的影响 | 零代码修改 |
|
||
| 运维故障定位时间 | ≤ 10 分钟(通过调用链) |
|
||
|
||
---
|
||
|
||
## 6. 交付计划
|
||
|
||
### 6.1 里程碑
|
||
|
||
| 里程碑 | 内容 | 交付物 |
|
||
|---|---|---|
|
||
| M1: MVP | 统一 API + 鉴权 + 队列 + 超时 + 取消 + Ollama 适配 + 基础指标 | 可部署的二进制 + 配置文件 + API 文档 |
|
||
| M2: 治理增强 | 会话持久化 + 幂等 + 模型路由 + 降级 + 熔断 + 背压 + vLLM 适配 + 监控大盘 | 增量功能 + Grafana 面板 |
|
||
| M3: 多节点多模态 | 多节点调度 + 视觉/语音接口 + WebSocket + 云端路由 + 多租户计量 | 集群部署方案 + 多模态适配器 |
|
||
|
||
### 6.2 MVP 交付清单
|
||
|
||
| 序号 | 功能项 | 优先级 | 对应需求 |
|
||
|---|---|---|---|
|
||
| 1 | `POST /v1/chat/completions`(流式 + 非流式) | P0 | FR-1.1 |
|
||
| 2 | `GET /v1/models` | P0 | FR-1.1 |
|
||
| 3 | `GET /health` + `GET /ready` | P0 | FR-1.1 |
|
||
| 4 | API Key 鉴权 | P0 | FR-2.1 |
|
||
| 5 | 应用级 + 模型级并发限制 | P0 | FR-2.3 |
|
||
| 6 | 五级优先级队列 | P0 | FR-4.2 |
|
||
| 7 | 加权公平调度 + 优先级老化 | P1 | FR-4.3 |
|
||
| 8 | Token 预算计算 + 基础裁剪 | P0 | FR-3.2, FR-3.3 |
|
||
| 9 | 分层超时(queue + first_token + inference + total) | P0 | FR-5.1 |
|
||
| 10 | SSE 流式输出 | P0 | FR-1.2 |
|
||
| 11 | 客户端断开 → 推理取消 | P0 | FR-5.2 |
|
||
| 12 | 任务状态机 | P0 | FR-6 |
|
||
| 13 | Ollama 模型适配器 | P0 | FR-11.1 |
|
||
| 14 | 请求/Token/延迟/错误指标 | P0 | FR-10.1 |
|
||
| 15 | 统一错误码 | P0 | FR-1.6 |
|
||
| 16 | 会话创建/查询/删除 | P1 | FR-3.4 |
|
||
| 17 | 逻辑模型映射 | P0 | FR-7.1 |
|
||
| 18 | GPU 使用率/显存指标 | P1 | FR-10.1 |
|
||
|
||
### 6.3 第二阶段交付清单
|
||
|
||
| 序号 | 功能项 | 对应需求 |
|
||
|---|---|---|
|
||
| 1 | 会话持久化与历史摘要 | FR-3.4, FR-3.5 |
|
||
| 2 | Redis 任务状态 + 幂等控制 | FR-8.1 |
|
||
| 3 | 动态模型路由 + 小模型降级 | FR-7.2, FR-7.3 |
|
||
| 4 | 显存准入估算 | FR-4.5 |
|
||
| 5 | 连续批处理控制 | FR-4.6 |
|
||
| 6 | 模型驻留 + 自动卸载 | FR-4.7 |
|
||
| 7 | 熔断 + 有限重试 + 背压 | FR-8.2, FR-8.3, FR-8.4 |
|
||
| 8 | vLLM 模型适配器 | FR-11.1 |
|
||
| 9 | 管理后台 API | 3.2.3 |
|
||
| 10 | Grafana 监控大盘 | FR-10.1 |
|
||
| 11 | 调用链日志 + 告警规则 | FR-10.2, FR-10.3 |
|
||
| 12 | 异步任务接口 (`/v1/tasks`) | FR-1.1 |
|
||
| 13 | Prompt 注入防护 | FR-3.7 |
|
||
|
||
---
|
||
|
||
## 7. 依赖与约束
|
||
|
||
### 7.1 外部依赖
|
||
|
||
| 依赖 | 用途 | MVP 必需 |
|
||
|---|---|---|
|
||
| Ollama | 推理引擎 | 是 |
|
||
| vLLM | 推理引擎 | 否(第二阶段) |
|
||
| SQLite | 会话/配置/任务状态存储 | 是 |
|
||
| Redis | 幂等/共享状态(可选) | 否 |
|
||
| Prometheus | 指标采集 | 是 |
|
||
| Grafana | 指标展示 | 否(第二阶段) |
|
||
|
||
### 7.2 技术约束
|
||
|
||
- 通讯层与推理服务必须独立进程部署。
|
||
- 通讯层必须能检测推理实例崩溃并重新接入。
|
||
- 第一阶段不引入分布式组件(除可选 Redis)。
|
||
- 配置文件为 YAML 格式,支持热重载。
|
||
- 日志为结构化 JSON,输出到 stdout 和/或文件。
|
||
|
||
### 7.3 安全约束
|
||
|
||
- 密钥不写入代码、日志或普通配置文件。
|
||
- 云端降级默认禁止。
|
||
- 敏感数据强制本地处理。
|
||
- 日志默认不记录完整 Prompt。
|
||
|
||
---
|
||
|
||
## 8. 开放问题与决策
|
||
|
||
### 8.1 决策汇总
|
||
|
||
| 编号 | 问题 | 决策 | 阶段 |
|
||
|---|---|---|---|
|
||
| Q1 | 通讯层实现语言选择 | **Go** | M1 |
|
||
| Q2 | MVP 认证方式 | **API Key 优先,JWT 预留接口** | M1 仅 API Key |
|
||
| Q3 | 管理后台形式 | **仅管理 API,无 UI** | M1-M2 |
|
||
| Q4 | Token 计数策略 | **引擎返回优先,估算兜底** | M1 实现 |
|
||
| Q5 | KV Cache 估算方式 | **配置静态值 → 引擎查询 → 历史回归** | M2 实现 |
|
||
| Q6 | 多 GPU/NPU 混合调度 | **不实现,预留接口** | M3 |
|
||
|
||
### 8.2 Q1:通讯层实现语言 — Go
|
||
|
||
| 维度 | Go | Rust | Python(FastAPI) |
|
||
|---|---|---|---|
|
||
| 并发模型 | goroutine 天然适合高并发流式 | async/tokio 性能最优但学习曲线陡 | asyncio 可用但 GCL 限制 CPU 密集 |
|
||
| SSE/流式 | 标准库 `net/http` 原生支持 | hyper/tokio 性能更好但复杂 | FastAPI StreamingResponse 够用 |
|
||
| 部署体积 | 单二进制,无运行时依赖 | 单二进制,无运行时依赖 | 需要 Python 运行时 + 依赖包 |
|
||
| 边缘适配 | 交叉编译简单,内存占用低 | 最优但开发慢 | 内存占用最高 |
|
||
| 团队上手 | 中等,语法简单 | 高,学习曲线陡 | 最低,但维护性差 |
|
||
| 生态 | HTTP/JSON/gRPC 一等公民 | 生态在完善中 | 最丰富但类型安全弱 |
|
||
|
||
**决策理由:**
|
||
- 边缘设备要求单二进制部署、低内存占用、无运行时依赖。
|
||
- goroutine 天然适配大量 SSE 流式连接和取消信号传播。
|
||
- HTTP/JSON/gRPC 生态成熟,开发效率高于 Rust。
|
||
- 配置热重载、结构化日志、Prometheus 客户端都有成熟库。
|
||
|
||
> 如果团队有 Rust 经验且对延迟有极致要求,Rust 是更优选择,但 MVP 阶段 Go 的开发效率优势更大。
|
||
|
||
### 8.3 Q2:MVP 认证方式 — API Key 优先,JWT 预留接口
|
||
|
||
**决策:** MVP 仅实现 API Key,认证模块设计为策略模式,预留 JWT 扩展点。
|
||
|
||
**理由:**
|
||
- API Key 实现简单(Header 校验 + 存储查表),MVP 阶段应用数量少,足够使用。
|
||
- JWT 需要签发、刷新、吊销机制,工作量大且 MVP 阶段无明确需求。
|
||
- 认证接口设计为可插拔策略,第二阶段加 JWT 不影响上层逻辑。
|
||
|
||
```go
|
||
// 预留接口
|
||
type Authenticator interface {
|
||
Authenticate(r *http.Request) (*Identity, error)
|
||
}
|
||
|
||
type APIKeyAuthenticator struct { ... } // MVP 实现
|
||
type JWTAuthenticator struct { ... } // 第二阶段实现
|
||
```
|
||
|
||
### 8.4 Q3:管理后台 — 仅管理 API,无 UI
|
||
|
||
**决策:** MVP 仅提供管理 API,第三阶段再考虑 UI。
|
||
|
||
**理由:**
|
||
- 管理后台 UI 开发量大(前端框架 + 组件 + 交互),对 MVP 核心价值无贡献。
|
||
- 边缘算力机的管理员通常是技术人员,API + YAML 配置 + curl 足够。
|
||
- 第二阶段可通过 Grafana 覆盖大部分监控需求。
|
||
- 如果后续需要 UI,可用轻量方案(如 Go 模板 + HTMX,不引入 React)。
|
||
|
||
MVP 管理接口清单:
|
||
|
||
| 接口 | 用途 |
|
||
|---|---|
|
||
| `GET/POST/PUT/DELETE /admin/applications` | 应用管理 |
|
||
| `GET/POST/PUT/DELETE /admin/models` | 模型配置 |
|
||
| `GET/PUT /admin/policies` | 安全策略 |
|
||
| `GET/PUT /admin/quotas` | 配额配置 |
|
||
| `GET /admin/tasks` | 任务监控 |
|
||
|
||
### 8.5 Q4:Token 计数 — 引擎返回优先,估算兜底
|
||
|
||
**决策:** 优先使用推理引擎返回的 Token 数,未返回时通讯层自行估算。
|
||
|
||
```
|
||
推理完成
|
||
│
|
||
├─ 引擎返回 usage?
|
||
│ ├─ 是 → 直接使用(最准确)
|
||
│ └─ 否 → 通讯层估算
|
||
│ ├─ input_tokens = tokenizer.encode(messages).count
|
||
│ └─ output_tokens = tokenizer.encode(response).count
|
||
│
|
||
└─ 估算场景:
|
||
├─ 排队阶段需要预估 input_tokens(必须估算)
|
||
└─ 流式输出中途需要实时 output_tokens(按 chunk 估算)
|
||
```
|
||
|
||
**实现建议:**
|
||
- 集成 `tiktoken` 或模型对应的 tokenizer,按模型配置选择。
|
||
- 排队准入阶段必须估算 input_tokens(引擎尚未执行)。
|
||
- 推理完成后优先用引擎返回值覆盖估算值。
|
||
- 流式输出中按 chunk 粗估 output_tokens,完成后用引擎值修正。
|
||
- 估算误差记录到指标,用于后续校准。
|
||
|
||
### 8.6 Q5:KV Cache 估算 — 配置静态值 → 引擎查询 → 历史回归
|
||
|
||
**决策:** 三步走策略。
|
||
|
||
**第一步(M2 初始):按模型配置静态值**
|
||
|
||
```yaml
|
||
models:
|
||
general-chat:
|
||
kv_cache_per_token_bytes: 131072 # 128KB/token (示例)
|
||
# 按架构推算公式:
|
||
# per_token_kv = 2 × num_layers × num_kv_heads × head_dim × bytes_per_element
|
||
```
|
||
|
||
例如 Qwen3-8B(32 层,8 KV heads,128 head dim,FP16):
|
||
|
||
```
|
||
2 × 32 × 8 × 128 × 2 = 131,072 bytes ≈ 128KB/token
|
||
```
|
||
|
||
**第二步(M2 运行中):从推理引擎查询实际值**
|
||
|
||
vLLM 等引擎可暴露 KV Cache 使用量,通讯层记录实际值与估算值的偏差,动态修正系数。
|
||
|
||
**第三步(M2 稳定后):历史数据回归**
|
||
|
||
收集 `(input_tokens, actual_kv_cache_used)` 样本,按模型回归修正系数,写入配置。
|
||
|
||
### 8.7 Q6:多 GPU/NPU 混合调度 — 不实现,预留接口
|
||
|
||
**决策:** MVP 不实现混合调度,但资源管理器接口设计为多设备感知。
|
||
|
||
**理由:**
|
||
- 边缘算力机大多数场景为单 GPU 或单 NPU,MVP 不需要混合调度。
|
||
- 混合调度涉及异构设备能力描述、跨设备显存管理、任务亲和性匹配,复杂度高。
|
||
- 接口设计应预留,第三阶段扩展为多设备调度器。
|
||
|
||
```go
|
||
type Device struct {
|
||
ID string
|
||
Type DeviceType // GPU / NPU / CPU
|
||
VRAM uint64
|
||
Compute uint64
|
||
Models []string // 已加载模型
|
||
}
|
||
|
||
type ResourceManager interface {
|
||
Estimate(task *Task, device *Device) (bool, error) // 预留多设备
|
||
Acquire(task *Task, device *Device) (*Slot, error)
|
||
Release(slot *Slot) error
|
||
ListDevices() []*Device
|
||
}
|
||
```
|
||
|
||
MVP 实现单设备版本,`ListDevices()` 返回单个设备,第三阶段扩展为多设备调度器。
|