Spaces:
Runtime error
Runtime error
J.B-Lin commited on
Commit ·
8193cd7
1
Parent(s): 269d57b
core: 更新 ModelLoader 对接远端 Modal API + README 同步
Browse files- README.md +7 -1
- core/model_loader.py +106 -14
- modal_deploy/README.md +7 -6
README.md
CHANGED
|
@@ -40,7 +40,13 @@ PregoPal 是一款面向孕期家庭的 AI 陪护工具,支持:
|
|
| 40 |
| 📝 饮食记录储存(JSON + Markdown) | 已实现 | `modules/diet_logger.py` + `modules/diet_extractor.py` |
|
| 41 |
| 📊 营养分析与可视化报告 | 基线可用 | `modules/nutrition_analyzer.py` + `modules/nutrition_standards.py` |
|
| 42 |
|
| 43 |
-
**技术栈**: Python 3.13 · Gradio 6.16 · MiniCPM-o 4.5 · Matplotlib · Pandas
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
---
|
| 46 |
|
|
|
|
| 40 |
| 📝 饮食记录储存(JSON + Markdown) | 已实现 | `modules/diet_logger.py` + `modules/diet_extractor.py` |
|
| 41 |
| 📊 营养分析与可视化报告 | 基线可用 | `modules/nutrition_analyzer.py` + `modules/nutrition_standards.py` |
|
| 42 |
|
| 43 |
+
**技术栈**: Python 3.13 · Gradio 6.16 · MiniCPM-o 4.5 · llama-cpp-python (CUDA) · Modal T4 · Matplotlib · Pandas
|
| 44 |
+
|
| 45 |
+
**模型部署**: MiniCPM-o-4_5 GGUF (Q4_K_M) on Modal (T4 GPU, $0.50/hr)
|
| 46 |
+
- API 端点: `https://andrew-jiabin--prego-pal-minicpm-serve.modal.run`
|
| 47 |
+
- 集成方式: `core/model_loader.py` ←HTTP→ Modal API (OpenAI 兼容)
|
| 48 |
+
- 模型数据: Modal Volume `minicpm-o-4_5-models`(持久化,仅需上传一次)
|
| 49 |
+
- 详见: `modal_deploy/README.md`
|
| 50 |
|
| 51 |
---
|
| 52 |
|
core/model_loader.py
CHANGED
|
@@ -1,26 +1,118 @@
|
|
| 1 |
"""
|
| 2 |
PregoPal - 模型加载器
|
| 3 |
======================
|
| 4 |
-
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
"""
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
class ModelLoader:
|
| 12 |
-
"""MiniCPM-o 4.5 模型加载器"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
def
|
| 15 |
-
|
| 16 |
-
self.
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
| 20 |
-
"""加载模型(待实现)"""
|
| 21 |
-
raise NotImplementedError("等待 MiniCPM-o 4.5 部署后实现")
|
| 22 |
|
| 23 |
def unload(self):
|
| 24 |
-
"""卸载
|
| 25 |
-
self.
|
| 26 |
-
self.
|
|
|
|
|
|
| 1 |
"""
|
| 2 |
PregoPal - 模型加载器
|
| 3 |
======================
|
| 4 |
+
对接 Modal 远端部署的 MiniCPM-o 4.5 API。
|
| 5 |
|
| 6 |
+
架构:
|
| 7 |
+
core/model_loader.py ←HTTP→ modal_deploy/deploy.py (Modal T4 GPU)
|
| 8 |
+
↕
|
| 9 |
+
llama-cpp-python (CUDA)
|
| 10 |
+
↕
|
| 11 |
+
MiniCPM-o-4_5 GGUF (Volume)
|
| 12 |
+
|
| 13 |
+
用法:
|
| 14 |
+
from core.model_loader import ModelLoader
|
| 15 |
+
|
| 16 |
+
loader = ModelLoader()
|
| 17 |
+
# 远程调用(走 Modal API)
|
| 18 |
+
resp = loader.chat("今天孕妇可以吃什么?")
|
| 19 |
+
# 本地调用(如果有本地模型)
|
| 20 |
+
loader.load_local("/path/to/model.gguf")
|
| 21 |
"""
|
| 22 |
|
| 23 |
+
import os
|
| 24 |
+
import logging
|
| 25 |
+
from typing import Optional
|
| 26 |
+
|
| 27 |
+
logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
# Modal 部署的 API 端点(由环境变量配置,默认使用线上 T4 部署)
|
| 30 |
+
DEFAULT_API_BASE = os.environ.get(
|
| 31 |
+
"MINICPM_API_BASE",
|
| 32 |
+
"https://andrew-jiabin--prego-pal-minicpm-serve.modal.run",
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
|
| 36 |
class ModelLoader:
|
| 37 |
+
"""MiniCPM-o 4.5 模型加载器(远端 API 模式 / 本地模式)"""
|
| 38 |
+
|
| 39 |
+
def __init__(self, api_base: str = None):
|
| 40 |
+
self.api_base = (api_base or DEFAULT_API_BASE).rstrip("/")
|
| 41 |
+
self._client = None # lazy init
|
| 42 |
+
self._local_model = None
|
| 43 |
+
self._local_mmproj = None
|
| 44 |
+
|
| 45 |
+
# ── 远端 API 模式(默认) ─────────────────────────────────
|
| 46 |
+
|
| 47 |
+
@property
|
| 48 |
+
def client(self):
|
| 49 |
+
if self._client is None:
|
| 50 |
+
from modal_deploy.client import MiniCPMClient
|
| 51 |
+
self._client = MiniCPMClient(base_url=self.api_base)
|
| 52 |
+
return self._client
|
| 53 |
+
|
| 54 |
+
def chat(self, messages: list[dict], **kwargs) -> dict:
|
| 55 |
+
"""远端对话(走 Modal T4)"""
|
| 56 |
+
return self.client.chat(messages, **kwargs)
|
| 57 |
+
|
| 58 |
+
def ask(self, prompt: str, system_prompt: Optional[str] = None) -> str:
|
| 59 |
+
"""远端简化调用"""
|
| 60 |
+
return self.client.ask(prompt, system_prompt=system_prompt)
|
| 61 |
+
|
| 62 |
+
def chat_with_image(
|
| 63 |
+
self, prompt: str, image_base64: str, **kwargs
|
| 64 |
+
) -> dict:
|
| 65 |
+
"""远端多模态"""
|
| 66 |
+
return self.client.chat_with_image(prompt, image_base64, **kwargs)
|
| 67 |
+
|
| 68 |
+
def health(self) -> dict:
|
| 69 |
+
"""检查远端服务状态"""
|
| 70 |
+
return self.client.health()
|
| 71 |
+
|
| 72 |
+
# ── 本地模式(备选,用于开发调试) ────────────────────────
|
| 73 |
+
|
| 74 |
+
def load_local(
|
| 75 |
+
self,
|
| 76 |
+
model_path: str,
|
| 77 |
+
mmproj_path: Optional[str] = None,
|
| 78 |
+
n_gpu_layers: int = -1,
|
| 79 |
+
n_ctx: int = 8192,
|
| 80 |
+
):
|
| 81 |
+
"""加载本地 GGUF 模型(需安装 llama-cpp-python)"""
|
| 82 |
+
try:
|
| 83 |
+
from llama_cpp import Llama
|
| 84 |
+
except ImportError:
|
| 85 |
+
raise ImportError(
|
| 86 |
+
"本地模式需要 llama-cpp-python: pip install llama-cpp-python"
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
kwargs = dict(
|
| 90 |
+
model_path=model_path,
|
| 91 |
+
n_gpu_layers=n_gpu_layers,
|
| 92 |
+
n_ctx=n_ctx,
|
| 93 |
+
verbose=False,
|
| 94 |
+
)
|
| 95 |
+
if mmproj_path and os.path.isfile(mmproj_path):
|
| 96 |
+
kwargs["mmproj"] = mmproj_path
|
| 97 |
+
|
| 98 |
+
logger.info(f"[ModelLoader] Loading local model: {model_path}")
|
| 99 |
+
self._local_model = Llama(**kwargs)
|
| 100 |
+
self._local_mmproj = mmproj_path
|
| 101 |
+
logger.info("[ModelLoader] [OK] Local model loaded")
|
| 102 |
|
| 103 |
+
def chat_local(self, messages: list[dict], **kwargs) -> dict:
|
| 104 |
+
"""本地推理"""
|
| 105 |
+
if self._local_model is None:
|
| 106 |
+
raise RuntimeError("本地模型未加载,请先调用 load_local()")
|
| 107 |
+
return self._local_model.create_chat_completion(
|
| 108 |
+
messages=messages,
|
| 109 |
+
**kwargs,
|
| 110 |
+
)
|
| 111 |
|
| 112 |
+
# ── 通用接口 ─────────────────────────────────────────────
|
|
|
|
|
|
|
| 113 |
|
| 114 |
def unload(self):
|
| 115 |
+
"""卸载释放资源"""
|
| 116 |
+
self._client = None
|
| 117 |
+
self._local_model = None
|
| 118 |
+
self._local_mmproj = None
|
modal_deploy/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
## 概述
|
| 4 |
|
| 5 |
-
在 Modal.com 的
|
| 6 |
|
| 7 |
## 架构
|
| 8 |
|
|
@@ -129,9 +129,9 @@ modal run modal_deploy/deploy.py::upload_models
|
|
| 129 |
|
| 130 |
在 `deploy.py` 中可调整的参数:
|
| 131 |
- `container_idle_timeout=300` — 空闲 5 分钟后自动关闭
|
| 132 |
-
- `gpu="
|
| 133 |
-
- `
|
| 134 |
-
- `-
|
| 135 |
|
| 136 |
## 与 PregoPal 集成
|
| 137 |
|
|
@@ -154,7 +154,8 @@ def ask_minicpm(prompt: str) -> str:
|
|
| 154 |
## 注意事项
|
| 155 |
|
| 156 |
1. **冷启动**:无请求 >5 分钟后容器关闭,下次请求需等待 ~30-60 秒(模型加载)
|
| 157 |
-
2. **成本**:
|
| 158 |
3. **模型上传**:Volume 是持久化的,模型只需上传一次
|
| 159 |
4. **并发**:`@modal.concurrent(max_inputs=10)` 支持 10 个并发请求
|
| 160 |
-
5. **
|
|
|
|
|
|
| 2 |
|
| 3 |
## 概述
|
| 4 |
|
| 5 |
+
在 Modal.com 的 **T4 GPU** 上部署 MiniCPM-o-4_5 模型,通过 llama-cpp-python (CUDA pre-built wheel) 提供 OpenAI 兼容的 API 接口。
|
| 6 |
|
| 7 |
## 架构
|
| 8 |
|
|
|
|
| 129 |
|
| 130 |
在 `deploy.py` 中可调整的参数:
|
| 131 |
- `container_idle_timeout=300` — 空闲 5 分钟后自动关闭
|
| 132 |
+
- `gpu="T4"` — GPU 类型(16GB VRAM,$0.50/hr。也可用 A100: $1.50/hr)
|
| 133 |
+
- `n_ctx=8192` — 上下文窗口大小
|
| 134 |
+
- `n_gpu_layers=-1` — GPU 层数(-1=全部到 GPU)
|
| 135 |
|
| 136 |
## 与 PregoPal 集成
|
| 137 |
|
|
|
|
| 154 |
## 注意事项
|
| 155 |
|
| 156 |
1. **冷启动**:无请求 >5 分钟后容器关闭,下次请求需等待 ~30-60 秒(模型加载)
|
| 157 |
+
2. **成本**:T4 约 $0.50/小时(vs A100 $1.50/hr),按实际使用计费。降级节省 **67%**
|
| 158 |
3. **模型上传**:Volume 是持久化的,模型只需上传一次
|
| 159 |
4. **并发**:`@modal.concurrent(max_inputs=10)` 支持 10 个并发请求
|
| 160 |
+
5. **VRAM 使用**:Q4_K_M 模型 4.7GB + vision 0.5GB + KV cache ≈ 6GB,T4 16GB 完全够用
|
| 161 |
+
6. **修复说明**:旧 deploy.py 的 `find_mmproj_files()` 递归搜索到 9 个 .gguf 文件并全部作为 `--mmproj` 传入,导致启动失败。新版已明确指定 `--mmproj` 使用 vision 投影层(仅一个文件)。
|