Spaces:
Runtime error
Runtime error
J.B-Lin commited on
Commit ยท
ff5d4ed
1
Parent(s): ed20b95
feat: llama.cpp deploys MiniCPM-o 4.5 on Modal - technical report and client API
Browse files- .gitignore +37 -1
- docs/llama_cpp_minicpm_ๆๆฏๆฅๅ.md +337 -0
- docs/ๅผๅๆฅๅฟ.md +62 -0
- modal_deploy/README.md +156 -0
- modal_deploy/client.py +202 -0
- modal_deploy/deploy.py +366 -0
- requirements.txt +1 -0
.gitignore
CHANGED
|
@@ -1,2 +1,38 @@
|
|
|
|
|
| 1 |
.vscode/
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# IDE
|
| 2 |
.vscode/
|
| 3 |
+
.idea/
|
| 4 |
+
|
| 5 |
+
# Python
|
| 6 |
+
__pycache__/
|
| 7 |
+
*.pyc
|
| 8 |
+
*.pyo
|
| 9 |
+
*.egg-info/
|
| 10 |
+
dist/
|
| 11 |
+
build/
|
| 12 |
+
|
| 13 |
+
# ๆจกๅๆไปถ๏ผๅคชๅคง๏ผไธ็บณๅ
ฅ็ๆฌๆงๅถ๏ผ
|
| 14 |
+
*.gguf
|
| 15 |
+
models/
|
| 16 |
+
llamacpp/
|
| 17 |
+
|
| 18 |
+
# llama.cpp ็ผ่ฏไบง็ฉ
|
| 19 |
+
llama.cpp/build/
|
| 20 |
+
|
| 21 |
+
# ๆฐๆฎๆไปถ
|
| 22 |
+
data/voices/
|
| 23 |
+
*.wav
|
| 24 |
+
*.mp3
|
| 25 |
+
|
| 26 |
+
# ็ฏๅข
|
| 27 |
+
.env
|
| 28 |
+
.venv/
|
| 29 |
+
venv/
|
| 30 |
+
|
| 31 |
+
# ไธดๆถๆไปถ
|
| 32 |
+
*.tmp
|
| 33 |
+
*.log
|
| 34 |
+
debug*.txt
|
| 35 |
+
|
| 36 |
+
# ๆฃๆฅ่ๆฌ๏ผไธๆฌกๆงไฝฟ็จ๏ผ
|
| 37 |
+
_check_hf.py
|
| 38 |
+
_download_models.py
|
docs/llama_cpp_minicpm_ๆๆฏๆฅๅ.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# llama.cpp ้จ็ฝฒ MiniCPM-o 4.5 ๆๆฏๆฅๅ
|
| 2 |
+
|
| 3 |
+
> ๆฌๆๆกฃ่ฎฐๅฝๅจ Windows ็ฏๅขไธไฝฟ็จ llama.cpp ็ผ่ฏใ้จ็ฝฒ MiniCPM-o 4.5 ๆจกๅ็ๅ
จ่ฟ็จ๏ผไปฅๅๅ็ปญ Modal ไบ็ซฏ้จ็ฝฒ็่ฎพ่ฎกๆนๆกใไฝไธบๆๅๆๅๅๆๆฏๅ่ใ
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## ไธใ็ฏๅขไธ็ๆฌ
|
| 8 |
+
|
| 9 |
+
| ้กน็ฎ | ๅผ |
|
| 10 |
+
|------|-----|
|
| 11 |
+
| ๆไฝ็ณป็ป | Windows 11 |
|
| 12 |
+
| ็ผ่ฏๅจ | CMake + MSVC (Visual Studio) |
|
| 13 |
+
| llama.cpp ็ๆฌ | master ๅๆฏ (2026-06-08) |
|
| 14 |
+
| MiniCPM-o 4.5 | HuggingFace `openbmb/MiniCPM-o-4_5` |
|
| 15 |
+
| GGUF ้ๅๆนๅผ | Q4_K_M (ๆๆฌ) + F16 (ๅคๆจกๆๆๅฝฑๅฑ) |
|
| 16 |
+
| Python | 3.11 |
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## ไบใ็ผ่ฏ llama.cpp โโ ่ธฉๅ่ฎฐๅฝ
|
| 21 |
+
|
| 22 |
+
### 2.1 ็ผ่ฏ็ฎๆ
|
| 23 |
+
|
| 24 |
+
้่ฆ็ผ่ฏไธคไธช็ฎๆ ๏ผ
|
| 25 |
+
- `llama-server`๏ผOpenAI ๅ
ผๅฎน็ HTTP API ๆๅกๅจ
|
| 26 |
+
- `llama-mtmd-cli`๏ผๅคๆจกๆ๏ผๅพ็/้ณ้ข๏ผๆจ็ๅฝไปค่กๅทฅๅ
ท
|
| 27 |
+
|
| 28 |
+
### 2.2 CMake ็ผ่ฏๅฝไปค
|
| 29 |
+
|
| 30 |
+
```powershell
|
| 31 |
+
cd llama.cpp
|
| 32 |
+
mkdir build
|
| 33 |
+
cd build
|
| 34 |
+
cmake .. -DBUILD_SHARED_LIBS=OFF `
|
| 35 |
+
-DCMAKE_BUILD_TYPE=Release `
|
| 36 |
+
-DGGML_CUDA=OFF `
|
| 37 |
+
-DLLAMA_CURL=ON `
|
| 38 |
+
-DLLAMA_BUILD_SERVER=ON `
|
| 39 |
+
-DLLAMA_BUILD_TESTS=OFF `
|
| 40 |
+
-DLLAMA_BUILD_EXAMPLES=OFF
|
| 41 |
+
cmake --build . --config Release -j 16 --target llama-server llama-mtmd-cli
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
### 2.3 ๅ
ณ้ฎ็ผ่ฏ้้กน่ฏดๆ
|
| 45 |
+
|
| 46 |
+
| ้้กน | ่ฏดๆ | ไธบไปไน |
|
| 47 |
+
|------|------|--------|
|
| 48 |
+
| `DBUILD_SHARED_LIBS=OFF` | ้ๆ้พๆฅ | ้จ็ฝฒๆถไธ้่ฆ้ขๅค็ dll/so ไพ่ต |
|
| 49 |
+
| `DGGML_CUDA=OFF` | ็ฆ็จ CUDA | Modal ไธไฝฟ็จ A100 ไฝไธๆฏ็ผ่ฏๆถ้พๆฅ CUDA๏ผModal ๆ่ชๅทฑ้ฉฑๅจ๏ผ |
|
| 50 |
+
| `DLLAMA_CURL=ON` | ๅฏ็จ CURL ๆฏๆ | llama-server ้่ฆ CURL ๆฅไธ่ฝฝ/ๆตๅผไผ ่พๆจกๅ |
|
| 51 |
+
| `DLLAMA_BUILD_SERVER=ON` | ๆๅปบ HTTP server | ่ฟๆฏๆไปฌ็ไธป่ฆๆๅกๅฝขๅผ |
|
| 52 |
+
| `DCMAKE_BUILD_TYPE=Release` | Release ๆจกๅผ | ็ไบง็ฏๅข๏ผๅผๅฏไผๅ |
|
| 53 |
+
|
| 54 |
+
### 2.4 ่ธฉๅ๏ผCMake ๆพไธๅฐ็ผ่ฏๅจ
|
| 55 |
+
|
| 56 |
+
**้ฎ้ข**๏ผๅจ Windows ไธ็ดๆฅ่ฟ่ก `cmake` ๆฅ้ๆพไธๅฐ C ็ผ่ฏๅจใ
|
| 57 |
+
|
| 58 |
+
**่งฃๅณ**๏ผ้่ฆไป Visual Studio ็ Developer Command Prompt ไธญ่ฟ่ก๏ผๆ่
ไฝฟ็จ `cmake -G "Visual Studio 17 2022"` ๆๅฎ็ๆๅจใ
|
| 59 |
+
|
| 60 |
+
### 2.5 ่ธฉๅ๏ผllama-mtmd-cli ้ป่ฎคไธ็ผ่ฏ
|
| 61 |
+
|
| 62 |
+
**้ฎ้ข**๏ผllama-mtmd-cli ๆฏ llama.cpp ็ `examples/llama-mtmd/` ็ฎๅฝไธ็ๅทฅๅ
ท๏ผ้ป่ฎคไธไผ้ `cmake` ๆๅปบใ
|
| 63 |
+
|
| 64 |
+
**่งฃๅณ**๏ผ็ฐไปฃ llama.cpp๏ผ2025+๏ผๅทฒ็ปๅฐ llama-mtmd-cli ็บณๅ
ฅ้กถๅฑ CMakeLists.txt๏ผๅช้ `--target llama-mtmd-cli` ๅณๅฏใๅฆๆๆฏ่็ๆฌ้่ฆๅป `examples/llama-mtmd/` ไธๅ็ฌ็ผ่ฏใ
|
| 65 |
+
|
| 66 |
+
### 2.6 ่ธฉๅ๏ผWindows ่ทฏๅพๅ้็ฌฆ
|
| 67 |
+
|
| 68 |
+
**้ฎ้ข**๏ผllama.cpp ็ `--mmproj` ๅๆฐๅจ Windows ไธ้่ฆ็นๅซๆณจๆ่ทฏๅพๆ ผๅผใ
|
| 69 |
+
|
| 70 |
+
**่งฃๅณ**๏ผๅ
จ้จไฝฟ็จ `/` ่ทฏๅพๅ้็ฌฆ๏ผCMake ๅ llama.cpp ๅ
้จ้ฝไผๅค็ๅฅฝ๏ผใ
|
| 71 |
+
|
| 72 |
+
---
|
| 73 |
+
|
| 74 |
+
## ไธใๆจกๅไธ่ฝฝไธ GGUF ่ฝฌๆข
|
| 75 |
+
|
| 76 |
+
### 3.1 ๆจกๅๆฅๆบ
|
| 77 |
+
|
| 78 |
+
MiniCPM-o 4.5 ๅฎๆน HuggingFace๏ผ`openbmb/MiniCPM-o-4_5`
|
| 79 |
+
|
| 80 |
+
GGUF ้ๅ็ๆฌ๏ผๆฅ่ช `bartowski/MiniCPM-o-4_5-GGUF`
|
| 81 |
+
|
| 82 |
+
### 3.2 ๆไปถๆธ
ๅ๏ผ10 ไธชๆไปถ๏ผ4.3 GB๏ผ
|
| 83 |
+
|
| 84 |
+
| ๆไปถ | ๅคงๅฐ | ็จ้ | ่ฏดๆ |
|
| 85 |
+
|------|------|------|------|
|
| 86 |
+
| `MiniCPM-o-4_5-Q4_K_M.gguf` | 688 MB | ไธปๆจกๅ๏ผLLM ๆๆฌๆจ็๏ผ | Q4_K_M ้ๅ๏ผๅนณ่กก้ๅบฆไธ่ดจ้ |
|
| 87 |
+
| `vision/MiniCPM-o-4_5-vision-F16.gguf` | 1044 MB | ่ง่ง็ผ็ ๅจๆๅฝฑๅฑ | F16 ไฟๆ่ง่ง็ฒพๅบฆ |
|
| 88 |
+
| `audio/MiniCPM-o-4_5-audio-F16.gguf` | 630 MB | ้ณ้ข็ผ็ ๅจๆๅฝฑๅฑ | F16 ไฟๆ้ณ้ข็ฒพๅบฆ |
|
| 89 |
+
| `tts/MiniCPM-o-4_5-tts-F16.gguf` | 1104 MB | TTS ่ฏญ้ณๅๆๆจกๅ | F16 ไฟ่ฏ่ฏญ้ณ่ดจ้ |
|
| 90 |
+
| `tts/MiniCPM-o-4_5-projector-F16.gguf` | 14 MB | ๅฃฐๅญฆๆๅฝฑๅฑ | ๅฐๆไปถ๏ผไฝๅฟ
้ |
|
| 91 |
+
| `token2wav-gguf/encoder.gguf` | 144 MB | TTS ็ผ็ ๅจ | token โ mel-spectrogram |
|
| 92 |
+
| `token2wav-gguf/flow_extra.gguf` | 13 MB | TTS flow ้ๅ ๅๆฐ | |
|
| 93 |
+
| `token2wav-gguf/flow_matching.gguf` | 437 MB | TTS flow matching | ็ๆ้ซ่ดจ้่ฏญ้ณ็ๆ ธๅฟ |
|
| 94 |
+
| `token2wav-gguf/hifigan2.gguf` | 79 MB | HiFi-GAN ๅฃฐ็ ๅจ | mel โ wav |
|
| 95 |
+
| `token2wav-gguf/prompt_cache.gguf` | 202 MB | Prompt ็ผๅญ | ๅ ้ TTS ๆจ็ |
|
| 96 |
+
|
| 97 |
+
### 3.3 ่ธฉๅ๏ผGGUF ไธ่ฝฝ้ฎ้ข
|
| 98 |
+
|
| 99 |
+
1. **HuggingFace ้้**๏ผ็ดๆฅไธ่ฝฝๅคงๆไปถๅฏ่ฝๅพๆ
ข๏ผๅปบ่ฎฎไฝฟ็จ `huggingface_hub` ๅบๆ `hf_transfer` ๅ ้
|
| 100 |
+
2. **ๆไปถๅฎๆดๆง**๏ผไธ่ฝฝๅๅกๅฟ
็จ `sha256sum` ้ช่ฏ๏ผGGUF ๆไปถๅ
็ฝฎๆ ก้ช๏ผ
|
| 101 |
+
3. **็ฎๅฝ็ปๆ**๏ผไธ่ฝฝๆถไฟๆๅๅง็ฎๅฝ็ปๆ๏ผvision/ใaudio/ใtts/ใtoken2wav-gguf/ ๅญ็ฎๅฝ๏ผ๏ผllama-mtmd-cli ไพ่ต่ฟไบ่ทฏๅพ
|
| 102 |
+
|
| 103 |
+
---
|
| 104 |
+
|
| 105 |
+
## ๅใๆฌๅฐๆจ็ๆต่ฏ๏ผllama-mtmd-cli๏ผ
|
| 106 |
+
|
| 107 |
+
### 4.1 ๅบๆฌๆๆฌๆจ็
|
| 108 |
+
|
| 109 |
+
```powershell
|
| 110 |
+
.\llama-mtmd-cli.exe `
|
| 111 |
+
-m ..\models\MiniCPM-o-4_5-gguf\MiniCPM-o-4_5-Q4_K_M.gguf `
|
| 112 |
+
--mmproj ..\models\MiniCPM-o-4_5-gguf\vision\MiniCPM-o-4_5-vision-F16.gguf `
|
| 113 |
+
-ngl 99 -c 4096 -n 256 `
|
| 114 |
+
-p "ไปๅคฉๅญๅฆ้ๅๅไปไน่๏ผ"
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
### 4.2 ๅพ็็่งฃๆจ็
|
| 118 |
+
|
| 119 |
+
```powershell
|
| 120 |
+
.\llama-mtmd-cli.exe `
|
| 121 |
+
-m ..\models\MiniCPM-o-4_5-gguf\MiniCPM-o-4_5-Q4_K_M.gguf `
|
| 122 |
+
--mmproj ..\models\MiniCPM-o-4_5-gguf\vision\MiniCPM-o-4_5-vision-F16.gguf `
|
| 123 |
+
-ngl 99 -c 4096 -n 512 `
|
| 124 |
+
--image ..\test_image.jpg `
|
| 125 |
+
-p "ๆ่ฟฐ่ฟๅผ ๅพ็ไธญ็้ฃ็ฉ"
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
### 4.3 ่ธฉๅ๏ผ--mmproj ๅๆฐ็่งฃ
|
| 129 |
+
|
| 130 |
+
**้ฎ้ข**๏ผ`--mmproj` ๅช้่ฆไผ ่ง่งๆๅฝฑๅฑๆไปถ๏ผ`MiniCPM-o-4_5-vision-F16.gguf`๏ผ๏ผไธ้่ฆไผ ๅคไธชใ
|
| 131 |
+
|
| 132 |
+
**็่งฃ**๏ผ
|
| 133 |
+
- **่ง่งๆจ็**๏ผไธปๆจกๅ + ่ง่งๆๅฝฑๅฑ
|
| 134 |
+
- **้ณ้ขๆจ็**๏ผไธปๆจกๅ + ้ณ้ขๆๅฝฑๅฑ
|
| 135 |
+
- **TTS ็ๆ**๏ผไธปๆจกๅ + TTS ๆจกๅ + token2wav ็ปไปถ
|
| 136 |
+
- ๅๆฌกๆจ็ๅช้่ฆๅ ่ฝฝๅฏนๅบ็ๆๅฝฑๅฑ
|
| 137 |
+
|
| 138 |
+
### 4.4 ่ธฉๅ๏ผ-ngl ๅๆฐ
|
| 139 |
+
|
| 140 |
+
**้ฎ้ข**๏ผ`-ngl`๏ผnumber of GPU layers๏ผ่ฎพ็ฝฎไธๅฝไผๅฏผ่ด OOM ๆๆจ็ๆๆ
ขใ
|
| 141 |
+
|
| 142 |
+
**่งๅ**๏ผ
|
| 143 |
+
- `-ngl 99` = ๅฐๆๆๅฑๅ ่ฝฝๅฐ GPU๏ผ้่ฆ่ถณๅคๆพๅญ๏ผ688MB Q4 ๆจกๅ โ 2-4GB ๆพๅญ๏ผ
|
| 144 |
+
- `-ngl 0` = ็บฏ CPU ๆจ็๏ผๆๆ
ข๏ผไธๆจ่๏ผ
|
| 145 |
+
- ๅฆๆๆ็ฌ็ซๆพๅก๏ผๅปบ่ฎฎ่ณๅฐ `-ngl 33`๏ผ็บฆ 1/3 ๅฑๅฐ GPU๏ผ
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
## ไบใllama-server ้จ็ฝฒ
|
| 150 |
+
|
| 151 |
+
### 5.1 ๅฏๅจๅฝไปค
|
| 152 |
+
|
| 153 |
+
```powershell
|
| 154 |
+
.\llama-server.exe `
|
| 155 |
+
-m ..\models\MiniCPM-o-4_5-gguf\MiniCPM-o-4_5-Q4_K_M.gguf `
|
| 156 |
+
--host 0.0.0.0 --port 8080 `
|
| 157 |
+
-ngl 99 -c 8192 `
|
| 158 |
+
--mlock --no-mmap
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
### 5.2 ๆไพ็ API ็ซฏ็น
|
| 162 |
+
|
| 163 |
+
| ็ซฏ็น | ๆนๆณ | ๅ่ฝ | OpenAI ๅ
ผๅฎน |
|
| 164 |
+
|------|------|------|-------------|
|
| 165 |
+
| `/health` | GET | ๅฅๅบทๆฃๆฅ | - |
|
| 166 |
+
| `/v1/chat/completions` | POST | ๆๆฌๅฏน่ฏ | โ
|
|
| 167 |
+
| `/v1/embeddings` | POST | ๆๆฌๅตๅ
ฅ | โ
|
|
| 168 |
+
| `/v1/models` | GET | ๆจกๅๅ่กจ | โ
|
|
| 169 |
+
| `/completion` | POST | ๆๆฌ่กฅๅ
จ | โ
|
|
| 170 |
+
| `/tokenize` | POST | ๅ่ฏ | โ
|
|
| 171 |
+
|
| 172 |
+
### 5.3 ่ธฉๅ๏ผ--mlock ๅ --no-mmap
|
| 173 |
+
|
| 174 |
+
**้ฎ้ข**๏ผๅจ Modal ็ญๅฎนๅจ็ฏๅขไธญ๏ผ้ป่ฎค็ mmap ๅฏ่ฝๅฏผ่ดๆง่ฝ้ฎ้ขใ
|
| 175 |
+
|
| 176 |
+
**่งฃๅณ**๏ผ
|
| 177 |
+
- `--mlock`๏ผๅฐๆจกๅ้ๅฎๅจๅ
ๅญไธญ๏ผ้ฒๆญข่ขซ swap
|
| 178 |
+
- `--no-mmap`๏ผไฝฟ็จๆ ๅๆไปถ I/O ่ไธๆฏๅ
ๅญๆ ๅฐ๏ผModal ็ tmpfs ไธ้ๅ mmap๏ผ
|
| 179 |
+
|
| 180 |
+
### 5.4 ่ธฉๅ๏ผๅคๆจกๆ API ้ๅถ
|
| 181 |
+
|
| 182 |
+
**้ฎ้ข**๏ผllama-server ็ `/v1/chat/completions` ็ซฏ็น**ไธๆฏๆๅพ็/้ณ้ข่พๅ
ฅ**๏ผๅฎๅชๆฏ็บฏๆๆฌ APIใๅคๆจกๆ้่ฆ llama-mtmd-cliใ
|
| 183 |
+
|
| 184 |
+
**่งฃๅณๆนๆก**๏ผๅจ Modal ้จ็ฝฒไธญ๏ผๆไปฌๅ
่ฃ
ไบไธคๅฑ๏ผ
|
| 185 |
+
1. ็บฏๆๆฌ่ฏทๆฑ โ ็ดๆฅไปฃ็ๅฐ llama-server ็ `/v1/chat/completions`
|
| 186 |
+
2. ๅคๆจกๆ่ฏทๆฑ โ ่ฐ็จ llama-mtmd-cli ๅญ่ฟ็จๅค็
|
| 187 |
+
|
| 188 |
+
---
|
| 189 |
+
|
| 190 |
+
## ๅ
ญใModal ไบ็ซฏ้จ็ฝฒ่ฎพ่ฎก
|
| 191 |
+
|
| 192 |
+
### 6.1 ๆถๆ
|
| 193 |
+
|
| 194 |
+
```
|
| 195 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 196 |
+
โ Modal Cloud โ
|
| 197 |
+
โ โ
|
| 198 |
+
โ โโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโ โ
|
| 199 |
+
โ โ FastAPI ASGI App โ โ Model Volume โ โ
|
| 200 |
+
โ โ (serve function) โ โ (ๆไน
ๅๅญๅจ) โ โ
|
| 201 |
+
โ โ โ โ โโโโโโโโโโโโโโโโโ โ โ
|
| 202 |
+
โ โ /v1/chat/completionsโโโโโโโ GGUF ๆจกๅๆไปถ โ โ โ
|
| 203 |
+
โ โ /v1/multimodal/chat โ โ โ (10 files) โ โ โ
|
| 204 |
+
โ โ /v1/embeddings โ โ โโโโโโโโโโโโโโโโโ โ โ
|
| 205 |
+
โ โ /health โ โ โ โ
|
| 206 |
+
โ โโโโโโโโโโโโฌโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโ โ
|
| 207 |
+
โ โ โ
|
| 208 |
+
โ โโโโโโโโโโโโผโโโโโโโโโโโโ โ
|
| 209 |
+
โ โ llama-server โ โ ็บฏๆๆฌๆจ็ โ
|
| 210 |
+
โ โ llama-mtmd-cli โ โ ๅคๆจกๆๆจ็ (ๆ้ๆ่ตท) โ
|
| 211 |
+
โ โโโโโโโโโโโโโโโโโโโโโโโโ โ
|
| 212 |
+
โ โ
|
| 213 |
+
โ GPU: A100 (40GB) โ
|
| 214 |
+
โ ๅฎนๅจ็ๅฝๅจๆ: ็ฉบ้ฒ 5min โ ่ชๅจๅ
ณ้ญ โ
|
| 215 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 216 |
+
```
|
| 217 |
+
|
| 218 |
+
### 6.2 Modal ้
็ฝฎ่ฆ็น
|
| 219 |
+
|
| 220 |
+
| ๅๆฐ | ๅผ | ๅๅ |
|
| 221 |
+
|------|-----|------|
|
| 222 |
+
| `gpu` | `A100` | MiniCPM-o 4.5 ้่ฆ GPU ๆจ็ |
|
| 223 |
+
| `container_idle_timeout` | `300` ็ง | ๆงๅถๆๆฌ๏ผๆ ไบบไฝฟ็จๆถ่ชๅจๅ
ณๆบ |
|
| 224 |
+
| `allow_concurrent_inputs` | `10` | ๆฏๆๅค็จๆทๅๆถ่ฏทๆฑ |
|
| 225 |
+
| `timeout` | `600` ็ง | ๅๆฌก่ฏทๆฑ่ถ
ๆถ 10 ๅ้๏ผๅซๆจกๅๅ ่ฝฝ๏ผ |
|
| 226 |
+
| ้ๅ | Debian Slim + Python 3.11 + llama.cpp ็ผ่ฏ | ่ชๅฎไน Docker ้ๅ |
|
| 227 |
+
|
| 228 |
+
### 6.3 Model Volume ่ฎพ่ฎก
|
| 229 |
+
|
| 230 |
+
**ไธบไปไน็จ Volume ่ไธๆฏ้ๅไธญๆๅ
ๆจกๅ**๏ผ
|
| 231 |
+
1. ๆจกๅๆไปถ 4.3 GB๏ผๆๅ
่ฟ้ๅไผไฝฟ้ๅ่ฟๅคง
|
| 232 |
+
2. ๆจกๅๆดๆฐ็้ข็้ซไบไปฃ็ ๆดๆฐ๏ผๅๅผ็ฎก็ๆด็ตๆดป
|
| 233 |
+
3. Volume ๅฏไปฅๅจๅคไธช App ้ดๅ
ฑไบซ
|
| 234 |
+
|
| 235 |
+
**ไธไผ ๆจกๅๅฐ Volume**๏ผ
|
| 236 |
+
```bash
|
| 237 |
+
modal volume put minicpm-o-4_5-models ./models/MiniCPM-o-4_5-gguf /
|
| 238 |
+
```
|
| 239 |
+
|
| 240 |
+
### 6.4 ่ธฉๅ๏ผModal ไธ็ผ่ฏ llama.cpp
|
| 241 |
+
|
| 242 |
+
**้ฎ้ข**๏ผModal ๅฎนๅจๆฏ Linux๏ผ่ๆไปฌๆฌๅฐๆฏ Windows ็ผ่ฏ็ .exe๏ผไธ่ฝ็ดๆฅไฝฟ็จใ
|
| 243 |
+
|
| 244 |
+
**่งฃๅณ**๏ผๅจ Modal ็ `Image.run_commands()` ไธญ็ดๆฅ็ผ่ฏ llama.cpp๏ผ
|
| 245 |
+
|
| 246 |
+
```python
|
| 247 |
+
llamacpp_image = (
|
| 248 |
+
Image.debian_slim(python_version="3.11")
|
| 249 |
+
.apt_install("curl", "git", "build-essential", "cmake", "libcurl4-openssl-dev")
|
| 250 |
+
.run_commands(
|
| 251 |
+
"git clone --depth 1 https://github.com/ggerganov/llama.cpp /llama.cpp",
|
| 252 |
+
"cd /llama.cpp && cmake -B build ...",
|
| 253 |
+
"cd /llama.cpp && cmake --build build --config Release -j $(nproc) ...",
|
| 254 |
+
)
|
| 255 |
+
)
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
**ๅ
ณ้ฎ**๏ผๅฟ
้กปๆ `libcurl4-openssl-dev` ๅ
๏ผๅฆๅ `LLAMA_CURL=ON` ไผ็ผ่ฏๅคฑ่ดฅใ
|
| 259 |
+
|
| 260 |
+
### 6.5 ่ธฉๅ๏ผModal ็ๅทๅฏๅจ
|
| 261 |
+
|
| 262 |
+
**้ฎ้ข**๏ผModal ๅจ็ฉบ้ฒๅๅ
ณ้ญๅฎนๅจ๏ผไธๆฌก่ฏทๆฑ้่ฆ้ๆฐๅฏๅจ๏ผๅฏผ่ด้ฆๆฌกๅๅบๅปถ่ฟ 30-60 ็ง๏ผ้ๅๅ ่ฝฝ + ๆจกๅไป Volume ๅ ่ฝฝๅฐ GPU๏ผใ
|
| 263 |
+
|
| 264 |
+
**็ผ่งฃๆนๆก**๏ผ
|
| 265 |
+
1. ่ฎพ็ฝฎ `container_idle_timeout` ไธ่ฆๅคช็ญ๏ผๅปบ่ฎฎ 300-600 ็ง๏ผ
|
| 266 |
+
2. ๅจๅฎขๆท็ซฏๆทปๅ ๅฅๅบทๆฃๆฅ + ้่ฏๆบๅถ
|
| 267 |
+
3. ๅฏนไบไธๅคช้ข็น็่ฐ็จๅบๆฏ๏ผๆฅๅๅทๅฏๅจๅปถ่ฟ
|
| 268 |
+
|
| 269 |
+
### 6.6 ๅฐๆช่งฃๅณ็้ฎ้ข
|
| 270 |
+
|
| 271 |
+
1. **้ณ้ข่พๅ
ฅๅค็**๏ผllama-mtmd-cli ็้ณ้ข่พๅ
ฅๆฅๅฃ้่ฆ่ฟไธๆญฅๆต่ฏ
|
| 272 |
+
2. **TTS ่ฏญ้ณ่พๅบ**๏ผtoken2wav ็ปไปถๅจ llama.cpp ไธญ็้ๆๆนๅผ้่ฆ้ช่ฏ
|
| 273 |
+
3. **Modal ๅฎ่ฃ
ๅคฑ่ดฅ**๏ผๅฝๅ็ฝ็ป SSL ้ฎ้ขๅฏผ่ด `pip install modal` ๅคฑ่ดฅ๏ผ้ๅจ็ฝ็ปๆขๅคๅๅฎ่ฃ
ๅนถ้จ็ฝฒ
|
| 274 |
+
|
| 275 |
+
---
|
| 276 |
+
|
| 277 |
+
## ไธใๅฎขๆท็ซฏๅฐ่ฃ
่ฎพ่ฎก
|
| 278 |
+
|
| 279 |
+
### 7.1 Python ๅฎขๆท็ซฏ๏ผ`modal_deploy/client.py`๏ผ
|
| 280 |
+
|
| 281 |
+
ๅฐ่ฃ
ไบไปฅไธ่ฝๅ๏ผ
|
| 282 |
+
- `chat()` / `ask()`๏ผๆๆฌๅฏน่ฏ
|
| 283 |
+
- `chat_with_image()` / `describe_image()`๏ผๅพ็็่งฃ
|
| 284 |
+
- `embed()`๏ผๆๆฌๅตๅ
ฅ
|
| 285 |
+
- ้ข่ฎพ System Prompt๏ผๅญๆ่ฅๅ
ป้กพ้ฎใๅฎถๅบญ่ฅๅ
ปๅธใ้ฃ็ฉๅๆๅจใ่ฅๅ
ปๆป็ป๏ผ
|
| 286 |
+
|
| 287 |
+
### 7.2 ไธ PregoPal ้ๆ
|
| 288 |
+
|
| 289 |
+
```python
|
| 290 |
+
from modal_deploy.client import MiniCPMClient, SYSTEM_PROMPTS
|
| 291 |
+
|
| 292 |
+
client = MiniCPMClient(base_url="https://your-app.modal.run")
|
| 293 |
+
|
| 294 |
+
# ๅญๆ้ฅฎ้ฃ่ฏข้ฎ
|
| 295 |
+
reply = client.ask(
|
| 296 |
+
"ๆไปๅคฉๅฏไปฅๅไปไน๏ผ",
|
| 297 |
+
system_prompt=SYSTEM_PROMPTS["diet_advisor"]
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
# ๅพ็ไธญ็้ฃ็ฉๅๆ
|
| 301 |
+
import base64
|
| 302 |
+
with open("food.jpg", "rb") as f:
|
| 303 |
+
img_b64 = base64.b64encode(f.read()).decode()
|
| 304 |
+
result = client.chat_with_image("่ฟๆฏไปไน้ฃ็ฉ๏ผ้ๅๅญๅฆๅ๏ผ", img_b64)
|
| 305 |
+
```
|
| 306 |
+
|
| 307 |
+
---
|
| 308 |
+
|
| 309 |
+
## ๅ
ซใๆๆฌไผฐ็ฎ
|
| 310 |
+
|
| 311 |
+
| ้กน็ฎ | ไผฐ็ฎ |
|
| 312 |
+
|------|------|
|
| 313 |
+
| A100 GPU ๅไปท | ~$1.10/ๅฐๆถ |
|
| 314 |
+
| ๆจกๅๅ ่ฝฝๆถ้ด | ~20-30 ็ง |
|
| 315 |
+
| ๅๆฌกๆจ็ๅปถ่ฟ | ~2-5 ็ง๏ผๆๆฌ๏ผ/ ~5-10 ็ง๏ผๅคๆจกๆ๏ผ |
|
| 316 |
+
| ๆ้ขไผฐ๏ผๆฏๆฅ 100 ๆฌกๆจ็๏ผ | ~$20-50๏ผ่่ๅทๅฏๅจๅ็ฉบ้ฒ๏ผ |
|
| 317 |
+
|
| 318 |
+
---
|
| 319 |
+
|
| 320 |
+
## ไนใๅ
ณ้ฎ่ธฉๅๆป็ป
|
| 321 |
+
|
| 322 |
+
| # | ๅ | ๅๅ | ่งฃๅณๆนๆก |
|
| 323 |
+
|---|-----|------|---------|
|
| 324 |
+
| 1 | CMake ๆพไธๅฐ็ผ่ฏๅจ | Windows ๆฒกๆๅ
จๅฑ MSVC ่ทฏๅพ | ไฝฟ็จ VS Developer Command Prompt |
|
| 325 |
+
| 2 | llama-mtmd-cli ไธ็ผ่ฏ | ้ป่ฎค target ไธๅ
ๅซ | ๆพๅผๆๅฎ `--target llama-mtmd-cli` |
|
| 326 |
+
| 3 | libcurl ็ผบๅคฑ | `LLAMA_CURL=ON` ้่ฆๅผๅๅ
| `apt install libcurl4-openssl-dev` |
|
| 327 |
+
| 4 | server ไธๆฏๆๅคๆจกๆ API | llama-server ๅชๆฏๆๆฌ | ๆๆฌ็จ server๏ผๅคๆจกๆ็จ mtmd-cli |
|
| 328 |
+
| 5 | GGUF ๆไปถ็ฎๅฝ็ปๆ | mtmd-cli ไพ่ต็นๅฎ่ทฏๅพ | ไฟๆๅๅง็ฎๅฝ็ปๆ |
|
| 329 |
+
| 6 | Modal ้ๅ vs ๆจกๅๅ็ฆป | ้ๅไธ่ฝๅคชๅคง | ็จ Volume ๅญๅจๆจกๅ |
|
| 330 |
+
| 7 | ๅทๅฏๅจๅปถ่ฟ | Modal ้ฒ็ฝฎ่ชๅจๅ
ณ้ญๅฎนๅจ | ๅ็่ฎพ็ฝฎ idle_timeout๏ผๅฎขๆท็ซฏๅ ้่ฏ |
|
| 331 |
+
| 8 | pip install modal ๅคฑ่ดฅ | SSL ่ฏไนฆ้ฎ้ข | ๆข็ฝ็ป็ฏๅขๆ้
็ฝฎไปฃ็ |
|
| 332 |
+
| 9 | --mmproj ๅๆฐ็่งฃ | ๅช้ไผ ไธไธชๆๅฝฑๅฑ | ่ง่งไผ vision.gguf๏ผ้ณ้ขไผ audio.gguf |
|
| 333 |
+
| 10 | Windows ่ทฏๅพไธญ็็ฉบๆ ผ | MSVC ่ทฏๅพๅซ็ฉบๆ ผ | ไฝฟ็จๅๅผๅทๅ
่ฃน่ทฏๅพ |
|
| 334 |
+
|
| 335 |
+
---
|
| 336 |
+
|
| 337 |
+
*ๆๆกฃ็ๆๆถ้ด๏ผ2026-06-09 | v1.0 - llama.cpp + MiniCPM-o ้จ็ฝฒๅ
จ่ฎฐๅฝ*
|
docs/ๅผๅๆฅๅฟ.md
CHANGED
|
@@ -61,3 +61,65 @@ PregoPal/
|
|
| 61 |
- ๆๆ 5 ไธช Tab ๆญฃๅธธๅ ่ฝฝ
|
| 62 |
- ๅฃฐ็บน่ฏๅซใ่ๅๆจ่ใ้ฅฎ้ฃ่ฎฐๅฝใ่ฅๅ
ปๆฅๅๅ่ฝๅฏ็จ
|
| 63 |
- core/ ๅฑ้ข็ๆฅๅฃ็ญๅพ
MiniCPM-o ้จ็ฝฒ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
- ๆๆ 5 ไธช Tab ๆญฃๅธธๅ ่ฝฝ
|
| 62 |
- ๅฃฐ็บน่ฏๅซใ่ๅๆจ่ใ้ฅฎ้ฃ่ฎฐๅฝใ่ฅๅ
ปๆฅๅๅ่ฝๅฏ็จ
|
| 63 |
- core/ ๅฑ้ข็ๆฅๅฃ็ญๅพ
MiniCPM-o ้จ็ฝฒ
|
| 64 |
+
|
| 65 |
+
## 2026-06-09 ๅๆจ๏ผllama.cpp ็ผ่ฏ + MiniCPM-o GGUF ๆจกๅไธ่ฝฝ
|
| 66 |
+
|
| 67 |
+
### ็ผ่ฏ็ฏๅข
|
| 68 |
+
- ๆๅ llama.cpp master ๅๆฏ๏ผ2026-06-08๏ผ
|
| 69 |
+
- CMake + MSVC (Visual Studio 2022) ็ผ่ฏ
|
| 70 |
+
- ็ฎๆ ๏ผ`llama-server` + `llama-mtmd-cli`
|
| 71 |
+
- ็ผ่ฏ้้กน๏ผ`DLLAMA_CURL=ON`, `DLLAMA_BUILD_SERVER=ON`, `DGGML_CUDA=OFF`
|
| 72 |
+
|
| 73 |
+
### ๆจกๅไธ่ฝฝ
|
| 74 |
+
- ๆฅๆบ๏ผHuggingFace `bartowski/MiniCPM-o-4_5-GGUF`
|
| 75 |
+
- ไธปๆจกๅ๏ผ`MiniCPM-o-4_5-Q4_K_M.gguf` (688 MB)
|
| 76 |
+
- ่ง่งๆๅฝฑๅฑ๏ผ`vision/MiniCPM-o-4_5-vision-F16.gguf` (1044 MB)
|
| 77 |
+
- ้ณ้ขๆๅฝฑๅฑ๏ผ`audio/MiniCPM-o-4_5-audio-F16.gguf` (630 MB)
|
| 78 |
+
- TTS ๆจกๅ๏ผ`tts/MiniCPM-o-4_5-tts-F16.gguf` (1104 MB) + projector (14 MB)
|
| 79 |
+
- token2wav ็ปไปถ๏ผ5 ไธชๆไปถ๏ผencoder, flow_extra, flow_matching, hifigan2, prompt_cache๏ผ
|
| 80 |
+
- ๅ่ฎก 10 ไธชๆไปถ๏ผ4.3 GB
|
| 81 |
+
|
| 82 |
+
### ่ธฉๅ่ฎฐๅฝ
|
| 83 |
+
1. CMake ๅจ Windows ๆฎ้ cmd ไธญๆพไธๅฐ MSVC ็ผ่ฏๅจ โ ้่ฆ VS Developer Command Prompt
|
| 84 |
+
2. llama-mtmd-cli ้ๆพๅผๆๅฎ `--target` ๅฆๅไธ็ผ่ฏ
|
| 85 |
+
3. `libcurl4-openssl-dev` ็ผบๅคฑๅฏผ่ด LLAMA_CURL=ON ็ผ่ฏๅคฑ่ดฅ๏ผLinux ็ฏๅขไธ๏ผ
|
| 86 |
+
4. llama-server ็ `/v1/chat/completions` ไธๆฏๆๅคๆจกๆ โ ๅคๆจกๆ้็จ llama-mtmd-cli
|
| 87 |
+
5. GGUF ๆไปถ้ไฟๆๅๅง็ฎๅฝ็ปๆ๏ผvision/ใaudio/ใtts/ใtoken2wav-gguf/๏ผ
|
| 88 |
+
6. pip install modal ๅ SSL ่ฏไนฆ้ฎ้ขๅคฑ่ดฅ๏ผๆธ
ๅ้ๅๅ PyPI ๅฎๆน้ฝ่ฟๅ SSLZeroReturnError๏ผ
|
| 89 |
+
|
| 90 |
+
## 2026-06-09 ๅๆจ๏ผModal ไบ็ซฏ้จ็ฝฒ่ฎพ่ฎก
|
| 91 |
+
|
| 92 |
+
### ้จ็ฝฒๆถๆ
|
| 93 |
+
- FastAPI ASGI App ไฝไธบๅ
ฅๅฃ๏ผๅ
่ฃ
llama-server + llama-mtmd-cli
|
| 94 |
+
- ๆจกๅๅญๅจๅจ Modal Volume๏ผๆไน
ๅ๏ผ๏ผไปฃ็ ้่ฟ App ้จ็ฝฒ
|
| 95 |
+
- GPU: A100
|
| 96 |
+
- ็ฉบ้ฒ 5 ๅ้ๅ่ชๅจๅ
ณ้ญไปฅๆงๅถๆๆฌ
|
| 97 |
+
|
| 98 |
+
### ๅทฒๅๅปบๆไปถ
|
| 99 |
+
- `modal_deploy/deploy.py` โ Modal ้จ็ฝฒไธป่ๆฌ๏ผๅซ OpenAI ๅ
ผๅฎน API + ๅคๆจกๆ็ซฏ็น
|
| 100 |
+
- `modal_deploy/client.py` โ Python ๅฎขๆท็ซฏๅฐ่ฃ
๏ผๅซๅญๆๅบๆฏ้ข่ฎพ System Prompt
|
| 101 |
+
- `modal_deploy/README.md` โ ้จ็ฝฒ่ฏดๆๆๆกฃ
|
| 102 |
+
- `docs/llama_cpp_minicpm_ๆๆฏๆฅๅ.md` โ ๅฎๆดๆๆฏๆฅๅ๏ผ็ผ่ฏ/้จ็ฝฒ/ๆๅ๏ผ
|
| 103 |
+
- `requirements.txt` โ ่ฟฝๅ modal>=0.60.0
|
| 104 |
+
|
| 105 |
+
### API ๆฅๅฃ่ฎพ่ฎก
|
| 106 |
+
| ็ซฏ็น | ๅ่ฝ |
|
| 107 |
+
|------|------|
|
| 108 |
+
| `/v1/chat/completions` | ็บฏๆๆฌๅฏน่ฏ๏ผOpenAI ๅ
ผๅฎน๏ผ |
|
| 109 |
+
| `/v1/multimodal/chat` | ๅคๆจกๆๅฏน่ฏ๏ผๅพ็็่งฃ๏ผ |
|
| 110 |
+
| `/v1/embeddings` | ๆๆฌๅตๅ
ฅ |
|
| 111 |
+
| `/health` | ๅฅๅบทๆฃๆฅ |
|
| 112 |
+
| `/v1/models` | ๆจกๅๅ่กจ |
|
| 113 |
+
|
| 114 |
+
### ๅพ
ๅฎๆ
|
| 115 |
+
- ็ฝ็ปๆขๅคๅ `pip install modal` ๅนถ้จ็ฝฒ
|
| 116 |
+
- ้ณ้ข่พๅ
ฅ/่พๅบๆฅๅฃๆต่ฏ
|
| 117 |
+
- Token2wav TTS ็ปไปถๅจ llama.cpp ไธญ็้ๆ้ช่ฏ
|
| 118 |
+
|
| 119 |
+
## 2026-06-09 ๆจ้ด๏ผๅๆญฅๅนถ่ก Cline ไปฃ็
|
| 120 |
+
|
| 121 |
+
### ่ฟ็จๆดๆฐ๏ผ0155d1a โ ed20b95๏ผ
|
| 122 |
+
- `README.md` ๅคงๅน
ๆฉๅ
๏ผ+565 ่ก๏ผ
|
| 123 |
+
- `app.py` ๅพฎ่ฐ๏ผ+7/-1๏ผ
|
| 124 |
+
- `ui/app_builder.py` ้ๆ๏ผ+633/-156๏ผ
|
| 125 |
+
- `utils.py` ๆฐๅขๅทฅๅ
ทๅฝๆฐ๏ผ+176 ่ก๏ผ
|
modal_deploy/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Modal Deployment: MiniCPM-o-4_5 via llama.cpp
|
| 2 |
+
|
| 3 |
+
## ๆฆ่ฟฐ
|
| 4 |
+
|
| 5 |
+
ๅจ Modal.com ็ A100 GPU ไธ้จ็ฝฒ MiniCPM-o-4_5 ๆจกๅ๏ผ้่ฟ llama.cpp server ๆไพ OpenAI ๅ
ผๅฎน็ API ๆฅๅฃใ
|
| 6 |
+
|
| 7 |
+
## ๆถๆ
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
็จๆท่ฏทๆฑ โ FastAPI (ASGI) โ llama-server (OpenAI ๅ
ผๅฎน) โ MiniCPM-o-4_5 GGUF
|
| 11 |
+
โ
|
| 12 |
+
็บฏๆๆฌ: /v1/chat/completions
|
| 13 |
+
ๅคๆจกๆ: /v1/multimodal/chat (้่ฟ llama-mtmd-cli)
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
## ๅ็ฝฎๆกไปถ
|
| 17 |
+
|
| 18 |
+
1. Modal ่ดฆๅท: https://modal.com/
|
| 19 |
+
2. ๅฎ่ฃ
Modal CLI: `pip install modal`
|
| 20 |
+
3. ็ปๅฝ: `modal token new`
|
| 21 |
+
|
| 22 |
+
## ๆจกๅๆไปถ
|
| 23 |
+
|
| 24 |
+
้่ฆ 10 ไธช GGUF ๆไปถ๏ผ็บฆ 4.3 GB๏ผ๏ผไป HuggingFace ไธ่ฝฝ๏ผ
|
| 25 |
+
|
| 26 |
+
```bash
|
| 27 |
+
# ไธ่ฝฝๅฐๆฌๅฐ
|
| 28 |
+
python _download_models.py # ๅจ PregoPal ๆ น็ฎๅฝ
|
| 29 |
+
|
| 30 |
+
# ไธไผ ๅฐ Modal Volume
|
| 31 |
+
modal volume put minicpm-o-4_5-models ../models/MiniCPM-o-4_5-gguf /
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
ๆจกๅๆไปถๅ่กจ๏ผ
|
| 35 |
+
| ๆไปถ | ๅคงๅฐ | ็จ้ |
|
| 36 |
+
|------|------|------|
|
| 37 |
+
| `MiniCPM-o-4_5-Q4_K_M.gguf` | 688 MB | ไธปๆจกๅ๏ผๆๆฌๆจ็๏ผ |
|
| 38 |
+
| `vision/MiniCPM-o-4_5-vision-F16.gguf` | 1044 MB | ่ง่งๆๅฝฑๅฑ |
|
| 39 |
+
| `audio/MiniCPM-o-4_5-audio-F16.gguf` | 630 MB | ้ณ้ขๆๅฝฑๅฑ |
|
| 40 |
+
| `tts/MiniCPM-o-4_5-tts-F16.gguf` | 1104 MB | TTS ่ฏญ้ณๅๆ |
|
| 41 |
+
| `tts/MiniCPM-o-4_5-projector-F16.gguf` | 14 MB | ๆๅฝฑๅฑ |
|
| 42 |
+
| `token2wav-gguf/*.gguf` (5 ๆไปถ) | 875 MB | ่ฏญ้ณๅๆ tokenโwav |
|
| 43 |
+
|
| 44 |
+
## ้จ็ฝฒ
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
cd modal_deploy
|
| 48 |
+
|
| 49 |
+
# ้จ็ฝฒไธบ Modal App๏ผๆ็ปญ่ฟ่ก็ๆๅก๏ผ
|
| 50 |
+
modal deploy deploy.py
|
| 51 |
+
|
| 52 |
+
# ๆๅผๅๆจกๅผ๏ผ็ญ้่ฝฝ๏ผๆฅๅฟๅจๅๅฐ๏ผ
|
| 53 |
+
modal serve deploy.py
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
้จ็ฝฒๅ Modal ไผ่พๅบไธไธช URL๏ผๅฆ `https://your-org--prego-pal-minicpm-serve.modal.run`
|
| 57 |
+
|
| 58 |
+
## API ๆฅๅฃ
|
| 59 |
+
|
| 60 |
+
### 1. ๅฅๅบทๆฃๆฅ
|
| 61 |
+
```bash
|
| 62 |
+
GET /health
|
| 63 |
+
```
|
| 64 |
+
่ฟๅ๏ผ`{"status": "ok"}`
|
| 65 |
+
|
| 66 |
+
### 2. ๆจกๅๅ่กจ
|
| 67 |
+
```bash
|
| 68 |
+
GET /v1/models
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
### 3. ๆๆฌๅฏน่ฏ (OpenAI ๅ
ผๅฎน)
|
| 72 |
+
```bash
|
| 73 |
+
POST /v1/chat/completions
|
| 74 |
+
Content-Type: application/json
|
| 75 |
+
|
| 76 |
+
{
|
| 77 |
+
"model": "MiniCPM-o-4_5",
|
| 78 |
+
"messages": [
|
| 79 |
+
{"role": "user", "content": "ไปๅคฉๅญๅฆๅฏไปฅๅไปไน๏ผ"}
|
| 80 |
+
],
|
| 81 |
+
"max_tokens": 1024,
|
| 82 |
+
"temperature": 0.7,
|
| 83 |
+
"stream": false
|
| 84 |
+
}
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### 4. ๅคๆจกๆๅฏน่ฏ๏ผๅพ็็่งฃ๏ผ
|
| 88 |
+
```bash
|
| 89 |
+
POST /v1/multimodal/chat
|
| 90 |
+
Content-Type: application/json
|
| 91 |
+
|
| 92 |
+
{
|
| 93 |
+
"messages": [
|
| 94 |
+
{"role": "user", "content": [
|
| 95 |
+
{"type": "text", "text": "่ฟๅผ ๅพ็้ๆไปไน้ฃ็ฉ๏ผ"},
|
| 96 |
+
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}}
|
| 97 |
+
]}
|
| 98 |
+
],
|
| 99 |
+
"max_tokens": 1024,
|
| 100 |
+
"temperature": 0.7
|
| 101 |
+
}
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
### 5. ๅตๅ
ฅๅ้
|
| 105 |
+
```bash
|
| 106 |
+
POST /v1/embeddings
|
| 107 |
+
Content-Type: application/json
|
| 108 |
+
|
| 109 |
+
{
|
| 110 |
+
"model": "MiniCPM-o-4_5",
|
| 111 |
+
"input": ["ๅญๆ่ฅๅ
ปๅปบ่ฎฎ"]
|
| 112 |
+
}
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
## ๆต่ฏ
|
| 116 |
+
|
| 117 |
+
```bash
|
| 118 |
+
# ๅฟซ้ๆต่ฏๆจกๅ่ฝๅฆๅ ่ฝฝๅๆจ็
|
| 119 |
+
modal run modal_deploy/deploy.py::test_inference
|
| 120 |
+
|
| 121 |
+
# ไธไผ ๆจกๅ
|
| 122 |
+
modal run modal_deploy/deploy.py::upload_models
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
## ้
็ฝฎ
|
| 126 |
+
|
| 127 |
+
ๅจ `deploy.py` ไธญๅฏ่ฐๆด็ๅๆฐ๏ผ
|
| 128 |
+
- `container_idle_timeout=300` โ ็ฉบ้ฒ 5 ๅ้ๅ่ชๅจๅ
ณ้ญ
|
| 129 |
+
- `gpu="A100"` โ GPU ็ฑปๅ๏ผไนๅฏ็จ H100ใA10G๏ผ
|
| 130 |
+
- `-c 8192` โ ไธไธๆ็ชๅฃๅคงๅฐ
|
| 131 |
+
- `-ngl 99` โ GPU ๅฑๆฐ๏ผ99=ๅ
จ้จๅฐ GPU๏ผ
|
| 132 |
+
|
| 133 |
+
## ไธ PregoPal ้ๆ
|
| 134 |
+
|
| 135 |
+
ๅ
ถไปๆๅก้่ฟ HTTP ่ฐ็จๆญค API๏ผ
|
| 136 |
+
|
| 137 |
+
```python
|
| 138 |
+
import requests
|
| 139 |
+
|
| 140 |
+
API_BASE = "https://your-app.modal.run"
|
| 141 |
+
|
| 142 |
+
def ask_minicpm(prompt: str) -> str:
|
| 143 |
+
r = requests.post(f"{API_BASE}/v1/chat/completions", json={
|
| 144 |
+
"messages": [{"role": "user", "content": prompt}],
|
| 145 |
+
"max_tokens": 512,
|
| 146 |
+
"temperature": 0.7,
|
| 147 |
+
})
|
| 148 |
+
return r.json()["choices"][0]["message"]["content"]
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
## ๆณจๆไบ้กน
|
| 152 |
+
|
| 153 |
+
1. **ๅทๅฏๅจ**๏ผๆ ่ฏทๆฑ >5 ๅ้ๅๅฎนๅจๅ
ณ้ญ๏ผไธๆฌก่ฏทๆฑ้็ญๅพ
~30-60 ็ง๏ผๆจกๅๅ ่ฝฝ๏ผ
|
| 154 |
+
2. **ๆๆฌ**๏ผA100 ็บฆ $1.10/ๅฐๆถ๏ผๆๅฎ้
ไฝฟ็จ่ฎก่ดน
|
| 155 |
+
3. **ๆจกๅไธไผ **๏ผVolume ๆฏๆไน
ๅ็๏ผๆจกๅๅช้ไธไผ ไธๆฌก
|
| 156 |
+
4. **ๅนถๅ**๏ผ`allow_concurrent_inputs=10` ๆฏๆ 10 ไธชๅนถๅ่ฏทๆฑ
|
modal_deploy/client.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
PregoPal - MiniCPM-o API ๅฎขๆท็ซฏ
|
| 3 |
+
|
| 4 |
+
็จๆณ๏ผ
|
| 5 |
+
from modal_deploy.client import MiniCPMClient
|
| 6 |
+
|
| 7 |
+
client = MiniCPMClient(base_url="https://your-app.modal.run")
|
| 8 |
+
response = client.chat("ไปๅคฉๅญๅฆๅฏไปฅๅไปไน๏ผ")
|
| 9 |
+
"""
|
| 10 |
+
import json
|
| 11 |
+
import logging
|
| 12 |
+
from typing import Optional
|
| 13 |
+
from dataclasses import dataclass, field
|
| 14 |
+
|
| 15 |
+
import requests
|
| 16 |
+
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@dataclass
|
| 21 |
+
class ChatMessage:
|
| 22 |
+
role: str
|
| 23 |
+
content: str
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@dataclass
|
| 27 |
+
class MultimodalContent:
|
| 28 |
+
type: str # "text" or "image_url"
|
| 29 |
+
text: Optional[str] = None
|
| 30 |
+
image_url: Optional[dict] = None
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class MiniCPMClient:
|
| 34 |
+
"""ๅฐ่ฃ
MiniCPM-o API ็ๅฎขๆท็ซฏ"""
|
| 35 |
+
|
| 36 |
+
def __init__(
|
| 37 |
+
self,
|
| 38 |
+
base_url: str = "http://localhost:8080",
|
| 39 |
+
timeout: int = 120,
|
| 40 |
+
default_max_tokens: int = 1024,
|
| 41 |
+
default_temperature: float = 0.7,
|
| 42 |
+
):
|
| 43 |
+
self.base_url = base_url.rstrip("/")
|
| 44 |
+
self.timeout = timeout
|
| 45 |
+
self.default_max_tokens = default_max_tokens
|
| 46 |
+
self.default_temperature = default_temperature
|
| 47 |
+
|
| 48 |
+
def _post(self, path: str, body: dict) -> dict:
|
| 49 |
+
url = f"{self.base_url}{path}"
|
| 50 |
+
try:
|
| 51 |
+
r = requests.post(url, json=body, timeout=self.timeout)
|
| 52 |
+
r.raise_for_status()
|
| 53 |
+
return r.json()
|
| 54 |
+
except requests.exceptions.RequestException as e:
|
| 55 |
+
logger.error(f"API request failed: {e}")
|
| 56 |
+
raise
|
| 57 |
+
|
| 58 |
+
def health(self) -> dict:
|
| 59 |
+
"""ๆฃๆฅๆๅกๅฅๅบท็ถๆ"""
|
| 60 |
+
r = requests.get(f"{self.base_url}/health", timeout=10)
|
| 61 |
+
return r.json()
|
| 62 |
+
|
| 63 |
+
# โโ ๆๆฌๅฏน่ฏ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 64 |
+
|
| 65 |
+
def chat(
|
| 66 |
+
self,
|
| 67 |
+
messages: list[dict],
|
| 68 |
+
system_prompt: Optional[str] = None,
|
| 69 |
+
max_tokens: Optional[int] = None,
|
| 70 |
+
temperature: Optional[float] = None,
|
| 71 |
+
stream: bool = False,
|
| 72 |
+
) -> dict | str:
|
| 73 |
+
"""
|
| 74 |
+
OpenAI ๅ
ผๅฎน็่ๅคฉๆฅๅฃ
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
messages: ๆถๆฏๅ่กจ [{"role": "user", "content": "..."}]
|
| 78 |
+
system_prompt: ็ณป็ปๆ็คบ่ฏ
|
| 79 |
+
max_tokens: ๆๅคง็ๆ้ฟๅบฆ
|
| 80 |
+
temperature: ๆธฉๅบฆๅๆฐ
|
| 81 |
+
stream: ๆฏๅฆๆตๅผ
|
| 82 |
+
|
| 83 |
+
Returns:
|
| 84 |
+
dict ๆๆตๅผๅญ็ฌฆไธฒ
|
| 85 |
+
"""
|
| 86 |
+
msgs = []
|
| 87 |
+
if system_prompt:
|
| 88 |
+
msgs.append({"role": "system", "content": system_prompt})
|
| 89 |
+
msgs.extend(messages)
|
| 90 |
+
|
| 91 |
+
body = {
|
| 92 |
+
"model": "MiniCPM-o-4_5",
|
| 93 |
+
"messages": msgs,
|
| 94 |
+
"max_tokens": max_tokens or self.default_max_tokens,
|
| 95 |
+
"temperature": temperature or self.default_temperature,
|
| 96 |
+
"stream": stream,
|
| 97 |
+
}
|
| 98 |
+
return self._post("/v1/chat/completions", body)
|
| 99 |
+
|
| 100 |
+
def ask(self, prompt: str, system_prompt: Optional[str] = None) -> str:
|
| 101 |
+
"""็ฎๅ่ฐ็จ๏ผๅ้้ฎ้ข๏ผ่ฟๅๆๆฌๅ็ญ"""
|
| 102 |
+
result = self.chat(
|
| 103 |
+
messages=[{"role": "user", "content": prompt}],
|
| 104 |
+
system_prompt=system_prompt,
|
| 105 |
+
)
|
| 106 |
+
try:
|
| 107 |
+
return result["choices"][0]["message"]["content"]
|
| 108 |
+
except (KeyError, IndexError, TypeError):
|
| 109 |
+
logger.error(f"Unexpected response format: {result}")
|
| 110 |
+
return str(result)
|
| 111 |
+
|
| 112 |
+
# โโ ๅคๆจกๆ๏ผๅพ็็่งฃ๏ผ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 113 |
+
|
| 114 |
+
def chat_with_image(
|
| 115 |
+
self,
|
| 116 |
+
prompt: str,
|
| 117 |
+
image_base64: str,
|
| 118 |
+
image_format: str = "jpeg",
|
| 119 |
+
max_tokens: Optional[int] = None,
|
| 120 |
+
temperature: Optional[float] = None,
|
| 121 |
+
) -> dict:
|
| 122 |
+
"""
|
| 123 |
+
ๅ้ๅพ็ + ๆๅญ่ฟ่กๅคๆจกๆๅฏน่ฏ
|
| 124 |
+
|
| 125 |
+
Args:
|
| 126 |
+
prompt: ๆๅญๆ็คบ
|
| 127 |
+
image_base64: base64 ็ผ็ ็ๅพ็
|
| 128 |
+
image_format: ๅพ็ๆ ผๅผ (jpeg, png, webp)
|
| 129 |
+
"""
|
| 130 |
+
body = {
|
| 131 |
+
"messages": [
|
| 132 |
+
{
|
| 133 |
+
"role": "user",
|
| 134 |
+
"content": [
|
| 135 |
+
{"type": "text", "text": prompt},
|
| 136 |
+
{
|
| 137 |
+
"type": "image_url",
|
| 138 |
+
"image_url": {
|
| 139 |
+
"url": f"data:image/{image_format};base64,{image_base64}"
|
| 140 |
+
},
|
| 141 |
+
},
|
| 142 |
+
],
|
| 143 |
+
}
|
| 144 |
+
],
|
| 145 |
+
"max_tokens": max_tokens or self.default_max_tokens,
|
| 146 |
+
"temperature": temperature or self.default_temperature,
|
| 147 |
+
}
|
| 148 |
+
return self._post("/v1/multimodal/chat", body)
|
| 149 |
+
|
| 150 |
+
def describe_image(self, image_base64: str, image_format: str = "jpeg") -> str:
|
| 151 |
+
"""็ฎๅ่ฐ็จ๏ผๆ่ฟฐๅพ็ๅ
ๅฎน"""
|
| 152 |
+
result = self.chat_with_image(
|
| 153 |
+
prompt="่ฏท่ฏฆ็ปๆ่ฟฐ่ฟๅผ ๅพ็ไธญ็ๅ
ๅฎนใ",
|
| 154 |
+
image_base64=image_base64,
|
| 155 |
+
image_format=image_format,
|
| 156 |
+
)
|
| 157 |
+
return result.get("response", str(result))
|
| 158 |
+
|
| 159 |
+
# โโ ๅตๅ
ฅๅ้ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 160 |
+
|
| 161 |
+
def embed(self, texts: list[str]) -> list[list[float]]:
|
| 162 |
+
"""่ทๅๆๆฌ็ๅตๅ
ฅๅ้"""
|
| 163 |
+
body = {
|
| 164 |
+
"model": "MiniCPM-o-4_5",
|
| 165 |
+
"input": texts,
|
| 166 |
+
}
|
| 167 |
+
result = self._post("/v1/embeddings", body)
|
| 168 |
+
return [item["embedding"] for item in result.get("data", [])]
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
# โโ ้ข่ฎพๆ็คบ่ฏ (้ๅฏนๅญๆ้ชๆคๅบๆฏ) โโโโโโโโโโโโโโโโโโโโโโโโ
|
| 172 |
+
|
| 173 |
+
SYSTEM_PROMPTS = {
|
| 174 |
+
"diet_advisor": """ไฝ ๆฏไธไฝไธไธ็ๅญๆ่ฅๅ
ป้กพ้ฎใ่ฏทๆ นๆฎไปฅไธๅๅๆไพๅปบ่ฎฎ๏ผ
|
| 175 |
+
1. ไผๅ
่่้ฃๆ็ๆๅพๆงๅๅฎถๅบญๅถไฝ่ฝๅ
|
| 176 |
+
2. ๆณจๆๅญๆๅ้ถๆฎต็่ฅๅ
ป้ๆฑๅทฎๅผ
|
| 177 |
+
3. ้ฟๅ
็ๅทใ่พ่พฃใ้ซๆฑ้ฑผ็ฑป็ญๅญๆ็ฆๅฟ้ฃ็ฉ
|
| 178 |
+
4. ๆจ่ๅฏๅซๅถ้
ธใ้ใ้ใDHA ็้ฃ็ฉ
|
| 179 |
+
5. ๅ็ญ็ฎๆดๅฎ็จ๏ผ็ปๅบๅ
ทไฝๅฏๆไฝ็่ๅๅปบ่ฎฎ""",
|
| 180 |
+
|
| 181 |
+
"meal_planner": """ไฝ ๆฏไธไฝๅฎถๅบญ่ฅๅ
ปๅธ๏ผไธ้จไธบๅญๅฆ่ฎพ่ฎกๆฅๅธธ่ๅใ่ฆๆฑ๏ผ
|
| 182 |
+
1. ๆฏๅคฉ่ฎพ่ฎกๆฉไธญๆไธ้ค + 2ๆฌกๅ ้ค
|
| 183 |
+
2. ็กฎไฟ่ฅๅ
ปๅ่กก๏ผ่ฆ็่็ฝ่ดจใ็ขณๆฐดใ่่ชใ็ปด็็ด ใ็ฟ็ฉ่ดจ
|
| 184 |
+
3. ่่ไธญๅฝๅฎถๅบญ็็น้ฅชไน ๆฏๅๅธธ่ง้ฃๆ
|
| 185 |
+
4. ๆ ๆณจๆฏ้ค็ๅ
ณ้ฎ่ฅๅ
ป็ด
|
| 186 |
+
5. ็ปๅบ็ฎ่ฆ็ๅถไฝๆญฅ้ชค""",
|
| 187 |
+
|
| 188 |
+
"food_analyzer": """ๅๆ็จๆทๆไพ็้ฃ็ฉ/่ๅๆ่ฟฐ๏ผๆๅไปฅไธไฟกๆฏ๏ผ
|
| 189 |
+
1. ่ๅ
|
| 190 |
+
2. ไธป่ฆ้ฃๆ
|
| 191 |
+
3. ่ฅๅ
ปๆๅ่ฏไผฐ๏ผ็ญ้ใ่็ฝ่ดจใ่่ชใ็ขณๆฐดใๅ
ณ้ฎๅพฎ้ๅ
็ด ๏ผ
|
| 192 |
+
4. ๅญๆ้ๅฎๅบฆ๏ผๆจ่/ๅฏ้ฃ็จ/ๆ
้ฃ/ๅฟ้ฃ๏ผ
|
| 193 |
+
5. ๅปบ่ฎฎ๏ผๅฆๆ๏ผ
|
| 194 |
+
่ฏท็จ JSON ๆ ผๅผๅๅคใ""",
|
| 195 |
+
|
| 196 |
+
"nutrition_summary": """ๆ นๆฎ็จๆทๆไพ็้ฅฎ้ฃ่ฎฐๅฝ๏ผๆป็ป่ฅๅ
ปๆๅ
ฅๆ
ๅต๏ผ
|
| 197 |
+
1. ๅๅคง็ฑป่ฅๅ
ป็ด ๆๅ
ฅ่ฏไผฐ
|
| 198 |
+
2. ไธๅญๆๆจ่ๆ ๅ็ๅฏนๆฏ
|
| 199 |
+
3. ้่ฆ่กฅๅ
ๆ่ฐๆด็ๆนๅ
|
| 200 |
+
4. ๅ
ทไฝ็ไธๅจ้ฅฎ้ฃๆนๅๅปบ่ฎฎ
|
| 201 |
+
่ฏท็จ็ปๆๅๆ ผๅผ่พๅบ๏ผไพฟไบๅ็ซฏๅฏ่งๅ""",
|
| 202 |
+
}
|
modal_deploy/deploy.py
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Modal deployment for MiniCPM-o-4_5 via llama.cpp server
|
| 3 |
+
Serves OpenAI-compatible chat API + multimodal (text/image/audio) endpoints
|
| 4 |
+
"""
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
import subprocess
|
| 8 |
+
import modal
|
| 9 |
+
from modal import Image, App, Volume, Secret, asgi_app
|
| 10 |
+
|
| 11 |
+
# โโ constants โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 12 |
+
MODEL_DIR = "/models"
|
| 13 |
+
MODEL_NAME = "MiniCPM-o-4_5"
|
| 14 |
+
# Model files expected in the volume
|
| 15 |
+
MMPROJ_VISION = f"{MODEL_DIR}/vision/MiniCPM-o-4_5-vision-F16.gguf"
|
| 16 |
+
MMPROJ_AUDIO = f"{MODEL_DIR}/audio/MiniCPM-o-4_5-audio-F16.gguf"
|
| 17 |
+
MMPROJ_TTS = f"{MODEL_DIR}/tts/MiniCPM-o-4_5-tts-F16.gguf"
|
| 18 |
+
MMPROJ_PROJ = f"{MODEL_DIR}/tts/MiniCPM-o-4_5-projector-F16.gguf"
|
| 19 |
+
MAIN_MODEL = f"{MODEL_DIR}/MiniCPM-o-4_5-Q4_K_M.gguf"
|
| 20 |
+
|
| 21 |
+
# Image with llama.cpp compiled
|
| 22 |
+
llamacpp_image = (
|
| 23 |
+
Image.debian_slim(python_version="3.11")
|
| 24 |
+
.apt_install("curl", "git", "build-essential", "cmake", "libcurl4-openssl-dev")
|
| 25 |
+
.pip_install("fastapi", "uvicorn", "httpx", "numpy", "Pillow", "soundfile")
|
| 26 |
+
.run_commands(
|
| 27 |
+
"git clone --depth 1 https://github.com/ggerganov/llama.cpp /llama.cpp",
|
| 28 |
+
"cd /llama.cpp && cmake -B build -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA=OFF -DLLAMA_CURL=ON -DLLAMA_BUILD_SERVER=ON -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF",
|
| 29 |
+
"cd /llama.cpp && cmake --build build --config Release -j $(nproc) --target llama-server llama-mtmd-cli",
|
| 30 |
+
)
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
# Volume to store models (persisted across restarts)
|
| 34 |
+
model_volume = Volume.from_name("minicpm-o-4_5-models", create_if_missing=True)
|
| 35 |
+
|
| 36 |
+
app = App("prego-pal-minicpm")
|
| 37 |
+
|
| 38 |
+
# โโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 39 |
+
|
| 40 |
+
def find_mmproj_files(model_dir: str) -> list[str]:
|
| 41 |
+
"""Locate mmproj GGUF files in model directory structure."""
|
| 42 |
+
mmproj = []
|
| 43 |
+
for root, _, files in os.walk(model_dir):
|
| 44 |
+
for f in files:
|
| 45 |
+
if f.endswith(".gguf") and "mmproj" in f.lower():
|
| 46 |
+
mmproj.append(os.path.join(root, f))
|
| 47 |
+
return sorted(mmproj)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def build_server_args(model_dir: str, port: int = 8080) -> list[str]:
|
| 51 |
+
"""Construct llama-server command line arguments."""
|
| 52 |
+
args = [
|
| 53 |
+
"/llama.cpp/build/bin/llama-server",
|
| 54 |
+
"-m", os.path.join(model_dir, "MiniCPM-o-4_5-Q4_K_M.gguf"),
|
| 55 |
+
"--host", "0.0.0.0",
|
| 56 |
+
"--port", str(port),
|
| 57 |
+
"-ngl", "99", # offload all layers to GPU if available
|
| 58 |
+
"-c", "8192", # context size
|
| 59 |
+
"--mlock", # prevent model from being swapped
|
| 60 |
+
"--no-mmap", # use standard I/O (better on Modal's tmpfs)
|
| 61 |
+
]
|
| 62 |
+
|
| 63 |
+
# Attach mmproj files for multimodal support
|
| 64 |
+
mmproj_files = find_mmproj_files(model_dir)
|
| 65 |
+
for mf in mmproj_files:
|
| 66 |
+
args.extend(["--mmproj", mf])
|
| 67 |
+
|
| 68 |
+
return args
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# โโ Entrypoint / API โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 72 |
+
|
| 73 |
+
@app.function(
|
| 74 |
+
image=llamacpp_image,
|
| 75 |
+
volumes={MODEL_DIR: model_volume},
|
| 76 |
+
container_idle_timeout=300, # shutdown after 5min idle
|
| 77 |
+
allow_concurrent_inputs=10,
|
| 78 |
+
gpu="A100", # MiniCPM-o-4_5 needs GPU
|
| 79 |
+
timeout=600,
|
| 80 |
+
)
|
| 81 |
+
@asgi_app()
|
| 82 |
+
def serve():
|
| 83 |
+
"""FastAPI app wrapping llama-server for OpenAI-compatible endpoints."""
|
| 84 |
+
import asyncio
|
| 85 |
+
import httpx
|
| 86 |
+
from fastapi import FastAPI, Request
|
| 87 |
+
from fastapi.responses import StreamingResponse, JSONResponse
|
| 88 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 89 |
+
|
| 90 |
+
web_app = FastAPI(title="PregoPal MiniCPM-o API")
|
| 91 |
+
|
| 92 |
+
web_app.add_middleware(
|
| 93 |
+
CORSMiddleware,
|
| 94 |
+
allow_origins=["*"],
|
| 95 |
+
allow_credentials=True,
|
| 96 |
+
allow_methods=["*"],
|
| 97 |
+
allow_headers=["*"],
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
# Start llama-server in background
|
| 101 |
+
server_port = 8080
|
| 102 |
+
server_args = build_server_args(MODEL_DIR, server_port)
|
| 103 |
+
|
| 104 |
+
print(f"[PregoPal] Starting llama-server: {' '.join(server_args)}")
|
| 105 |
+
proc = subprocess.Popen(
|
| 106 |
+
server_args,
|
| 107 |
+
stdout=subprocess.PIPE,
|
| 108 |
+
stderr=subprocess.STDOUT,
|
| 109 |
+
text=True,
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
llama_url = f"http://127.0.0.1:{server_port}"
|
| 113 |
+
|
| 114 |
+
# Wait for server to be ready
|
| 115 |
+
async def wait_for_server(timeout: float = 120.0):
|
| 116 |
+
async with httpx.AsyncClient() as client:
|
| 117 |
+
start = asyncio.get_event_loop().time()
|
| 118 |
+
while True:
|
| 119 |
+
try:
|
| 120 |
+
r = await client.get(f"{llama_url}/health", timeout=5)
|
| 121 |
+
if r.status_code == 200:
|
| 122 |
+
print("[PregoPal] llama-server ready!")
|
| 123 |
+
return
|
| 124 |
+
except Exception:
|
| 125 |
+
pass
|
| 126 |
+
if asyncio.get_event_loop().time() - start > timeout:
|
| 127 |
+
raise RuntimeError("llama-server startup timed out")
|
| 128 |
+
await asyncio.sleep(1)
|
| 129 |
+
|
| 130 |
+
@web_app.on_event("startup")
|
| 131 |
+
async def startup():
|
| 132 |
+
await wait_for_server()
|
| 133 |
+
|
| 134 |
+
@web_app.on_event("shutdown")
|
| 135 |
+
async def shutdown():
|
| 136 |
+
proc.terminate()
|
| 137 |
+
try:
|
| 138 |
+
proc.wait(timeout=10)
|
| 139 |
+
except subprocess.TimeoutExpired:
|
| 140 |
+
proc.kill()
|
| 141 |
+
|
| 142 |
+
# โโ Proxy endpoints to llama-server โโโโโโโโโโโโโโโโโโ
|
| 143 |
+
|
| 144 |
+
@web_app.post("/v1/chat/completions")
|
| 145 |
+
async def chat_completions(request: Request):
|
| 146 |
+
"""OpenAI-compatible chat completions (text)."""
|
| 147 |
+
body = await request.json()
|
| 148 |
+
async with httpx.AsyncClient(timeout=300) as client:
|
| 149 |
+
r = await client.post(f"{llama_url}/v1/chat/completions", json=body)
|
| 150 |
+
# Stream if requested
|
| 151 |
+
if body.get("stream", False):
|
| 152 |
+
return StreamingResponse(
|
| 153 |
+
r.aiter_bytes(),
|
| 154 |
+
media_type="text/event-stream",
|
| 155 |
+
headers=dict(r.headers),
|
| 156 |
+
)
|
| 157 |
+
return JSONResponse(r.json(), status_code=r.status_code)
|
| 158 |
+
|
| 159 |
+
@web_app.post("/v1/embeddings")
|
| 160 |
+
async def embeddings(request: Request):
|
| 161 |
+
"""OpenAI-compatible embeddings."""
|
| 162 |
+
body = await request.json()
|
| 163 |
+
async with httpx.AsyncClient(timeout=120) as client:
|
| 164 |
+
r = await client.post(f"{llama_url}/v1/embeddings", json=body)
|
| 165 |
+
return JSONResponse(r.json(), status_code=r.status_code)
|
| 166 |
+
|
| 167 |
+
@web_app.get("/health")
|
| 168 |
+
async def health():
|
| 169 |
+
async with httpx.AsyncClient() as client:
|
| 170 |
+
r = await client.get(f"{llama_url}/health")
|
| 171 |
+
return JSONResponse(r.json(), status_code=r.status_code)
|
| 172 |
+
|
| 173 |
+
@web_app.get("/v1/models")
|
| 174 |
+
async def list_models():
|
| 175 |
+
async with httpx.AsyncClient() as client:
|
| 176 |
+
r = await client.get(f"{llama_url}/v1/models")
|
| 177 |
+
return JSONResponse(r.json(), status_code=r.status_code)
|
| 178 |
+
|
| 179 |
+
# โโ Multimodal endpoint โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 180 |
+
|
| 181 |
+
@web_app.post("/v1/multimodal/chat")
|
| 182 |
+
async def multimodal_chat(request: Request):
|
| 183 |
+
"""
|
| 184 |
+
Custom multimodal chat endpoint.
|
| 185 |
+
|
| 186 |
+
Request format:
|
| 187 |
+
{
|
| 188 |
+
"messages": [
|
| 189 |
+
{"role": "user", "content": [
|
| 190 |
+
{"type": "text", "text": "Describe this image"},
|
| 191 |
+
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}}
|
| 192 |
+
]}
|
| 193 |
+
],
|
| 194 |
+
"max_tokens": 1024,
|
| 195 |
+
"temperature": 0.7
|
| 196 |
+
}
|
| 197 |
+
"""
|
| 198 |
+
import base64
|
| 199 |
+
import tempfile
|
| 200 |
+
import json as json_mod
|
| 201 |
+
|
| 202 |
+
body = await request.json()
|
| 203 |
+
messages = body.get("messages", [])
|
| 204 |
+
max_tokens = body.get("max_tokens", 1024)
|
| 205 |
+
temperature = body.get("temperature", 0.7)
|
| 206 |
+
|
| 207 |
+
# Extract text prompt and image
|
| 208 |
+
prompt_parts = []
|
| 209 |
+
image_path = None
|
| 210 |
+
|
| 211 |
+
for msg in messages:
|
| 212 |
+
if msg["role"] == "user":
|
| 213 |
+
content = msg["content"]
|
| 214 |
+
if isinstance(content, str):
|
| 215 |
+
prompt_parts.append(content)
|
| 216 |
+
elif isinstance(content, list):
|
| 217 |
+
for item in content:
|
| 218 |
+
if item["type"] == "text":
|
| 219 |
+
prompt_parts.append(item["text"])
|
| 220 |
+
elif item["type"] == "image_url":
|
| 221 |
+
data_url = item["image_url"]["url"]
|
| 222 |
+
if data_url.startswith("data:image/"):
|
| 223 |
+
# Parse base64 image
|
| 224 |
+
header, b64 = data_url.split(",", 1)
|
| 225 |
+
fmt = header.split("/")[1].split(";")[0]
|
| 226 |
+
img_data = base64.b64decode(b64)
|
| 227 |
+
fd, image_path = tempfile.mkstemp(suffix=f".{fmt}")
|
| 228 |
+
os.close(fd)
|
| 229 |
+
with open(image_path, "wb") as f:
|
| 230 |
+
f.write(img_data)
|
| 231 |
+
|
| 232 |
+
prompt = "\n".join(prompt_parts)
|
| 233 |
+
|
| 234 |
+
# Build llama-mtmd-cli command with JSON output
|
| 235 |
+
cmd = [
|
| 236 |
+
"/llama.cpp/build/bin/llama-mtmd-cli",
|
| 237 |
+
"-m", MAIN_MODEL,
|
| 238 |
+
"--mmproj", MMPROJ_VISION,
|
| 239 |
+
"-ngl", "99",
|
| 240 |
+
"-c", "8192",
|
| 241 |
+
"-n", str(max_tokens),
|
| 242 |
+
"--temp", str(temperature),
|
| 243 |
+
"--json-schema", json_mod.dumps({
|
| 244 |
+
"type": "object",
|
| 245 |
+
"properties": {
|
| 246 |
+
"response": {"type": "string"}
|
| 247 |
+
}
|
| 248 |
+
}),
|
| 249 |
+
"-p", prompt,
|
| 250 |
+
]
|
| 251 |
+
|
| 252 |
+
if image_path:
|
| 253 |
+
cmd.extend(["--image", image_path])
|
| 254 |
+
|
| 255 |
+
try:
|
| 256 |
+
proc = await asyncio.create_subprocess_exec(
|
| 257 |
+
*cmd,
|
| 258 |
+
stdout=asyncio.subprocess.PIPE,
|
| 259 |
+
stderr=asyncio.subprocess.PIPE,
|
| 260 |
+
)
|
| 261 |
+
stdout, stderr = await asyncio.wait_for(
|
| 262 |
+
proc.communicate(), timeout=120
|
| 263 |
+
)
|
| 264 |
+
if image_path:
|
| 265 |
+
os.unlink(image_path)
|
| 266 |
+
|
| 267 |
+
output = stdout.decode("utf-8", errors="replace").strip()
|
| 268 |
+
try:
|
| 269 |
+
result = json_mod.loads(output)
|
| 270 |
+
return JSONResponse(result)
|
| 271 |
+
except json_mod.JSONDecodeError:
|
| 272 |
+
return JSONResponse({"response": output, "raw": True})
|
| 273 |
+
|
| 274 |
+
except asyncio.TimeoutError:
|
| 275 |
+
if image_path and os.path.exists(image_path):
|
| 276 |
+
os.unlink(image_path)
|
| 277 |
+
return JSONResponse(
|
| 278 |
+
{"error": "Request timed out"}, status_code=504
|
| 279 |
+
)
|
| 280 |
+
except Exception as e:
|
| 281 |
+
if image_path and os.path.exists(image_path):
|
| 282 |
+
os.unlink(image_path)
|
| 283 |
+
return JSONResponse({"error": str(e)}, status_code=500)
|
| 284 |
+
|
| 285 |
+
@web_app.get("/")
|
| 286 |
+
async def root():
|
| 287 |
+
return {"service": "PregoPal MiniCPM-o-4_5 API", "version": "1.0.0"}
|
| 288 |
+
|
| 289 |
+
return web_app
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
# โโ Deploy helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 293 |
+
|
| 294 |
+
@app.function(
|
| 295 |
+
image=llamacpp_image,
|
| 296 |
+
volumes={MODEL_DIR: model_volume},
|
| 297 |
+
gpu="A100",
|
| 298 |
+
timeout=3600,
|
| 299 |
+
)
|
| 300 |
+
def upload_models():
|
| 301 |
+
"""One-time function to upload model files to Modal Volume.
|
| 302 |
+
|
| 303 |
+
Run: modal run modal_deploy/deploy.py::upload_models
|
| 304 |
+
Requires models in ../models/MiniCPM-o-4_5-gguf/
|
| 305 |
+
"""
|
| 306 |
+
import shutil
|
| 307 |
+
local_dir = "/local_models"
|
| 308 |
+
|
| 309 |
+
# This function assumes models are mounted locally
|
| 310 |
+
# On Modal, you'd use modal volume put instead
|
| 311 |
+
print("Model upload placeholder - use 'modal volume put' CLI")
|
| 312 |
+
print(f" modal volume put minicpm-o-4_5-models ../models/MiniCPM-o-4_5-gguf /")
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
# โโ Health check function โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 316 |
+
|
| 317 |
+
@app.function(
|
| 318 |
+
image=llamacpp_image,
|
| 319 |
+
volumes={MODEL_DIR: model_volume},
|
| 320 |
+
gpu="A100",
|
| 321 |
+
timeout=300,
|
| 322 |
+
)
|
| 323 |
+
def test_inference():
|
| 324 |
+
"""Quick test to verify model loads and runs.
|
| 325 |
+
|
| 326 |
+
Run: modal run modal_deploy/deploy.py::test_inference
|
| 327 |
+
"""
|
| 328 |
+
import httpx
|
| 329 |
+
import time
|
| 330 |
+
|
| 331 |
+
port = 8081
|
| 332 |
+
args = build_server_args(MODEL_DIR, port)
|
| 333 |
+
|
| 334 |
+
print(f"Starting server: {' '.join(args)}")
|
| 335 |
+
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
| 336 |
+
|
| 337 |
+
url = f"http://127.0.0.1:{port}"
|
| 338 |
+
deadline = time.time() + 120
|
| 339 |
+
while time.time() < deadline:
|
| 340 |
+
try:
|
| 341 |
+
r = httpx.get(f"{url}/health", timeout=5)
|
| 342 |
+
if r.status_code == 200:
|
| 343 |
+
print("Server healthy!")
|
| 344 |
+
break
|
| 345 |
+
except Exception:
|
| 346 |
+
pass
|
| 347 |
+
time.sleep(2)
|
| 348 |
+
else:
|
| 349 |
+
proc.terminate()
|
| 350 |
+
raise RuntimeError("Server failed to start")
|
| 351 |
+
|
| 352 |
+
# Test completion
|
| 353 |
+
r = httpx.post(
|
| 354 |
+
f"{url}/v1/chat/completions",
|
| 355 |
+
json={
|
| 356 |
+
"model": "MiniCPM-o-4_5",
|
| 357 |
+
"messages": [
|
| 358 |
+
{"role": "user", "content": "Say hello in Chinese"}
|
| 359 |
+
],
|
| 360 |
+
"max_tokens": 50,
|
| 361 |
+
"temperature": 0.1,
|
| 362 |
+
},
|
| 363 |
+
timeout=120,
|
| 364 |
+
)
|
| 365 |
+
print(f"Response: {r.json()}")
|
| 366 |
+
proc.terminate()
|
requirements.txt
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
# ๅฎ่ฃ
: pip install -r requirements.txt
|
| 3 |
|
| 4 |
gradio>=6.0.0
|
|
|
|
| 5 |
numpy>=1.24.0
|
| 6 |
matplotlib>=3.7.0
|
| 7 |
pandas>=2.0.0
|
|
|
|
| 2 |
# ๅฎ่ฃ
: pip install -r requirements.txt
|
| 3 |
|
| 4 |
gradio>=6.0.0
|
| 5 |
+
modal>=0.60.0
|
| 6 |
numpy>=1.24.0
|
| 7 |
matplotlib>=3.7.0
|
| 8 |
pandas>=2.0.0
|