# 一键切换模型环境方案

## 背景

单台 A100 80G ×2，需要在以下模式间一键切换：

| 模式 | LLM | Embedding | Reranker | OCR |
|------|-----|-----------|----------|-----|
| **stable**（生产稳定） | Qwen3.5-27B-AWQ | bge-m3 | bge-reranker-v2-m3 | — |
| **dev**（开发调试） | Qwen3.6-27B (BF16) | Qwen3-Embedding-8B | Qwen3-Reranker-8B | Qwen3-VL-8B-Instruct（常驻） |
| **training**（微调） | — (停止) | — (停止) | — (停止) | — |

## 显存规划

### stable 模式（~18GB/卡，1-5 并发 × 32K context）

```
GPU 0: vLLM(AWQ权重 ~6.75GB + KV cache ~65GB) + bge-m3 (~1.2GB)        ≈ 73GB
GPU 1: vLLM(AWQ权重 ~6.75GB + KV cache ~65GB) + bge-reranker (~1.2GB)  ≈ 73GB
```

- AWQ 量化权重，KV cache 仍为 FP16
- bge 模型各占 ~1.2GB，剩余显存全给 KV cache
- `--gpu-memory-utilization 0.90`（72GB/卡），KV cache ~65GB，支持超长上下文

### dev 模式（~37.5GB/卡 + KV cache，1-5 并发 × 32K context）

```
GPU 0: vLLM LLM(~13.5GB) + Qwen3-Embedding-8B(~16GB) + VL-8B TP(~8GB) + KV cache  ≈ 37.5GB + KV
GPU 1: vLLM LLM(~13.5GB) + Qwen3-Reranker-8B(~16GB)  + VL-8B TP(~8GB) + KV cache  ≈ 37.5GB + KV
```

- Qwen3-VL-8B TP=2，每卡 ~8GB，双卡负载均衡
- 剩余 ~42.5GB/卡给 LLM KV cache
- LLM `--gpu-memory-utilization 0.45`（36GB/卡：权重 13.5GB + KV cache 22.5GB）
- OCR `--gpu-memory-utilization 0.12`（9.6GB/卡，TP=2 共 ~19.2GB 覆盖 VL-8B 16GB）

### training 模式（全卡给训练）

```
GPU 0+1: LLaMA-Factory (DDP, Qwen3.6-27B LoRA)
```

## 使用方式

```bash
bash scripts/llm/switch_mode.sh stable    # 切换到生产稳定环境
bash scripts/llm/switch_mode.sh dev       # 切换到开发环境（含 OCR 常驻）
bash scripts/llm/switch_mode.sh training  # 切换到训练模式
```

## 实施方案

### 新增启动脚本

dev 三件套复用现有脚本，新建 stable 三件套和 OCR 脚本：

| 脚本 | 说明 |
|------|------|
| `start_vllm_stable.sh` | Qwen3.5-27B-AWQ，TP=2，端口 8100 |
| `start_embedding_stable.sh` | bge-m3，GPU 0，端口 8200 |
| `start_reranker_stable.sh` | bge-reranker-v2-m3，GPU 1，端口 8300 |
| `start_ocr.sh` | Qwen3-VL-8B-Instruct，TP=2，端口 8400（dev 模式自动启动） |

### switch_mode.sh 改动

1. 新增 `stable` 模式：停止所有服务（含 OCR 8400）→ 启动 stable 三件套 → 更新数据库
2. `dev` 作为 `inference` 的别名，额外启动 OCR 服务 → 更新数据库
3. 删除独立 `ocr` 模式（OCR 随 dev 模式常驻）
4. 保留 `training` 模式（切换前停止所有服务含 OCR）
5. 新增 `stop_embedding()` / `stop_reranker()` / `stop_ocr()` 辅助函数
6. 新增 `update_db_models()` 函数，切换后同步更新数据库模型名
7. Redis 状态同步更新为新模式名

### update_db_models() 实现

数据库连接：`postgresql://user:kAvtYfAUMmwQgezaXz6Scw@localhost:5433/base_platform`

```bash
update_db_models() {
  local mode="$1"
  local DB="postgresql://user:kAvtYfAUMmwQgezaXz6Scw@localhost:5433/base_platform"

  if [[ "$mode" == "stable" ]]; then
    psql "$DB" <<'SQL'
UPDATE models SET remote_model_id = 'Qwen3.5-27B-AWQ'    WHERE code = 'local-qwen-chat';
UPDATE models SET remote_model_id = 'bge-m3',        dimension = 1024 WHERE code = 'local-bge-m3';
UPDATE models SET remote_model_id = 'bge-reranker-v2-m3'  WHERE code = 'local-bge-reranker';
SQL
  elif [[ "$mode" == "dev" ]]; then
    psql "$DB" <<'SQL'
UPDATE models SET remote_model_id = 'Qwen3.6-27B'         WHERE code = 'local-qwen-chat';
UPDATE models SET remote_model_id = 'Qwen3-Embedding-8B', dimension = 4096 WHERE code = 'local-bge-m3';
UPDATE models SET remote_model_id = 'Qwen3-Reranker-8B'   WHERE code = 'local-bge-reranker';
SQL
  fi
  echo "   DB model names updated for mode: $mode"
}
```

