J.B-Lin commited on
Commit
a911bdf
·
1 Parent(s): 96ae0a2

docs: 更新开发日志和本地部署经验,记录全双工实现细节

Browse files
Files changed (2) hide show
  1. docs/开发日志.md +72 -0
  2. 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 是去掉扩展名的文件路径