Compare commits
10 Commits
35678e3ee4
...
2df0929440
| Author | SHA1 | Date | |
|---|---|---|---|
| 2df0929440 | |||
| e29ddc589d | |||
| b164779af4 | |||
| a2d0f64c5b | |||
| cb2fe57359 | |||
| c1b84f880e | |||
| 69af1ceba5 | |||
| 791f3ea7e3 | |||
| 257057de07 | |||
| 0ac15554a3 |
Generated
+1
-1
@@ -21,7 +21,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aha"
|
name = "aha"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|||||||
+2
-4
@@ -1,10 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "aha"
|
name = "aha"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
repository = "https://github.com/jhqxxx/aha"
|
repository = "https://github.com/jhqxxx/aha"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
description = "aha model inference library, now supports Qwen(2.5VL/3/3VL/3.5/ASR/3Embedding/3Reranker), MiniCPM4, VoxCPM(0.5B/1.5/2), DeepSeek-OCR/2, Hunyuan-OCR, PaddleOCR-VL/1.5, RMBG2.0, GLM(ASR-Nano-2512/OCR), Fun-ASR-Nano-2512, LFM(2/2.5/2VL/2.5VL)"
|
description = "aha model inference library, now supports Qwen(2.5VL/3/3VL/3.5/ASR/3Embedding/3Reranker), MiniCPM(4/5), VoxCPM(0.5B/1.5/2), DeepSeek-OCR/2, Hunyuan-OCR, PaddleOCR-VL/1.5, RMBG2.0, GLM(ASR-Nano-2512/OCR), Fun-ASR-Nano-2512, LFM(2/2.5/2VL/2.5VL)"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
candle-core = { version = "0.9.2" }
|
candle-core = { version = "0.9.2" }
|
||||||
@@ -21,9 +21,7 @@ base64 = "0.22.1"
|
|||||||
num = "0.4.3"
|
num = "0.4.3"
|
||||||
minijinja = "2.12.0"
|
minijinja = "2.12.0"
|
||||||
tokenizers = "0.22.1"
|
tokenizers = "0.22.1"
|
||||||
# aha_openai_dive = { version = "1.4", features = ["stream"] }
|
|
||||||
uuid = { version = "1.18.1", features = ["v4"] }
|
uuid = { version = "1.18.1", features = ["v4"] }
|
||||||
# chrono = "0.4"
|
|
||||||
rocket = { version = "0.5.1", features = ["serde_json", "json"] }
|
rocket = { version = "0.5.1", features = ["serde_json", "json"] }
|
||||||
tokio = "1.47.1"
|
tokio = "1.47.1"
|
||||||
hound = "3.5.1"
|
hound = "3.5.1"
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
<a href="README.zh-CN.md">简体中文</a> | <strong>English</strong>
|
<a href="README.zh-CN.md">简体中文</a> | <strong>English</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Official Website:</strong> <a href="https://s5dgj23f.pub.makeit.coderbox.cn/">https://s5dgj23f.pub.makeit.coderbox.cn/</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
# aha
|
# aha
|
||||||
|
|
||||||
**Lightweight AI Inference Engine — All-in-one Solution for Text, Vision, Speech, and OCR**
|
**Lightweight AI Inference Engine — All-in-one Solution for Text, Vision, Speech, and OCR**
|
||||||
@@ -31,7 +35,7 @@ aha is a high-performance, cross-platform AI inference engine built with Rust an
|
|||||||
|----------|--------|
|
|----------|--------|
|
||||||
| **Text** | Qwen3, MiniCPM4, MiniCPM5, LFM2, LFM2.5 |
|
| **Text** | Qwen3, MiniCPM4, MiniCPM5, LFM2, LFM2.5 |
|
||||||
| **Vision** | Qwen2.5-VL, Qwen3-VL, Qwen3.5, <br> LFM2.5-VL, LFM2-VL |
|
| **Vision** | Qwen2.5-VL, Qwen3-VL, Qwen3.5, <br> LFM2.5-VL, LFM2-VL |
|
||||||
| **OCR** | DeepSeek-OCR, DeepSeek-OCR-2 , PaddleOCR-VL <br> PaddleOCR-VL1.5, Hunyuan-OCR, GLM-OCR |
|
| **OCR** | DeepSeek-OCR, DeepSeek-OCR-2 , PaddleOCR-VL <br> PaddleOCR-VL1.5, PaddleOCR-VL1.6, Hunyuan-OCR <br> GLM-OCR |
|
||||||
| **ASR** | GLM-ASR-Nano, Fun-ASR-Nano, Qwen3-ASR |
|
| **ASR** | GLM-ASR-Nano, Fun-ASR-Nano, Qwen3-ASR |
|
||||||
| **TTS** | VoxCPM, VoxCPM1.5, VoxCPM2, Moss-TTS-Nano |
|
| **TTS** | VoxCPM, VoxCPM1.5, VoxCPM2, Moss-TTS-Nano |
|
||||||
| **Image** | RMBG-2.0 (background removal) |
|
| **Image** | RMBG-2.0 (background removal) |
|
||||||
@@ -39,6 +43,15 @@ aha is a high-performance, cross-platform AI inference engine built with Rust an
|
|||||||
| **Reranker** | Qwen3-Reranker |
|
| **Reranker** | Qwen3-Reranker |
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
### 2026-06-06
|
||||||
|
- add PaddleOCR-VL-1.6
|
||||||
|
|
||||||
|
### 2026-05-29
|
||||||
|
- generate code refactored
|
||||||
|
|
||||||
|
### 2026-05-28
|
||||||
|
- generate code refactoring progress 1/3
|
||||||
|
|
||||||
### 2026-05-27
|
### 2026-05-27
|
||||||
- add MiniCPM5
|
- add MiniCPM5
|
||||||
|
|
||||||
@@ -48,17 +61,6 @@ aha is a high-performance, cross-platform AI inference engine built with Rust an
|
|||||||
### 2026-05-11
|
### 2026-05-11
|
||||||
- add Moss-TTS-Nano,its performance is worse than the original Python version
|
- add Moss-TTS-Nano,its performance is worse than the original Python version
|
||||||
|
|
||||||
### 2026-05-09
|
|
||||||
- merge pr/eastgold15/46, add aha-ui
|
|
||||||
|
|
||||||
### 2026-04-25
|
|
||||||
- VoxCPM update stream
|
|
||||||
|
|
||||||
### 2026-04-17
|
|
||||||
- Qwen3ASR add vad data recognition
|
|
||||||
|
|
||||||
### 2026-04-16
|
|
||||||
- fix FireRedVAD fsmn cache bug
|
|
||||||
|
|
||||||
**[View full changelog](docs/changelog.md)** →
|
**[View full changelog](docs/changelog.md)** →
|
||||||
|
|
||||||
|
|||||||
+14
-13
@@ -18,6 +18,10 @@
|
|||||||
<a href="README.md">English</a> | <strong>简体中文</strong>
|
<a href="README.md">English</a> | <strong>简体中文</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>官方网站:</strong> <a href="https://s5dgj23f.pub.makeit.coderbox.cn/">https://s5dgj23f.pub.makeit.coderbox.cn/</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
# aha
|
# aha
|
||||||
|
|
||||||
**轻量 AI 推理引擎 —— 文本、视觉、语音与 OCR 一站式解决方案**
|
**轻量 AI 推理引擎 —— 文本、视觉、语音与 OCR 一站式解决方案**
|
||||||
@@ -30,7 +34,7 @@ aha 是一款基于 Rust 和 Candle 框架构建的高性能跨平台 AI 推理
|
|||||||
|------|------|
|
|------|------|
|
||||||
| **文本** | Qwen3, MiniCPM4, MiniCPM5, LFM2, LFM2.5 |
|
| **文本** | Qwen3, MiniCPM4, MiniCPM5, LFM2, LFM2.5 |
|
||||||
| **视觉** | Qwen2.5-VL, Qwen3-VL, Qwen3.5 <br> LFM2.5-VL, LFM2-VL |
|
| **视觉** | Qwen2.5-VL, Qwen3-VL, Qwen3.5 <br> LFM2.5-VL, LFM2-VL |
|
||||||
| **OCR** | DeepSeek-OCR, DeepSeek-OCR-2, PaddleOCR-VL, <br>PaddleOCR-VL1.5, Hunyuan-OCR, GLM-OCR |
|
| **OCR** | DeepSeek-OCR, DeepSeek-OCR-2, PaddleOCR-VL, <br>PaddleOCR-VL1.5, PaddleOCR-VL1.6, Hunyuan-OCR <br> GLM-OCR |
|
||||||
| **ASR** | GLM-ASR-Nano, Fun-ASR-Nano, Qwen3-ASR |
|
| **ASR** | GLM-ASR-Nano, Fun-ASR-Nano, Qwen3-ASR |
|
||||||
| **TTS** | VoxCPM, VoxCPM1.5, VoxCPM2, Moss-TTS-Nano |
|
| **TTS** | VoxCPM, VoxCPM1.5, VoxCPM2, Moss-TTS-Nano |
|
||||||
| **图像** | RMBG-2.0 (背景移除) |
|
| **图像** | RMBG-2.0 (背景移除) |
|
||||||
@@ -38,6 +42,15 @@ aha 是一款基于 Rust 和 Candle 框架构建的高性能跨平台 AI 推理
|
|||||||
| **重排序** | Qwen3-Reranker |
|
| **重排序** | Qwen3-Reranker |
|
||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
### 2026-06-06
|
||||||
|
- 新增 PaddleOCR-VL-1.6
|
||||||
|
|
||||||
|
### 2026-05-29
|
||||||
|
- generate代码重构完成
|
||||||
|
|
||||||
|
### 2026-05-28
|
||||||
|
- generate代码重构进度 1/3
|
||||||
|
|
||||||
### 2026-05-27
|
### 2026-05-27
|
||||||
- 新增 MiniCPM5
|
- 新增 MiniCPM5
|
||||||
|
|
||||||
@@ -47,18 +60,6 @@ aha 是一款基于 Rust 和 Candle 框架构建的高性能跨平台 AI 推理
|
|||||||
### 2026-05-11
|
### 2026-05-11
|
||||||
- 添加Moss-TTS-Nano,该模型比较小,计算误差影响较大,效果比python原版差
|
- 添加Moss-TTS-Nano,该模型比较小,计算误差影响较大,效果比python原版差
|
||||||
|
|
||||||
### 2026-05-09
|
|
||||||
- 合并 pr/eastgold15/46, 添加 aha-ui
|
|
||||||
|
|
||||||
### 2026-04-25
|
|
||||||
- VoxCPM 更新流式生成
|
|
||||||
|
|
||||||
### 2026-04-17
|
|
||||||
- Qwen3ASR 增加 vad 数据识别
|
|
||||||
|
|
||||||
### 2026-04-16
|
|
||||||
- 修复 FireRedVAD fsmn 缓存问题
|
|
||||||
|
|
||||||
**[查看完整更新日志](docs/changelog.zh-CN.md)** →
|
**[查看完整更新日志](docs/changelog.zh-CN.md)** →
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 153 KiB |
+1
-2
@@ -356,8 +356,7 @@ curl http://127.0.0.1:10100/audio/speech \
|
|||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": [
|
"content": [
|
||||||
{"type": "text", "text": "Hello, this is AHA speaking."},
|
{"type": "text", "text": "Hello, this is AHA speaking."}
|
||||||
{"type": "audio", "audio_url": {"url": "https://package-release.coderbox.cn/aiway/test/other/%E5%93%AA%E5%90%92.wav"}}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-2
@@ -359,8 +359,7 @@ curl http://127.0.0.1:10100/audio/speech \
|
|||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": [
|
"content": [
|
||||||
{"type": "text", "text": "你好,这是 AHA 在说话。"},
|
{"type": "text", "text": "你好,这是 AHA 在说话。"}
|
||||||
{"type": "audio", "audio_url": {"url": "https://package-release.coderbox.cn/aiway/test/other/%E5%93%AA%E5%90%92.wav"}}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,6 +5,15 @@ All notable changes to aha will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
### 2026-06-06
|
||||||
|
- add PaddleOCR-VL-1.6
|
||||||
|
|
||||||
|
### 2026-05-29
|
||||||
|
- generate code refactored
|
||||||
|
|
||||||
|
### 2026-05-28
|
||||||
|
- generate code refactoring progress 1/3
|
||||||
|
|
||||||
### 2026-05-27
|
### 2026-05-27
|
||||||
- add MiniCPM5
|
- add MiniCPM5
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,15 @@
|
|||||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||||
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/spec/v2.0.0.html)。
|
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/spec/v2.0.0.html)。
|
||||||
|
|
||||||
|
### 2026-06-06
|
||||||
|
- 新增 PaddleOCR-VL-1.6
|
||||||
|
|
||||||
|
### 2026-05-29
|
||||||
|
- generate代码重构完成
|
||||||
|
|
||||||
|
### 2026-05-28
|
||||||
|
- generate代码重构进度 1/3
|
||||||
|
|
||||||
### 2026-05-27
|
### 2026-05-27
|
||||||
- 新增 MiniCPM5
|
- 新增 MiniCPM5
|
||||||
|
|
||||||
|
|||||||
@@ -179,8 +179,7 @@ curl http://127.0.0.1:10100/audio/speech \
|
|||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": [
|
"content": [
|
||||||
{"type": "text", "text": "Hello, this is AHA speaking."},
|
{"type": "text", "text": "Hello, this is AHA speaking."}
|
||||||
{"type": "audio", "audio_url": {"url": "https://package-release.coderbox.cn/aiway/test/other/%E5%93%AA%E5%90%92.wav"}}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -180,8 +180,7 @@ curl http://127.0.0.1:10100/audio/speech \
|
|||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": [
|
"content": [
|
||||||
{"type": "text", "text": "你好,这是 AHA 在说话。"},
|
{"type": "text", "text": "你好,这是 AHA 在说话。"}
|
||||||
{"type": "audio", "audio_url": {"url": "https://package-release.coderbox.cn/aiway/test/other/%E5%93%AA%E5%90%92.wav"}}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+238
-1
@@ -3,7 +3,7 @@
|
|||||||
#### Options prompt:
|
#### Options prompt:
|
||||||
"OCR:" | "Table Recognition:" | "Formula Recognition:" | "Chart Recognition:"
|
"OCR:" | "Table Recognition:" | "Formula Recognition:" | "Chart Recognition:"
|
||||||
|
|
||||||
## PaddleOCR-VL1.5
|
## PaddleOCR-VL1.5/1.6
|
||||||
#### Options prompt:
|
#### Options prompt:
|
||||||
"OCR:" | "Table Recognition:" | "Formula Recognition:" | "Chart Recognition:" | "Spotting:" | "Seal Recognition:"
|
"OCR:" | "Table Recognition:" | "Formula Recognition:" | "Chart Recognition:" | "Spotting:" | "Seal Recognition:"
|
||||||
|
|
||||||
@@ -75,3 +75,240 @@ Please output the information in the image in the following JSON format:
|
|||||||
| Parsing | 1. Identify the formula in the image and represent it using LaTeX format. <br> 2.Parse the table in the image into HTML. <br> 3. Parse the chart in the image; use Mermaid format for flowcharts and Markdown for other charts. <br> 4.Extract all information from the main body of the document image and represent it in markdown format, ignoring headers and footers. Tables should be expressed in HTML format, formulas in the document should be represented using LaTeX format, and the parsing should be organized according to the reading order. | 1. 识别图片中的公式,用 LaTeX 格式表示。 <br> 2. 把图中的表格解析为 HTML。 <br> 3. 解析图中的图表,对于流程图使用 Mermaid 格式表示,其他图表使用 Markdown 格式表示。 <br> 4. 提取文档图片中正文的所有信息用 markdown 格式表示,其中页眉、页脚部分忽略,表格用 html 格式表达,文档中公式用 latex 格式表示,按照阅读顺序组织进行解析。 |
|
| Parsing | 1. Identify the formula in the image and represent it using LaTeX format. <br> 2.Parse the table in the image into HTML. <br> 3. Parse the chart in the image; use Mermaid format for flowcharts and Markdown for other charts. <br> 4.Extract all information from the main body of the document image and represent it in markdown format, ignoring headers and footers. Tables should be expressed in HTML format, formulas in the document should be represented using LaTeX format, and the parsing should be organized according to the reading order. | 1. 识别图片中的公式,用 LaTeX 格式表示。 <br> 2. 把图中的表格解析为 HTML。 <br> 3. 解析图中的图表,对于流程图使用 Mermaid 格式表示,其他图表使用 Markdown 格式表示。 <br> 4. 提取文档图片中正文的所有信息用 markdown 格式表示,其中页眉、页脚部分忽略,表格用 html 格式表达,文档中公式用 latex 格式表示,按照阅读顺序组织进行解析。 |
|
||||||
| Information Extraction | 1. Output the value of Key. <br> 2. Extract the content of the fields: ['key1','key2', ...] from the image and return it in JSON format. <br> 3. Extract the subtitles from the image. | 1. 输出 Key 的值。 <br> 2. 提取图片中的: ['key1','key2', ...] 的字段内容,并按照 JSON 格式返回。 <br> 3. 提取图片中的字幕。 |
|
| Information Extraction | 1. Output the value of Key. <br> 2. Extract the content of the fields: ['key1','key2', ...] from the image and return it in JSON format. <br> 3. Extract the subtitles from the image. | 1. 输出 Key 的值。 <br> 2. 提取图片中的: ['key1','key2', ...] 的字段内容,并按照 JSON 格式返回。 <br> 3. 提取图片中的字幕。 |
|
||||||
| Translation | First extract the text, then translate the text content into English. If it is a document, ignore the header and footer. Formulas should be represented in LaTeX format, and tables should be represented in HTML format. | 先提取文字,再将文字内容翻译为英文。若是文档,则其中页眉、页脚忽略。公式用latex格式表示,表格用html格式表示。 |
|
| Translation | First extract the text, then translate the text content into English. If it is a document, ignore the header and footer. Formulas should be represented in LaTeX format, and tables should be represented in HTML format. | 先提取文字,再将文字内容翻译为英文。若是文档,则其中页眉、页脚忽略。公式用latex格式表示,表格用html格式表示。 |
|
||||||
|
|
||||||
|
|
||||||
|
Here is the continuation for the English [model-card.md](file:///home/jhq/rust_code/aha/docs/model-card.md) file, translating and adapting the content from the Chinese version regarding VoxCPM models.
|
||||||
|
|
||||||
|
# TTS
|
||||||
|
## VoxCPM (0.5B / 1.5)
|
||||||
|
|
||||||
|
#### Mode Description
|
||||||
|
* **Zero-shot TTS (Default)**: Generates speech directly without reference audio.
|
||||||
|
* **Voice Cloning**: Requires reference audio (`audio_url`) and its corresponding transcript (`prompt_text`).
|
||||||
|
* **Note**: For versions 0.5B/1.5, if `audio_url` is provided, `prompt_text` **must** be provided in `metadata`. Conversely, if there is no `audio_url`, `prompt_text` should not be provided.
|
||||||
|
|
||||||
|
#### Metadata Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `prompt_text` | String | Conditional | **Required for Cloning**. The transcript of the reference audio. Used to extract speaker characteristics. |
|
||||||
|
| `min_len` | Integer | No | Minimum generation length (tokens/steps), default `2`. |
|
||||||
|
| `max_len` | Integer | No | Maximum generation length (tokens/steps), default `4096`. |
|
||||||
|
| `inference_timesteps` | Integer | No | Number of inference steps. Affects quality and speed. Default `10`. Higher values yield better quality but slower speed. |
|
||||||
|
| `cfg_value` | Float | No | Classifier-Free Guidance value. Controls adherence to the prompt. Default `2.0`. |
|
||||||
|
| `retry_badcase_ratio_threshold` | Float | No | Threshold for retrying bad cases. Default `6.0`. |
|
||||||
|
|
||||||
|
#### Example 1: Zero-shot TTS (No Reference)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "OpenBMB/VoxCPM-0.5B",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Hello, this is a test speech."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example 2: Voice Cloning (With Reference)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "OpenBMB/VoxCPM-0.5B",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "audio",
|
||||||
|
"audio_url": {
|
||||||
|
"url": "https://example.com/reference.wav"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "This is the target text I want to generate."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"prompt_text": "This is the text content corresponding to the reference audio."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response Example
|
||||||
|
The model returns a JSON object containing Base64-encoded audio data. The audio format is WAV.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "chatcmpl-uuid...",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": 1710000000,
|
||||||
|
"model": "OpenBMB/VoxCPM-0.5B",
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "audio",
|
||||||
|
"audio_url": {
|
||||||
|
"url": "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"finish_reason": "stop"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## VoxCPM2
|
||||||
|
|
||||||
|
VoxCPM2 supports three advanced voice generation modes. Switch between modes by combining content types in `messages` and parameters in `metadata`.
|
||||||
|
|
||||||
|
### 1. Voice Design
|
||||||
|
Generate a completely new voice from natural language descriptions alone (gender, age, tone, emotion, pace, etc.). **No reference audio required.**
|
||||||
|
|
||||||
|
* **Input**: Text message only.
|
||||||
|
* **Metadata**: Optional `control_instruction` for finer control (e.g., emotion, pace).
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "OpenBMB/VoxCPM2",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Welcome to the future world."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"control_instruction": "Young female, energetic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Controllable Cloning
|
||||||
|
Clone a voice from a short audio clip, with optional style guidance to steer emotion, pace, and expression while preserving timbre.
|
||||||
|
|
||||||
|
* **Input**: Reference audio (`audio_url`) + Target text.
|
||||||
|
* **Metadata**: Optional `control_instruction` to adjust style (emotion, pace, etc.). **`prompt_text` is NOT required.**
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "OpenBMB/VoxCPM2",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "audio",
|
||||||
|
"audio_url": {
|
||||||
|
"url": "https://example.com/short_clip.wav"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "This news broadcast needs to be more serious and calm."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"control_instruction": "serious, slow pace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Ultimate Cloning
|
||||||
|
Provide reference audio and its verbatim transcript for audio-continuation cloning or high-fidelity cloning. Every vocal nuance is faithfully reproduced.
|
||||||
|
|
||||||
|
* **Input**: Reference audio (`audio_url`) + Target text.
|
||||||
|
* **Metadata**: **Must** provide `prompt_text` (accurate transcript of the reference audio).
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "OpenBMB/VoxCPM2",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "audio",
|
||||||
|
"audio_url": {
|
||||||
|
"url": "https://example.com/reference.wav"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "This is the new content to be spoken next."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"prompt_text": "This is the actual text spoken in the reference audio."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### General Metadata Parameters (Applicable to all VoxCPM2 modes)
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `prompt_text` | String | Ultimate Cloning Only | Verbatim transcript of the reference audio. Required only for Ultimate Cloning mode. |
|
||||||
|
| `control_instruction` | String | No | Natural language instruction to control style for Voice Design or Controllable Cloning (e.g., "angry", "whispering", "fast"). |
|
||||||
|
| `min_len` | Integer | No | Minimum generation length, default `2`. |
|
||||||
|
| `max_len` | Integer | No | Maximum generation length, default `4096`. |
|
||||||
|
| `inference_timesteps` | Integer | No | Inference steps, default `10`. |
|
||||||
|
| `cfg_value` | Float | No | CFG value, default `2.0`. |
|
||||||
|
| `retry_badcase_ratio_threshold` | Float | No | Bad case retry threshold, default `6.0`. |
|
||||||
|
|
||||||
|
|
||||||
|
#### Response Example
|
||||||
|
Same as VoxCPM 0.5B/1.5, returns a JSON object containing Base64-encoded WAV audio.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "chatcmpl-uuid...",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": 1710000000,
|
||||||
|
"model": "OpenBMB/VoxCPM2",
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "audio",
|
||||||
|
"audio_url": {
|
||||||
|
"url": "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"finish_reason": "stop"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": null
|
||||||
|
}
|
||||||
|
```
|
||||||
+234
-2
@@ -3,7 +3,7 @@
|
|||||||
#### 可选提示词:
|
#### 可选提示词:
|
||||||
"OCR:" | "Table Recognition:" | "Formula Recognition:" | "Chart Recognition:"
|
"OCR:" | "Table Recognition:" | "Formula Recognition:" | "Chart Recognition:"
|
||||||
|
|
||||||
## PaddleOCR-VL1.5
|
## PaddleOCR-VL1.5/1.6
|
||||||
#### 可选提示词:
|
#### 可选提示词:
|
||||||
"OCR:" | "Table Recognition:" | "Formula Recognition:" | "Chart Recognition:" | "Spotting:" | "Seal Recognition:"
|
"OCR:" | "Table Recognition:" | "Formula Recognition:" | "Chart Recognition:" | "Spotting:" | "Seal Recognition:"
|
||||||
|
|
||||||
@@ -75,5 +75,237 @@ extract structured information from documents. Prompts must follow a strict JSON
|
|||||||
| 信息提取 | 1. Output the value of Key. <br> 2. Extract the content of the fields: ['key1','key2', ...] from the image and return it in JSON format. <br> 3. Extract the subtitles from the image. | 1. 输出 Key 的值。 <br> 2. 提取图片中的: ['key1','key2', ...] 的字段内容,并按照 JSON 格式返回。 <br> 3. 提取图片中的字幕。 |
|
| 信息提取 | 1. Output the value of Key. <br> 2. Extract the content of the fields: ['key1','key2', ...] from the image and return it in JSON format. <br> 3. Extract the subtitles from the image. | 1. 输出 Key 的值。 <br> 2. 提取图片中的: ['key1','key2', ...] 的字段内容,并按照 JSON 格式返回。 <br> 3. 提取图片中的字幕。 |
|
||||||
| 翻译 | First extract the text, then translate the text content into English. If it is a document, ignore the header and footer. Formulas should be represented in LaTeX format, and tables should be represented in HTML format. | 先提取文字,再将文字内容翻译为英文。若是文档,则其中页眉、页脚忽略。公式用latex格式表示,表格用html格式表示。 |
|
| 翻译 | First extract the text, then translate the text content into English. If it is a document, ignore the header and footer. Formulas should be represented in LaTeX format, and tables should be represented in HTML format. | 先提取文字,再将文字内容翻译为英文。若是文档,则其中页眉、页脚忽略。公式用latex格式表示,表格用html格式表示。 |
|
||||||
|
|
||||||
|
|
||||||
# TTS
|
# TTS
|
||||||
## VoxCPM
|
## VoxCPM (0.5B / 1.5)
|
||||||
|
|
||||||
|
### 模式说明
|
||||||
|
* **Zero-shot TTS (默认模式)**: 无需参考音频,直接生成语音。
|
||||||
|
* **Voice Cloning (克隆模式)**: 需要提供参考音频 (`audio_url`) 和对应的参考文本 (`prompt_text`)。
|
||||||
|
* **注意**: 对于 0.5B/1.5 版本,如果提供了 `audio_url`,则**必须**在 `metadata` 中提供 `prompt_text`;反之,如果没有 `audio_url`,则不应提供 `prompt_text`。
|
||||||
|
|
||||||
|
### Metadata 参数
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `prompt_text` | String | 条件必填 | **克隆模式必填**。参考音频对应的转录文本。用于提取说话人特征。 |
|
||||||
|
| `min_len` | Integer | 否 | 最小生成长度 (tokens/steps),默认 `2`。 |
|
||||||
|
| `max_len` | Integer | 否 | 最大生成长度 (tokens/steps),默认 `4096`。 |
|
||||||
|
| `inference_timesteps` | Integer | 否 | 推理步数,影响生成质量和速度,默认 `10`。值越大质量越高,速度越慢。 |
|
||||||
|
| `cfg_value` | Float | 否 | Classifier-Free Guidance 值,控制对提示词的遵循程度,默认 `2.0`。 |
|
||||||
|
| `retry_badcase_ratio_threshold` | Float | 否 | 坏案例重试阈值,默认 `6.0`。 |
|
||||||
|
|
||||||
|
#### 示例 1: Zero-shot TTS (无参考)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "OpenBMB/VoxCPM-0.5B",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "你好,这是一个测试语音。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 示例 2: Voice Cloning (有参考)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "OpenBMB/VoxCPM-0.5B",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "audio",
|
||||||
|
"audio_url": {
|
||||||
|
"url": "https://example.com/reference.wav"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "这是我要生成的目标文本。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"prompt_text": "这是参考音频对应的文字内容。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 返回结果示例
|
||||||
|
模型将返回一个包含 Base64 编码音频数据的 JSON 对象。音频格式为 WAV。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "chatcmpl-uuid...",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": 1710000000,
|
||||||
|
"model": "OpenBMB/VoxCPM-0.5B",
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "audio",
|
||||||
|
"audio_url": {
|
||||||
|
"url": "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"finish_reason": "stop"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## VoxCPM2
|
||||||
|
|
||||||
|
VoxCPM2 支持三种高级语音生成模式。通过组合 `messages` 中的内容类型和 `metadata` 参数来切换模式。
|
||||||
|
|
||||||
|
### 1. Voice Design (声音设计)
|
||||||
|
仅通过自然语言描述生成全新声音(性别、年龄、语气、情感、语速等),**不需要**参考音频。
|
||||||
|
|
||||||
|
* **输入**: 纯文本消息。
|
||||||
|
* **Metadata**: 可选 `control_instruction` 用于更精细的控制(如情绪、语速)。
|
||||||
|
|
||||||
|
#### 示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "OpenBMB/VoxCPM2",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "欢迎来到未来世界。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"control_instruction": "年轻女性,活力"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Controllable Cloning (可控克隆)
|
||||||
|
从短音频片段中克隆声音,并可选地通过风格指导来控制情感、语速和表达,同时保留音色。
|
||||||
|
|
||||||
|
* **输入**: 参考音频 (`audio_url`) + 目标文本。
|
||||||
|
* **Metadata**: 可选 `control_instruction` 用于调整风格(情绪、语速等)。**不需要** `prompt_text`。
|
||||||
|
|
||||||
|
#### 示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "OpenBMB/VoxCPM2",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "audio",
|
||||||
|
"audio_url": {
|
||||||
|
"url": "https://example.com/short_clip.wav"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "这段新闻播报需要更加严肃和沉稳。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"control_instruction": "serious, slow pace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Ultimate Cloning (极致克隆)
|
||||||
|
提供参考音频及其逐字稿,进行音频续写或高保真克隆。每个声音细节都被忠实还原。
|
||||||
|
|
||||||
|
* **输入**: 参考音频 (`audio_url`) + 目标文本。
|
||||||
|
* **Metadata**: **必须**提供 `prompt_text` (参考音频的准确转录)。
|
||||||
|
|
||||||
|
#### 示例:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "OpenBMB/VoxCPM2",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "audio",
|
||||||
|
"audio_url": {
|
||||||
|
"url": "https://example.com/reference.wav"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "这是接下来要说的新内容。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"prompt_text": "这是参考音频中实际说的文字。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 通用 Metadata 参数 (适用于所有 VoxCPM2 模式)
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 说明 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `prompt_text` | String | 仅 Ultimate Cloning | 参考音频的逐字稿。仅在极致克隆模式下需要。 |
|
||||||
|
| `control_instruction` | String | 否 | 自然语言指令,用于控制 Voice Design 或 Controllable Cloning 的风格(如 "angry", "whispering", "fast")。 |
|
||||||
|
| `min_len` | Integer | 否 | 最小生成长度,默认 `2`。 |
|
||||||
|
| `max_len` | Integer | 否 | 最大生成长度,默认 `4096`。 |
|
||||||
|
| `inference_timesteps` | Integer | 否 | 推理步数,默认 `10`。 |
|
||||||
|
| `cfg_value` | Float | 否 | CFG 值,默认 `2.0`。 |
|
||||||
|
| `retry_badcase_ratio_threshold` | Float | 否 | 坏案例重试阈值,默认 `6.0`。 |
|
||||||
|
|
||||||
|
#### 返回结果示例
|
||||||
|
与 VoxCPM 0.5B/1.5 相同,返回包含 Base64 编码 WAV 音频的 JSON 对象。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "chatcmpl-uuid...",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": 1710000000,
|
||||||
|
"model": "OpenBMB/VoxCPM2",
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "audio",
|
||||||
|
"audio_url": {
|
||||||
|
"url": "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"finish_reason": "stop"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": null
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -41,6 +41,7 @@ deepseek-ai/DeepSeek-OCR-2 deepseek-ai ocr
|
|||||||
Tencent-Hunyuan/HunyuanOCR Tencent-Hunyuan ocr ✔
|
Tencent-Hunyuan/HunyuanOCR Tencent-Hunyuan ocr ✔
|
||||||
PaddlePaddle/PaddleOCR-VL PaddlePaddle ocr ✔
|
PaddlePaddle/PaddleOCR-VL PaddlePaddle ocr ✔
|
||||||
PaddlePaddle/PaddleOCR-VL-1.5 PaddlePaddle ocr
|
PaddlePaddle/PaddleOCR-VL-1.5 PaddlePaddle ocr
|
||||||
|
PaddlePaddle/PaddleOCR-VL-1.6 PaddlePaddle ocr ✔
|
||||||
AI-ModelScope/RMBG-2.0 AI-ModelScope image ✔
|
AI-ModelScope/RMBG-2.0 AI-ModelScope image ✔
|
||||||
OpenBMB/VoxCPM-0.5B OpenBMB tts ✔
|
OpenBMB/VoxCPM-0.5B OpenBMB tts ✔
|
||||||
OpenBMB/VoxCPM1.5 OpenBMB tts ✔
|
OpenBMB/VoxCPM1.5 OpenBMB tts ✔
|
||||||
@@ -88,7 +89,7 @@ ZhipuAI/GLM-OCR ZhipuAI ocr ✔
|
|||||||
|
|
||||||
| Model | Languages | Model Id | License |
|
| Model | Languages | Model Id | License |
|
||||||
|-------|-----------|--------|---------|
|
|-------|-----------|--------|---------|
|
||||||
| **PaddleOCR-VL** | Multi | PaddlePaddle/PaddleOCR-VL <br> PaddlePaddle/PaddleOCR-VL-1.5 | [Apache 2.0](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md) |
|
| **PaddleOCR-VL** | Multi | PaddlePaddle/PaddleOCR-VL <br> PaddlePaddle/PaddleOCR-VL-1.5 <br> PaddlePaddle/PaddleOCR-VL-1.6 | [Apache 2.0](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md) |
|
||||||
| **Hunyuan-OCR** | Chinese | Tencent-Hunyuan/HunyuanOCR | [Tencent Hunyuan Community License](https://huggingface.co/tencent/HunyuanOCR/blob/main/LICENSE) |
|
| **Hunyuan-OCR** | Chinese | Tencent-Hunyuan/HunyuanOCR | [Tencent Hunyuan Community License](https://huggingface.co/tencent/HunyuanOCR/blob/main/LICENSE) |
|
||||||
| **DeepSeek-OCR** | Multi | deepseek-ai/DeepSeek-OCR <br> deepseek-ai/DeepSeek-OCR-2 | [MIT](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/mit.md) |
|
| **DeepSeek-OCR** | Multi | deepseek-ai/DeepSeek-OCR <br> deepseek-ai/DeepSeek-OCR-2 | [MIT](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/mit.md) |
|
||||||
| **GLM-OCR** | 8 | ZhipuAI/GLM-OCR | [MIT](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/mit.md) |
|
| **GLM-OCR** | 8 | ZhipuAI/GLM-OCR | [MIT](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/mit.md) |
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ deepseek-ai/DeepSeek-OCR-2 deepseek-ai ocr
|
|||||||
Tencent-Hunyuan/HunyuanOCR Tencent-Hunyuan ocr ✔
|
Tencent-Hunyuan/HunyuanOCR Tencent-Hunyuan ocr ✔
|
||||||
PaddlePaddle/PaddleOCR-VL PaddlePaddle ocr ✔
|
PaddlePaddle/PaddleOCR-VL PaddlePaddle ocr ✔
|
||||||
PaddlePaddle/PaddleOCR-VL-1.5 PaddlePaddle ocr
|
PaddlePaddle/PaddleOCR-VL-1.5 PaddlePaddle ocr
|
||||||
|
PaddlePaddle/PaddleOCR-VL-1.6 PaddlePaddle ocr ✔
|
||||||
AI-ModelScope/RMBG-2.0 AI-ModelScope image ✔
|
AI-ModelScope/RMBG-2.0 AI-ModelScope image ✔
|
||||||
OpenBMB/VoxCPM-0.5B OpenBMB tts ✔
|
OpenBMB/VoxCPM-0.5B OpenBMB tts ✔
|
||||||
OpenBMB/VoxCPM1.5 OpenBMB tts ✔
|
OpenBMB/VoxCPM1.5 OpenBMB tts ✔
|
||||||
@@ -86,7 +87,7 @@ ZhipuAI/GLM-OCR ZhipuAI ocr ✔
|
|||||||
|
|
||||||
| 模型 | 语言 | 模型id | 开源协议 |
|
| 模型 | 语言 | 模型id | 开源协议 |
|
||||||
|------|------|------|---------|
|
|------|------|------|---------|
|
||||||
| **PaddleOCR-VL** | 多语言 | PaddlePaddle/PaddleOCR-VL <br> PaddlePaddle/PaddleOCR-VL-1.5 | [Apache 2.0](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md) |
|
| **PaddleOCR-VL** | 多语言 | PaddlePaddle/PaddleOCR-VL <br> PaddlePaddle/PaddleOCR-VL-1.5 <br> PaddlePaddle/PaddleOCR-VL-1.6 | [Apache 2.0](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md) |
|
||||||
| **Hunyuan-OCR** | 中文 | Tencent-Hunyuan/HunyuanOCR | [腾讯混元社区许可协议](https://huggingface.co/tencent/HunyuanOCR/blob/main/LICENSE) |
|
| **Hunyuan-OCR** | 中文 | Tencent-Hunyuan/HunyuanOCR | [腾讯混元社区许可协议](https://huggingface.co/tencent/HunyuanOCR/blob/main/LICENSE) |
|
||||||
| **DeepSeek-OCR** | 多语言 | deepseek-ai/DeepSeek-OCR <br> deepseek-ai/DeepSeek-OCR-2 | [MIT](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/mit.md) |
|
| **DeepSeek-OCR** | 多语言 | deepseek-ai/DeepSeek-OCR <br> deepseek-ai/DeepSeek-OCR-2 | [MIT](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/mit.md) |
|
||||||
| **GLM-OCR** | 8 | ZhipuAI/GLM-OCR | [MIT](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/mit.md) |
|
| **GLM-OCR** | 8 | ZhipuAI/GLM-OCR | [MIT](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/mit.md) |
|
||||||
|
|||||||
+1
-1
@@ -298,7 +298,7 @@ pub(crate) fn run_run(args: RunArgs) -> anyhow::Result<()> {
|
|||||||
WhichModel::HunyuanOCR => {
|
WhichModel::HunyuanOCR => {
|
||||||
hunyuan_ocr::HunyuanORExec::run(&input, output.as_deref(), &weight_path)?;
|
hunyuan_ocr::HunyuanORExec::run(&input, output.as_deref(), &weight_path)?;
|
||||||
}
|
}
|
||||||
WhichModel::PaddleOCRVL | WhichModel::PaddleOCRVL1_5 => {
|
WhichModel::PaddleOCRVL | WhichModel::PaddleOCRVL1_5 | WhichModel::PaddleOCRVL1_6 => {
|
||||||
paddleocr_vl::PaddleOVLExec::run(&input, output.as_deref(), &weight_path)?;
|
paddleocr_vl::PaddleOVLExec::run(&input, output.as_deref(), &weight_path)?;
|
||||||
}
|
}
|
||||||
WhichModel::RMBG2_0 => {
|
WhichModel::RMBG2_0 => {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::{
|
|||||||
InferenceModel, MultiModalData,
|
InferenceModel, MultiModalData,
|
||||||
sample::{get_logit_processor, use_repeat_penalty},
|
sample::{get_logit_processor, use_repeat_penalty},
|
||||||
},
|
},
|
||||||
params::chat::{ChatCompletionChunkResponse, ChatCompletionResponse},
|
params::chat::{ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse},
|
||||||
tokenizer::TokenizerModel,
|
tokenizer::TokenizerModel,
|
||||||
utils::response_utils::{
|
utils::response_utils::{
|
||||||
build_chunk_response_with_reasoning, build_chunk_response_with_usage,
|
build_chunk_response_with_reasoning, build_chunk_response_with_usage,
|
||||||
@@ -366,3 +366,116 @@ pub fn generate_stream_generic<M: InferenceModel>(
|
|||||||
};
|
};
|
||||||
Ok(stream)
|
Ok(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PrepareData {
|
||||||
|
pub in_reasoning: bool,
|
||||||
|
pub input_ids: Tensor,
|
||||||
|
pub multi_model_data: MultiModalData,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GenerationDataProvider {
|
||||||
|
fn get_temperature(&self, req_temp: Option<f32>) -> Option<f32> {
|
||||||
|
req_temp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_top_p(&self, req_top_p: Option<f32>) -> Option<f32> {
|
||||||
|
req_top_p
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_top_k(&self, top_k: Option<usize>) -> Option<usize> {
|
||||||
|
top_k
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_in_reasoning(&self, text: &str) -> bool {
|
||||||
|
text.ends_with("<think>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_multi_model_data(&self) -> MultiModalData {
|
||||||
|
MultiModalData::new(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data(&self, mes: &ChatCompletionParameters) -> Result<PrepareData>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_generate_model {
|
||||||
|
($struct_name: ty) => {
|
||||||
|
impl<'a> $crate::models::GenerateModel for $struct_name {
|
||||||
|
fn generate(
|
||||||
|
&mut self,
|
||||||
|
mes: $crate::params::chat::ChatCompletionParameters,
|
||||||
|
) -> anyhow::Result<$crate::params::chat::ChatCompletionResponse> {
|
||||||
|
let seed = mes.seed.unwrap_or(299792458) as u64;
|
||||||
|
let sample_len = mes.max_tokens.unwrap_or(1024);
|
||||||
|
let temperature = self.get_temperature(mes.temperature);
|
||||||
|
let top_p = self.get_top_p(mes.top_p);
|
||||||
|
let top_k = self.get_top_k(mes.top_k);
|
||||||
|
let prepare_data = self.get_data(&mes)?;
|
||||||
|
let input_ids = prepare_data.input_ids;
|
||||||
|
let data = prepare_data.multi_model_data;
|
||||||
|
let mut ctx = $crate::models::common::generate::GenerationContext::new(
|
||||||
|
temperature,
|
||||||
|
top_p,
|
||||||
|
top_k,
|
||||||
|
mes.repeat_penalty,
|
||||||
|
mes.repeat_last_n,
|
||||||
|
seed,
|
||||||
|
input_ids.dim(1)?,
|
||||||
|
sample_len,
|
||||||
|
self.device.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$crate::models::common::generate::generate_generic(
|
||||||
|
&mut self.model,
|
||||||
|
&self.tokenizer,
|
||||||
|
input_ids,
|
||||||
|
data,
|
||||||
|
&mut ctx,
|
||||||
|
&self.model_name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_stream(
|
||||||
|
&mut self,
|
||||||
|
mes: $crate::params::chat::ChatCompletionParameters,
|
||||||
|
) -> anyhow::Result<
|
||||||
|
Box<
|
||||||
|
dyn rocket::futures::Stream<
|
||||||
|
Item = anyhow::Result<
|
||||||
|
$crate::params::chat::ChatCompletionChunkResponse,
|
||||||
|
>,
|
||||||
|
> + Send
|
||||||
|
+ Unpin
|
||||||
|
+ '_,
|
||||||
|
>,
|
||||||
|
> {
|
||||||
|
let seed = mes.seed.unwrap_or(299792458) as u64;
|
||||||
|
let prepare_data = self.get_data(&mes)?;
|
||||||
|
let input_ids = prepare_data.input_ids;
|
||||||
|
let data = prepare_data.multi_model_data;
|
||||||
|
let in_reasoning = prepare_data.in_reasoning;
|
||||||
|
let sample_len = mes.max_tokens.unwrap_or(1024);
|
||||||
|
let temperature = self.get_temperature(mes.temperature);
|
||||||
|
let top_p = self.get_top_p(mes.top_p);
|
||||||
|
let top_k = self.get_top_k(mes.top_k);
|
||||||
|
let stream = $crate::models::common::generate::generate_stream_generic(
|
||||||
|
&mut self.model,
|
||||||
|
&self.tokenizer,
|
||||||
|
input_ids,
|
||||||
|
data,
|
||||||
|
temperature,
|
||||||
|
top_p,
|
||||||
|
top_k,
|
||||||
|
mes.repeat_penalty,
|
||||||
|
mes.repeat_last_n,
|
||||||
|
seed,
|
||||||
|
sample_len,
|
||||||
|
in_reasoning,
|
||||||
|
&self.device,
|
||||||
|
&self.model_name,
|
||||||
|
)?;
|
||||||
|
Ok(Box::new(Box::pin(stream)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ pub enum WhichModel {
|
|||||||
PaddleOCRVL,
|
PaddleOCRVL,
|
||||||
#[value(name = "PaddlePaddle/PaddleOCR-VL-1.5")]
|
#[value(name = "PaddlePaddle/PaddleOCR-VL-1.5")]
|
||||||
PaddleOCRVL1_5,
|
PaddleOCRVL1_5,
|
||||||
|
#[value(name = "PaddlePaddle/PaddleOCR-VL-1.6")]
|
||||||
|
PaddleOCRVL1_6,
|
||||||
#[value(name = "AI-ModelScope/RMBG-2.0")]
|
#[value(name = "AI-ModelScope/RMBG-2.0")]
|
||||||
RMBG2_0,
|
RMBG2_0,
|
||||||
#[value(name = "OpenBMB/VoxCPM-0.5B")]
|
#[value(name = "OpenBMB/VoxCPM-0.5B")]
|
||||||
@@ -165,7 +167,8 @@ impl WhichModel {
|
|||||||
| WhichModel::HunyuanOCR
|
| WhichModel::HunyuanOCR
|
||||||
| WhichModel::GlmOCR
|
| WhichModel::GlmOCR
|
||||||
| WhichModel::PaddleOCRVL
|
| WhichModel::PaddleOCRVL
|
||||||
| WhichModel::PaddleOCRVL1_5 => "ocr",
|
| WhichModel::PaddleOCRVL1_5
|
||||||
|
| WhichModel::PaddleOCRVL1_6 => "ocr",
|
||||||
// ASR models
|
// ASR models
|
||||||
WhichModel::Qwen3ASR0_6B
|
WhichModel::Qwen3ASR0_6B
|
||||||
| WhichModel::Qwen3ASR1_7B
|
| WhichModel::Qwen3ASR1_7B
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
use crate::{
|
use crate::models::common::{
|
||||||
models::common::{
|
MultiModalData,
|
||||||
MultiModalData,
|
generate::{GenerationDataProvider, PrepareData},
|
||||||
generate::{GenerationContext, generate_generic, generate_stream_generic},
|
|
||||||
},
|
|
||||||
params::chat::{ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse},
|
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use candle_core::{DType, Device};
|
use candle_core::{DType, Device};
|
||||||
use candle_nn::VarBuilder;
|
use candle_nn::VarBuilder;
|
||||||
use rocket::futures::Stream;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
models::{
|
models::deepseek_ocr::{
|
||||||
GenerateModel,
|
config::DeepseekOCRConfig, model::DeepseekOCRModel, processor::DeepseekOCRProcessor,
|
||||||
deepseek_ocr::{
|
|
||||||
config::DeepseekOCRConfig, model::DeepseekOCRModel, processor::DeepseekOCRProcessor,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tokenizer::TokenizerModel,
|
tokenizer::TokenizerModel,
|
||||||
utils::{extract_metadata_value, find_type_files, get_device, get_dtype},
|
utils::{extract_metadata_value, find_type_files, get_device, get_dtype},
|
||||||
@@ -24,9 +17,7 @@ use crate::{
|
|||||||
pub struct DeepseekOCRGenerateModel {
|
pub struct DeepseekOCRGenerateModel {
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
processor: DeepseekOCRProcessor,
|
processor: DeepseekOCRProcessor,
|
||||||
deepseekocr_model: DeepseekOCRModel,
|
model: DeepseekOCRModel,
|
||||||
// bos_token_id: u32,
|
|
||||||
// eos_token_id: u32,
|
|
||||||
device: Device,
|
device: Device,
|
||||||
size: Vec<u32>,
|
size: Vec<u32>,
|
||||||
model_name: String,
|
model_name: String,
|
||||||
@@ -52,19 +43,15 @@ impl DeepseekOCRGenerateModel {
|
|||||||
1usize
|
1usize
|
||||||
};
|
};
|
||||||
let processor = DeepseekOCRProcessor::new(device, dtype, version)?;
|
let processor = DeepseekOCRProcessor::new(device, dtype, version)?;
|
||||||
// let eos_token_id = cfg.eos_token_id;
|
|
||||||
// let bos_token_id = cfg.bos_token_id;
|
|
||||||
let model_list = find_type_files(path, "safetensors")?;
|
let model_list = find_type_files(path, "safetensors")?;
|
||||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, device)? };
|
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, device)? };
|
||||||
let deepseekocr_model = DeepseekOCRModel::new(vb, cfg, version)?;
|
let model = DeepseekOCRModel::new(vb, cfg, version)?;
|
||||||
let size = vec![512u32, 640, 1024, 1280];
|
let size = vec![512u32, 640, 1024, 1280];
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
tokenizer,
|
tokenizer,
|
||||||
processor,
|
processor,
|
||||||
deepseekocr_model,
|
model,
|
||||||
// bos_token_id,
|
|
||||||
// eos_token_id,
|
|
||||||
device: device.clone(),
|
device: device.clone(),
|
||||||
size,
|
size,
|
||||||
model_name: model_name.to_string(),
|
model_name: model_name.to_string(),
|
||||||
@@ -73,8 +60,8 @@ impl DeepseekOCRGenerateModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateModel for DeepseekOCRGenerateModel {
|
impl GenerationDataProvider for DeepseekOCRGenerateModel {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_data(&self, mes: &crate::params::chat::ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
let base_size = extract_metadata_value::<u32>(&mes.metadata, "base_size").unwrap_or(640);
|
let base_size = extract_metadata_value::<u32>(&mes.metadata, "base_size").unwrap_or(640);
|
||||||
let base_size = if self.size.contains(&base_size) {
|
let base_size = if self.size.contains(&base_size) {
|
||||||
base_size
|
base_size
|
||||||
@@ -92,93 +79,20 @@ impl GenerateModel for DeepseekOCRGenerateModel {
|
|||||||
let crop_mode = extract_metadata_value::<bool>(&mes.metadata, "crop_mode").unwrap_or(false);
|
let crop_mode = extract_metadata_value::<bool>(&mes.metadata, "crop_mode").unwrap_or(false);
|
||||||
let (input_ids, images_ori, image_crop, images_seq_mask, images_spatial_crop_t) = self
|
let (input_ids, images_ori, image_crop, images_seq_mask, images_spatial_crop_t) = self
|
||||||
.processor
|
.processor
|
||||||
.process_info(&mes, &self.tokenizer, base_size, image_size, crop_mode)?;
|
.process_info(mes, &self.tokenizer, base_size, image_size, crop_mode)?;
|
||||||
let max_tokens = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let mut ctx = GenerationContext::new(
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
mes.seed.unwrap_or(34562) as u64,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
max_tokens,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
let data_vec = vec![
|
let data_vec = vec![
|
||||||
Some(images_ori),
|
Some(images_ori),
|
||||||
Some(image_crop),
|
Some(image_crop),
|
||||||
Some(images_seq_mask),
|
Some(images_seq_mask),
|
||||||
Some(images_spatial_crop_t),
|
Some(images_spatial_crop_t),
|
||||||
];
|
];
|
||||||
let data = MultiModalData::new(data_vec);
|
let multi_model_data = MultiModalData::new(data_vec);
|
||||||
generate_generic(
|
Ok(PrepareData {
|
||||||
&mut self.deepseekocr_model,
|
in_reasoning: false,
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
input_ids,
|
||||||
data,
|
multi_model_data,
|
||||||
&mut ctx,
|
})
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_stream(
|
|
||||||
&mut self,
|
|
||||||
mes: ChatCompletionParameters,
|
|
||||||
) -> Result<
|
|
||||||
Box<
|
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
|
||||||
+ Send
|
|
||||||
+ Unpin
|
|
||||||
+ '_,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let base_size = extract_metadata_value::<u32>(&mes.metadata, "base_size").unwrap_or(640);
|
|
||||||
let base_size = if self.size.contains(&base_size) {
|
|
||||||
base_size
|
|
||||||
} else {
|
|
||||||
640
|
|
||||||
};
|
|
||||||
let image_size = extract_metadata_value::<u32>(&mes.metadata, "image_size").unwrap_or(640);
|
|
||||||
let image_size = if self.size.contains(&image_size) {
|
|
||||||
image_size
|
|
||||||
} else {
|
|
||||||
640
|
|
||||||
};
|
|
||||||
let base_size = if self.version == 2 { 1024 } else { base_size };
|
|
||||||
let image_size = if self.version == 2 { 768 } else { image_size };
|
|
||||||
let crop_mode = extract_metadata_value::<bool>(&mes.metadata, "crop_mode").unwrap_or(false);
|
|
||||||
let (input_ids, images_ori, image_crop, images_seq_mask, images_spatial_crop_t) = self
|
|
||||||
.processor
|
|
||||||
.process_info(&mes, &self.tokenizer, base_size, image_size, crop_mode)?;
|
|
||||||
let data_vec = vec![
|
|
||||||
images_ori.into(),
|
|
||||||
image_crop.into(),
|
|
||||||
images_seq_mask.into(),
|
|
||||||
images_spatial_crop_t.into(),
|
|
||||||
];
|
|
||||||
let data = MultiModalData::new(data_vec);
|
|
||||||
|
|
||||||
let temperature = mes.temperature;
|
|
||||||
let top_p = mes.top_p;
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let max_tokens = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.deepseekocr_model,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
temperature,
|
|
||||||
top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
max_tokens,
|
|
||||||
false,
|
|
||||||
&self.device,
|
|
||||||
&self.model_name,
|
|
||||||
)?;
|
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(DeepseekOCRGenerateModel);
|
||||||
|
|||||||
@@ -3,18 +3,16 @@ use std::collections::HashMap;
|
|||||||
use crate::{
|
use crate::{
|
||||||
models::common::{
|
models::common::{
|
||||||
MultiModalData,
|
MultiModalData,
|
||||||
generate::{GenerationContext, generate_generic, generate_stream_generic},
|
generate::{GenerationDataProvider, PrepareData},
|
||||||
},
|
},
|
||||||
params::chat::{ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse},
|
params::chat::ChatCompletionParameters,
|
||||||
};
|
};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use candle_core::{DType, Device, pickle::read_all_with_key};
|
use candle_core::{DType, Device, pickle::read_all_with_key};
|
||||||
use candle_nn::VarBuilder;
|
use candle_nn::VarBuilder;
|
||||||
use rocket::futures::Stream;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
models::{
|
models::{
|
||||||
GenerateModel,
|
|
||||||
fun_asr_nano::{
|
fun_asr_nano::{
|
||||||
config::FunASRNanoConfig, model::FunAsrNanoModel, processor::FunAsrNanoProcessor,
|
config::FunASRNanoConfig, model::FunAsrNanoModel, processor::FunAsrNanoProcessor,
|
||||||
},
|
},
|
||||||
@@ -27,7 +25,7 @@ use crate::{
|
|||||||
pub struct FunAsrNanoGenerateModel {
|
pub struct FunAsrNanoGenerateModel {
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
processor: FunAsrNanoProcessor,
|
processor: FunAsrNanoProcessor,
|
||||||
fun_asr_nano: FunAsrNanoModel,
|
model: FunAsrNanoModel,
|
||||||
device: Device,
|
device: Device,
|
||||||
dtype: DType,
|
dtype: DType,
|
||||||
generation_config: Qwen3GenerationConfig,
|
generation_config: Qwen3GenerationConfig,
|
||||||
@@ -75,7 +73,7 @@ impl FunAsrNanoGenerateModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let vb = VarBuilder::from_tensors(dict_to_hashmap, dtype, &device);
|
let vb = VarBuilder::from_tensors(dict_to_hashmap, dtype, &device);
|
||||||
let fun_asr_nano =
|
let model =
|
||||||
FunAsrNanoModel::new(vb, &cfg, &llm_cfg, generation_config.eos_token_id.clone())?;
|
FunAsrNanoModel::new(vb, &cfg, &llm_cfg, generation_config.eos_token_id.clone())?;
|
||||||
let model_name = std::path::Path::new(path)
|
let model_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -85,7 +83,7 @@ impl FunAsrNanoGenerateModel {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
tokenizer,
|
tokenizer,
|
||||||
processor,
|
processor,
|
||||||
fun_asr_nano,
|
model,
|
||||||
device,
|
device,
|
||||||
dtype,
|
dtype,
|
||||||
generation_config,
|
generation_config,
|
||||||
@@ -94,79 +92,30 @@ impl FunAsrNanoGenerateModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateModel for FunAsrNanoGenerateModel {
|
impl GenerationDataProvider for FunAsrNanoGenerateModel {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_temperature(&self, req_temp: Option<f32>) -> Option<f32> {
|
||||||
let temperature = mes
|
Some(req_temp.unwrap_or(self.generation_config.temperature))
|
||||||
.temperature
|
|
||||||
.unwrap_or(self.generation_config.temperature);
|
|
||||||
let top_p = mes.top_p.unwrap_or(self.generation_config.top_p);
|
|
||||||
let top_k = self.generation_config.top_k;
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let max_tokens = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let (speech, fbank_mask, input_ids) = self.processor.process_info(&mes, &self.tokenizer)?;
|
|
||||||
let speech = speech.to_dtype(self.dtype)?;
|
|
||||||
let mut ctx = GenerationContext::new(
|
|
||||||
temperature.into(),
|
|
||||||
top_p.into(),
|
|
||||||
top_k.into(),
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
max_tokens,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data_vec = vec![speech.into(), fbank_mask.into()];
|
|
||||||
let data = MultiModalData::new(data_vec);
|
|
||||||
generate_generic(
|
|
||||||
&mut self.fun_asr_nano,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
&mut ctx,
|
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_stream(
|
fn get_top_p(&self, req_top_p: Option<f32>) -> Option<f32> {
|
||||||
&mut self,
|
Some(req_top_p.unwrap_or(self.generation_config.top_p))
|
||||||
mes: ChatCompletionParameters,
|
}
|
||||||
) -> Result<
|
|
||||||
Box<
|
fn get_top_k(&self, top_k: Option<usize>) -> Option<usize> {
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
Some(top_k.unwrap_or(self.generation_config.top_k))
|
||||||
+ Send
|
}
|
||||||
+ Unpin
|
fn get_data(&self, mes: &ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
+ '_,
|
let (speech, fbank_mask, input_ids) = self.processor.process_info(mes, &self.tokenizer)?;
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let temperature = mes
|
|
||||||
.temperature
|
|
||||||
.unwrap_or(self.generation_config.temperature);
|
|
||||||
let top_p = mes.top_p.unwrap_or(self.generation_config.top_p);
|
|
||||||
let top_k = self.generation_config.top_k;
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let max_tokens = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let (speech, fbank_mask, input_ids) = self.processor.process_info(&mes, &self.tokenizer)?;
|
|
||||||
let speech = speech.to_dtype(self.dtype)?;
|
let speech = speech.to_dtype(self.dtype)?;
|
||||||
let data_vec = vec![speech.into(), fbank_mask.into()];
|
let data_vec = vec![speech.into(), fbank_mask.into()];
|
||||||
let data = MultiModalData::new(data_vec);
|
let multi_model_data = MultiModalData::new(data_vec);
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.fun_asr_nano,
|
Ok(PrepareData {
|
||||||
&self.tokenizer,
|
in_reasoning: false,
|
||||||
input_ids,
|
input_ids,
|
||||||
data,
|
multi_model_data,
|
||||||
temperature.into(),
|
})
|
||||||
top_p.into(),
|
|
||||||
top_k.into(),
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
max_tokens,
|
|
||||||
false,
|
|
||||||
&self.device,
|
|
||||||
&self.model_name,
|
|
||||||
)?;
|
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(FunAsrNanoGenerateModel);
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
models::common::{
|
models::common::{
|
||||||
MultiModalData,
|
MultiModalData,
|
||||||
generate::{GenerationContext, generate_generic, generate_stream_generic},
|
generate::{GenerationDataProvider, PrepareData},
|
||||||
},
|
},
|
||||||
params::chat::{ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse},
|
params::chat::ChatCompletionParameters,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use candle_core::{DType, Device, Tensor};
|
use candle_core::{DType, Device, Tensor};
|
||||||
use candle_nn::VarBuilder;
|
use candle_nn::VarBuilder;
|
||||||
use rocket::futures::Stream;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chat_template::ChatTemplate,
|
chat_template::ChatTemplate,
|
||||||
models::{
|
models::glm_asr_nano::{
|
||||||
GenerateModel,
|
config::GlmAsrNanoConfig, model::GlmAsrNanoModel, processor::GlmAsrNanoProcessor,
|
||||||
glm_asr_nano::{
|
|
||||||
config::GlmAsrNanoConfig, model::GlmAsrNanoModel, processor::GlmAsrNanoProcessor,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tokenizer::TokenizerModel,
|
tokenizer::TokenizerModel,
|
||||||
utils::{find_type_files, get_device, get_dtype},
|
utils::{find_type_files, get_device, get_dtype},
|
||||||
@@ -26,7 +22,7 @@ pub struct GlmAsrNanoGenerateModel<'a> {
|
|||||||
chat_template: ChatTemplate<'a>,
|
chat_template: ChatTemplate<'a>,
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
processor: GlmAsrNanoProcessor,
|
processor: GlmAsrNanoProcessor,
|
||||||
glm_asr_nano: GlmAsrNanoModel,
|
model: GlmAsrNanoModel,
|
||||||
device: Device,
|
device: Device,
|
||||||
dtype: DType,
|
dtype: DType,
|
||||||
model_name: String,
|
model_name: String,
|
||||||
@@ -45,7 +41,7 @@ impl<'a> GlmAsrNanoGenerateModel<'a> {
|
|||||||
let model_list = find_type_files(path, "safetensors")?;
|
let model_list = find_type_files(path, "safetensors")?;
|
||||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, &device)? };
|
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, &device)? };
|
||||||
let eos_ids = vec![59246u32, 59253, 59255];
|
let eos_ids = vec![59246u32, 59253, 59255];
|
||||||
let glm_asr_nano = GlmAsrNanoModel::new(vb, cfg, eos_ids)?;
|
let model = GlmAsrNanoModel::new(vb, cfg, eos_ids)?;
|
||||||
let model_name = std::path::Path::new(path)
|
let model_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
@@ -55,7 +51,7 @@ impl<'a> GlmAsrNanoGenerateModel<'a> {
|
|||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
processor,
|
processor,
|
||||||
glm_asr_nano,
|
model,
|
||||||
device,
|
device,
|
||||||
dtype,
|
dtype,
|
||||||
model_name,
|
model_name,
|
||||||
@@ -63,77 +59,22 @@ impl<'a> GlmAsrNanoGenerateModel<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GenerateModel for GlmAsrNanoGenerateModel<'a> {
|
impl<'a> GenerationDataProvider for GlmAsrNanoGenerateModel<'a> {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_data(&self, mes: &ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
let render_text: String = self.chat_template.apply_chat_template(mes)?;
|
||||||
let render_text: String = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let (input_features, audio_token_lengths, replace_text) =
|
let (input_features, audio_token_lengths, replace_text) =
|
||||||
self.processor.process_info(&mes, &render_text)?;
|
self.processor.process_info(mes, &render_text)?;
|
||||||
let input_ids = self.tokenizer.text_encode(replace_text, &self.device)?;
|
let input_ids = self.tokenizer.text_encode(replace_text, &self.device)?;
|
||||||
let input_features = input_features.to_dtype(self.dtype)?;
|
let input_features = input_features.to_dtype(self.dtype)?;
|
||||||
let audio_token_lengths = Tensor::new(audio_token_lengths, &self.device)?;
|
let audio_token_lengths = Tensor::new(audio_token_lengths, &self.device)?;
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let mut ctx = GenerationContext::new(
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
mes.top_k,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data_vec = vec![input_features.into(), audio_token_lengths.into()];
|
let data_vec = vec![input_features.into(), audio_token_lengths.into()];
|
||||||
let data = MultiModalData::new(data_vec);
|
let multi_model_data = MultiModalData::new(data_vec);
|
||||||
generate_generic(
|
Ok(PrepareData {
|
||||||
&mut self.glm_asr_nano,
|
in_reasoning: false,
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
input_ids,
|
||||||
data,
|
multi_model_data,
|
||||||
&mut ctx,
|
})
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_stream(
|
|
||||||
&mut self,
|
|
||||||
mes: ChatCompletionParameters,
|
|
||||||
) -> Result<
|
|
||||||
Box<
|
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
|
||||||
+ Send
|
|
||||||
+ Unpin
|
|
||||||
+ '_,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let render_text = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let (input_features, audio_token_lengths, replace_text) =
|
|
||||||
self.processor.process_info(&mes, &render_text)?;
|
|
||||||
let input_ids = self.tokenizer.text_encode(replace_text, &self.device)?;
|
|
||||||
let input_features = input_features.to_dtype(self.dtype)?;
|
|
||||||
let audio_token_lengths = Tensor::new(audio_token_lengths, &self.device)?;
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let data_vec = vec![input_features.into(), audio_token_lengths.into()];
|
|
||||||
let data = MultiModalData::new(data_vec);
|
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.glm_asr_nano,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
false,
|
|
||||||
&self.device,
|
|
||||||
&self.model_name,
|
|
||||||
)?;
|
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(GlmAsrNanoGenerateModel<'a>);
|
||||||
|
|||||||
+20
-108
@@ -2,33 +2,26 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
models::common::{
|
models::common::{
|
||||||
MultiModalData,
|
MultiModalData,
|
||||||
generate::{GenerationContext, generate_generic, generate_stream_generic},
|
generate::{GenerationDataProvider, PrepareData},
|
||||||
},
|
},
|
||||||
params::chat::{ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse},
|
params::chat::ChatCompletionParameters,
|
||||||
};
|
};
|
||||||
use anyhow::{Result, anyhow};
|
|
||||||
use candle_core::{DType, Device};
|
|
||||||
use candle_nn::VarBuilder;
|
|
||||||
use rocket::futures::Stream;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
// chat_template::ChatTemplate,
|
models::glm_ocr::{
|
||||||
models::{
|
config::{GlmOcrConfig, GlmOcrGenerationConfig},
|
||||||
GenerateModel,
|
model::GlmOcrModel,
|
||||||
glm_ocr::{
|
processor::GlmOcrProcessor,
|
||||||
config::{GlmOcrConfig, GlmOcrGenerationConfig},
|
|
||||||
model::GlmOcrModel,
|
|
||||||
processor::GlmOcrProcessor,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tokenizer::TokenizerModel,
|
tokenizer::TokenizerModel,
|
||||||
utils::{
|
utils::{
|
||||||
extract_user_text, find_type_files, get_device, get_dtype, img_utils::extract_image_url,
|
extract_user_text, find_type_files, get_device, get_dtype, img_utils::extract_image_url,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use candle_core::{DType, Device};
|
||||||
|
use candle_nn::VarBuilder;
|
||||||
|
|
||||||
pub struct GlmOcrGenerateModel {
|
pub struct GlmOcrGenerateModel {
|
||||||
// chat_template: ChatTemplate<'a>,
|
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
processor: GlmOcrProcessor,
|
processor: GlmOcrProcessor,
|
||||||
model: GlmOcrModel,
|
model: GlmOcrModel,
|
||||||
@@ -44,7 +37,6 @@ pub struct GlmOcrGenerateModel {
|
|||||||
|
|
||||||
impl GlmOcrGenerateModel {
|
impl GlmOcrGenerateModel {
|
||||||
pub fn init(path: &str, device: Option<&Device>, dtype: Option<DType>) -> Result<Self> {
|
pub fn init(path: &str, device: Option<&Device>, dtype: Option<DType>) -> Result<Self> {
|
||||||
// let chat_template = ChatTemplate::init(path)?;
|
|
||||||
let tokenizer = TokenizerModel::init(path)?;
|
let tokenizer = TokenizerModel::init(path)?;
|
||||||
let config_path = path.to_string() + "/config.json";
|
let config_path = path.to_string() + "/config.json";
|
||||||
let cfg: GlmOcrConfig = serde_json::from_slice(&std::fs::read(config_path)?)?;
|
let cfg: GlmOcrConfig = serde_json::from_slice(&std::fs::read(config_path)?)?;
|
||||||
@@ -64,7 +56,6 @@ impl GlmOcrGenerateModel {
|
|||||||
.unwrap_or("ZhipuAI/GLM-OCR")
|
.unwrap_or("ZhipuAI/GLM-OCR")
|
||||||
.to_string();
|
.to_string();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
// chat_template,
|
|
||||||
tokenizer,
|
tokenizer,
|
||||||
processor,
|
processor,
|
||||||
model,
|
model,
|
||||||
@@ -80,17 +71,15 @@ impl GlmOcrGenerateModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenerateModel for GlmOcrGenerateModel {
|
impl GenerationDataProvider for GlmOcrGenerateModel {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_data(&self, mes: &ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
let image_urls = extract_image_url(mes);
|
||||||
// Extract image path and prompt from messages
|
|
||||||
let image_urls = extract_image_url(&mes);
|
|
||||||
let image_path = image_urls
|
let image_path = image_urls
|
||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| anyhow!("No image provided"))?;
|
.ok_or_else(|| anyhow!("No image provided"))?;
|
||||||
|
|
||||||
// Get prompt text from messages
|
// Get prompt text from messages
|
||||||
let mut prompt = extract_user_text(&mes)?;
|
let mut prompt = extract_user_text(mes)?;
|
||||||
if prompt.chars().count() == 0 {
|
if prompt.chars().count() == 0 {
|
||||||
prompt = "Extract all text from this image.".to_string()
|
prompt = "Extract all text from this image.".to_string()
|
||||||
}
|
}
|
||||||
@@ -108,95 +97,18 @@ impl GenerateModel for GlmOcrGenerateModel {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let input_ids = processed.input_ids;
|
let input_ids = processed.input_ids;
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let mut ctx = GenerationContext::new(
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
mes.top_k,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data_vec = vec![
|
let data_vec = vec![
|
||||||
processed.pixel_values.into(),
|
processed.pixel_values.into(),
|
||||||
processed.grid_thw.into(),
|
processed.grid_thw.into(),
|
||||||
processed.image_mask.into(),
|
processed.image_mask.into(),
|
||||||
];
|
];
|
||||||
let data = MultiModalData::new(data_vec);
|
let multi_model_data = MultiModalData::new(data_vec);
|
||||||
generate_generic(
|
Ok(PrepareData {
|
||||||
&mut self.model,
|
in_reasoning: false,
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
input_ids,
|
||||||
data,
|
multi_model_data,
|
||||||
&mut ctx,
|
})
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_stream(
|
|
||||||
&mut self,
|
|
||||||
mes: ChatCompletionParameters,
|
|
||||||
) -> Result<
|
|
||||||
Box<
|
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
|
||||||
+ Send
|
|
||||||
+ Unpin
|
|
||||||
+ '_,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
// Extract image path and prompt from messages
|
|
||||||
let image_urls = extract_image_url(&mes);
|
|
||||||
let image_path = image_urls
|
|
||||||
.first()
|
|
||||||
.ok_or_else(|| anyhow!("No image provided"))?;
|
|
||||||
|
|
||||||
// Get prompt text from messages
|
|
||||||
let mut prompt = extract_user_text(&mes)?;
|
|
||||||
if prompt.chars().count() == 0 {
|
|
||||||
prompt = "Extract all text from this image.".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
let processed = self.processor.process_info(
|
|
||||||
image_path,
|
|
||||||
&prompt,
|
|
||||||
&self.tokenizer,
|
|
||||||
self.image_token_id,
|
|
||||||
self.image_start_token_id,
|
|
||||||
self.image_end_token_id,
|
|
||||||
self.patch_size,
|
|
||||||
self.temporal_patch_size,
|
|
||||||
self.spatial_merge_size,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let input_ids = processed.input_ids;
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let data_vec = vec![
|
|
||||||
processed.pixel_values.into(),
|
|
||||||
processed.grid_thw.into(),
|
|
||||||
processed.image_mask.into(),
|
|
||||||
];
|
|
||||||
let data = MultiModalData::new(data_vec);
|
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.model,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
false,
|
|
||||||
&self.device,
|
|
||||||
&self.model_name,
|
|
||||||
)?;
|
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(GlmOcrGenerateModel);
|
||||||
|
|||||||
@@ -1,24 +1,20 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
models::common::{
|
models::common::{
|
||||||
MultiModalData,
|
MultiModalData,
|
||||||
generate::{GenerationContext, generate_generic, generate_stream_generic},
|
generate::{GenerationDataProvider, PrepareData},
|
||||||
},
|
},
|
||||||
params::chat::{ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse},
|
params::chat::ChatCompletionParameters,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use candle_core::{DType, Device};
|
use candle_core::{DType, Device};
|
||||||
use candle_nn::VarBuilder;
|
use candle_nn::VarBuilder;
|
||||||
use rocket::futures::Stream;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chat_template::ChatTemplate,
|
chat_template::ChatTemplate,
|
||||||
models::{
|
models::hunyuan_ocr::{
|
||||||
GenerateModel,
|
config::{HunYuanVLConfig, HunyuanOCRGenerationConfig},
|
||||||
hunyuan_ocr::{
|
model::HunyuanVLModel,
|
||||||
config::{HunYuanVLConfig, HunyuanOCRGenerationConfig},
|
processor::HunyuanVLProcessor,
|
||||||
model::HunyuanVLModel,
|
|
||||||
processor::HunyuanVLProcessor,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tokenizer::TokenizerModel,
|
tokenizer::TokenizerModel,
|
||||||
utils::{find_type_files, get_device, get_dtype},
|
utils::{find_type_files, get_device, get_dtype},
|
||||||
@@ -28,7 +24,7 @@ pub struct HunyuanOCRGenerateModel<'a> {
|
|||||||
chat_template: ChatTemplate<'a>,
|
chat_template: ChatTemplate<'a>,
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
pre_processor: HunyuanVLProcessor,
|
pre_processor: HunyuanVLProcessor,
|
||||||
hunyuan_vl: HunyuanVLModel,
|
model: HunyuanVLModel,
|
||||||
device: Device,
|
device: Device,
|
||||||
generation_config: HunyuanOCRGenerationConfig,
|
generation_config: HunyuanOCRGenerationConfig,
|
||||||
model_name: String,
|
model_name: String,
|
||||||
@@ -49,8 +45,7 @@ impl<'a> HunyuanOCRGenerateModel<'a> {
|
|||||||
let generation_config_path = path.to_string() + "/generation_config.json";
|
let generation_config_path = path.to_string() + "/generation_config.json";
|
||||||
let generation_config: HunyuanOCRGenerationConfig =
|
let generation_config: HunyuanOCRGenerationConfig =
|
||||||
serde_json::from_slice(&std::fs::read(generation_config_path)?)?;
|
serde_json::from_slice(&std::fs::read(generation_config_path)?)?;
|
||||||
let hunyuan_vl =
|
let model = HunyuanVLModel::new(vb, cfg.clone(), generation_config.eos_token_id.clone())?;
|
||||||
HunyuanVLModel::new(vb, cfg.clone(), generation_config.eos_token_id.clone())?;
|
|
||||||
|
|
||||||
let model_name = std::path::Path::new(path)
|
let model_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -61,7 +56,7 @@ impl<'a> HunyuanOCRGenerateModel<'a> {
|
|||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
pre_processor,
|
pre_processor,
|
||||||
hunyuan_vl,
|
model,
|
||||||
device,
|
device,
|
||||||
generation_config,
|
generation_config,
|
||||||
model_name,
|
model_name,
|
||||||
@@ -69,72 +64,24 @@ impl<'a> HunyuanOCRGenerateModel<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GenerateModel for HunyuanOCRGenerateModel<'a> {
|
impl<'a> GenerationDataProvider for HunyuanOCRGenerateModel<'a> {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_temperature(&self, req_temp: Option<f32>) -> Option<f32> {
|
||||||
let temperature = mes
|
Some(req_temp.unwrap_or(self.generation_config.temperature))
|
||||||
.temperature
|
|
||||||
.unwrap_or(self.generation_config.temperature);
|
|
||||||
let top_p = mes.top_p.unwrap_or(self.generation_config.top_p);
|
|
||||||
let top_k = self.generation_config.top_k;
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let data = self
|
|
||||||
.pre_processor
|
|
||||||
.process_info(&mes, &self.tokenizer, &mes_render)?;
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let input_ids = data.input_ids;
|
|
||||||
let mut ctx = GenerationContext::new(
|
|
||||||
temperature.into(),
|
|
||||||
top_p.into(),
|
|
||||||
top_k.into(),
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data_vec = vec![
|
|
||||||
data.pixel_values,
|
|
||||||
data.image_grid_thw,
|
|
||||||
data.image_mask.into(),
|
|
||||||
data.position_ids.into(),
|
|
||||||
];
|
|
||||||
let data = MultiModalData::new(data_vec);
|
|
||||||
generate_generic(
|
|
||||||
&mut self.hunyuan_vl,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
&mut ctx,
|
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_stream(
|
fn get_top_p(&self, req_top_p: Option<f32>) -> Option<f32> {
|
||||||
&mut self,
|
Some(req_top_p.unwrap_or(self.generation_config.top_p))
|
||||||
mes: ChatCompletionParameters,
|
}
|
||||||
) -> Result<
|
|
||||||
Box<
|
fn get_top_k(&self, top_k: Option<usize>) -> Option<usize> {
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
Some(top_k.unwrap_or(self.generation_config.top_k))
|
||||||
+ Send
|
}
|
||||||
+ Unpin
|
|
||||||
+ '_,
|
fn get_data(&self, mes: &ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
>,
|
let mes_render = self.chat_template.apply_chat_template(mes)?;
|
||||||
> {
|
|
||||||
let temperature = mes
|
|
||||||
.temperature
|
|
||||||
.unwrap_or(self.generation_config.temperature);
|
|
||||||
let top_p = mes.top_p.unwrap_or(self.generation_config.top_p);
|
|
||||||
let top_k = self.generation_config.top_k;
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let data = self
|
let data = self
|
||||||
.pre_processor
|
.pre_processor
|
||||||
.process_info(&mes, &self.tokenizer, &mes_render)?;
|
.process_info(mes, &self.tokenizer, &mes_render)?;
|
||||||
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let input_ids = data.input_ids;
|
let input_ids = data.input_ids;
|
||||||
let data_vec = vec![
|
let data_vec = vec![
|
||||||
data.pixel_values,
|
data.pixel_values,
|
||||||
@@ -142,23 +89,13 @@ impl<'a> GenerateModel for HunyuanOCRGenerateModel<'a> {
|
|||||||
data.image_mask.into(),
|
data.image_mask.into(),
|
||||||
data.position_ids.into(),
|
data.position_ids.into(),
|
||||||
];
|
];
|
||||||
let data = MultiModalData::new(data_vec);
|
let multi_model_data = MultiModalData::new(data_vec);
|
||||||
let stream = generate_stream_generic(
|
Ok(PrepareData {
|
||||||
&mut self.hunyuan_vl,
|
in_reasoning: false,
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
input_ids,
|
||||||
data,
|
multi_model_data,
|
||||||
temperature.into(),
|
})
|
||||||
top_p.into(),
|
|
||||||
top_k.into(),
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
false,
|
|
||||||
&self.device,
|
|
||||||
&self.model_name,
|
|
||||||
)?;
|
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(HunyuanOCRGenerateModel<'a>);
|
||||||
|
|||||||
+15
-72
@@ -1,16 +1,9 @@
|
|||||||
use crate::models::common::MultiModalData;
|
use crate::models::common::generate::{GenerationDataProvider, PrepareData};
|
||||||
use crate::models::common::generate::{
|
|
||||||
GenerationContext, generate_generic, generate_stream_generic,
|
|
||||||
};
|
|
||||||
use crate::params::chat::{ChatCompletionParameters, ChatCompletionResponse};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chat_template::ChatTemplate,
|
chat_template::ChatTemplate,
|
||||||
models::{
|
models::lfm2::{
|
||||||
GenerateModel,
|
config::{Lfm2Config, Lfm2GenerateConfig},
|
||||||
lfm2::{
|
model::Lfm2Model,
|
||||||
config::{Lfm2Config, Lfm2GenerateConfig},
|
|
||||||
model::Lfm2Model,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tokenizer::TokenizerModel,
|
tokenizer::TokenizerModel,
|
||||||
utils::{find_type_files, get_device, get_dtype},
|
utils::{find_type_files, get_device, get_dtype},
|
||||||
@@ -62,68 +55,18 @@ impl<'a> Lfm2GenerateModel<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GenerateModel for Lfm2GenerateModel<'a> {
|
impl<'a> GenerationDataProvider for Lfm2GenerateModel<'a> {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_data(&self, mes: &crate::params::chat::ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
let mes_render = self.chat_template.apply_chat_template(mes)?;
|
||||||
|
let in_reasoning = self.is_in_reasoning(&mes_render);
|
||||||
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
let multi_model_data = self.get_multi_model_data();
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
Ok(PrepareData {
|
||||||
let mut ctx = GenerationContext::new(
|
in_reasoning,
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = MultiModalData::new(vec![]);
|
|
||||||
generate_generic(
|
|
||||||
&mut self.model,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
input_ids,
|
||||||
data,
|
multi_model_data,
|
||||||
&mut ctx,
|
})
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_stream(
|
|
||||||
&mut self,
|
|
||||||
mes: ChatCompletionParameters,
|
|
||||||
) -> Result<
|
|
||||||
Box<
|
|
||||||
dyn rocket::futures::Stream<
|
|
||||||
Item = Result<crate::params::chat::ChatCompletionChunkResponse, anyhow::Error>,
|
|
||||||
> + Send
|
|
||||||
+ Unpin
|
|
||||||
+ '_,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let data = MultiModalData::new(vec![]);
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.model,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
false,
|
|
||||||
&self.device,
|
|
||||||
&self.model_name,
|
|
||||||
)?;
|
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(Lfm2GenerateModel<'a>);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
models::common::{
|
models::common::{
|
||||||
MultiModalData,
|
MultiModalData,
|
||||||
generate::{GenerationContext, generate_generic, generate_stream_generic},
|
generate::{GenerationDataProvider, PrepareData},
|
||||||
},
|
},
|
||||||
params::chat::{ChatCompletionParameters, ChatCompletionResponse},
|
params::chat::ChatCompletionParameters,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use candle_core::{DType, Device};
|
use candle_core::{DType, Device};
|
||||||
@@ -12,7 +12,6 @@ use candle_nn::VarBuilder;
|
|||||||
use crate::{
|
use crate::{
|
||||||
chat_template::ChatTemplate,
|
chat_template::ChatTemplate,
|
||||||
models::{
|
models::{
|
||||||
GenerateModel,
|
|
||||||
lfm2::config::Lfm2GenerateConfig,
|
lfm2::config::Lfm2GenerateConfig,
|
||||||
lfm2vl::{config::Lfm2VLConfig, model::Lfm2VLModel, processor::Lfm2VLProcessor},
|
lfm2vl::{config::Lfm2VLConfig, model::Lfm2VLModel, processor::Lfm2VLProcessor},
|
||||||
},
|
},
|
||||||
@@ -60,82 +59,24 @@ impl<'a> Lfm2VLGenerateModel<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GenerateModel for Lfm2VLGenerateModel<'a> {
|
impl<'a> GenerationDataProvider for Lfm2VLGenerateModel<'a> {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_data(&self, mes: &ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
let mes_render = self.chat_template.apply_chat_template(mes)?;
|
||||||
let (pixel_values, pixel_attention_mask, spatial_shapes, text) =
|
let (pixel_values, pixel_attention_mask, spatial_shapes, text) =
|
||||||
self.processor.process_info(&mes, &mes_render)?;
|
self.processor.process_info(mes, &mes_render)?;
|
||||||
let input_ids = self.tokenizer.text_encode(text, &self.device)?;
|
let input_ids = self.tokenizer.text_encode(text, &self.device)?;
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let mut ctx = GenerationContext::new(
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data_vec = vec![
|
let data_vec = vec![
|
||||||
pixel_values.into(),
|
pixel_values.into(),
|
||||||
pixel_attention_mask.into(),
|
pixel_attention_mask.into(),
|
||||||
spatial_shapes.into(),
|
spatial_shapes.into(),
|
||||||
];
|
];
|
||||||
let data = MultiModalData::new(data_vec);
|
let multi_model_data = MultiModalData::new(data_vec);
|
||||||
generate_generic(
|
Ok(PrepareData {
|
||||||
&mut self.model,
|
in_reasoning: false,
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
input_ids,
|
||||||
data,
|
multi_model_data,
|
||||||
&mut ctx,
|
})
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_stream(
|
|
||||||
&mut self,
|
|
||||||
mes: ChatCompletionParameters,
|
|
||||||
) -> Result<
|
|
||||||
Box<
|
|
||||||
dyn rocket::futures::Stream<
|
|
||||||
Item = Result<crate::params::chat::ChatCompletionChunkResponse, anyhow::Error>,
|
|
||||||
> + Send
|
|
||||||
+ Unpin
|
|
||||||
+ '_,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let (pixel_values, pixel_attention_mask, spatial_shapes, text) =
|
|
||||||
self.processor.process_info(&mes, &mes_render)?;
|
|
||||||
let input_ids = self.tokenizer.text_encode(text, &self.device)?;
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let data_vec = vec![
|
|
||||||
pixel_values.into(),
|
|
||||||
pixel_attention_mask.into(),
|
|
||||||
spatial_shapes.into(),
|
|
||||||
];
|
|
||||||
let data = MultiModalData::new(data_vec);
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.model,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
false,
|
|
||||||
&self.device,
|
|
||||||
&self.model_name,
|
|
||||||
)?;
|
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(Lfm2VLGenerateModel<'a>);
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
use crate::models::common::MultiModalData;
|
use crate::models::common::generate::{GenerationDataProvider, PrepareData};
|
||||||
use crate::models::common::generate::{
|
|
||||||
GenerationContext, generate_generic, generate_stream_generic,
|
|
||||||
};
|
|
||||||
use crate::params::chat::{
|
|
||||||
ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use candle_core::{DType, Device};
|
use candle_core::{DType, Device};
|
||||||
use candle_nn::VarBuilder;
|
use candle_nn::VarBuilder;
|
||||||
use rocket::futures::Stream;
|
|
||||||
|
|
||||||
use crate::models::minicpm4::config::MiniCPM4Config;
|
use crate::models::minicpm4::config::MiniCPM4Config;
|
||||||
use crate::models::minicpm4::model::MiniCPMModel;
|
use crate::models::minicpm4::model::MiniCPMModel;
|
||||||
// use crate::models::GenerateStream;
|
|
||||||
use crate::utils::{find_type_files, get_device, get_dtype};
|
use crate::utils::{find_type_files, get_device, get_dtype};
|
||||||
use crate::{chat_template::ChatTemplate, models::GenerateModel, tokenizer::TokenizerModel};
|
use crate::{chat_template::ChatTemplate, tokenizer::TokenizerModel};
|
||||||
|
|
||||||
pub struct MiniCPM4GenerateModel<'a> {
|
pub struct MiniCPM4GenerateModel<'a> {
|
||||||
chat_template: ChatTemplate<'a>,
|
chat_template: ChatTemplate<'a>,
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
minicpm: MiniCPMModel,
|
model: MiniCPMModel,
|
||||||
device: Device,
|
device: Device,
|
||||||
model_name: String,
|
model_name: String,
|
||||||
}
|
}
|
||||||
@@ -35,7 +28,7 @@ impl<'a> MiniCPM4GenerateModel<'a> {
|
|||||||
let dtype = get_dtype(dtype, cfg_dtype);
|
let dtype = get_dtype(dtype, cfg_dtype);
|
||||||
let model_list = find_type_files(path, "safetensors")?;
|
let model_list = find_type_files(path, "safetensors")?;
|
||||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, device)? };
|
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, device)? };
|
||||||
let minicpm = MiniCPMModel::new(vb, cfg)?;
|
let model = MiniCPMModel::new(vb, cfg)?;
|
||||||
let model_name = std::path::Path::new(path)
|
let model_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
@@ -44,73 +37,44 @@ impl<'a> MiniCPM4GenerateModel<'a> {
|
|||||||
Ok(MiniCPM4GenerateModel {
|
Ok(MiniCPM4GenerateModel {
|
||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
minicpm,
|
model,
|
||||||
device: device.clone(),
|
device: device.clone(),
|
||||||
model_name,
|
model_name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> GenerateModel for MiniCPM4GenerateModel<'a> {
|
/// 文本向量嵌入。
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
///
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
/// 将输入文本编码为固定长度向量(mean pool + L2 normalize)。
|
||||||
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
pub fn embed_text(&mut self, text: &str) -> Result<Vec<f32>> {
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
let input_ids = self.tokenizer.text_encode(text.to_string(), &self.device)?;
|
||||||
let sample_len = mes.max_tokens.unwrap_or(2048);
|
let embedding = self.model.embed(&input_ids)?;
|
||||||
let mut ctx = GenerationContext::new(
|
let vec: Vec<f32> = embedding.flatten_all()?.to_vec1()?;
|
||||||
mes.temperature,
|
Ok(vec)
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = MultiModalData::new(vec![]);
|
|
||||||
generate_generic(
|
|
||||||
&mut self.minicpm,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
&mut ctx,
|
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
fn generate_stream(
|
|
||||||
&mut self,
|
/// 批量文本向量嵌入。
|
||||||
mes: ChatCompletionParameters,
|
pub fn embed_text_batch(&mut self, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
|
||||||
) -> Result<
|
let mut results = Vec::with_capacity(texts.len());
|
||||||
Box<
|
for text in texts {
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
results.push(self.embed_text(text)?);
|
||||||
+ Send
|
}
|
||||||
+ Unpin
|
Ok(results)
|
||||||
+ '_,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
|
||||||
let data = MultiModalData::new(vec![]);
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(512);
|
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.minicpm,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
false,
|
|
||||||
&self.device,
|
|
||||||
&self.model_name,
|
|
||||||
)?;
|
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> GenerationDataProvider for MiniCPM4GenerateModel<'a> {
|
||||||
|
fn get_data(&self, mes: &crate::params::chat::ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
|
let mes_render = self.chat_template.apply_chat_template(mes)?;
|
||||||
|
let in_reasoning = self.is_in_reasoning(&mes_render);
|
||||||
|
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
||||||
|
let multi_model_data = self.get_multi_model_data();
|
||||||
|
Ok(PrepareData {
|
||||||
|
in_reasoning,
|
||||||
|
input_ids,
|
||||||
|
multi_model_data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(MiniCPM4GenerateModel<'a>);
|
||||||
|
|||||||
@@ -320,6 +320,60 @@ impl MiniCPMModel {
|
|||||||
layer.clear_kv_cache()
|
layer.clear_kv_cache()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 文本向量嵌入 — mean pool + L2 normalize。
|
||||||
|
///
|
||||||
|
/// 复用完整 forward pass(embed → layers → norm),
|
||||||
|
/// 在 lm_head 之前截取 hidden states,做均值池化后 L2 归一化。
|
||||||
|
pub fn embed(&mut self, input_ids: &Tensor) -> Result<Tensor> {
|
||||||
|
let (bs, seq_len) = input_ids.dims2()?;
|
||||||
|
let input_embeds = self
|
||||||
|
.embed_tokens
|
||||||
|
.forward(input_ids)?
|
||||||
|
.affine(self.cfg.scale_emb, 0.0)?;
|
||||||
|
let attention_mask: Option<Tensor> = {
|
||||||
|
if seq_len <= 1 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(prepare_causal_attention_mask(
|
||||||
|
bs,
|
||||||
|
seq_len,
|
||||||
|
0,
|
||||||
|
input_ids.device(),
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cos, sin) = self.rope_emb.forward(0, seq_len)?;
|
||||||
|
let mut hidden_states = input_embeds;
|
||||||
|
for layer in &self.layers {
|
||||||
|
hidden_states =
|
||||||
|
layer.forward(&hidden_states, &cos, &sin, attention_mask.as_ref())?;
|
||||||
|
}
|
||||||
|
hidden_states = self.norm.forward(&hidden_states)?;
|
||||||
|
|
||||||
|
// Mean pool across sequence dimension
|
||||||
|
let embedding = hidden_states.mean(1)?;
|
||||||
|
let embedding = embedding.affine(
|
||||||
|
1.0 / (self.cfg.hidden_size / self.cfg.dim_model_base) as f64,
|
||||||
|
0.0,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// L2 normalize
|
||||||
|
let norm = embedding.sqr()?.sum_keepdim(1)?.sqrt()?;
|
||||||
|
let embedding = embedding.broadcast_div(&norm)?;
|
||||||
|
|
||||||
|
Ok(embedding)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 批量文本向量嵌入。
|
||||||
|
pub fn embed_batch(&mut self, input_ids: &[&Tensor]) -> Result<Vec<Tensor>> {
|
||||||
|
let mut results = Vec::with_capacity(input_ids.len());
|
||||||
|
for ids in input_ids {
|
||||||
|
results.push(self.embed(ids)?);
|
||||||
|
}
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InferenceModel for MiniCPMModel {
|
impl InferenceModel for MiniCPMModel {
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
use crate::models::common::MultiModalData;
|
use crate::models::common::generate::{GenerationDataProvider, PrepareData};
|
||||||
use crate::models::common::generate::{
|
|
||||||
GenerationContext, generate_generic, generate_stream_generic,
|
|
||||||
};
|
|
||||||
use crate::models::llama::LlamaForCausalLM;
|
use crate::models::llama::LlamaForCausalLM;
|
||||||
use crate::models::minicpm5::config::MiniCPM5Config;
|
use crate::models::minicpm5::config::MiniCPM5Config;
|
||||||
use crate::params::chat::{
|
|
||||||
ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use candle_core::{DType, Device};
|
use candle_core::{DType, Device};
|
||||||
use candle_nn::VarBuilder;
|
use candle_nn::VarBuilder;
|
||||||
use rocket::futures::Stream;
|
|
||||||
|
|
||||||
use crate::utils::{find_type_files, get_device, get_dtype};
|
use crate::utils::{find_type_files, get_device, get_dtype};
|
||||||
use crate::{chat_template::ChatTemplate, models::GenerateModel, tokenizer::TokenizerModel};
|
use crate::{chat_template::ChatTemplate, tokenizer::TokenizerModel};
|
||||||
|
|
||||||
pub struct MiniCPM5GenerateModel<'a> {
|
pub struct MiniCPM5GenerateModel<'a> {
|
||||||
chat_template: ChatTemplate<'a>,
|
chat_template: ChatTemplate<'a>,
|
||||||
@@ -70,66 +64,18 @@ impl<'a> MiniCPM5GenerateModel<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GenerateModel for MiniCPM5GenerateModel<'a> {
|
impl<'a> GenerationDataProvider for MiniCPM5GenerateModel<'a> {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_data(&self, mes: &crate::params::chat::ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
let mes_render = self.chat_template.apply_chat_template(mes)?;
|
||||||
|
let in_reasoning = self.is_in_reasoning(&mes_render);
|
||||||
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
let multi_model_data = self.get_multi_model_data();
|
||||||
let sample_len = mes.max_tokens.unwrap_or(2048);
|
Ok(PrepareData {
|
||||||
let mut ctx = GenerationContext::new(
|
in_reasoning,
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = MultiModalData::new(vec![]);
|
|
||||||
generate_generic(
|
|
||||||
&mut self.model,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
input_ids,
|
||||||
data,
|
multi_model_data,
|
||||||
&mut ctx,
|
})
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fn generate_stream(
|
|
||||||
&mut self,
|
|
||||||
mes: ChatCompletionParameters,
|
|
||||||
) -> Result<
|
|
||||||
Box<
|
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
|
||||||
+ Send
|
|
||||||
+ Unpin
|
|
||||||
+ '_,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
|
||||||
let data = MultiModalData::new(vec![]);
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(512);
|
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.model,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
false,
|
|
||||||
&self.device,
|
|
||||||
&self.model_name,
|
|
||||||
)?;
|
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(MiniCPM5GenerateModel<'a>);
|
||||||
|
|||||||
+1
-1
@@ -284,7 +284,7 @@ pub fn load_model<'a>(
|
|||||||
let model = HunyuanOCRGenerateModel::init(path, device, dtype)?;
|
let model = HunyuanOCRGenerateModel::init(path, device, dtype)?;
|
||||||
ModelInstance::HunyuanOCR(model)
|
ModelInstance::HunyuanOCR(model)
|
||||||
}
|
}
|
||||||
WhichModel::PaddleOCRVL | WhichModel::PaddleOCRVL1_5 => {
|
WhichModel::PaddleOCRVL | WhichModel::PaddleOCRVL1_5 | WhichModel::PaddleOCRVL1_6 => {
|
||||||
let model = PaddleOCRVLGenerateModel::init(path, device, dtype)?;
|
let model = PaddleOCRVLGenerateModel::init(path, device, dtype)?;
|
||||||
ModelInstance::PaddleOCRVL(Box::new(model))
|
ModelInstance::PaddleOCRVL(Box::new(model))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,22 @@
|
|||||||
use crate::models::common::MultiModalData;
|
use crate::models::common::MultiModalData;
|
||||||
use crate::models::common::generate::{
|
use crate::models::common::generate::{GenerationDataProvider, PrepareData};
|
||||||
GenerationContext, generate_generic, generate_stream_generic,
|
use crate::params::chat::ChatCompletionParameters;
|
||||||
};
|
|
||||||
use crate::params::chat::{
|
|
||||||
ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use candle_core::{D, DType, Device, IndexOp, Tensor};
|
use candle_core::{D, DType, Device, IndexOp, Tensor};
|
||||||
use candle_nn::VarBuilder;
|
use candle_nn::VarBuilder;
|
||||||
use rocket::futures::Stream;
|
|
||||||
|
|
||||||
use crate::models::paddleocr_vl::config::{PaddleOCRVLConfig, PaddleOCRVLPreprocessorConfig};
|
use crate::models::paddleocr_vl::config::{PaddleOCRVLConfig, PaddleOCRVLPreprocessorConfig};
|
||||||
use crate::models::paddleocr_vl::model::PaddleOCRVLModel;
|
use crate::models::paddleocr_vl::model::PaddleOCRVLModel;
|
||||||
use crate::models::paddleocr_vl::processor::PaddleOCRVLProcessor;
|
use crate::models::paddleocr_vl::processor::PaddleOCRVLProcessor;
|
||||||
use crate::utils::tensor_utils::get_equal_mask;
|
use crate::utils::tensor_utils::get_equal_mask;
|
||||||
use crate::utils::{find_type_files, get_device, get_dtype};
|
use crate::utils::{find_type_files, get_device, get_dtype};
|
||||||
use crate::{chat_template::ChatTemplate, models::GenerateModel, tokenizer::TokenizerModel};
|
use crate::{chat_template::ChatTemplate, tokenizer::TokenizerModel};
|
||||||
|
|
||||||
pub struct PaddleOCRVLGenerateModel<'a> {
|
pub struct PaddleOCRVLGenerateModel<'a> {
|
||||||
chat_template: ChatTemplate<'a>,
|
chat_template: ChatTemplate<'a>,
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
pre_processor: PaddleOCRVLProcessor,
|
pre_processor: PaddleOCRVLProcessor,
|
||||||
paddleocr_vl: PaddleOCRVLModel,
|
model: PaddleOCRVLModel,
|
||||||
cfg: PaddleOCRVLConfig,
|
cfg: PaddleOCRVLConfig,
|
||||||
device: Device,
|
device: Device,
|
||||||
model_name: String,
|
model_name: String,
|
||||||
@@ -42,7 +37,7 @@ impl<'a> PaddleOCRVLGenerateModel<'a> {
|
|||||||
let pre_processor = PaddleOCRVLProcessor::new(processor_cfg, device, dtype)?;
|
let pre_processor = PaddleOCRVLProcessor::new(processor_cfg, device, dtype)?;
|
||||||
let model_list = find_type_files(path, "safetensors")?;
|
let model_list = find_type_files(path, "safetensors")?;
|
||||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, device)? };
|
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, device)? };
|
||||||
let paddleocr_vl = PaddleOCRVLModel::new(cfg.clone(), vb, vec![2])?;
|
let model = PaddleOCRVLModel::new(cfg.clone(), vb, vec![2])?;
|
||||||
let model_name = std::path::Path::new(path)
|
let model_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
@@ -52,7 +47,7 @@ impl<'a> PaddleOCRVLGenerateModel<'a> {
|
|||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
pre_processor,
|
pre_processor,
|
||||||
paddleocr_vl,
|
model,
|
||||||
cfg,
|
cfg,
|
||||||
device: device.clone(),
|
device: device.clone(),
|
||||||
model_name,
|
model_name,
|
||||||
@@ -60,11 +55,11 @@ impl<'a> PaddleOCRVLGenerateModel<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GenerateModel for PaddleOCRVLGenerateModel<'a> {
|
impl<'a> GenerationDataProvider for PaddleOCRVLGenerateModel<'a> {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_data(&self, mes: &ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
let mes_render = self.chat_template.apply_chat_template(mes)?;
|
||||||
let (replace_text, pixel_values, image_grid_thw) =
|
let (replace_text, pixel_values, image_grid_thw) =
|
||||||
self.pre_processor.process_info(&mes, &mes_render)?;
|
self.pre_processor.process_info(mes, &mes_render)?;
|
||||||
let input_ids = self.tokenizer.text_encode(replace_text, &self.device)?;
|
let input_ids = self.tokenizer.text_encode(replace_text, &self.device)?;
|
||||||
let image_mask = get_equal_mask(&input_ids, self.cfg.image_token_id)?;
|
let image_mask = get_equal_mask(&input_ids, self.cfg.image_token_id)?;
|
||||||
|
|
||||||
@@ -73,84 +68,19 @@ impl<'a> GenerateModel for PaddleOCRVLGenerateModel<'a> {
|
|||||||
.cumsum(D::Minus1)?
|
.cumsum(D::Minus1)?
|
||||||
.to_dtype(candle_core::DType::U32)?
|
.to_dtype(candle_core::DType::U32)?
|
||||||
.broadcast_sub(&Tensor::new(vec![1_u32], input_ids.device())?)?;
|
.broadcast_sub(&Tensor::new(vec![1_u32], input_ids.device())?)?;
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let mut ctx = GenerationContext::new(
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
let data_vec = vec![
|
let data_vec = vec![
|
||||||
pixel_values,
|
pixel_values,
|
||||||
image_grid_thw,
|
image_grid_thw,
|
||||||
image_mask.into(),
|
image_mask.into(),
|
||||||
cache_position.into(),
|
cache_position.into(),
|
||||||
];
|
];
|
||||||
let data = MultiModalData::new(data_vec);
|
let multi_model_data = MultiModalData::new(data_vec);
|
||||||
generate_generic(
|
Ok(PrepareData {
|
||||||
&mut self.paddleocr_vl,
|
in_reasoning: false,
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
input_ids,
|
||||||
data,
|
multi_model_data,
|
||||||
&mut ctx,
|
})
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_stream(
|
|
||||||
&mut self,
|
|
||||||
mes: ChatCompletionParameters,
|
|
||||||
) -> Result<
|
|
||||||
Box<
|
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
|
||||||
+ Send
|
|
||||||
+ Unpin
|
|
||||||
+ '_,
|
|
||||||
>,
|
|
||||||
> {
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let (replace_text, pixel_values, image_grid_thw) =
|
|
||||||
self.pre_processor.process_info(&mes, &mes_render)?;
|
|
||||||
let input_ids = self.tokenizer.text_encode(replace_text, &self.device)?;
|
|
||||||
let image_mask = get_equal_mask(&input_ids, self.cfg.image_token_id)?;
|
|
||||||
|
|
||||||
let cache_position = Tensor::ones_like(&input_ids.i(0)?)?
|
|
||||||
.to_dtype(candle_core::DType::F64)?
|
|
||||||
.cumsum(D::Minus1)?
|
|
||||||
.to_dtype(candle_core::DType::U32)?
|
|
||||||
.broadcast_sub(&Tensor::new(vec![1_u32], input_ids.device())?)?;
|
|
||||||
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let data_vec = vec![
|
|
||||||
pixel_values,
|
|
||||||
image_grid_thw,
|
|
||||||
image_mask.into(),
|
|
||||||
cache_position.into(),
|
|
||||||
];
|
|
||||||
let data = MultiModalData::new(data_vec);
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.paddleocr_vl,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
false,
|
|
||||||
&self.device,
|
|
||||||
&self.model_name,
|
|
||||||
)?;
|
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(PaddleOCRVLGenerateModel<'a>);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ pub struct Qwen2_5VLGenerateModel<'a> {
|
|||||||
chat_template: ChatTemplate<'a>,
|
chat_template: ChatTemplate<'a>,
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
pre_processor: Qwen2_5VLProcessor,
|
pre_processor: Qwen2_5VLProcessor,
|
||||||
qwen2_5_vl: Qwen2_5VLModel,
|
model: Qwen2_5VLModel,
|
||||||
device: Device,
|
device: Device,
|
||||||
endoftext_id: u32,
|
endoftext_id: u32,
|
||||||
im_end_id: u32,
|
im_end_id: u32,
|
||||||
@@ -52,7 +52,7 @@ impl<'a> Qwen2_5VLGenerateModel<'a> {
|
|||||||
// let model_list = find_safetensors_files(&path)?;
|
// let model_list = find_safetensors_files(&path)?;
|
||||||
let model_list = find_type_files(path, "safetensors")?;
|
let model_list = find_type_files(path, "safetensors")?;
|
||||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, device)? };
|
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, device)? };
|
||||||
let qwen2_5_vl = Qwen2_5VLModel::new(cfg, vb)?;
|
let model = Qwen2_5VLModel::new(cfg, vb)?;
|
||||||
let model_name = std::path::Path::new(path)
|
let model_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
@@ -62,7 +62,7 @@ impl<'a> Qwen2_5VLGenerateModel<'a> {
|
|||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
pre_processor,
|
pre_processor,
|
||||||
qwen2_5_vl,
|
model,
|
||||||
device: device.clone(),
|
device: device.clone(),
|
||||||
endoftext_id,
|
endoftext_id,
|
||||||
im_end_id,
|
im_end_id,
|
||||||
@@ -102,7 +102,7 @@ impl<'a> GenerateModel for Qwen2_5VLGenerateModel<'a> {
|
|||||||
let mut completion_secs = 0.0f64;
|
let mut completion_secs = 0.0f64;
|
||||||
for _ in 0..sample_len {
|
for _ in 0..sample_len {
|
||||||
let i_start = Instant::now();
|
let i_start = Instant::now();
|
||||||
let logits = self.qwen2_5_vl.forward(
|
let logits = self.model.forward(
|
||||||
&input_ids,
|
&input_ids,
|
||||||
pixel_values,
|
pixel_values,
|
||||||
image_grid_thw,
|
image_grid_thw,
|
||||||
@@ -136,7 +136,7 @@ impl<'a> GenerateModel for Qwen2_5VLGenerateModel<'a> {
|
|||||||
}
|
}
|
||||||
let num_token = generate.len() as u32;
|
let num_token = generate.len() as u32;
|
||||||
let res = self.tokenizer.token_decode(generate)?;
|
let res = self.tokenizer.token_decode(generate)?;
|
||||||
self.qwen2_5_vl.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
let response = build_completion_response_with_time(
|
let response = build_completion_response_with_time(
|
||||||
res,
|
res,
|
||||||
&self.model_name,
|
&self.model_name,
|
||||||
@@ -190,7 +190,7 @@ impl<'a> GenerateModel for Qwen2_5VLGenerateModel<'a> {
|
|||||||
let mut tool_call_content = String::new();
|
let mut tool_call_content = String::new();
|
||||||
for _ in 0..sample_len {
|
for _ in 0..sample_len {
|
||||||
let i_start = Instant::now();
|
let i_start = Instant::now();
|
||||||
let logits = self.qwen2_5_vl.forward(
|
let logits = self.model.forward(
|
||||||
&input_ids,
|
&input_ids,
|
||||||
pixel_values,
|
pixel_values,
|
||||||
image_grid_thw,
|
image_grid_thw,
|
||||||
@@ -293,7 +293,7 @@ impl<'a> GenerateModel for Qwen2_5VLGenerateModel<'a> {
|
|||||||
pixel_values = None;
|
pixel_values = None;
|
||||||
pixel_values_video = None;
|
pixel_values_video = None;
|
||||||
}
|
}
|
||||||
self.qwen2_5_vl.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
};
|
};
|
||||||
Ok(Box::new(Box::pin(stream)))
|
Ok(Box::new(Box::pin(stream)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
use crate::models::common::MultiModalData;
|
use crate::models::common::generate::{GenerationDataProvider, PrepareData};
|
||||||
use crate::models::common::generate::{
|
|
||||||
GenerationContext, generate_generic, generate_stream_generic,
|
|
||||||
};
|
|
||||||
use crate::params::chat::{
|
|
||||||
ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse,
|
|
||||||
};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use candle_core::{DType, Device};
|
use candle_core::{DType, Device};
|
||||||
use candle_nn::VarBuilder;
|
use candle_nn::VarBuilder;
|
||||||
use rocket::futures::Stream;
|
|
||||||
|
|
||||||
use crate::models::qwen3::config::{Qwen3Config, Qwen3GenerationConfig};
|
use crate::models::qwen3::config::{Qwen3Config, Qwen3GenerationConfig};
|
||||||
use crate::models::qwen3::model::Qwen3Model;
|
use crate::models::qwen3::model::Qwen3Model;
|
||||||
use crate::utils::{find_type_files, get_device, get_dtype};
|
use crate::utils::{find_type_files, get_device, get_dtype};
|
||||||
use crate::{chat_template::ChatTemplate, models::GenerateModel, tokenizer::TokenizerModel};
|
use crate::{chat_template::ChatTemplate, tokenizer::TokenizerModel};
|
||||||
|
|
||||||
pub struct Qwen3GenerateModel<'a> {
|
pub struct Qwen3GenerateModel<'a> {
|
||||||
chat_template: ChatTemplate<'a>,
|
chat_template: ChatTemplate<'a>,
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
qwen3: Qwen3Model,
|
model: Qwen3Model,
|
||||||
device: Device,
|
device: Device,
|
||||||
generation_config: Qwen3GenerationConfig,
|
generation_config: Qwen3GenerationConfig,
|
||||||
model_name: String,
|
model_name: String,
|
||||||
@@ -38,7 +32,7 @@ impl<'a> Qwen3GenerateModel<'a> {
|
|||||||
let generation_config_path = path.to_string() + "/generation_config.json";
|
let generation_config_path = path.to_string() + "/generation_config.json";
|
||||||
let generation_config: Qwen3GenerationConfig =
|
let generation_config: Qwen3GenerationConfig =
|
||||||
serde_json::from_slice(&std::fs::read(generation_config_path)?)?;
|
serde_json::from_slice(&std::fs::read(generation_config_path)?)?;
|
||||||
let qwen3 = Qwen3Model::new(&cfg, vb, generation_config.eos_token_id.clone())?;
|
let model = Qwen3Model::new(&cfg, vb, generation_config.eos_token_id.clone())?;
|
||||||
|
|
||||||
let model_name = std::path::Path::new(path)
|
let model_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -48,7 +42,7 @@ impl<'a> Qwen3GenerateModel<'a> {
|
|||||||
Ok(Qwen3GenerateModel {
|
Ok(Qwen3GenerateModel {
|
||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
qwen3,
|
model,
|
||||||
device: device.clone(),
|
device: device.clone(),
|
||||||
generation_config,
|
generation_config,
|
||||||
model_name,
|
model_name,
|
||||||
@@ -56,77 +50,30 @@ impl<'a> Qwen3GenerateModel<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GenerateModel for Qwen3GenerateModel<'a> {
|
impl<'a> GenerationDataProvider for Qwen3GenerateModel<'a> {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_temperature(&self, req_temp: Option<f32>) -> Option<f32> {
|
||||||
let temperature = mes
|
Some(req_temp.unwrap_or(self.generation_config.temperature))
|
||||||
.temperature
|
|
||||||
.unwrap_or(self.generation_config.temperature);
|
|
||||||
let top_p = mes.top_p.unwrap_or(self.generation_config.top_p);
|
|
||||||
let top_k = self.generation_config.top_k;
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(2048);
|
|
||||||
let mut ctx = GenerationContext::new(
|
|
||||||
temperature.into(),
|
|
||||||
top_p.into(),
|
|
||||||
top_k.into(),
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = MultiModalData::new(vec![]);
|
|
||||||
generate_generic(
|
|
||||||
&mut self.qwen3,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
&mut ctx,
|
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
fn generate_stream(
|
|
||||||
&mut self,
|
fn get_top_p(&self, req_top_p: Option<f32>) -> Option<f32> {
|
||||||
mes: ChatCompletionParameters,
|
Some(req_top_p.unwrap_or(self.generation_config.top_p))
|
||||||
) -> Result<
|
}
|
||||||
Box<
|
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
fn get_top_k(&self, top_k: Option<usize>) -> Option<usize> {
|
||||||
+ Send
|
Some(top_k.unwrap_or(self.generation_config.top_k))
|
||||||
+ Unpin
|
}
|
||||||
+ '_,
|
|
||||||
>,
|
fn get_data(&self, mes: &crate::params::chat::ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
> {
|
let mes_render = self.chat_template.apply_chat_template(mes)?;
|
||||||
let temperature = mes
|
let in_reasoning = self.is_in_reasoning(&mes_render);
|
||||||
.temperature
|
|
||||||
.unwrap_or(self.generation_config.temperature);
|
|
||||||
let top_p = mes.top_p.unwrap_or(self.generation_config.top_p);
|
|
||||||
let top_k = self.generation_config.top_k;
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let in_reasoning = mes_render.ends_with("<think>\n");
|
|
||||||
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
let input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
|
||||||
let data = MultiModalData::new(vec![]);
|
let multi_model_data = self.get_multi_model_data();
|
||||||
let sample_len = mes.max_tokens.unwrap_or(512);
|
Ok(PrepareData {
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.qwen3,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
temperature.into(),
|
|
||||||
top_p.into(),
|
|
||||||
top_k.into(),
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
in_reasoning,
|
in_reasoning,
|
||||||
&self.device,
|
input_ids,
|
||||||
&self.model_name,
|
multi_model_data,
|
||||||
)?;
|
})
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(Qwen3GenerateModel<'a>);
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ use crate::{
|
|||||||
models::common::{
|
models::common::{
|
||||||
MultiModalData,
|
MultiModalData,
|
||||||
generate::{
|
generate::{
|
||||||
GenerationContext, generate_generic, generate_generic_text, generate_stream_generic,
|
GenerationContext, GenerationDataProvider, PrepareData, generate_generic_text,
|
||||||
generate_stream_generic_text,
|
generate_stream_generic_text,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
params::chat::{ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse},
|
params::chat::ChatCompletionParameters,
|
||||||
};
|
};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use candle_core::{DType, Device, quantized::gguf_file};
|
use candle_core::{DType, Device, quantized::gguf_file};
|
||||||
@@ -16,7 +16,6 @@ use rocket::futures::Stream;
|
|||||||
use crate::{
|
use crate::{
|
||||||
chat_template::ChatTemplate,
|
chat_template::ChatTemplate,
|
||||||
models::{
|
models::{
|
||||||
GenerateModel,
|
|
||||||
common::gguf::Gguf,
|
common::gguf::Gguf,
|
||||||
qwen3_5::{config::Qwen3_5Config, model::Qwen3_5Model},
|
qwen3_5::{config::Qwen3_5Config, model::Qwen3_5Model},
|
||||||
qwen3vl::processor::Qwen3VLProcessor,
|
qwen3vl::processor::Qwen3VLProcessor,
|
||||||
@@ -29,7 +28,7 @@ pub struct Qwen3_5GenerateModel<'a> {
|
|||||||
chat_template: ChatTemplate<'a>,
|
chat_template: ChatTemplate<'a>,
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
pre_processor: Option<Qwen3VLProcessor>,
|
pre_processor: Option<Qwen3VLProcessor>,
|
||||||
qwen3_5: Qwen3_5Model,
|
model: Qwen3_5Model,
|
||||||
device: Device,
|
device: Device,
|
||||||
model_name: String,
|
model_name: String,
|
||||||
repeat_penalty: f32,
|
repeat_penalty: f32,
|
||||||
@@ -53,13 +52,13 @@ impl<'a> Qwen3_5GenerateModel<'a> {
|
|||||||
let model_list = find_type_files(path, "safetensors")?;
|
let model_list = find_type_files(path, "safetensors")?;
|
||||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, &device)? };
|
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, &device)? };
|
||||||
let eos_ids = vec![cfg.text_config.eos_token_id];
|
let eos_ids = vec![cfg.text_config.eos_token_id];
|
||||||
let qwen3_5 = Qwen3_5Model::new_from_vb(vb, cfg, eos_ids)?;
|
let model = Qwen3_5Model::new_from_vb(vb, cfg, eos_ids)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
pre_processor: Some(pre_processor),
|
pre_processor: Some(pre_processor),
|
||||||
qwen3_5,
|
model,
|
||||||
device,
|
device,
|
||||||
model_name: model_name.to_string(),
|
model_name: model_name.to_string(),
|
||||||
repeat_penalty: 1.0,
|
repeat_penalty: 1.0,
|
||||||
@@ -89,13 +88,13 @@ impl<'a> Qwen3_5GenerateModel<'a> {
|
|||||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, &device)? };
|
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, &device)? };
|
||||||
let eos_ids = vec![cfg.text_config.eos_token_id];
|
let eos_ids = vec![cfg.text_config.eos_token_id];
|
||||||
// let qwen3_5 = Qwen3_5Model::new_from_vb(vb, cfg, eos_ids)?;
|
// let qwen3_5 = Qwen3_5Model::new_from_vb(vb, cfg, eos_ids)?;
|
||||||
let qwen3_5 = Qwen3_5Model::new_from_vb_without_visual(vb, cfg, eos_ids)?;
|
let model = Qwen3_5Model::new_from_vb_without_visual(vb, cfg, eos_ids)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
pre_processor,
|
pre_processor,
|
||||||
qwen3_5,
|
model,
|
||||||
device,
|
device,
|
||||||
model_name: model_name.to_string(),
|
model_name: model_name.to_string(),
|
||||||
repeat_penalty: 1.0,
|
repeat_penalty: 1.0,
|
||||||
@@ -142,7 +141,7 @@ impl<'a> Qwen3_5GenerateModel<'a> {
|
|||||||
.get_matedata("tokenizer.ggml.eos_token_id")?
|
.get_matedata("tokenizer.ggml.eos_token_id")?
|
||||||
.to_u32()?;
|
.to_u32()?;
|
||||||
let eos_ids = vec![eos_token_id];
|
let eos_ids = vec![eos_token_id];
|
||||||
let qwen3_5 =
|
let model =
|
||||||
Qwen3_5Model::new_from_gguf(&mut model_gguf, mmproj_gguf.as_mut(), &device, eos_ids)?;
|
Qwen3_5Model::new_from_gguf(&mut model_gguf, mmproj_gguf.as_mut(), &device, eos_ids)?;
|
||||||
let stem = std::path::Path::new(model_file)
|
let stem = std::path::Path::new(model_file)
|
||||||
.file_stem() // 获取文件名主干(不含扩展名)
|
.file_stem() // 获取文件名主干(不含扩展名)
|
||||||
@@ -152,7 +151,7 @@ impl<'a> Qwen3_5GenerateModel<'a> {
|
|||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
pre_processor,
|
pre_processor,
|
||||||
qwen3_5,
|
model,
|
||||||
device,
|
device,
|
||||||
model_name: stem.to_string(),
|
model_name: stem.to_string(),
|
||||||
repeat_penalty: 1.2,
|
repeat_penalty: 1.2,
|
||||||
@@ -199,13 +198,7 @@ impl<'a> Qwen3_5GenerateModel<'a> {
|
|||||||
];
|
];
|
||||||
let data = MultiModalData::new(data_vec);
|
let data = MultiModalData::new(data_vec);
|
||||||
|
|
||||||
generate_generic_text(
|
generate_generic_text(&mut self.model, &self.tokenizer, input_ids, data, &mut ctx)
|
||||||
&mut self.qwen3_5,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
&mut ctx,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_stream_text(
|
pub fn generate_stream_text(
|
||||||
@@ -237,7 +230,7 @@ impl<'a> Qwen3_5GenerateModel<'a> {
|
|||||||
let data = MultiModalData::new(data_vec);
|
let data = MultiModalData::new(data_vec);
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
let seed = mes.seed.unwrap_or(34562) as u64;
|
||||||
generate_stream_generic_text(
|
generate_stream_generic_text(
|
||||||
&mut self.qwen3_5,
|
&mut self.model,
|
||||||
&self.tokenizer,
|
&self.tokenizer,
|
||||||
input_ids,
|
input_ids,
|
||||||
data,
|
data,
|
||||||
@@ -253,71 +246,25 @@ impl<'a> Qwen3_5GenerateModel<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GenerateModel for Qwen3_5GenerateModel<'a> {
|
impl<'a> GenerationDataProvider for Qwen3_5GenerateModel<'a> {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_temperature(&self, req_temp: Option<f32>) -> Option<f32> {
|
||||||
let seed = mes.seed.unwrap_or(32768) as u64;
|
Some(req_temp.unwrap_or(0.4))
|
||||||
let temperature = mes.temperature.unwrap_or(0.4);
|
|
||||||
let top_p = mes.top_p.unwrap_or(0.95);
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let (mes_text, pixel_values, image_grid_thw, pixel_values_video, video_grid_thw) =
|
|
||||||
if let Some(processor) = &self.pre_processor {
|
|
||||||
let input = processor.process_info(&mes, &mes_render)?;
|
|
||||||
(
|
|
||||||
input.replace_text,
|
|
||||||
input.pixel_values,
|
|
||||||
input.image_grid_thw,
|
|
||||||
input.pixel_values_video,
|
|
||||||
input.video_grid_thw,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(mes_render, None, None, None, None)
|
|
||||||
};
|
|
||||||
let input_ids = self.tokenizer.text_encode(mes_text, &self.device)?;
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let mut ctx = GenerationContext::new(
|
|
||||||
temperature.into(),
|
|
||||||
top_p.into(),
|
|
||||||
Some(20),
|
|
||||||
self.repeat_penalty.into(),
|
|
||||||
self.repeat_last_n.into(),
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
let data_vec = vec![
|
|
||||||
pixel_values,
|
|
||||||
image_grid_thw,
|
|
||||||
pixel_values_video,
|
|
||||||
video_grid_thw,
|
|
||||||
];
|
|
||||||
let data = MultiModalData::new(data_vec);
|
|
||||||
generate_generic(
|
|
||||||
&mut self.qwen3_5,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
&mut ctx,
|
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_stream(
|
fn get_top_p(&self, req_top_p: Option<f32>) -> Option<f32> {
|
||||||
&mut self,
|
Some(req_top_p.unwrap_or(0.95))
|
||||||
mes: ChatCompletionParameters,
|
}
|
||||||
) -> Result<
|
|
||||||
Box<
|
fn get_top_k(&self, top_k: Option<usize>) -> Option<usize> {
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
Some(top_k.unwrap_or(40))
|
||||||
+ Send
|
}
|
||||||
+ Unpin
|
|
||||||
+ '_,
|
fn get_data(&self, mes: &ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
>,
|
let mes_render = self.chat_template.apply_chat_template(mes)?;
|
||||||
> {
|
let in_reasoning = self.is_in_reasoning(&mes_render);
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let in_reasoning = mes_render.ends_with("<think>\n");
|
|
||||||
let (mes_text, pixel_values, image_grid_thw, pixel_values_video, video_grid_thw) =
|
let (mes_text, pixel_values, image_grid_thw, pixel_values_video, video_grid_thw) =
|
||||||
if let Some(processor) = &self.pre_processor {
|
if let Some(processor) = &self.pre_processor {
|
||||||
let input = processor.process_info(&mes, &mes_render)?;
|
let input = processor.process_info(mes, &mes_render)?;
|
||||||
(
|
(
|
||||||
input.replace_text,
|
input.replace_text,
|
||||||
input.pixel_values,
|
input.pixel_values,
|
||||||
@@ -329,31 +276,19 @@ impl<'a> GenerateModel for Qwen3_5GenerateModel<'a> {
|
|||||||
(mes_render, None, None, None, None)
|
(mes_render, None, None, None, None)
|
||||||
};
|
};
|
||||||
let input_ids = self.tokenizer.text_encode(mes_text, &self.device)?;
|
let input_ids = self.tokenizer.text_encode(mes_text, &self.device)?;
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let data_vec = vec![
|
let data_vec = vec![
|
||||||
pixel_values,
|
pixel_values,
|
||||||
image_grid_thw,
|
image_grid_thw,
|
||||||
pixel_values_video,
|
pixel_values_video,
|
||||||
video_grid_thw,
|
video_grid_thw,
|
||||||
];
|
];
|
||||||
let data = MultiModalData::new(data_vec);
|
let multi_model_data = MultiModalData::new(data_vec);
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
Ok(PrepareData {
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.qwen3_5,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
mes.temperature,
|
|
||||||
mes.top_p,
|
|
||||||
None,
|
|
||||||
self.repeat_penalty.into(),
|
|
||||||
self.repeat_last_n.into(),
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
in_reasoning,
|
in_reasoning,
|
||||||
&self.device,
|
input_ids,
|
||||||
&self.model_name,
|
multi_model_data,
|
||||||
)?;
|
})
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(Qwen3_5GenerateModel<'a>);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ pub struct Qwen3AsrGenerateModel<'a> {
|
|||||||
chat_template: ChatTemplate<'a>,
|
chat_template: ChatTemplate<'a>,
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
processor: Qwen3AsrProcessor,
|
processor: Qwen3AsrProcessor,
|
||||||
qwen3_asr: Qwen3ASRModel,
|
model: Qwen3ASRModel,
|
||||||
device: Device,
|
device: Device,
|
||||||
dtype: DType,
|
dtype: DType,
|
||||||
eos_token_id1: u32,
|
eos_token_id1: u32,
|
||||||
@@ -65,7 +65,7 @@ impl<'a> Qwen3AsrGenerateModel<'a> {
|
|||||||
let dtype = get_dtype(dtype, cfg_dtype);
|
let dtype = get_dtype(dtype, cfg_dtype);
|
||||||
let model_list = find_type_files(path, "safetensors")?;
|
let model_list = find_type_files(path, "safetensors")?;
|
||||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, &device)? };
|
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, &device)? };
|
||||||
let qwen3_asr = Qwen3ASRModel::new(vb, &cfg, generation_config.eos_token_id.clone())?;
|
let model = Qwen3ASRModel::new(vb, &cfg, generation_config.eos_token_id.clone())?;
|
||||||
let model_name = std::path::Path::new(path)
|
let model_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
@@ -75,7 +75,7 @@ impl<'a> Qwen3AsrGenerateModel<'a> {
|
|||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
processor,
|
processor,
|
||||||
qwen3_asr,
|
model,
|
||||||
device,
|
device,
|
||||||
dtype,
|
dtype,
|
||||||
eos_token_id1: generation_config.eos_token_id[0] as u32,
|
eos_token_id1: generation_config.eos_token_id[0] as u32,
|
||||||
@@ -116,13 +116,8 @@ impl<'a> Qwen3AsrGenerateModel<'a> {
|
|||||||
);
|
);
|
||||||
let data_vec = vec![input_features];
|
let data_vec = vec![input_features];
|
||||||
let data = MultiModalData::new(data_vec);
|
let data = MultiModalData::new(data_vec);
|
||||||
let mut text = generate_generic_text(
|
let mut text =
|
||||||
&mut self.qwen3_asr,
|
generate_generic_text(&mut self.model, &self.tokenizer, input_ids, data, &mut ctx)?;
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
&mut ctx,
|
|
||||||
)?;
|
|
||||||
if text.contains("<asr_text>") {
|
if text.contains("<asr_text>") {
|
||||||
let mut split: Vec<&str> = text.split("<asr_text>").collect();
|
let mut split: Vec<&str> = text.split("<asr_text>").collect();
|
||||||
text = split.pop().unwrap_or(&text).to_string();
|
text = split.pop().unwrap_or(&text).to_string();
|
||||||
@@ -156,7 +151,7 @@ impl<'a> GenerateModel for Qwen3AsrGenerateModel<'a> {
|
|||||||
for _ in 0..sample_len {
|
for _ in 0..sample_len {
|
||||||
let i_start = Instant::now();
|
let i_start = Instant::now();
|
||||||
let logits =
|
let logits =
|
||||||
self.qwen3_asr
|
self.model
|
||||||
.forward(&input_ids, seqlen_offset, input_features.as_ref())?;
|
.forward(&input_ids, seqlen_offset, input_features.as_ref())?;
|
||||||
let logits = logits.squeeze(0)?.squeeze(0)?.to_dtype(DType::F32)?;
|
let logits = logits.squeeze(0)?.squeeze(0)?.to_dtype(DType::F32)?;
|
||||||
let next_token = logit_processor.sample(&logits)?;
|
let next_token = logit_processor.sample(&logits)?;
|
||||||
@@ -175,7 +170,7 @@ impl<'a> GenerateModel for Qwen3AsrGenerateModel<'a> {
|
|||||||
input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?;
|
input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?;
|
||||||
input_features = None;
|
input_features = None;
|
||||||
}
|
}
|
||||||
self.qwen3_asr.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
}
|
}
|
||||||
let num_token = generate.len() as u32;
|
let num_token = generate.len() as u32;
|
||||||
let res = self.tokenizer.token_decode(generate)?;
|
let res = self.tokenizer.token_decode(generate)?;
|
||||||
@@ -226,7 +221,7 @@ impl<'a> GenerateModel for Qwen3AsrGenerateModel<'a> {
|
|||||||
for _ in 0..sample_len {
|
for _ in 0..sample_len {
|
||||||
let i_start = Instant::now();
|
let i_start = Instant::now();
|
||||||
let logits =
|
let logits =
|
||||||
self.qwen3_asr
|
self.model
|
||||||
.forward(&input_ids, seqlen_offset, input_features.as_ref())?;
|
.forward(&input_ids, seqlen_offset, input_features.as_ref())?;
|
||||||
let logits = logits.squeeze(0)?.squeeze(0)?.to_dtype(DType::F32)?;
|
let logits = logits.squeeze(0)?.squeeze(0)?.to_dtype(DType::F32)?;
|
||||||
let next_token = logit_processor.sample(&logits)?;
|
let next_token = logit_processor.sample(&logits)?;
|
||||||
@@ -266,7 +261,7 @@ impl<'a> GenerateModel for Qwen3AsrGenerateModel<'a> {
|
|||||||
input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?;
|
input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?;
|
||||||
input_features = None;
|
input_features = None;
|
||||||
}
|
}
|
||||||
self.qwen3_asr.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Box::new(Box::pin(stream)))
|
Ok(Box::new(Box::pin(stream)))
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
models::common::{
|
models::common::{
|
||||||
MultiModalData,
|
MultiModalData,
|
||||||
generate::{GenerationContext, generate_generic, generate_stream_generic},
|
generate::{GenerationDataProvider, PrepareData},
|
||||||
},
|
},
|
||||||
params::chat::{ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse},
|
params::chat::ChatCompletionParameters,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use candle_core::{DType, Device, Tensor};
|
use candle_core::{DType, Device, Tensor};
|
||||||
use candle_nn::VarBuilder;
|
use candle_nn::VarBuilder;
|
||||||
use rocket::futures::Stream;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
chat_template::ChatTemplate,
|
chat_template::ChatTemplate,
|
||||||
models::{
|
models::{
|
||||||
GenerateModel,
|
|
||||||
qwen3::config::Qwen3GenerationConfig,
|
qwen3::config::Qwen3GenerationConfig,
|
||||||
qwen3vl::{config::Qwen3VLConfig, model::Qwen3VLModel, processor::Qwen3VLProcessor},
|
qwen3vl::{config::Qwen3VLConfig, model::Qwen3VLModel, processor::Qwen3VLProcessor},
|
||||||
},
|
},
|
||||||
@@ -25,7 +23,7 @@ pub struct Qwen3VLGenerateModel<'a> {
|
|||||||
chat_template: ChatTemplate<'a>,
|
chat_template: ChatTemplate<'a>,
|
||||||
tokenizer: TokenizerModel,
|
tokenizer: TokenizerModel,
|
||||||
pre_processor: Qwen3VLProcessor,
|
pre_processor: Qwen3VLProcessor,
|
||||||
qwen3_vl: Qwen3VLModel,
|
model: Qwen3VLModel,
|
||||||
device: Device,
|
device: Device,
|
||||||
generation_config: Qwen3GenerationConfig,
|
generation_config: Qwen3GenerationConfig,
|
||||||
model_name: String,
|
model_name: String,
|
||||||
@@ -46,7 +44,7 @@ impl<'a> Qwen3VLGenerateModel<'a> {
|
|||||||
let generation_config_path = path.to_string() + "/generation_config.json";
|
let generation_config_path = path.to_string() + "/generation_config.json";
|
||||||
let generation_config: Qwen3GenerationConfig =
|
let generation_config: Qwen3GenerationConfig =
|
||||||
serde_json::from_slice(&std::fs::read(generation_config_path)?)?;
|
serde_json::from_slice(&std::fs::read(generation_config_path)?)?;
|
||||||
let qwen3_vl = Qwen3VLModel::new(cfg, vb, generation_config.eos_token_id.clone())?;
|
let model = Qwen3VLModel::new(cfg, vb, generation_config.eos_token_id.clone())?;
|
||||||
|
|
||||||
let model_name = std::path::Path::new(path)
|
let model_name = std::path::Path::new(path)
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -57,7 +55,7 @@ impl<'a> Qwen3VLGenerateModel<'a> {
|
|||||||
chat_template,
|
chat_template,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
pre_processor,
|
pre_processor,
|
||||||
qwen3_vl,
|
model,
|
||||||
device,
|
device,
|
||||||
generation_config,
|
generation_config,
|
||||||
model_name,
|
model_name,
|
||||||
@@ -65,76 +63,28 @@ impl<'a> Qwen3VLGenerateModel<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> GenerateModel for Qwen3VLGenerateModel<'a> {
|
impl<'a> GenerationDataProvider for Qwen3VLGenerateModel<'a> {
|
||||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
fn get_temperature(&self, req_temp: Option<f32>) -> Option<f32> {
|
||||||
let temperature = mes
|
Some(req_temp.unwrap_or(self.generation_config.temperature))
|
||||||
.temperature
|
|
||||||
.unwrap_or(self.generation_config.temperature);
|
|
||||||
let top_p = mes.top_p.unwrap_or(self.generation_config.top_p);
|
|
||||||
let top_k = self.generation_config.top_k;
|
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let input = self.pre_processor.process_info(&mes, &mes_render)?;
|
|
||||||
let input_ids = self
|
|
||||||
.tokenizer
|
|
||||||
.text_encode(input.replace_text.clone(), &self.device)?;
|
|
||||||
let seq_len = input_ids.dim(1)?;
|
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let mut ctx = GenerationContext::new(
|
|
||||||
temperature.into(),
|
|
||||||
top_p.into(),
|
|
||||||
top_k.into(),
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
input_ids.dim(1)?,
|
|
||||||
sample_len,
|
|
||||||
self.device.clone(),
|
|
||||||
);
|
|
||||||
let cache_position = Tensor::arange(0u32, seq_len as u32, &self.device)?;
|
|
||||||
let data_vec = vec![
|
|
||||||
input.pixel_values,
|
|
||||||
input.image_grid_thw,
|
|
||||||
input.pixel_values_video,
|
|
||||||
input.video_grid_thw,
|
|
||||||
cache_position.into(),
|
|
||||||
];
|
|
||||||
let data = MultiModalData::new(data_vec);
|
|
||||||
generate_generic(
|
|
||||||
&mut self.qwen3_vl,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
&mut ctx,
|
|
||||||
&self.model_name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_stream(
|
fn get_top_p(&self, req_top_p: Option<f32>) -> Option<f32> {
|
||||||
&mut self,
|
Some(req_top_p.unwrap_or(self.generation_config.top_p))
|
||||||
mes: ChatCompletionParameters,
|
}
|
||||||
) -> Result<
|
|
||||||
Box<
|
fn get_top_k(&self, top_k: Option<usize>) -> Option<usize> {
|
||||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
Some(top_k.unwrap_or(self.generation_config.top_k))
|
||||||
+ Send
|
}
|
||||||
+ Unpin
|
|
||||||
+ '_,
|
fn get_data(&self, mes: &ChatCompletionParameters) -> Result<PrepareData> {
|
||||||
>,
|
let mes_render = self.chat_template.apply_chat_template(mes)?;
|
||||||
> {
|
let in_reasoning = self.is_in_reasoning(&mes_render);
|
||||||
let temperature = mes
|
let input = self.pre_processor.process_info(mes, &mes_render)?;
|
||||||
.temperature
|
|
||||||
.unwrap_or(self.generation_config.temperature);
|
|
||||||
let top_p = mes.top_p.unwrap_or(self.generation_config.top_p);
|
|
||||||
let top_k = self.generation_config.top_k;
|
|
||||||
let mes_render = self.chat_template.apply_chat_template(&mes)?;
|
|
||||||
let in_reasoning = mes_render.ends_with("<think>\n");
|
|
||||||
let input = self.pre_processor.process_info(&mes, &mes_render)?;
|
|
||||||
let input_ids = self
|
let input_ids = self
|
||||||
.tokenizer
|
.tokenizer
|
||||||
.text_encode(input.replace_text.clone(), &self.device)?;
|
.text_encode(input.replace_text.clone(), &self.device)?;
|
||||||
let seq_len = input_ids.dim(1)?;
|
let seq_len = input_ids.dim(1)?;
|
||||||
let cache_position = Tensor::arange(0u32, seq_len as u32, &self.device)?;
|
let cache_position = Tensor::arange(0u32, seq_len as u32, &self.device)?;
|
||||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
|
||||||
let data_vec = vec![
|
let data_vec = vec![
|
||||||
input.pixel_values,
|
input.pixel_values,
|
||||||
input.image_grid_thw,
|
input.image_grid_thw,
|
||||||
@@ -142,24 +92,13 @@ impl<'a> GenerateModel for Qwen3VLGenerateModel<'a> {
|
|||||||
input.video_grid_thw,
|
input.video_grid_thw,
|
||||||
cache_position.into(),
|
cache_position.into(),
|
||||||
];
|
];
|
||||||
let data = MultiModalData::new(data_vec);
|
let multi_model_data = MultiModalData::new(data_vec);
|
||||||
let seed = mes.seed.unwrap_or(34562) as u64;
|
Ok(PrepareData {
|
||||||
let stream = generate_stream_generic(
|
|
||||||
&mut self.qwen3_vl,
|
|
||||||
&self.tokenizer,
|
|
||||||
input_ids,
|
|
||||||
data,
|
|
||||||
temperature.into(),
|
|
||||||
top_p.into(),
|
|
||||||
top_k.into(),
|
|
||||||
mes.repeat_penalty,
|
|
||||||
mes.repeat_last_n,
|
|
||||||
seed,
|
|
||||||
sample_len,
|
|
||||||
in_reasoning,
|
in_reasoning,
|
||||||
&self.device,
|
input_ids,
|
||||||
&self.model_name,
|
multi_model_data,
|
||||||
)?;
|
})
|
||||||
Ok(Box::new(Box::pin(stream)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crate::impl_generate_model!(Qwen3VLGenerateModel<'a>);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct VoxCPMGenerate {
|
pub struct VoxCPMGenerate {
|
||||||
voxcpm: VoxCPMModel,
|
model: VoxCPMModel,
|
||||||
prompt_cache: Option<HashMap<String, Tensor>>,
|
prompt_cache: Option<HashMap<String, Tensor>>,
|
||||||
out_sample_rate: usize,
|
out_sample_rate: usize,
|
||||||
model_name: String,
|
model_name: String,
|
||||||
@@ -106,12 +106,12 @@ impl VoxCPMGenerate {
|
|||||||
VarBuilder::from_tensors(dict_to_hashmap, m_dtype, device)
|
VarBuilder::from_tensors(dict_to_hashmap, m_dtype, device)
|
||||||
};
|
};
|
||||||
let tokenizer = SingleChineseTokenizer::new(path)?;
|
let tokenizer = SingleChineseTokenizer::new(path)?;
|
||||||
let voxcpm = VoxCPMModel::new(vb_voxcpm, config, tokenizer, audio_vae)?;
|
let model = VoxCPMModel::new(vb_voxcpm, config, tokenizer, audio_vae)?;
|
||||||
let out_sample_rate = audio_config
|
let out_sample_rate = audio_config
|
||||||
.out_sample_rate
|
.out_sample_rate
|
||||||
.unwrap_or(audio_config.sample_rate);
|
.unwrap_or(audio_config.sample_rate);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
voxcpm,
|
model,
|
||||||
prompt_cache: None,
|
prompt_cache: None,
|
||||||
out_sample_rate,
|
out_sample_rate,
|
||||||
model_name,
|
model_name,
|
||||||
@@ -124,7 +124,7 @@ impl VoxCPMGenerate {
|
|||||||
prompt_wav_path: String,
|
prompt_wav_path: String,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let cache = self
|
let cache = self
|
||||||
.voxcpm
|
.model
|
||||||
.build_prompt_cache(prompt_text, prompt_wav_path)?;
|
.build_prompt_cache(prompt_text, prompt_wav_path)?;
|
||||||
self.prompt_cache = Some(cache);
|
self.prompt_cache = Some(cache);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -143,7 +143,7 @@ impl VoxCPMGenerate {
|
|||||||
let audio = match &self.prompt_cache {
|
let audio = match &self.prompt_cache {
|
||||||
Some(cache) => {
|
Some(cache) => {
|
||||||
let prompt_cache = cache.clone();
|
let prompt_cache = cache.clone();
|
||||||
self.voxcpm.generate_with_prompt_cache(
|
self.model.generate_with_prompt_cache(
|
||||||
target_text,
|
target_text,
|
||||||
prompt_cache,
|
prompt_cache,
|
||||||
min_len,
|
min_len,
|
||||||
@@ -156,7 +156,7 @@ impl VoxCPMGenerate {
|
|||||||
}
|
}
|
||||||
None => self.generate_simple(target_text)?,
|
None => self.generate_simple(target_text)?,
|
||||||
};
|
};
|
||||||
self.voxcpm.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
Ok(audio)
|
Ok(audio)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ impl VoxCPMGenerate {
|
|||||||
// retry_badcase: bool,
|
// retry_badcase: bool,
|
||||||
retry_badcase_ratio_threshold: f64,
|
retry_badcase_ratio_threshold: f64,
|
||||||
) -> Result<Tensor> {
|
) -> Result<Tensor> {
|
||||||
let audio = self.voxcpm.generate(
|
let audio = self.model.generate(
|
||||||
target_text,
|
target_text,
|
||||||
prompt_text,
|
prompt_text,
|
||||||
prompt_wav_path,
|
prompt_wav_path,
|
||||||
@@ -207,7 +207,7 @@ impl VoxCPMGenerate {
|
|||||||
// retry_badcase,
|
// retry_badcase,
|
||||||
retry_badcase_ratio_threshold,
|
retry_badcase_ratio_threshold,
|
||||||
)?;
|
)?;
|
||||||
self.voxcpm.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
Ok(audio)
|
Ok(audio)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ impl GenerateModel for VoxCPMGenerate {
|
|||||||
target_text = format!("({instruction}){target_text}");
|
target_text = format!("({instruction}){target_text}");
|
||||||
}
|
}
|
||||||
let audio = self
|
let audio = self
|
||||||
.voxcpm
|
.model
|
||||||
.generate(
|
.generate(
|
||||||
target_text,
|
target_text,
|
||||||
prompt_text,
|
prompt_text,
|
||||||
@@ -262,13 +262,13 @@ impl GenerateModel for VoxCPMGenerate {
|
|||||||
retry_badcase_ratio_threshold,
|
retry_badcase_ratio_threshold,
|
||||||
)
|
)
|
||||||
.inspect_err(|_| {
|
.inspect_err(|_| {
|
||||||
self.voxcpm.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
})?;
|
})?;
|
||||||
let wav_u8 = get_audio_wav_u8(&audio, self.out_sample_rate as u32)?;
|
let wav_u8 = get_audio_wav_u8(&audio, self.out_sample_rate as u32)?;
|
||||||
// let wave_u8_str = String::from_utf8(wav_u8)?;
|
// let wave_u8_str = String::from_utf8(wav_u8)?;
|
||||||
let base64_audio = BASE64_STANDARD.encode(wav_u8);
|
let base64_audio = BASE64_STANDARD.encode(wav_u8);
|
||||||
let response = build_audio_completion_response(&base64_audio, &self.model_name);
|
let response = build_audio_completion_response(&base64_audio, &self.model_name);
|
||||||
self.voxcpm.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct VoxCPMGenerateRefact {
|
pub struct VoxCPMGenerateRefact {
|
||||||
voxcpm: VoxCPMModelRefact,
|
model: VoxCPMModelRefact,
|
||||||
tokenizer: SingleChineseTokenizer,
|
tokenizer: SingleChineseTokenizer,
|
||||||
audio_vae: AudioVAE,
|
audio_vae: AudioVAE,
|
||||||
processor: VoxCPMProcessor,
|
processor: VoxCPMProcessor,
|
||||||
@@ -113,13 +113,13 @@ impl VoxCPMGenerateRefact {
|
|||||||
VarBuilder::from_tensors(dict_to_hashmap, m_dtype, device)
|
VarBuilder::from_tensors(dict_to_hashmap, m_dtype, device)
|
||||||
};
|
};
|
||||||
let tokenizer = SingleChineseTokenizer::new(path)?;
|
let tokenizer = SingleChineseTokenizer::new(path)?;
|
||||||
let voxcpm =
|
let model =
|
||||||
VoxCPMModelRefact::new(vb_voxcpm, config, audio_vae.latent_dim, decode_chunk_size)?;
|
VoxCPMModelRefact::new(vb_voxcpm, config, audio_vae.latent_dim, decode_chunk_size)?;
|
||||||
let out_sample_rate = audio_config
|
let out_sample_rate = audio_config
|
||||||
.out_sample_rate
|
.out_sample_rate
|
||||||
.unwrap_or(audio_config.sample_rate);
|
.unwrap_or(audio_config.sample_rate);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
voxcpm,
|
model,
|
||||||
tokenizer,
|
tokenizer,
|
||||||
audio_vae,
|
audio_vae,
|
||||||
processor,
|
processor,
|
||||||
@@ -162,7 +162,7 @@ impl VoxCPMGenerateRefact {
|
|||||||
} else {
|
} else {
|
||||||
max_len
|
max_len
|
||||||
};
|
};
|
||||||
let audio = self.voxcpm.inference(
|
let audio = self.model.inference(
|
||||||
&text_token,
|
&text_token,
|
||||||
audio_feat.as_ref(),
|
audio_feat.as_ref(),
|
||||||
audio_mask.as_ref(),
|
audio_mask.as_ref(),
|
||||||
@@ -172,7 +172,7 @@ impl VoxCPMGenerateRefact {
|
|||||||
cfg_value,
|
cfg_value,
|
||||||
&self.audio_vae,
|
&self.audio_vae,
|
||||||
)?;
|
)?;
|
||||||
self.voxcpm.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
Ok(audio)
|
Ok(audio)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ impl VoxCPMGenerateRefact {
|
|||||||
} else {
|
} else {
|
||||||
max_len
|
max_len
|
||||||
};
|
};
|
||||||
self.voxcpm.inference(
|
self.model.inference(
|
||||||
&text_token,
|
&text_token,
|
||||||
audio_feat.as_ref(),
|
audio_feat.as_ref(),
|
||||||
audio_mask.as_ref(),
|
audio_mask.as_ref(),
|
||||||
@@ -255,7 +255,7 @@ impl VoxCPMGenerateRefact {
|
|||||||
return Err(anyhow!("need prompt_cache"));
|
return Err(anyhow!("need prompt_cache"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.voxcpm.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
Ok(audio)
|
Ok(audio)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ impl VoxCPMGenerateRefact {
|
|||||||
} else {
|
} else {
|
||||||
max_len
|
max_len
|
||||||
};
|
};
|
||||||
self.voxcpm.inference_stream(
|
self.model.inference_stream(
|
||||||
text_token,
|
text_token,
|
||||||
audio_feat,
|
audio_feat,
|
||||||
audio_mask,
|
audio_mask,
|
||||||
@@ -344,13 +344,13 @@ impl GenerateModel for VoxCPMGenerateRefact {
|
|||||||
retry_badcase_ratio_threshold,
|
retry_badcase_ratio_threshold,
|
||||||
)
|
)
|
||||||
.inspect_err(|_| {
|
.inspect_err(|_| {
|
||||||
self.voxcpm.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
})?;
|
})?;
|
||||||
let wav_u8 = get_audio_wav_u8(&audio, self.out_sample_rate as u32)?;
|
let wav_u8 = get_audio_wav_u8(&audio, self.out_sample_rate as u32)?;
|
||||||
// let wave_u8_str = String::from_utf8(wav_u8)?;
|
// let wave_u8_str = String::from_utf8(wav_u8)?;
|
||||||
let base64_audio = BASE64_STANDARD.encode(wav_u8);
|
let base64_audio = BASE64_STANDARD.encode(wav_u8);
|
||||||
let response = build_audio_completion_response(&base64_audio, &self.model_name);
|
let response = build_audio_completion_response(&base64_audio, &self.model_name);
|
||||||
self.voxcpm.clear_kv_cache();
|
self.model.clear_kv_cache();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
|
|||||||
+2
-2
@@ -5,11 +5,11 @@
|
|||||||
// use std::io::{Read, Seek};
|
// use std::io::{Read, Seek};
|
||||||
// use std::{io::Cursor, time::Instant};
|
// use std::{io::Cursor, time::Instant};
|
||||||
|
|
||||||
use aha::utils::tensor_utils::get_mask_from_lengths;
|
// use aha::utils::tensor_utils::get_mask_from_lengths;
|
||||||
// use aha::utils::tensor_utils::repeat_interleave;
|
// use aha::utils::tensor_utils::repeat_interleave;
|
||||||
// use crate::params::chat::ChatCompletionParameters;
|
// use crate::params::chat::ChatCompletionParameters;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use candle_core::Tensor;
|
// use candle_core::Tensor;
|
||||||
// use kaldi_native_fbank::{
|
// use kaldi_native_fbank::{
|
||||||
// FbankComputer, FbankOptions,
|
// FbankComputer, FbankOptions,
|
||||||
// window::{Window, extract_window},
|
// window::{Window, extract_window},
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ fn fun_asr_nano_generate() -> Result<()> {
|
|||||||
"type": "audio",
|
"type": "audio",
|
||||||
"audio_url":
|
"audio_url":
|
||||||
{
|
{
|
||||||
"url": "file://./assets/audio/voice_01.wav"
|
"url": "https://package-release.coderbox.cn/aiway/test/other/%E5%93%AA%E5%90%92.wav"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ fn glm_asr_nano_generate() -> Result<()> {
|
|||||||
"type": "audio",
|
"type": "audio",
|
||||||
"audio_url":
|
"audio_url":
|
||||||
{
|
{
|
||||||
"url": "file://./assets/audio/voice_01.wav"
|
"url": "https://package-release.coderbox.cn/aiway/test/other/%E5%93%AA%E5%90%92.wav"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ fn glm_ocr_generate() -> Result<()> {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn glm_ocr_stream() -> Result<()> {
|
async fn glm_ocr_stream() -> Result<()> {
|
||||||
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda glm_ocr_stream -r -- --nocapture
|
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda --test test_glm_ocr glm_ocr_stream -r -- --nocapture
|
||||||
|
|
||||||
let message = r#"
|
let message = r#"
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ fn hunyuan_ocr_generate() -> Result<()> {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn hunyuan_ocr_stream() -> Result<()> {
|
async fn hunyuan_ocr_stream() -> Result<()> {
|
||||||
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda hunyuan_ocr_stream -r -- --nocapture
|
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda --test test_hunyuan_ocr hunyuan_ocr_stream -r -- --nocapture
|
||||||
|
|
||||||
let message = r#"
|
let message = r#"
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ async fn lfm2vl_stream() -> Result<()> {
|
|||||||
let save_dir =
|
let save_dir =
|
||||||
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
||||||
// let model_path = format!("{}/LiquidAI/LFM2-1.2B/", save_dir);
|
// let model_path = format!("{}/LiquidAI/LFM2-1.2B/", save_dir);
|
||||||
let model_path = format!("{}/LiquidAI/LFM2.5-VL-1.6B/", save_dir);
|
let model_path = format!("{}/LiquidAI/LFM2.5-VL-450M/", save_dir);
|
||||||
let message = r#"
|
let message = r#"
|
||||||
{
|
{
|
||||||
"model": "lfm2vl",
|
"model": "lfm2vl",
|
||||||
|
|||||||
+41
-2
@@ -1,10 +1,11 @@
|
|||||||
use std::time::Instant;
|
use std::{pin::pin, time::Instant};
|
||||||
|
|
||||||
use aha::{
|
use aha::{
|
||||||
models::{GenerateModel, minicpm5::generate::MiniCPM5GenerateModel},
|
models::{GenerateModel, minicpm5::generate::MiniCPM5GenerateModel},
|
||||||
params::chat::ChatCompletionParameters,
|
params::chat::ChatCompletionParameters,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use rocket::futures::StreamExt;
|
||||||
#[test]
|
#[test]
|
||||||
fn minicpm5_generate() -> Result<()> {
|
fn minicpm5_generate() -> Result<()> {
|
||||||
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda --test test_minicpm5 minicpm5_generate -r -- --nocapture
|
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda --test test_minicpm5 minicpm5_generate -r -- --nocapture
|
||||||
@@ -16,7 +17,7 @@ fn minicpm5_generate() -> Result<()> {
|
|||||||
{
|
{
|
||||||
"temperature": 0.3,
|
"temperature": 0.3,
|
||||||
"top_p": 0.8,
|
"top_p": 0.8,
|
||||||
"model": "minicpm4",
|
"model": "minicpm5",
|
||||||
"messages": [
|
"messages": [
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
@@ -39,3 +40,41 @@ fn minicpm5_generate() -> Result<()> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn minicpm5_stream() -> Result<()> {
|
||||||
|
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda --test test_minicpm5 minicpm5_stream -r -- --nocapture
|
||||||
|
|
||||||
|
let save_dir =
|
||||||
|
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
||||||
|
let model_path = format!("{}/OpenBMB/MiniCPM5-1B/", save_dir);
|
||||||
|
|
||||||
|
let message = r#"
|
||||||
|
{
|
||||||
|
"model": "minicpm5",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "什么是AI"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"enable_thinking": true
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let mes: ChatCompletionParameters = serde_json::from_str(message)?;
|
||||||
|
let i_start = Instant::now();
|
||||||
|
let mut model = MiniCPM5GenerateModel::init(&model_path, None, None)?;
|
||||||
|
let i_duration = i_start.elapsed();
|
||||||
|
println!("Time elapsed in load model is: {:?}", i_duration);
|
||||||
|
|
||||||
|
let i_start = Instant::now();
|
||||||
|
let mut stream = pin!(model.generate_stream(mes)?);
|
||||||
|
while let Some(item) = stream.next().await {
|
||||||
|
println!("generate: \n {:?}", item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let i_duration = i_start.elapsed();
|
||||||
|
println!("Time elapsed in generate is: {:?}", i_duration);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,55 @@ use aha::params::chat::ChatCompletionParameters;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use rocket::futures::StreamExt;
|
use rocket::futures::StreamExt;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn paddleocr_vl_1_6_generate() -> Result<()> {
|
||||||
|
// RUST_BACKTRACE=1 cargo test -F cuda --test test_paddleocr_vl paddleocr_vl_1_6_generate -r -- --nocapture
|
||||||
|
let message = r#"
|
||||||
|
{
|
||||||
|
"model": "paddleocr_vl1.6",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"image_url":
|
||||||
|
{
|
||||||
|
"url": "file://./assets/img/ocr_test1.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Spotting:"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stream": false
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
let save_dir =
|
||||||
|
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
||||||
|
let model_path = format!("{}/PaddlePaddle/PaddleOCR-VL-1.6/", save_dir);
|
||||||
|
let mes: ChatCompletionParameters = serde_json::from_str(message)?;
|
||||||
|
let i_start = Instant::now();
|
||||||
|
let mut model = PaddleOCRVLGenerateModel::init(&model_path, None, None)?;
|
||||||
|
let i_duration = i_start.elapsed();
|
||||||
|
println!("Time elapsed in load model is: {:?}", i_duration);
|
||||||
|
let i_start = Instant::now();
|
||||||
|
let res = model.generate(mes)?;
|
||||||
|
let i_duration = i_start.elapsed();
|
||||||
|
println!("generate: \n {:?}", res);
|
||||||
|
if let Some(usage) = &res.usage {
|
||||||
|
let num_token = usage.total_tokens;
|
||||||
|
let duration_secs = i_duration.as_secs_f64();
|
||||||
|
let tps = num_token as f64 / duration_secs;
|
||||||
|
println!("Tokens per second (TPS): {:.2}", tps);
|
||||||
|
}
|
||||||
|
println!("Time elapsed in generate is: {:?}", i_duration);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn paddleocr_vl_1_5_generate() -> Result<()> {
|
fn paddleocr_vl_1_5_generate() -> Result<()> {
|
||||||
// RUST_BACKTRACE=1 cargo test -F cuda --test test_paddleocr_vl paddleocr_vl_1_5_generate -r -- --nocapture
|
// RUST_BACKTRACE=1 cargo test -F cuda --test test_paddleocr_vl paddleocr_vl_1_5_generate -r -- --nocapture
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ fn qwen3_0_6b_generate() -> Result<()> {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn qwen3_0_6b_stream() -> Result<()> {
|
async fn qwen3_0_6b_stream() -> Result<()> {
|
||||||
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda qwen3_0_6b_stream -r -- --nocapture
|
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda --test test_qwen3 qwen3_0_6b_stream -r -- --nocapture
|
||||||
|
|
||||||
let save_dir =
|
let save_dir =
|
||||||
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ fn qwen3vl_generate() -> Result<()> {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn qwen3vl_stream() -> Result<()> {
|
async fn qwen3vl_stream() -> Result<()> {
|
||||||
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda,ffmpeg qwen3vl_stream -r -- --nocapture
|
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda --test test_qwen3vl qwen3vl_stream -r -- --nocapture
|
||||||
|
|
||||||
let save_dir =
|
let save_dir =
|
||||||
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
||||||
@@ -118,15 +118,15 @@ async fn qwen3vl_stream() -> Result<()> {
|
|||||||
"role": "user",
|
"role": "user",
|
||||||
"content": [
|
"content": [
|
||||||
{
|
{
|
||||||
"type": "video",
|
"type": "image",
|
||||||
"video_url":
|
"image_url":
|
||||||
{
|
{
|
||||||
"url": "./assets/video/video_test.mp4"
|
"url": "file://./assets/img/ocr_test1.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"text": "视频中发生了什么?"
|
"text": "OCR"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user