`update_db_models` 在服务启动完成后调用（健康检查通过之后），确保数据库与实际运行的模型一致。

### start_vllm_stable.sh 关键参数

```bash
MODEL_PATH=".../Qwen3.5-27B-AWQ"
--served-model-name Qwen3.5-27B-AWQ
--tensor-parallel-size 2
--dtype auto
--quantization awq
--enforce-eager
--gpu-memory-utilization 0.90   # 72GB/卡：AWQ权重 ~6.75GB + KV cache ~65GB
--max-model-len 32768
--max-num-seqs 5
```

### start_vllm.sh（dev 模式）参数调整

```bash
--gpu-memory-utilization 0.45   # 36GB/卡：BF16权重 ~13.5GB + KV cache ~22.5GB
--max-num-seqs 5
```

### start_ocr.sh 关键参数

```bash
# 不设 CUDA_VISIBLE_DEVICES，让 vLLM 自动使用双卡
--served-model-name Qwen3-VL-8B-Instruct
--port 8400
--tensor-parallel-size 2
--dtype bfloat16
--gpu-memory-utilization 0.12   # 9.6GB/卡，TP=2 共 ~19.2GB 覆盖 VL-8B 16GB
--max-model-len 8192
--max-num-seqs 2
--trust-remote-code
--limit-mm-per-prompt image=5,video=1
```

## 文件改动清单

**新建（4个）：**
- `scripts/llm/start_vllm_stable.sh`
- `scripts/llm/start_embedding_stable.sh`
- `scripts/llm/start_reranker_stable.sh`
- `scripts/llm/start_ocr.sh`

**修改（3个）：**
- `scripts/llm/switch_mode.sh` — 新增 stable/dev 模式，删除 ocr 模式，补充 stop_ocr()
- `scripts/llm/start_all.sh` — 默认改为 stable 模式
- `scripts/llm/start_vllm.sh` — gpu_util 0.50→0.45，max-num-seqs 32→5
- `scripts/llm/check_status.sh` — 补充 OCR 端口 8400 健康检查

**保留不变：**
- `start_embedding_vllm.sh` / `start_reranker_vllm.sh`（dev 模式直接复用）
- `stop_all.sh`

## 验证步骤

```bash
# 1. 切换到 stable
bash scripts/llm/switch_mode.sh stable
nvidia-smi                            # 确认每卡 ~73GB（KV cache 充分利用）
curl http://localhost:8100/v1/models  # 应返回 Qwen3.5-27B-AWQ
curl http://localhost:8200/health
curl http://localhost:8300/health

# 2. 切换到 dev（OCR 自动常驻）
bash scripts/llm/switch_mode.sh dev
nvidia-smi                            # 确认每卡 ~37.5GB + KV cache
curl http://localhost:8100/v1/models  # 应返回 Qwen3.6-27B
curl http://localhost:8400/health     # OCR 服务应已就绪
curl http://localhost:8400/v1/chat/completions \
  -d '{"model":"Qwen3-VL-8B-Instruct","messages":[{"role":"user","content":[{"type":"image_url","image_url":{"url":"..."}},{"type":"text","text":"请识别图片中的文字"}]}]}'

# 3. 切换到训练模式
bash scripts/llm/switch_mode.sh training
nvidia-smi                            # 确认所有推理服务已释放显存

# 4. 健康检查
bash scripts/llm/check_status.sh
```

## 注意事项

1. **切换耗时**：stable→dev 约 3-5 分钟（BF16 + VL-8B 同时加载），切换期间服务不可用
2. **stable 模式客户端适配**：模型名为 `Qwen3.5-27B-AWQ`，客户端通过 Redis `llm:service:status` 判断当前模式选择正确模型名
3. **embedding 维度差异**：stable(bge-m3=1024维) 和 dev(Qwen3-Embedding-8B=4096维) 向量不兼容，两套环境对应两套独立的知识库索引
4. **OCR 与 LLM 共享 KV cache 空间**：dev 模式下 OCR 的 `gpu_util 0.12` 和 LLM 的 `gpu_util 0.45` 是独立计算的，两者合计 0.57（~45.6GB/卡），加上 Embedding/Reranker ~16GB，总计 ~61.6GB < 80GB，安全
