Spaces:
Runtime error
Runtime error
J.B-Lin commited on
Commit ·
a911bdf
1
Parent(s): 96ae0a2
docs: 更新开发日志和本地部署经验,记录全双工实现细节
Browse files- docs/开发日志.md +72 -0
- docs/本地部署经验.md +105 -0
docs/开发日志.md
CHANGED
|
@@ -218,3 +218,75 @@ PregoPal/
|
|
| 218 |
- **Emoji 只在 UI 标签中使用,不在数据/图表中**:避免 matplotlib 渲染乱码
|
| 219 |
- **首页极简主义**:一个 Chatbot + 一个表格,无冗余卡片,聚焦对话
|
| 220 |
- **字体自适应**:`font-family:inherit` 让所有 HTML 内联内容跟随 Gradio Soft 主题字体
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
- **Emoji 只在 UI 标签中使用,不在数据/图表中**:避免 matplotlib 渲染乱码
|
| 219 |
- **首页极简主义**:一个 Chatbot + 一个表格,无冗余卡片,聚焦对话
|
| 220 |
- **字体自适应**:`font-family:inherit` 让所有 HTML 内联内容跟随 Gradio Soft 主题字体
|
| 221 |
+
|
| 222 |
+
## 2026-06-10 全双工语音对话实现(llama.cpp-omni)
|
| 223 |
+
|
| 224 |
+
### 背景
|
| 225 |
+
PregoPal 原有的 AI 模块全部是占位符(raise NotImplementedError),核心层 core/ 只有空接口。本次改造利用本地 RTX 4060Ti 16GB 部署的 llama.cpp-omni 实现真正的全双工语音交互。
|
| 226 |
+
|
| 227 |
+
### 模型文件
|
| 228 |
+
位于 C:\Users\Andre\codes\LJB\llama.cpp-omni\models\
|
| 229 |
+
- MiniCPM-o-4_5-Q4_K_M.gguf (4.68GB) — 主模型
|
| 230 |
+
- vision/MiniCPM-o-4_5-vision-F16.gguf (1.04GB) — 视觉投影
|
| 231 |
+
- audio/MiniCPM-o-4_5-audio-F16.gguf (630MB) — 音频投影
|
| 232 |
+
- tts/ — TTS 模型 (1.16GB) + projector (15MB)
|
| 233 |
+
- token2wav-gguf/ — token2wav 组件 (~890MB)
|
| 234 |
+
|
| 235 |
+
### llama.cpp-omni 端点
|
| 236 |
+
| 端点 | 作用 |
|
| 237 |
+
|------|------|
|
| 238 |
+
| POST /v1/stream/omni_init | 初始化 omni context |
|
| 239 |
+
| POST /v1/stream/reset | 重置 KV cache |
|
| 240 |
+
| POST /v1/stream/prefill | 音频/图像 token 填充 |
|
| 241 |
+
| POST /v1/stream/decode | 解码输出文本+TTS token |
|
| 242 |
+
| POST /v1/stream/break | 打断生成 |
|
| 243 |
+
|
| 244 |
+
### 全双工架构
|
| 245 |
+
用户说话 → Gradio gr.Audio 录音 → voice_helper.py → PregoAPI(FastAPI:8090) → llama-server omni(:8081) → omni_init → prefill(WAV)→ decode → llm_text(chunks)+TTS WAV(chunks) → 文本+音频 base64 → Gradio聊天框+语音播放
|
| 246 |
+
|
| 247 |
+
### 关键文件(本次新增/修改)
|
| 248 |
+
- api/go_server.py — FastAPI 全双工后端,封装 /v1/omni/voice_chat
|
| 249 |
+
- api/voice_helper.py — Python API 封装 (chat_text/chat_voice)
|
| 250 |
+
- core/model_loader.py — 支持 chat + voice_chat 双模式
|
| 251 |
+
- core/conversation_manager.py — 系统提示词 + 结构化标记解析
|
| 252 |
+
- ui/app_builder.py — Gradio 语音对话 UI
|
| 253 |
+
- start_services.py — 一键启动脚本
|
| 254 |
+
|
| 255 |
+
### 显存管控 (RTX 4060Ti 16GB)
|
| 256 |
+
- token2wav_device=cpu — token2wav 放 CPU
|
| 257 |
+
- tts_gpu_layers=0 — TTS 不放 GPU
|
| 258 |
+
- omni_init 后显存约 10.9GB/16GB (余量 5GB)
|
| 259 |
+
|
| 260 |
+
### 启动命令
|
| 261 |
+
```powershell
|
| 262 |
+
# 1. llama-server
|
| 263 |
+
C:\Users\Andre\codes\LJB\llama.cpp-omni\build\bin\Release\llama-server.exe -m C:\Users\Andre\codes\LJB\llama.cpp-omni\models\MiniCPM-o-4_5-Q4_K_M.gguf --mmproj C:\Users\Andre\codes\LJB\llama.cpp-omni\models\vision\MiniCPM-o-4_5-vision-F16.gguf -c 8192 --temp 0.7 --host 127.0.0.1 --port 8081 -ngl 99
|
| 264 |
+
|
| 265 |
+
# 2. PregoAPI
|
| 266 |
+
$env:LLAMA_SERVER_URL="http://127.0.0.1:8081"
|
| 267 |
+
$env:OMNI_OUTPUT_DIR="C:\Users\Andre\codes\LJB\hackthon\for_qclaw_llamacpp\PregoPal\omni_output"
|
| 268 |
+
C:\Users\Andre\miniconda3\envs\trader_stable\python.exe api\go_server.py
|
| 269 |
+
|
| 270 |
+
# 3. Gradio
|
| 271 |
+
C:\Users\Andre\miniconda3\envs\trader_stable\python.exe app.py
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
### 端到端测试
|
| 275 |
+
```python
|
| 276 |
+
import base64, requests, soundfile as sf, numpy as np, io
|
| 277 |
+
sr = 16000; t = np.linspace(0, 1.0, sr); audio = (0.3*np.sin(2*np.pi*200*t)).astype(np.float32)
|
| 278 |
+
buf = io.BytesIO(); sf.write(buf, audio, sr, format='WAV', subtype='PCM_16')
|
| 279 |
+
b64 = base64.b64encode(buf.getvalue()).decode()
|
| 280 |
+
r = requests.post('http://127.0.0.1:8090/v1/omni/voice_chat', json={'audio_base64':b64,'sample_rate':sr,'max_tokens':300}, timeout=600)
|
| 281 |
+
print(r.json().get('text',''))
|
| 282 |
+
```
|
| 283 |
+
|
| 284 |
+
### 踩坑记录
|
| 285 |
+
1. omni_init OOM: token2wav_device=cpu + tts_gpu_layers=0
|
| 286 |
+
2. decode 卡死后 reset 超时: 重启 llama-server
|
| 287 |
+
3. llm_text.txt 在 chunk_X/ 下: 修复→_read_llm_text() 合并所有 chunk
|
| 288 |
+
4. TTS 只返回第一个 wav: 修复→_merge_wavs_to_base64() 合并全部 wav
|
| 289 |
+
|
| 290 |
+
### Git
|
| 291 |
+
仓库: C:\Users\Andre\codes\LJB\hackthon\for_qclaw_llamacpp\PregoPal
|
| 292 |
+
关键提交: f54083c
|
docs/本地部署经验.md
CHANGED
|
@@ -72,3 +72,108 @@
|
|
| 72 |
- **结果**:成功识别图片中的 "↓买入" 图标并输出中文描述 ✓
|
| 73 |
- **加载时间**:约 **4 秒**(GPU CUDA)✓
|
| 74 |
- **警告**:`n_ctx_seq (4096) < n_ctx_train (40960)` — 不影响功能
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
- **结果**:成功识别图片中的 "↓买入" 图标并输出中文描述 ✓
|
| 73 |
- **加载时间**:约 **4 秒**(GPU CUDA)✓
|
| 74 |
- **警告**:`n_ctx_seq (4096) < n_ctx_train (40960)` — 不影响功能
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
## llama.cpp-omni 本地全双工部署(2026-06-10 添加)
|
| 79 |
+
|
| 80 |
+
### 项目目标
|
| 81 |
+
利用本地 RTX 4060Ti 16GB 部署的 llama.cpp-omni 实现 MiniCPM-o 4.5 的全双工语音交互,将 PregoPal 从占位模式升级为真正的 AI 驱动孕期陪护应用。
|
| 82 |
+
|
| 83 |
+
### 环境
|
| 84 |
+
- GPU: NVIDIA GeForce RTX 4060 Ti (16GB VRAM)
|
| 85 |
+
- OS: Windows 11
|
| 86 |
+
- Python: trader_stable (3.11.15)
|
| 87 |
+
- llama.cpp: omni 分支 (build 38, commit 891e0b1)
|
| 88 |
+
|
| 89 |
+
### 源码编译(llama.cpp-omni)
|
| 90 |
+
```powershell
|
| 91 |
+
cd C:\Users\Andre\codes\LJB\llama.cpp-omni
|
| 92 |
+
mkdir build && cd build
|
| 93 |
+
cmake .. -DGGML_CUDA=ON -DCMAKE_BUILD_TYPE=Release -DLLAMA_BUILD_SERVER=ON
|
| 94 |
+
cmake --build . --config Release -j
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
关键:源码中包含 omni/ 目录(全双工扩展),cmake 会自动编译 omni.dll + 链接到 server。
|
| 98 |
+
|
| 99 |
+
### 启动 llama-server(含 omni 模式)
|
| 100 |
+
```powershell
|
| 101 |
+
# 关键参数:必须指定 vision mmproj,否则 omni_init 会崩溃
|
| 102 |
+
$server = "C:\Users\Andre\codes\LJB\llama.cpp-omni\build\bin\Release\llama-server.exe"
|
| 103 |
+
$modelDir = "C:\Users\Andre\codes\LJB\llama.cpp-omni\models"
|
| 104 |
+
& $server -m "$modelDir\MiniCPM-o-4_5-Q4_K_M.gguf" `
|
| 105 |
+
--mmproj "$modelDir\vision\MiniCPM-o-4_5-vision-F16.gguf" `
|
| 106 |
+
-c 8192 --temp 0.7 --host 127.0.0.1 --port 8081 -ngl 99
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
### omni 初始化(关键步骤)
|
| 110 |
+
```python
|
| 111 |
+
import requests
|
| 112 |
+
base = "http://127.0.0.1:8081"
|
| 113 |
+
init_data = {
|
| 114 |
+
"media_type": 2, # audio 模式
|
| 115 |
+
"use_tts": True, # 开启 TTS
|
| 116 |
+
"duplex_mode": False, # 先用半双工
|
| 117 |
+
"token2wav_device": "cpu", # ★ 关键:token2wav 放 CPU 节省显存
|
| 118 |
+
"tts_gpu_layers": 0, # ★ 关键:TTS 不放 GPU
|
| 119 |
+
}
|
| 120 |
+
r = requests.post(f"{base}/v1/stream/omni_init", json=init_data, timeout=120)
|
| 121 |
+
assert r.status_code == 200 and r.json()["success"]
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
### 全双工 pipeline
|
| 125 |
+
1. omni_init — 加载 audio + TTS + token2wav 模块
|
| 126 |
+
2. prefill — 传入 16kHz WAV 音频路径,编码为音频 token
|
| 127 |
+
3. decode — 模型生成文本 token + TTS token
|
| 128 |
+
4. TTS 输出 — 多个 wav_X.wav 文件生成到 output_dir/round_NNN/tts_wav/
|
| 129 |
+
5. 文本输出 — 多个 chunk_X/llm_text.txt 生成到 output_dir/round_NNN/llm_debug/
|
| 130 |
+
|
| 131 |
+
### 输出文件结构
|
| 132 |
+
```
|
| 133 |
+
omni_output/round_NNN/
|
| 134 |
+
├── llm_debug/
|
| 135 |
+
│ ├── chunk_0/llm_text.txt # 文本片段
|
| 136 |
+
│ ├── chunk_1/llm_text.txt
|
| 137 |
+
│ └── ... # 8-10 个 chunk
|
| 138 |
+
└── tts_wav/
|
| 139 |
+
├── wav_0.wav # TTS 音频片段
|
| 140 |
+
├── wav_1.wav
|
| 141 |
+
├── wav_20.wav # ~21 个 wav
|
| 142 |
+
└── generation_done.flag # 完成标志
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
### API 封装(PregoAPI)
|
| 146 |
+
`api/go_server.py` 封装了 FastAPI 后端,暴露:
|
| 147 |
+
- GET /health — 健康检查(含 omni 初始化状态)
|
| 148 |
+
- POST /v1/chat/completions — 文本对话(proxy 到 llama-server)
|
| 149 |
+
- POST /v1/omni/voice_chat — 全双工语音对话(核心接口)
|
| 150 |
+
- POST /v1/omni/streaming_voice — SSE 流式语音对话
|
| 151 |
+
|
| 152 |
+
voice_chat 实现细节:
|
| 153 |
+
1. 解码 audio_base64 → np.float32
|
| 154 |
+
2. 保存临时 WAV 文件
|
| 155 |
+
3. omni_init(按需)
|
| 156 |
+
4. POST /prefill(传入 WAV 路径)
|
| 157 |
+
5. POST /decode(stream=False,轮询输出目录)
|
| 158 |
+
6. 合并所有 chunk_X/llm_text.txt → 完整文本
|
| 159 |
+
7. 合并所有 wav_X.wav → 完整 WAV → base64
|
| 160 |
+
8. 返回 {success, round, text, audio_base64}
|
| 161 |
+
|
| 162 |
+
### 显存分析
|
| 163 |
+
```
|
| 164 |
+
阶段 | 显存
|
| 165 |
+
llama-server 纯文本 | ~5.7GB
|
| 166 |
+
+ vision mmproj | ~6.7GB
|
| 167 |
+
+ KV cache (8192) | ~7.9GB
|
| 168 |
+
+ compute buffers | ~8.6GB
|
| 169 |
+
+ omni_init (audio+tts) | ~9.6GB (+1GB, CPU offload)
|
| 170 |
+
+ runtime 动态 | ~10.9GB
|
| 171 |
+
余量 | ~5GB/16GB
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
### 关键教训
|
| 175 |
+
1. **token2wav_device=cpu 是救命配置**:如果默认 GPU 加载,omni_init 直接 OOM(16GB 不够)→ 把 ~890MB 的 token2wav 放 CPU
|
| 176 |
+
2. **每次重启 llama-server 后要重新 omni_init**:omni 状态不持久化
|
| 177 |
+
3. **decode 要等够时间**:合成 TTS 可能需 30-120s,timeout 设 300s
|
| 178 |
+
4. **reset 在 decode 卡死后可能超时**:kill 进程比等 reset 快
|
| 179 |
+
5. **llama-server 接收 WAV 路径前缀而非 base64**:prefill 的 audio_path_prefix 是去掉扩展名的文件路径
|