yongqiang commited on
Commit
aa969ca
·
1 Parent(s): 5142710

Add AX650 MiniCPM-V 4.6 release package

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +10 -32
  2. .gitignore +4 -0
  3. README.md +401 -1
  4. assets/smoke_image.png +3 -0
  5. bin/axllm +3 -0
  6. bin/axllm.version.json +26 -0
  7. config.json +25 -0
  8. minicpm_v46_tokenizer.txt +3 -0
  9. minicpm_v46_tokenizer/.gitattributes +36 -0
  10. minicpm_v46_tokenizer/README.md +498 -0
  11. minicpm_v46_tokenizer/chat_template.jinja +145 -0
  12. minicpm_v46_tokenizer/config.json +90 -0
  13. minicpm_v46_tokenizer/generation_config.json +13 -0
  14. minicpm_v46_tokenizer/preprocessor_config.json +19 -0
  15. minicpm_v46_tokenizer/tokenizer.json +3 -0
  16. minicpm_v46_tokenizer/tokenizer_config.json +42 -0
  17. minicpmv4_6_vision_448.axmodel +3 -0
  18. model.embed_tokens.weight.bfloat16.bin +3 -0
  19. post_config.json +14 -0
  20. python/infer_axmodel.py +701 -0
  21. python/infer_torch.py +93 -0
  22. python/minicpm_v46_tokenizer/.gitattributes +36 -0
  23. python/minicpm_v46_tokenizer/README.md +498 -0
  24. python/minicpm_v46_tokenizer/chat_template.jinja +145 -0
  25. python/minicpm_v46_tokenizer/config.json +90 -0
  26. python/minicpm_v46_tokenizer/generation_config.json +13 -0
  27. python/minicpm_v46_tokenizer/preprocessor_config.json +19 -0
  28. python/minicpm_v46_tokenizer/tokenizer.json +3 -0
  29. python/minicpm_v46_tokenizer/tokenizer_config.json +42 -0
  30. qwen3_5_text_p128_l0_together.axmodel +3 -0
  31. qwen3_5_text_p128_l10_together.axmodel +3 -0
  32. qwen3_5_text_p128_l11_together.axmodel +3 -0
  33. qwen3_5_text_p128_l12_together.axmodel +3 -0
  34. qwen3_5_text_p128_l13_together.axmodel +3 -0
  35. qwen3_5_text_p128_l14_together.axmodel +3 -0
  36. qwen3_5_text_p128_l15_together.axmodel +3 -0
  37. qwen3_5_text_p128_l16_together.axmodel +3 -0
  38. qwen3_5_text_p128_l17_together.axmodel +3 -0
  39. qwen3_5_text_p128_l18_together.axmodel +3 -0
  40. qwen3_5_text_p128_l19_together.axmodel +3 -0
  41. qwen3_5_text_p128_l1_together.axmodel +3 -0
  42. qwen3_5_text_p128_l20_together.axmodel +3 -0
  43. qwen3_5_text_p128_l21_together.axmodel +3 -0
  44. qwen3_5_text_p128_l22_together.axmodel +3 -0
  45. qwen3_5_text_p128_l23_together.axmodel +3 -0
  46. qwen3_5_text_p128_l2_together.axmodel +3 -0
  47. qwen3_5_text_p128_l3_together.axmodel +3 -0
  48. qwen3_5_text_p128_l4_together.axmodel +3 -0
  49. qwen3_5_text_p128_l5_together.axmodel +3 -0
  50. qwen3_5_text_p128_l6_together.axmodel +3 -0
.gitattributes CHANGED
@@ -1,35 +1,13 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
  *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
  *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.axmodel filter=lfs diff=lfs merge=lfs -text
 
2
  *.bin filter=lfs diff=lfs merge=lfs -text
3
+ *.png filter=lfs diff=lfs merge=lfs -text
4
+ *.jpg filter=lfs diff=lfs merge=lfs -text
5
+ *.JPG filter=lfs diff=lfs merge=lfs -text
6
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
7
+ bin/axllm filter=lfs diff=lfs merge=lfs -text
8
+ *.gguf filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
9
  *.onnx filter=lfs diff=lfs merge=lfs -text
10
+ *.mp4 filter=lfs diff=lfs merge=lfs -text
11
+ *.npy filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
12
  *.safetensors filter=lfs diff=lfs merge=lfs -text
13
+ minicpm_v46_tokenizer.txt filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ vision_cache/
4
+ *.tmp
README.md CHANGED
@@ -1,3 +1,403 @@
1
  ---
2
- license: bsd-3-clause
 
 
 
 
 
 
 
 
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: transformers
3
+ license: apache-2.0
4
+ base_model:
5
+ - openbmb/MiniCPM-V-4.6
6
+ pipeline_tag: image-text-to-text
7
+ tags:
8
+ - minicpm-v
9
+ - vlm
10
+ - axera
11
+ - AX650
12
+ language:
13
+ - zh
14
+ - en
15
  ---
16
+
17
+ # MiniCPM-V-4.6 on AXERA NPU
18
+
19
+ Ready-to-run AX650 deployment package for `openbmb/MiniCPM-V-4.6`.
20
+
21
+ - Runtime: `axllm serve` and `axllm run`
22
+ - Target: `AX650 / AX650N aarch64`
23
+ - Text backend: Qwen3.5 dense LLM, `bf16`, non-GPTQ weights
24
+ - Vision backend: fixed-shape `448x448` MiniCPM-V-4.6 vision encoder
25
+ - Context layout: `prefill_len=128`, `kv_cache_len=2047`, `prefill_max_token_num=1280`
26
+ - Validated capabilities in this release:
27
+ - text-only chat
28
+ - long prompt multi-chunk prefill
29
+ - single-image understanding
30
+ - video understanding
31
+ - Included board binary:
32
+ - `bin/axllm` for `AX650/AX650N aarch64`
33
+
34
+ This package has been validated for text chat, single-image understanding, and video understanding through the OpenAI-compatible `axllm serve` API.
35
+
36
+ ## Supported Platform
37
+
38
+ - [x] AX650 / AX650N
39
+
40
+ ## Performance and Footprint
41
+
42
+ Measurements below are from the preserved AX650 runtime log in [Inference Log](#inference-log), plus a repeated text-only TTFT recheck on `2026-06-05`. `TTFT` means time to first token.
43
+ The text-only smoke prompt is intentionally kept within one `128`-token prefill group. Its TTFT is the 5-run average for the prompt `1+1等于几?只输出数字。`; the response is one token, so decode throughput is not reported for that row.
44
+
45
+ | Scenario | Input tokens | Prefill chunks | TTFT | Decode |
46
+ |---|---:|---:|---:|---:|
47
+ | Text-only smoke prompt | `22` | `1 x 128` | `240.45 ms avg` (`239.20-242.75 ms`) | `n/a` |
48
+ | Video red-panda prompt | `1278` | `10 x 128` | `1795.28 ms` | `18.79 token/s` |
49
+ | Image prompt | `358` | `3 x 128` | `518.23 ms` | `18.64 token/s` |
50
+
51
+ ### Flash Footprint
52
+
53
+ | Item | Value |
54
+ |---|---:|
55
+ | Text + post + vision axmodels | `1460.19 MiB` |
56
+ | Package flash footprint, excluding `vision_cache/` | `1999.34 MiB` |
57
+
58
+ ### Startup Runtime Footprint
59
+
60
+ Measured on AX650 on `2026-06-05` by reading `/proc/ax_proc/mem_cmm_info` before startup and after the `/health` endpoint became ready. The measurement kept the existing GPTQ service on port `8000` running and records only the incremental CMM used by this extra service.
61
+
62
+ | Item | Value |
63
+ |---|---:|
64
+ | Baseline CMM before startup | `1823 MB` |
65
+ | CMM after `/health` ready | `3388 MB` |
66
+ | Incremental runtime CMM | `1565 MB` |
67
+ | Startup log `remain_cmm` after post axmodel init | `5388 MB` |
68
+
69
+ The preserved standalone inference log below records `remain_cmm(6933 MB)` for an earlier single-service startup; use the incremental CMM row above for the board-side footprint measured during this README refresh.
70
+
71
+ The runtime log also records the video sampling decision:
72
+
73
+ ```text
74
+ MiniCPM-V-4.6 video frames selected: 19/59 (configured_cap=0, tail_tokens=1278, max_tail=1280, precompute_len=0)
75
+ ```
76
+
77
+ ## Repository Layout
78
+
79
+ ```text
80
+ .
81
+ ├── README.md
82
+ ├── bin/
83
+ │ ├── axllm
84
+ │ └── axllm.version.json
85
+ ├── assets/
86
+ │ └── smoke_image.png
87
+ ├── minicpm_v46_tokenizer/
88
+ ├── python/
89
+ │ ├── infer_axmodel.py
90
+ │ ├── infer_torch.py
91
+ │ └── minicpm_v46_tokenizer/
92
+ ├── minicpmv4_6_vision_448.axmodel
93
+ ├── qwen3_5_text_p128_l0_together.axmodel
94
+ ├── ...
95
+ ├── qwen3_5_text_p128_l23_together.axmodel
96
+ ├── qwen3_5_text_post.axmodel
97
+ ├── model.embed_tokens.weight.bfloat16.bin
98
+ ├── config.json
99
+ ├── post_config.json
100
+ └── minicpm_v46_tokenizer.txt
101
+ ```
102
+
103
+ ## Install `axllm`
104
+
105
+ Option 1: use the validated binary included in this repository:
106
+
107
+ ```bash
108
+ chmod +x ./bin/axllm
109
+ ```
110
+
111
+ Option 2: build or install the latest `ax-llm` yourself:
112
+
113
+ - Repo: `https://github.com/AXERA-TECH/ax-llm`
114
+ - The included binary metadata is recorded in `bin/axllm.version.json`.
115
+
116
+ ## Run on AX650
117
+
118
+ From the repository root on the board:
119
+
120
+ ```bash
121
+ chmod +x ./bin/axllm
122
+ ./bin/axllm serve . --port 18080
123
+ ```
124
+
125
+ Expected model id:
126
+
127
+ ```text
128
+ AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047
129
+ ```
130
+
131
+ ### Text Request
132
+
133
+ ```bash
134
+ curl http://127.0.0.1:18080/v1/chat/completions \
135
+ -H 'Content-Type: application/json' \
136
+ -d '{
137
+ "model": "AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047",
138
+ "messages": [
139
+ {
140
+ "role": "user",
141
+ "content": [
142
+ {"type": "text", "text": "1+1等于几?只输出数字。"}
143
+ ]
144
+ }
145
+ ],
146
+ "max_tokens": 32
147
+ }'
148
+ ```
149
+
150
+ ### Image Request
151
+
152
+ ```bash
153
+ python - <<'PY'
154
+ import base64
155
+ import json
156
+ from pathlib import Path
157
+ from urllib.request import Request, urlopen
158
+
159
+ img = Path("assets/smoke_image.png").read_bytes()
160
+ payload = {
161
+ "model": "AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047",
162
+ "messages": [
163
+ {
164
+ "role": "user",
165
+ "content": [
166
+ {"type": "text", "text": "Please briefly describe this image."},
167
+ {
168
+ "type": "image_url",
169
+ "image_url": {
170
+ "url": "data:image/png;base64," + base64.b64encode(img).decode()
171
+ },
172
+ },
173
+ ],
174
+ }
175
+ ],
176
+ "max_tokens": 64,
177
+ }
178
+ req = Request(
179
+ "http://127.0.0.1:18080/v1/chat/completions",
180
+ data=json.dumps(payload).encode(),
181
+ headers={"Content-Type": "application/json"},
182
+ )
183
+ with urlopen(req, timeout=60) as resp:
184
+ print(resp.read().decode())
185
+ PY
186
+ ```
187
+
188
+ ### Video Request
189
+
190
+ `axllm serve` supports a frames directory or a raw video file:
191
+
192
+ ```bash
193
+ curl http://127.0.0.1:18080/v1/chat/completions \
194
+ -H 'Content-Type: application/json' \
195
+ -d '{
196
+ "model": "AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047",
197
+ "messages": [
198
+ {
199
+ "role": "user",
200
+ "content": [
201
+ {"type": "image_url", "image_url": {"url": "video:/path/to/frames_dir"}},
202
+ {"type": "text", "text": "Describe this video."}
203
+ ]
204
+ }
205
+ ],
206
+ "max_tokens": 256
207
+ }'
208
+ ```
209
+
210
+ For a raw video file, use `video:/path/to/video.mp4` or `video:/path/to/video.mp4:2` to request a sampling FPS of `2`.
211
+
212
+ ### Interactive CLI
213
+
214
+ ```bash
215
+ ./bin/axllm run .
216
+ ```
217
+
218
+ After each prompt:
219
+
220
+ - press `Enter` directly for text-only chat
221
+ - input an image path for single-image chat
222
+ - input `video:<frames_dir>` for video chat
223
+
224
+ ## Python Reference Scripts
225
+
226
+ ### Official PyTorch Reference
227
+
228
+ `python/infer_torch.py` is a developer-side reference script. It requires the full upstream HuggingFace model with weights and is intended for x86/GPU validation.
229
+
230
+ ```bash
231
+ cd python
232
+ python infer_torch.py \
233
+ --model-path /data/tmp/yongqiang/nfs/auto_model_deployment/Minicpm-V-4.6-hf-original/MiniCPM-V-4.6 \
234
+ --prompt "1+1等于几?请直接回答。"
235
+ ```
236
+
237
+ ### Python `.axmodel` Debug Runner
238
+
239
+ `python/infer_axmodel.py` is a text-only per-layer debug runner for the compiled LLM package:
240
+
241
+ ```bash
242
+ cd python
243
+ python3 infer_axmodel.py \
244
+ --hf-model ./minicpm_v46_tokenizer \
245
+ --axmodel-dir .. \
246
+ --mode generate \
247
+ --prompt "1+1等于几?请直接回答。" \
248
+ --prompt-mode prefill \
249
+ --max-new-tokens 16 \
250
+ --kv-cache-len 2047
251
+ ```
252
+
253
+ ## Notes
254
+
255
+ 1. `.axmodel` files are board-only. Do not run them on x86.
256
+ 2. This package was compiled with `--prefill_len 128 --kv_cache_len 2047`.
257
+ 3. The AX650 build keeps `FLOAT_MATMUL_USE_CONV_EU=1` enabled to reduce TTFT.
258
+ 4. `bin/axllm.version.json` records the exact runtime binary commit and verification metadata.
259
+ 5. The packaged vision encoder is fixed at `448x448`; arbitrary image/video sizes are resized by the runtime preprocessor.
260
+ 6. Video requests are isolated to the current user turn and drop KV cache after the response to avoid stale multimodal history reuse.
261
+
262
+
263
+ ## Inference Log
264
+
265
+ ```sh
266
+ root@ax650 ~/yongqiang/auto_model_deployment/MiniCPM-V-4.6 # ./bin/axllm serve .
267
+ 14:59:39.575 INF sidecar_string_list_value:251 | loaded layer_types[24] from ./minicpm_v46_tokenizer/config.json
268
+ 14:59:39.577 INF Init:2239 | LLM init start
269
+ 14:59:39.577 INF Init:2248 | mixed attention enabled: full_attention_interval=4 ref_full_layer_idx=3
270
+ 14:59:39.577 INF Init:2270 | attention config: layers=24 sliding=0 full=6 linear=18 sliding_window=0 ref_full_layer_idx=3
271
+ tokenizer_type = 3
272
+ huggingface tokenizer mode = gpt2_byte_bpe
273
+ 44% | ############## | 12 / 27 [4.73s<10.63s, 2.54 count/s] init 10 axmodel ok,remain_cmm(7587 MB 96% | ############################## | 26 / 27 [6.15s<6.39s, 4.23 count/s] init post axmodel ok,remain_cmm(6933 MB)
274
+ 14:59:45.729 INF Init:2420 | max_token_len : 2047
275
+ 14:59:45.729 INF Init:2423 | kv_cache_size : 512, kv_cache_num: 2047
276
+ 14:59:45.729 INF init_groups_from_model:1622 | prefill_token_num : 128
277
+ 14:59:45.729 INF init_groups_from_model:1870 | decode grp: 0, gid: 0, max_token_len : 2047
278
+ 14:59:45.729 INF init_groups_from_model:1874 | prefill grp: 0, gid: 1, history_cap: 0, total_cap: 128, symbolic_cap: 1
279
+ 14:59:45.729 INF init_groups_from_model:1874 | prefill grp: 1, gid: 2, history_cap: 128, total_cap: 256, symbolic_cap: 128
280
+ 14:59:45.729 INF init_groups_from_model:1874 | prefill grp: 2, gid: 3, history_cap: 256, total_cap: 384, symbolic_cap: 256
281
+ 14:59:45.729 INF init_groups_from_model:1874 | prefill grp: 3, gid: 4, history_cap: 384, total_cap: 512, symbolic_cap: 384
282
+ 14:59:45.729 INF init_groups_from_model:1874 | prefill grp: 4, gid: 5, history_cap: 512, total_cap: 640, symbolic_cap: 512
283
+ 14:59:45.729 INF init_groups_from_model:1874 | prefill grp: 5, gid: 6, history_cap: 640, total_cap: 768, symbolic_cap: 640
284
+ 14:59:45.729 INF init_groups_from_model:1874 | prefill grp: 6, gid: 7, history_cap: 768, total_cap: 896, symbolic_cap: 768
285
+ 14:59:45.729 INF init_groups_from_model:1874 | prefill grp: 7, gid: 8, history_cap: 896, total_cap: 1024, symbolic_cap: 896
286
+ 14:59:45.729 INF init_groups_from_model:1874 | prefill grp: 8, gid: 9, history_cap: 1024, total_cap: 1152, symbolic_cap: 1024
287
+ 14:59:45.729 INF init_groups_from_model:1874 | prefill grp: 9, gid: 10, history_cap: 1152, total_cap: 1280, symbolic_cap: 1152
288
+ 14:59:45.729 INF init_groups_from_model:1881 | prefill_max_token_num : 1280
289
+ 14:59:45.729 INF init_layer_groups:1026 | layer 0 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
290
+ 14:59:45.729 INF init_layer_groups:1026 | layer 1 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
291
+ 14:59:45.729 INF init_layer_groups:1026 | layer 2 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
292
+ 14:59:45.729 INF init_layer_groups:1026 | layer 4 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
293
+ 14:59:45.729 INF init_layer_groups:1026 | layer 5 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
294
+ 14:59:45.729 INF init_layer_groups:1026 | layer 6 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
295
+ 14:59:45.729 INF init_layer_groups:1026 | layer 8 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
296
+ 14:59:45.729 INF init_layer_groups:1026 | layer 9 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
297
+ 14:59:45.729 INF init_layer_groups:1026 | layer 10 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
298
+ 14:59:45.729 INF init_layer_groups:1026 | layer 12 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
299
+ 14:59:45.729 INF init_layer_groups:1026 | layer 13 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
300
+ 14:59:45.729 INF init_layer_groups:1026 | layer 14 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
301
+ 14:59:45.730 INF init_layer_groups:1026 | layer 16 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
302
+ 14:59:45.730 INF init_layer_groups:1026 | layer 17 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
303
+ 14:59:45.730 INF init_layer_groups:1026 | layer 18 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
304
+ 14:59:45.730 INF init_layer_groups:1026 | layer 20 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
305
+ 14:59:45.730 INF init_layer_groups:1026 | layer 21 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
306
+ 14:59:45.730 INF init_layer_groups:1026 | layer 22 prefill groups=2 ref=10, reuse gid 2 for later prefill chunks
307
+ 14:59:45.730 INF Init:27 | LLaMaEmbedSelector use mmap
308
+ 100% | ################################ | 27 / 27 [6.16s<6.16s, 4.39 count/s] embed_selector init ok
309
+ 14:59:47.002 INF Init:1507 | MiniCPM-V-4.6 token ids: image_pad=248056 video_pad=248057
310
+ 14:59:47.002 INF Init:1514 | VisionModule init ok: type=MiniCPMV46VL, tokens_per_block=64, embed_size=1024, out_dtype=fp32
311
+ 14:59:47.002 WRN Init:1523 | Vision preprocess backend: SimpleCV (OpenCV not found at build time; minor differences vs OpenCV are possible)
312
+ 14:59:47.004 INF load_config:444 | load config:
313
+ 14:59:47.004 INF load_config:444 | {
314
+ 14:59:47.004 INF load_config:444 | "enable_repetition_penalty": false,
315
+ 14:59:47.004 INF load_config:444 | "enable_temperature": false,
316
+ 14:59:47.004 INF load_config:444 | "enable_top_k_sampling": false,
317
+ 14:59:47.004 INF load_config:444 | "enable_top_p_sampling": false,
318
+ 14:59:47.004 INF load_config:444 | "penalty_window": 20,
319
+ 14:59:47.004 INF load_config:444 | "repetition_penalty": 1.2,
320
+ 14:59:47.004 INF load_config:444 | "temperature": 0.9,
321
+ 14:59:47.004 INF load_config:444 | "top_k": 10,
322
+ 14:59:47.004 INF load_config:444 | "top_p": 0.8
323
+ 14:59:47.004 INF load_config:444 | }
324
+ 14:59:47.004 INF Init:2532 | LLM init ok
325
+ Starting server on port 8000 with model 'AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047'...
326
+ API URLs:
327
+ GET http://127.0.0.1:8000/health
328
+ GET http://127.0.0.1:8000/v1/models
329
+ POST http://127.0.0.1:8000/v1/chat/completions
330
+ GET http://10.168.232.217:8000/health
331
+ GET http://10.168.232.217:8000/v1/models
332
+ POST http://10.168.232.217:8000/v1/chat/completions
333
+ GET http://172.17.0.1:8000/health
334
+ GET http://172.17.0.1:8000/v1/models
335
+ POST http://172.17.0.1:8000/v1/chat/completions
336
+ Aliases:
337
+ GET http://127.0.0.1:8000/models
338
+ POST http://127.0.0.1:8000/chat/completions
339
+ GET http://10.168.232.217:8000/models
340
+ POST http://10.168.232.217:8000/chat/completions
341
+ GET http://172.17.0.1:8000/models
342
+ POST http://172.17.0.1:8000/chat/completions
343
+ OpenAI API Server starting on http://0.0.0.0:8000
344
+ Max concurrency: 1
345
+ Models: AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047
346
+ 15:00:35.571 INF operator():2136 | OpenAI chat request: model=AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047 stream=1 max_tokens=2048 has_temperature=0 temperature=1.0000 has_top_p=0 top_p=1.0000 messages=1 stop=0
347
+ 15:00:35.572 INF SetKVCache:3134 | decode_grpid:0 prefill_grpid:1 history_cap:0 total_cap:128 symbolic_cap:1 precompute_len:0 input_num_token:16 prefer_symbolic_group:0
348
+ 15:00:35.572 INF SetKVCache:3156 | current prefill_max_token_num:1280
349
+ 15:00:35.588 INF SetKVCache:3172 | first run
350
+ 15:00:35.602 INF Run:3376 | input token num : 16, prefill_split_num : 1
351
+ 15:00:35.602 INF Run:3387 | MiniCPM-V-4.6 short text prefill uses decode replay: input_tokens=16 prefill_tokens=128 precompute_len=0
352
+ 15:00:35.602 INF Run:3471 | prefill chunk p=0 history_len=0 grpid=1 kv_cache_num=0 input_tokens=16
353
+ 15:00:36.091 INF Run:3886 | ttft: 488.83 ms
354
+ I'm a model from the MiniCPM series, developed by Modelbest and OpenBMB. For more details, you can visit https://github.com/OpenBMB/
355
+
356
+ 15:00:37.997 NTC Run:4298 | hit eos,decode avg 17.84 token/s
357
+ 15:00:37.997 INF GetKVCache:3087 | precompute_len:51, remaining:1229 (tracked)
358
+ 15:01:11.728 INF operator():2136 | OpenAI chat request: model=AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047 stream=1 max_tokens=2048 has_temperature=0 temperature=1.0000 has_top_p=0 top_p=1.0000 messages=3 stop=0
359
+ 15:01:11.941 WRN Run:4330 | video history is isolated to current user turn: old_history=3 new_history=1 old_media_inputs=1 new_media_inputs=1
360
+ 15:01:12.096 INF extract_video_frames_ffmpeg:386 | Extracting raw video container to frames: /tmp/axllm_media/media_863804170273309_0.mp4 -> /tmp/axllm_video_frames/video_863804326631725_0 fps=1.9907760708716282
361
+ 15:01:13.679 INF collect_video_frame_paths:466 | Video fps sampling: path=/tmp/axllm_media/media_863804170273309_0.mp4 fps=2 duration=30.139s target_frames=60 selected=59
362
+ 15:01:13.687 INF Prepare:2594 | MiniCPM-V-4.6 video frames selected: 19/59 (configured_cap=0, tail_tokens=1278, max_tail=1280, precompute_len=0)
363
+ 15:01:24.499 INF SetKVCache:3134 | decode_grpid:0 prefill_grpid:1 history_cap:0 total_cap:128 symbolic_cap:1 precompute_len:0 input_num_token:1278 prefer_symbolic_group:0
364
+ 15:01:24.499 INF SetKVCache:3156 | current prefill_max_token_num:1280
365
+ 15:01:24.516 INF SetKVCache:3172 | first run
366
+ 15:01:24.531 INF Run:3376 | input token num : 1278, prefill_split_num : 10
367
+ 15:01:24.531 INF Run:3471 | prefill chunk p=0 history_len=0 grpid=1 kv_cache_num=0 input_tokens=128
368
+ 15:01:24.689 INF Run:3471 | prefill chunk p=1 history_len=128 grpid=3 kv_cache_num=256 input_tokens=128
369
+ 15:01:24.865 INF Run:3471 | prefill chunk p=2 history_len=256 grpid=4 kv_cache_num=384 input_tokens=128
370
+ 15:01:25.041 INF Run:3471 | prefill chunk p=3 history_len=384 grpid=5 kv_cache_num=512 input_tokens=128
371
+ 15:01:25.219 INF Run:3471 | prefill chunk p=4 history_len=512 grpid=6 kv_cache_num=640 input_tokens=128
372
+ 15:01:25.398 INF Run:3471 | prefill chunk p=5 history_len=640 grpid=7 kv_cache_num=768 input_tokens=128
373
+ 15:01:25.578 INF Run:3471 | prefill chunk p=6 history_len=768 grpid=8 kv_cache_num=896 input_tokens=128
374
+ 15:01:25.759 INF Run:3471 | prefill chunk p=7 history_len=896 grpid=9 kv_cache_num=1024 input_tokens=128
375
+ 15:01:25.943 INF Run:3471 | prefill chunk p=8 history_len=1024 grpid=10 kv_cache_num=1152 input_tokens=128
376
+ 15:01:26.128 INF Run:3471 | prefill chunk p=9 history_len=1152 grpid=10 kv_cache_num=1152 input_tokens=126
377
+ 15:01:26.327 INF Run:3886 | ttft: 1795.28 ms
378
+ 好的,我很乐意为您描述这个视频的内容。
379
+
380
+ 这张图片展示了一个活泼可爱的红熊猫,它正积极地参与着某种游戏或探索活动。从画面来看,这只红熊猫有着标志性的红棕色和黑色相间的毛色,面部和胸部有白色的标记,非常醒目。它正用前爪紧紧抓住一根由竹子或类似材料搭建的木制梯子或支架,身体微微前倾,似乎正在用力或试图攀爬。它的姿态充满了动感和专注,显示出它对这个环境非常感兴趣。
381
+
382
+ 在画面的下方,还有一只较小的红熊猫,它正抬头向上看,似乎是在观察上方那只正在攀爬的同伴,或者是在等待自己的机会。这两只红熊猫被安置在一个绿色的草地上,背景中可以看到一些绿色的围栏或墙壁,暗示这可能是在一个动物园或野生动物保护区的围栏内。
383
+
384
+ 整个场景充满了自然和活力,红熊猫们通过互动和探索,展现了它们的天性和对环境的适应。这个视频捕捉到了它们 playful 和 curious 的瞬间,让人感受到它们活泼可爱的性格。
385
+
386
+ 15:01:37.610 NTC Run:4298 | hit eos,decode avg 18.79 token/s
387
+ 15:01:37.610 WRN Run:4631 | drop KV cache after isolated video-history request
388
+ ^@15:02:51.406 INF operator():2136 | OpenAI chat request: model=AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047 stream=1 max_tokens=2048 has_temperature=0 temperature=1.0000 has_top_p=0 top_p=1.0000 messages=4 stop=0
389
+ 15:02:51.622 INF EncodeForContent:2080 | MiniCPM-V-4.6 pixel_values bytes=602112 min=2 max=250 (w=448 h=448 ps=14)
390
+ 15:02:52.151 INF EncodeForContent:2124 | vision cache store: /tmp/axllm_media/media_863903737595189_1.png
391
+ 15:02:52.225 INF SetKVCache:3134 | decode_grpid:0 prefill_grpid:1 history_cap:0 total_cap:128 symbolic_cap:1 precompute_len:0 input_num_token:358 prefer_symbolic_group:0
392
+ 15:02:52.225 INF SetKVCache:3156 | current prefill_max_token_num:1280
393
+ 15:02:52.242 INF SetKVCache:3172 | first run
394
+ 15:02:52.258 INF Run:3376 | input token num : 358, prefill_split_num : 3
395
+ 15:02:52.258 INF Run:3471 | prefill chunk p=0 history_len=0 grpid=1 kv_cache_num=0 input_tokens=128
396
+ 15:02:52.414 INF Run:3471 | prefill chunk p=1 history_len=128 grpid=3 kv_cache_num=256 input_tokens=128
397
+ 15:02:52.587 INF Run:3471 | prefill chunk p=2 history_len=256 grpid=4 kv_cache_num=384 input_tokens=102
398
+ 15:02:52.776 INF Run:3886 | ttft: 518.23 ms
399
+ 这张图片展示了一个年轻的女性,她有着长长的银灰色头发,头发上装饰着一些花朵。她穿着一件浅灰色的比基尼,比基尼上有褶皱和蕾丝细节,显得非常优雅。她的面部表情平静,眼神直视前方,给人一种温柔和自信的感觉。背景是海滩,可以看到海浪轻轻拍打着沙滩,远处有绿色的树木,整个场景显得非常宁静和自然。女性的姿态轻松,似乎在享受海滩上的美好时光。
400
+
401
+ 15:02:57.605 NTC Run:4298 | hit eos,decode avg 18.64 token/s
402
+ 15:02:57.605 INF GetKVCache:3087 | precompute_len:449, remaining:831 (tracked)
403
+ ```
assets/smoke_image.png ADDED

Git LFS Details

  • SHA256: 622ae2d01ff4467fa69a7888728d776650117a0f4887e96ba0fb9a8a6d77b3c3
  • Pointer size: 131 Bytes
  • Size of remote file: 355 kB
bin/axllm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:16a71dad6e173db36de93f4e57c6587461005c6e9e20f4c491c6538d4ba63268
3
+ size 2327944
bin/axllm.version.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "binary": "bin/axllm",
3
+ "target": "AX650/AX650N aarch64",
4
+ "ax_llm_branch": "ax-minicpm-v-4-6",
5
+ "ax_llm_commit": "0152d36423c888f28922722951d1d9ca08c1b840",
6
+ "simplecv_commit": "7729953c7c75ffaea441f70e0a9eb62790f3eb2b",
7
+ "openai_api_cpp_commit": "f56cf8c296d1002f6602226db392325ba42f6775",
8
+ "tokenizer_axera_commit": "731018211a62dcb0d606d474af9e760a0c4c44a5",
9
+ "build_command": "./build.sh",
10
+ "sha256": "16a71dad6e173db36de93f4e57c6587461005c6e9e20f4c491c6538d4ba63268",
11
+ "verified": {
12
+ "date": "2026-06-05",
13
+ "board": "AX650",
14
+ "command": "./bin/axllm serve . --port 18080",
15
+ "api_url": "http://10.168.232.217:18080/v1/chat/completions",
16
+ "model": "AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047",
17
+ "smoke_tests": [
18
+ "text-only chat returned correct answer for 1+1",
19
+ "long prompt multi-chunk prefill completed successfully",
20
+ "single-image request via OpenAI-compatible API returned a valid image description",
21
+ "append-only text history reused KV cache correctly",
22
+ "image added after long text history reused prior text KV incrementally",
23
+ "MiniCPM text, image, and video requests completed with native prefill after removing decode replay"
24
+ ]
25
+ }
26
+ }
config.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "system_prompt": "",
3
+ "model_name": "AXERA-TECH/MiniCPM-V-4.6-AX650-C128-P1152-CTX2047",
4
+ "url_tokenizer_model": "minicpm_v46_tokenizer.txt",
5
+ "tokenizer_type": "MiniCPMV46VL",
6
+ "post_config_path": "post_config.json",
7
+ "template_filename_axmodel": "qwen3_5_text_p128_l%d_together.axmodel",
8
+ "axmodel_num": 24,
9
+ "full_attention_interval": 4,
10
+ "filename_post_axmodel": "qwen3_5_text_post.axmodel",
11
+ "filename_tokens_embed": "model.embed_tokens.weight.bfloat16.bin",
12
+ "tokens_embed_num": 248094,
13
+ "tokens_embed_size": 1024,
14
+ "vlm_type": "MiniCPMV46VL",
15
+ "filename_image_encoder_axmodel": "minicpmv4_6_vision_448.axmodel",
16
+ "vision_cache_dir": "vision_cache",
17
+ "vision_width": 448,
18
+ "vision_height": 448,
19
+ "vision_patch_size": 14,
20
+ "b_use_mmap_load_embed": true,
21
+ "b_use_mmap_load_layer": true,
22
+ "devices": [
23
+ 0
24
+ ]
25
+ }
minicpm_v46_tokenizer.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ef38624ba70ed2757dd6c8c98c125bf620b99714fd2318f4fa1d383029eb2a05
3
+ size 12102524
minicpm_v46_tokenizer/.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
minicpm_v46_tokenizer/README.md ADDED
@@ -0,0 +1,498 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ pipeline_tag: image-text-to-text
4
+ tags:
5
+ - minicpm-v
6
+ - multimodal
7
+ - On-Device Model
8
+ - lightweight
9
+ ---
10
+
11
+ A Pocket-Sized MLLM for Ultra-Efficient Image and Video Understanding on Your Phone
12
+
13
+ [GitHub](https://github.com/OpenBMB/MiniCPM-o) | [CookBook](https://github.com/OpenSQZ/MiniCPM-V-CookBook) | [Demo](https://huggingface.co/spaces/openbmb/MiniCPM-V-4.6-Demo) |
14
+ [Feishu (Lark)](https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/feishu_qrcode.png)
15
+
16
+ ## MiniCPM-V 4.6
17
+
18
+ **MiniCPM-V 4.6** is our most edge-deployment-friendly model to date. The model is built based on SigLIP2-400M and the Qwen3.5-0.8B LLM. It inherits the strong single-image, multi-image, and video understanding capabilities of MiniCPM-V family, while significantly improving computation efficiency. It also introduces mixed 4x/16x visual token compression. Notable features of MiniCPM-V 4.6 include:
19
+
20
+ - 🔥 **Leading Foundation Capability.**
21
+ MiniCPM-V 4.6 scores 13 on the Artificial Analysis Intelligence Index benchmark, outperforming Qwen3.5-0.8B's score of 10 with 19x fewer token cost, and Qwen3.5-0.8B-Thinking's score of 11 with 43x fewer token cost. It also surpasses the larger Ministral 3 3B (score of 11).
22
+
23
+ - 💪 **Strong Multimodal Capability.**
24
+ MiniCPM-V 4.6 outperforms Qwen3.5-0.8B on most vision-language understanding tasks, and reaches Qwen3.5 2B-level capability on many benchmarks including OpenCompass, RefCOCO, HallusionBench, MUIRBench, and OCRBench.
25
+ - 🚀 **Ultra-Efficient Architecture.**
26
+ Based on the latest technique in [LLaVA-UHD v4](https://github.com/THUMAI-Lab/LLaVA-UHD-v4), MiniCPM-V 4.6 reduces the visual encoding computation FLOPs by more than 50%. It enables MiniCPM-V 4.6 to achieve better efficiency to even smaller models, achieving ~1.5x token throughput compared to Qwen3.5-0.8B.
27
+ It also supports mixed 4x/16x visual token compression rate, allowing flexible switching between accuracy and speed.
28
+ - 📱 **Broad Mobile Platform Coverage.**
29
+ MiniCPM-V 4.6 can be deployed across all three mainstream mobile platforms — iOS, Android, and HarmonyOS. With every edge adaptation code open-sourced, developers can reproduce the on-device experience in [just a few steps](#deploy-minicpm-v-46-on-ios-android-and-harmonyos-platforms).
30
+ - 🛠️ **Developer Friendly.**
31
+ MiniCPM-V 4.6 is adapted to [inference frameworks](#inference-and-training) such as vLLM, SGLang, llama.cpp, Ollama, and supports [fine-tuning ecosystems](#inference-and-training) such as SWIFT and LLaMA-Factory. Developers can quickly customize models for new domains and tasks on consumer-grade GPUs. We provide multiple quantized variants across GGUF, BNB, AWQ, and GPTQ formats.
32
+
33
+
34
+ ### Evaluation <!-- omit in toc -->
35
+
36
+ **Overall Performance (Instruct)**
37
+
38
+ <p align="center">
39
+ <img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/instruct.png" width="90%"></img>
40
+ </p>
41
+
42
+
43
+ <details>
44
+ <summary>Click to view MiniCPM-V 4.6-Thinking performance.</summary>
45
+
46
+
47
+ <p align="center">
48
+ <img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/thinking.png" width="90%"></img>
49
+ </p>
50
+
51
+
52
+ </details>
53
+
54
+
55
+ <details>
56
+ <summary>Click to view MiniCPM-V 4.6 inference efficiency results.</summary>
57
+
58
+
59
+ **High-Concurrency Throughput**
60
+
61
+ <p align="center">
62
+ <img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/throughput.png" width="60%"></img>
63
+ </p>
64
+
65
+ **Single Request TTFT (ms)**
66
+
67
+ <p align="center">
68
+ <img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/ttft.png" width="60%"></img>
69
+ </p>
70
+
71
+
72
+ </details>
73
+
74
+
75
+ ### Examples <!-- omit in toc -->
76
+
77
+ #### Overall
78
+
79
+ <div align="center">
80
+ <a href="https://www.youtube.com/watch?v=Ch5UG1FoysM"><img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/video_play.png" width="70%"></a>
81
+ </div>
82
+
83
+ MiniCPM-V 4.6 can be deployed across three mainstream end-side platforms — **iOS, Android and HarmonyOS**. The clips below are raw screen recordings on phone devices without edition.
84
+
85
+ <table align="center">
86
+ <tr>
87
+ <td align="center"><b>iPhone</b><br><sub>iPhone 17 Pro Max</sub></td>
88
+ <td align="center"><b>Android</b><br><sub>Redmi K70</sub></td>
89
+ <td align="center"><b>HarmonyOS</b><br><sub>HUAWEI nova 14</sub></td>
90
+ </tr>
91
+ <tr>
92
+ <td align="center"><img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/v46_iphone_en_handwriting.gif" width="100%"/></td>
93
+ <td align="center"><img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/v46_android_en_refraction.gif" width="100%"/></td>
94
+ <td align="center"><img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/v46_harmonyos_en_ticket.gif" width="100%"/></td>
95
+ </tr>
96
+ </table>
97
+
98
+
99
+ ### Usages
100
+
101
+ #### Inference with Transformers <!-- omit in toc -->
102
+ ##### Installation <!-- omit in toc -->
103
+
104
+ ```bash
105
+ pip install "transformers[torch]>=5.7.0" torchvision torchcodec
106
+ ```
107
+
108
+ > **Note on CUDA compatibility:** `torchcodec` (used for video decoding) may have compatibility issues with certain CUDA versions. For example, `torch>=2.11` bundles CUDA 13.1 by default, while environments with CUDA 12.x may encounter errors such as `RuntimeError: Could not load libtorchcodec`. Two workarounds:
109
+ >
110
+ > 1. **Replace `torchcodec` with `PyAV`** — supports both image and video inference without CUDA version constraints:
111
+ > ```bash
112
+ > pip install "transformers[torch]>=5.7.0" torchvision av
113
+ > ```
114
+ > 2. **Pin the CUDA version** when installing torch to match your environment (e.g. CUDA 12.8):
115
+ > ```bash
116
+ > pip install "transformers>=5.7.0" torchvision torchcodec --index-url https://download.pytorch.org/whl/cu128
117
+ > ```
118
+
119
+ ##### Load Model <!-- omit in toc -->
120
+
121
+ ```python
122
+ from transformers import AutoModelForImageTextToText, AutoProcessor
123
+
124
+ model_id = "openbmb/MiniCPM-V-4.6"
125
+
126
+ processor = AutoProcessor.from_pretrained(model_id)
127
+ model = AutoModelForImageTextToText.from_pretrained(
128
+ model_id, torch_dtype="auto", device_map="auto"
129
+ )
130
+
131
+ # Flash Attention 2 is recommended for better acceleration and memory saving,
132
+ # especially in multi-image and video scenarios.
133
+ # model = AutoModelForImageTextToText.from_pretrained(
134
+ # model_id,
135
+ # torch_dtype=torch.bfloat16,
136
+ # attn_implementation="flash_attention_2",
137
+ # device_map="auto",
138
+ # )
139
+ ```
140
+
141
+ ##### Image Inference <!-- omit in toc -->
142
+
143
+ ```python
144
+ messages = [
145
+ {
146
+ "role": "user",
147
+ "content": [
148
+ {"type": "image", "url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/refract.png"},
149
+ {"type": "text", "text": "What causes this phenomenon?"},
150
+ ],
151
+ }
152
+ ]
153
+
154
+ downsample_mode = "16x" # Using `downsample_mode="4x"` for Finer Detail
155
+
156
+ inputs = processor.apply_chat_template(
157
+ messages, tokenize=True, add_generation_prompt=True,
158
+ return_dict=True, return_tensors="pt",
159
+ downsample_mode=downsample_mode,
160
+ max_slice_nums=36,
161
+ ).to(model.device)
162
+
163
+ generated_ids = model.generate(**inputs, downsample_mode=downsample_mode, max_new_tokens=512)
164
+ generated_ids_trimmed = [
165
+ out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
166
+ ]
167
+ output_text = processor.batch_decode(
168
+ generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
169
+ )
170
+ print(output_text[0])
171
+ ```
172
+
173
+ ##### Video Inference <!-- omit in toc -->
174
+
175
+ ```python
176
+ messages = [
177
+ {
178
+ "role": "user",
179
+ "content": [
180
+ {"type": "video", "url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/football.mp4"},
181
+ {"type": "text", "text": "Describe this video in detail. Follow the timeline and focus on on-screen text, interface changes, main actions, and scene changes."},
182
+ ],
183
+ }
184
+ ]
185
+
186
+ downsample_mode = "16x" # Using `downsample_mode="4x"` for Finer Detail
187
+
188
+ inputs = processor.apply_chat_template(
189
+ messages, tokenize=True, add_generation_prompt=True,
190
+ return_dict=True, return_tensors="pt",
191
+ downsample_mode=downsample_mode,
192
+ max_num_frames=128,
193
+ stack_frames=1,
194
+ max_slice_nums=1,
195
+ use_image_id=False,
196
+ ).to(model.device)
197
+
198
+ generated_ids = model.generate(**inputs, downsample_mode=downsample_mode, max_new_tokens=2048)
199
+ generated_ids_trimmed = [
200
+ out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
201
+ ]
202
+ output_text = processor.batch_decode(
203
+ generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
204
+ )
205
+ print(output_text[0])
206
+ ```
207
+
208
+ ##### Advanced Parameters <!-- omit in toc -->
209
+
210
+ You can customize image/video processing by passing additional parameters to `apply_chat_template`:
211
+
212
+ | Parameter | Default | Applies to | Description |
213
+ |-----------|---------|------------|-------------|
214
+ | `downsample_mode` | `"16x"` | Image & Video | Visual token downsampling. `"16x"` merges tokens for efficiency; `"4x"` keeps 4× more tokens for finer detail. Must also be passed to `generate()`. |
215
+ | `max_slice_nums` | `9` | Image & Video | Maximum number of slices when splitting a high-resolution image. Higher values preserve more detail for large images. Recommended: `36` for image, `1` for video. |
216
+ | `max_num_frames` | `128` | Video only | Maximum number of main frames sampled from the video. |
217
+ | `stack_frames` | `1` | Video only | Total sample points per second. `1` = main frame only (no stacking). `N` (N>1) = 1 main frame + N−1 sub-frames per second; the sub-frames are composited into a grid image and interleaved with main frames. Recommended setting is `1` for short videos, and `3` or `5` for long videos. |
218
+ | `use_image_id` | `True` | Image & Video | Whether to prepend `<image_id>N</image_id>` tags before each image/frame placeholder. Set `True` for image, `False` for video. |
219
+
220
+ > **Note:** `downsample_mode` must be passed to **both** `apply_chat_template` (for correct placeholder count) and `generate` (for the vision encoder). All other parameters only need to be passed to `apply_chat_template`.
221
+
222
+ ##### Serving with `transformers serve` <!-- omit in toc -->
223
+
224
+ Hugging Face Transformers includes a lightweight OpenAI-compatible server for quick testing and moderate-load deployment.
225
+
226
+ ```bash
227
+ pip install "transformers[serving]>=5.7.0"
228
+ ```
229
+
230
+ Start the server:
231
+
232
+ ```bash
233
+ transformers serve openbmb/MiniCPM-V-4.6 --port 8000 --host 0.0.0.0 --continuous-batching
234
+ ```
235
+
236
+ Send a request:
237
+
238
+ ```bash
239
+ curl -s http://localhost:8000/v1/chat/completions \
240
+ -H 'Content-Type: application/json' \
241
+ -d '{
242
+ "model": "openbmb/MiniCPM-V-4.6",
243
+ "messages": [{
244
+ "role": "user",
245
+ "content": [
246
+ {"type": "image_url", "image_url": {"url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/refract.png"}},
247
+ {"type": "text", "text": "What causes this phenomenon?"}
248
+ ]
249
+ }]
250
+ }'
251
+ ```
252
+
253
+ Tool calling example:
254
+
255
+ ```bash
256
+ curl -s http://localhost:8000/v1/chat/completions -H 'Content-Type: application/json' -d '{
257
+ "model": "openbmb/MiniCPM-V-4.6",
258
+ "messages": [{"role": "user", "content": [
259
+ {"type": "text", "text": "the weather of Beijing"}
260
+ ]}],
261
+ "tools": [{
262
+ "type": "function",
263
+ "function": {
264
+ "name": "get_weather",
265
+ "description": "Get the current weather for a given location",
266
+ "parameters": {
267
+ "type": "object",
268
+ "properties": {
269
+ "location": {"type": "string", "description": "City name"}
270
+ },
271
+ "required": ["location"]
272
+ }
273
+ }
274
+ }]
275
+ }'
276
+ ```
277
+
278
+ The model returns a natural-language explanation followed by a structured <tool_call> block embedded in the content field. Note that a dedicated tool call parser for this format has not yet been added to the transformers library, so the tool calls need to be extracted manually via regex for now.
279
+
280
+ ```
281
+ {
282
+ "id": "f4f09c7d-8045-4cb1-ade9-07aa5dee637d",
283
+ "choices": [
284
+ {
285
+ "finish_reason": "stop",
286
+ "index": 0,
287
+ "message": {
288
+ "content": "I need to check the current weather for Beijing, so I will call the get_weather function.\n\n<tool_call>\n<function=get_weather>\n<parameter=location>\nBeijing\n</parameter>\n</function>\n</tool_call>",
289
+ "role": "assistant"
290
+ }
291
+ }
292
+ ],
293
+ "created": 1778748859,
294
+ "model": "openbmb/MiniCPM-V-4.6@main",
295
+ "object": "chat.completion",
296
+ "usage": {
297
+ "completion_tokens": 47,
298
+ "prompt_tokens": 283,
299
+ "total_tokens": 330
300
+ }
301
+ }
302
+ ```
303
+
304
+ #### Handling Escaped Newlines in Model Outputs <!-- omit in toc -->
305
+
306
+ In some cases, the model might output escaped newline characters `\n` as string literals instead of actual newlines. To render the text correctly, especially in UI layers, you can use the following utility function. This function carefully replaces literal `\n` with real newlines while protecting scenarios where `\n` has specific semantic meaning.
307
+
308
+ **Utility Function:**
309
+
310
+ ```python
311
+ import re
312
+
313
+ _PATTERN = re.compile(
314
+ r'(```[\s\S]*?```' # fenced code blocks
315
+ r'|`[^`]+`' # inline code
316
+ r'|\$\$[\s\S]*?\$\$' # display math
317
+ r'|\$[^$]+\$' # inline math
318
+ r'|\\\([\s\S]*?\\\)' # \(...\)
319
+ r'|\\\[[\s\S]*?\\\]' # \[...\]
320
+ r')'
321
+ r'|(?<!\\)(?:\\r\\n|\\[nr])'
322
+ )
323
+
324
+ def normalize_response_text(text: str) -> str:
325
+ """
326
+ Lightweight post-processing: Converts literal '\\n' to actual newlines,
327
+ while protecting code blocks, inline code, and LaTeX commands.
328
+ """
329
+ if not isinstance(text, str) or "\\" not in text:
330
+ return text
331
+ return _PATTERN.sub(lambda m: m.group(1) or '\n', text)
332
+ ```
333
+
334
+ #### Deploy MiniCPM-V 4.6 on iOS, Android, and HarmonyOS Platforms <!-- omit in toc -->
335
+
336
+ We have adapted MiniCPM-V 4.6 for deployment on **iOS, Android, and HarmonyOS** platforms, with **all edge adaptation code fully open-sourced**. Developers can reproduce the on-device experience in just a few steps. Visit our [edge deployment repository](https://github.com/OpenBMB/MiniCPM-V-edge-demo) for platform-specific build guides, or go to the [download page](https://github.com/OpenBMB/MiniCPM-V-edge-demo/blob/main/DOWNLOAD.md) to try pre-built apps directly.
337
+
338
+ <a id="inference-and-training"></a>
339
+ #### Use MiniCPM-V 4.6 in Other Inference and Training Frameworks <!-- omit in toc -->
340
+
341
+ MiniCPM-V 4.6 supports multiple inference and training frameworks. Below are quick-start commands for each. For full details, see our [Cookbook](https://github.com/OpenSQZ/MiniCPM-V-CookBook).
342
+
343
+ <details>
344
+ <summary><b>vLLM</b> — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/deployment/vllm/minicpm-v4_6_vllm.md">Full Guide</a></summary>
345
+
346
+ ```bash
347
+ vllm serve openbmb/MiniCPM-V-4.6 \
348
+ --port 8000 \
349
+ --enable-auto-tool-choice \
350
+ --tool-call-parser qwen3_coder \
351
+ --default-chat-template-kwargs '{"enable_thinking": false}'
352
+ ```
353
+
354
+ > **Note:** `--enable-auto-tool-choice` and `--tool-call-parser qwen3_coder` enable tool/function calling support. If you don't need tool use, you can omit these flags and simply run `vllm serve openbmb/MiniCPM-V-4.6`.
355
+
356
+ ```bash
357
+ curl -s http://localhost:8000/v1/chat/completions -H 'Content-Type: application/json' -d '{
358
+ "model": "openbmb/MiniCPM-V-4.6",
359
+ "messages": [{"role": "user", "content": [
360
+ {"type": "image_url", "image_url": {"url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/refract.png"}},
361
+ {"type": "text", "text": "What causes this phenomenon?"}
362
+ ]}]
363
+ }'
364
+ ```
365
+
366
+
367
+ Tool calling example:
368
+
369
+ ```bash
370
+ curl -s http://localhost:8000/v1/chat/completions -H 'Content-Type: application/json' -d '{
371
+ "model": "openbmb/MiniCPM-V-4.6",
372
+ "messages": [{"role": "user", "content": [
373
+ {"type": "text", "text": "北京的天气"}
374
+ ]}],
375
+ "tools": [{
376
+ "type": "function",
377
+ "function": {
378
+ "name": "get_weather",
379
+ "description": "Get the current weather for a given location",
380
+ "parameters": {
381
+ "type": "object",
382
+ "properties": {
383
+ "location": {"type": "string", "description": "City name"}
384
+ },
385
+ "required": ["location"]
386
+ }
387
+ }
388
+ }]
389
+ }'
390
+ ```
391
+
392
+ </details>
393
+
394
+ <details>
395
+ <summary><b>SGLang</b> — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/deployment/sglang/minicpm-v4_6_sglang.md">Full Guide</a></summary>
396
+
397
+ ```bash
398
+ python -m sglang.launch_server --model openbmb/MiniCPM-V-4.6 --port 30000
399
+ ```
400
+
401
+ ```bash
402
+ curl -s http://localhost:30000/v1/chat/completions -H 'Content-Type: application/json' -d '{
403
+ "model": "openbmb/MiniCPM-V-4.6",
404
+ "messages": [{"role": "user", "content": [
405
+ {"type": "image_url", "image_url": {"url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/refract.png"}},
406
+ {"type": "text", "text": "What causes this phenomenon?"}
407
+ ]}]
408
+ }'
409
+ ```
410
+
411
+ </details>
412
+
413
+ <details>
414
+ <summary><b>llama.cpp</b> — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/deployment/llama.cpp/minicpm-v4_6_llamacpp.md">Full Guide</a></summary>
415
+
416
+ ```bash
417
+ llama-server -m MiniCPM-V-4.6-Q4_K_M.gguf --port 8080
418
+ ```
419
+
420
+ ```bash
421
+ curl -s http://localhost:8080/v1/chat/completions -H 'Content-Type: application/json' -d '{
422
+ "model": "MiniCPM-V-4.6",
423
+ "messages": [{"role": "user", "content": [
424
+ {"type": "image_url", "image_url": {"url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/refract.png"}},
425
+ {"type": "text", "text": "What causes this phenomenon?"}
426
+ ]}]
427
+ }'
428
+ ```
429
+
430
+ </details>
431
+
432
+ <details>
433
+ <summary><b>Ollama</b> — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/deployment/ollama/minicpm-v4_6_ollama.md">Full Guide</a></summary>
434
+
435
+ ```bash
436
+ ollama run minicpm-v-4.6
437
+ ```
438
+
439
+ In the interactive session, paste an image path or URL directly to chat with the model.
440
+
441
+ </details>
442
+
443
+ <details>
444
+ <summary><b>LLaMA-Factory</b> (Fine-tuning) — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/finetune/llamafactory_minicpmv46.md">Full Guide</a></summary>
445
+
446
+ ```bash
447
+ llamafactory-cli train examples/train_lora/minicpmv4_6_lora_sft.yaml
448
+ ```
449
+
450
+ </details>
451
+
452
+ <details>
453
+ <summary><b>ms-swift</b> (Fine-tuning) — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/finetune/swift_minicpmv46.md">Full Guide</a></summary>
454
+
455
+ ```bash
456
+ swift sft --model_type minicpm-v-4_6 --dataset <your-dataset>
457
+ ```
458
+
459
+ </details>
460
+
461
+ ## License
462
+
463
+ #### Model License
464
+ * The MiniCPM-o/V model weights and code are open-sourced under the [Apache-2.0](https://github.com/OpenBMB/MiniCPM-V/blob/main/LICENSE) license.
465
+
466
+ #### Statement
467
+ * As MLLMs, MiniCPM-o/V models generate content by learning a large number of multimodal corpora, but they cannot comprehend, express personal opinions, or make value judgements. Anything generated by MiniCPM-o/V models does not represent the views and positions of the model developers
468
+ * We will not be liable for any problems arising from the use of MiniCPM-o/V models, including but not limited to data security issues, risk of public opinion, or any risks and problems arising from the misdirection, misuse, dissemination, or misuse of the model.
469
+
470
+
471
+ ## Technical Reports and Key Techniques Papers
472
+
473
+ 👏 Welcome to explore key techniques of MiniCPM-o/V and other multimodal projects of our team:
474
+
475
+ **Technical Reports:** [MiniCPM-o 4.5](https://huggingface.co/papers/2604.27393) | [MiniCPM-V 4.5](https://arxiv.org/abs/2509.18154) | [MiniCPM-o 2.6](https://openbmb.notion.site/MiniCPM-o-2-6-A-GPT-4o-Level-MLLM-for-Vision-Speech-and-Multimodal-Live-Streaming-on-Your-Phone-185ede1b7a558042b5d5e45e6b237da9) | [MiniCPM-Llama3-V 2.5](https://arxiv.org/abs/2408.01800) | [MiniCPM-V 2.0](https://openbmb.vercel.app/minicpm-v-2)
476
+
477
+ **Other Multimodal Projects:** [VisCPM](https://github.com/OpenBMB/VisCPM/tree/main) | [RLPR](https://github.com/OpenBMB/RLPR) | [RLHF-V](https://github.com/RLHF-V/RLHF-V) | [LLaVA-UHD](https://github.com/thunlp/LLaVA-UHD) | [RLAIF-V](https://github.com/RLHF-V/RLAIF-V) | [LLaVA-UHD-v4](https://arxiv.org/abs/2605.08985 )
478
+
479
+
480
+ ## Citation <!-- omit in toc -->
481
+
482
+ If you find our model/code/paper helpful, please consider citing our papers 📝 and staring us ⭐️!
483
+
484
+ ```bib
485
+ @proceedings{yu2025minicpmv45cookingefficient,
486
+ title={MiniCPM-V 4.5: Cooking Efficient MLLMs via Architecture, Data, and Training Recipe},
487
+ author={Tianyu Yu and Zefan Wang and Chongyi Wang and Fuwei Huang and Wenshuo Ma and Zhihui He and Tianchi Cai and Weize Chen and Yuxiang Huang and Yuanqian Zhao and others},
488
+ year={2025},
489
+ url={https://arxiv.org/abs/2509.18154},
490
+ }
491
+
492
+ @article{yao2024minicpm,
493
+ title={MiniCPM-V: A GPT-4V Level MLLM on Your Phone},
494
+ author={Yao, Yuan and Yu, Tianyu and Zhang, Ao and Wang, Chongyi and Cui, Junbo and Zhu, Hongji and Cai, Tianchi and Li, Haoyu and Zhao, Weilin and He, Zhihui and others},
495
+ journal={arXiv preprint arXiv:2408.01800},
496
+ year={2024}
497
+ }
498
+ ```
minicpm_v46_tokenizer/chat_template.jinja ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- if enable_thinking is not defined -%}
2
+ {%- set enable_thinking = false -%}
3
+ {%- endif -%}
4
+ {%- macro render_content(content, is_system_content=false) -%}
5
+ {%- if content is string -%}
6
+ {{- content -}}
7
+ {%- elif content is iterable and content is not mapping -%}
8
+ {%- set ns = namespace(parts=[]) -%}
9
+ {%- for item in content -%}
10
+ {%- if 'image' in item or 'image_url' in item or item.type == 'image' -%}
11
+ {%- if is_system_content -%}
12
+ {{- raise_exception('System message cannot contain images.') -}}
13
+ {%- endif -%}
14
+ {%- set ns.parts = ns.parts + ['<|image_pad|>'] -%}
15
+ {%- elif 'video' in item or item.type == 'video' -%}
16
+ {%- if is_system_content -%}
17
+ {{- raise_exception('System message cannot contain videos.') -}}
18
+ {%- endif -%}
19
+ {%- set ns.parts = ns.parts + ['<|video_pad|>'] -%}
20
+ {%- elif 'text' in item -%}
21
+ {%- set ns.parts = ns.parts + [item.text] -%}
22
+ {%- else -%}
23
+ {{- raise_exception('Unexpected item type in content.') -}}
24
+ {%- endif -%}
25
+ {%- endfor -%}
26
+ {{- ns.parts | join('\n') -}}
27
+ {%- elif content is none or content is undefined -%}
28
+ {{- '' -}}
29
+ {%- else -%}
30
+ {{- raise_exception('Unexpected content type.') -}}
31
+ {%- endif -%}
32
+ {%- endmacro -%}
33
+ {%- if not messages %}
34
+ {{- raise_exception('No messages provided.') }}
35
+ {%- endif %}
36
+ {%- if tools and tools is iterable and tools is not mapping %}
37
+ {{- '<|im_start|>system\n' }}
38
+ {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
39
+ {%- for tool in tools %}
40
+ {{- "\n" }}
41
+ {{- tool | tojson }}
42
+ {%- endfor %}
43
+ {{- "\n</tools>" }}
44
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
45
+ {%- if messages[0].role == 'system' %}
46
+ {%- set content = render_content(messages[0].content, true)|trim %}
47
+ {%- if content %}
48
+ {{- '\n\n' + content }}
49
+ {%- endif %}
50
+ {%- endif %}
51
+ {{- '<|im_end|>\n' }}
52
+ {%- else %}
53
+ {%- if messages[0].role == 'system' %}
54
+ {%- set content = render_content(messages[0].content, true)|trim %}
55
+ {{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
56
+ {%- endif %}
57
+ {%- endif %}
58
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
59
+ {%- for message in messages[::-1] %}
60
+ {%- set index = (messages|length - 1) - loop.index0 %}
61
+ {%- if ns.multi_step_tool and message.role == "user" %}
62
+ {%- set content = render_content(message.content)|trim %}
63
+ {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
64
+ {%- set ns.multi_step_tool = false %}
65
+ {%- set ns.last_query_index = index %}
66
+ {%- endif %}
67
+ {%- endif %}
68
+ {%- endfor %}
69
+ {%- if ns.multi_step_tool %}
70
+ {{- raise_exception('No user query found in messages.') }}
71
+ {%- endif %}
72
+ {%- for message in messages %}
73
+ {%- set content = render_content(message.content)|trim %}
74
+ {%- if message.role == "system" %}
75
+ {%- if not loop.first %}
76
+ {{- raise_exception('System message must be at the beginning.') }}
77
+ {%- endif %}
78
+ {%- elif message.role == "user" %}
79
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
80
+ {%- elif message.role == "assistant" %}
81
+ {%- set reasoning_content = '' %}
82
+ {%- if message.reasoning_content is string %}
83
+ {%- set reasoning_content = message.reasoning_content %}
84
+ {%- else %}
85
+ {%- if '</think>' in content %}
86
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
87
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
88
+ {%- endif %}
89
+ {%- endif %}
90
+ {%- set reasoning_content = reasoning_content|trim %}
91
+ {%- if loop.index0 > ns.last_query_index %}
92
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
93
+ {%- else %}
94
+ {{- '<|im_start|>' + message.role + '\n' + content }}
95
+ {%- endif %}
96
+ {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
97
+ {%- for tool_call in message.tool_calls %}
98
+ {%- if tool_call.function is defined %}
99
+ {%- set tool_call = tool_call.function %}
100
+ {%- endif %}
101
+ {%- if loop.first %}
102
+ {%- if content|trim %}
103
+ {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
104
+ {%- else %}
105
+ {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
106
+ {%- endif %}
107
+ {%- else %}
108
+ {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
109
+ {%- endif %}
110
+ {%- if tool_call.arguments is defined %}
111
+ {%- for args_name, args_value in tool_call.arguments|items %}
112
+ {{- '<parameter=' + args_name + '>\n' }}
113
+ {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
114
+ {{- args_value }}
115
+ {{- '\n</parameter>\n' }}
116
+ {%- endfor %}
117
+ {%- endif %}
118
+ {{- '</function>\n</tool_call>' }}
119
+ {%- endfor %}
120
+ {%- endif %}
121
+ {{- '<|im_end|>\n' }}
122
+ {%- elif message.role == "tool" %}
123
+ {%- if loop.previtem and loop.previtem.role != "tool" %}
124
+ {{- '<|im_start|>user' }}
125
+ {%- endif %}
126
+ {{- '\n<tool_response>\n' }}
127
+ {{- content }}
128
+ {{- '\n</tool_response>' }}
129
+ {%- if not loop.last and loop.nextitem.role != "tool" %}
130
+ {{- '<|im_end|>\n' }}
131
+ {%- elif loop.last %}
132
+ {{- '<|im_end|>\n' }}
133
+ {%- endif %}
134
+ {%- else %}
135
+ {{- raise_exception('Unexpected message role.') }}
136
+ {%- endif %}
137
+ {%- endfor %}
138
+ {%- if add_generation_prompt %}
139
+ {{- '<|im_start|>assistant\n' }}
140
+ {%- if enable_thinking is defined and enable_thinking is false %}
141
+ {{- '<think>\n\n</think>\n\n' }}
142
+ {%- else %}
143
+ {{- '<think>\n' }}
144
+ {%- endif %}
145
+ {%- endif %}
minicpm_v46_tokenizer/config.json ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "MiniCPMV4_6ForConditionalGeneration"
4
+ ],
5
+ "bos_token_id": null,
6
+ "drop_vision_last_layer": false,
7
+ "eos_token_id": 248044,
8
+ "image_size": 1120,
9
+ "model_type": "minicpmv4_6",
10
+ "pad_token_id": null,
11
+ "tie_word_embeddings": true,
12
+ "transformers_version": "5.7.0",
13
+ "use_cache": true,
14
+ "vision_config": {
15
+ "attention_dropout": 0.0,
16
+ "hidden_act": "gelu_pytorch_tanh",
17
+ "hidden_size": 1152,
18
+ "image_size": 980,
19
+ "intermediate_size": 4304,
20
+ "layer_norm_eps": 1e-06,
21
+ "model_type": "minicpmv4_6_vision",
22
+ "num_attention_heads": 16,
23
+ "num_channels": 3,
24
+ "num_hidden_layers": 27,
25
+ "patch_size": 14
26
+ },
27
+ "text_config": {
28
+ "attention_bias": false,
29
+ "attention_dropout": 0.0,
30
+ "attn_output_gate": true,
31
+ "full_attention_interval": 4,
32
+ "head_dim": 256,
33
+ "hidden_act": "silu",
34
+ "hidden_size": 1024,
35
+ "initializer_range": 0.02,
36
+ "intermediate_size": 3584,
37
+ "layer_types": [
38
+ "linear_attention",
39
+ "linear_attention",
40
+ "linear_attention",
41
+ "full_attention",
42
+ "linear_attention",
43
+ "linear_attention",
44
+ "linear_attention",
45
+ "full_attention",
46
+ "linear_attention",
47
+ "linear_attention",
48
+ "linear_attention",
49
+ "full_attention",
50
+ "linear_attention",
51
+ "linear_attention",
52
+ "linear_attention",
53
+ "full_attention",
54
+ "linear_attention",
55
+ "linear_attention",
56
+ "linear_attention",
57
+ "full_attention",
58
+ "linear_attention",
59
+ "linear_attention",
60
+ "linear_attention",
61
+ "full_attention"
62
+ ],
63
+ "linear_conv_kernel_dim": 4,
64
+ "linear_key_head_dim": 128,
65
+ "linear_num_key_heads": 16,
66
+ "linear_num_value_heads": 16,
67
+ "linear_value_head_dim": 128,
68
+ "mamba_ssm_dtype": "float32",
69
+ "max_position_embeddings": 262144,
70
+ "mlp_only_layers": [],
71
+ "mtp_num_hidden_layers": 1,
72
+ "mtp_use_dedicated_embeddings": false,
73
+ "num_attention_heads": 8,
74
+ "num_hidden_layers": 24,
75
+ "num_key_value_heads": 2,
76
+ "partial_rotary_factor": 0.25,
77
+ "rms_norm_eps": 1e-06,
78
+ "rope_parameters": {
79
+ "partial_rotary_factor": 0.25,
80
+ "rope_theta": 10000000,
81
+ "rope_type": "default"
82
+ },
83
+ "vocab_size": 248094,
84
+ "model_type": "qwen3_5_text",
85
+ "tie_word_embeddings": true
86
+ },
87
+ "insert_layer_id": 6,
88
+ "image_token_id": 248056,
89
+ "video_token_id": 248057
90
+ }
minicpm_v46_tokenizer/generation_config.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 248045,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 248044,
6
+ 248046
7
+ ],
8
+ "temperature": 0.7,
9
+ "top_k": 0,
10
+ "top_p": 1.0,
11
+ "repetition_penalty": 1.0,
12
+ "transformers_version": "5.7.0"
13
+ }
minicpm_v46_tokenizer/preprocessor_config.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "image_processor_type": "MiniCPMV4_6ImageProcessor",
3
+ "processor_class": "MiniCPMV4_6Processor",
4
+ "max_slice_nums": 9,
5
+ "scale_resolution": 448,
6
+ "patch_size": 14,
7
+ "use_image_id": true,
8
+ "slice_mode": true,
9
+ "image_mean": [
10
+ 0.5,
11
+ 0.5,
12
+ 0.5
13
+ ],
14
+ "image_std": [
15
+ 0.5,
16
+ 0.5,
17
+ 0.5
18
+ ]
19
+ }
minicpm_v46_tokenizer/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:33861e37bb955af1e3f3061182b820f347eba2b9c2c1011c82794bf0d6e77b54
3
+ size 19992481
minicpm_v46_tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "audio_bos_token": "<|audio_start|>",
4
+ "audio_eos_token": "<|audio_end|>",
5
+ "audio_token": "<|audio_pad|>",
6
+ "backend": "tokenizers",
7
+ "bos_token": "<|im_start|>",
8
+ "clean_up_tokenization_spaces": false,
9
+ "eos_token": "<|im_end|>",
10
+ "errors": "replace",
11
+ "extra_special_tokens": {
12
+ "image_token": "<|image_pad|>",
13
+ "video_token": "<|video_pad|>",
14
+ "image_start_token": "<image>",
15
+ "image_end_token": "</image>",
16
+ "slice_start_token": "<slice>",
17
+ "slice_end_token": "</slice>",
18
+ "image_id_start_token": "<image_id>",
19
+ "image_id_end_token": "</image_id>"
20
+ },
21
+ "image_token": "<|image_pad|>",
22
+ "is_local": true,
23
+ "model_max_length": 262144,
24
+ "model_specific_special_tokens": {
25
+ "audio_bos_token": "<|audio_start|>",
26
+ "audio_eos_token": "<|audio_end|>",
27
+ "audio_token": "<|audio_pad|>",
28
+ "image_token": "<|image_pad|>",
29
+ "video_token": "<|video_pad|>",
30
+ "vision_bos_token": "<|vision_start|>",
31
+ "vision_eos_token": "<|vision_end|>"
32
+ },
33
+ "pad_token": "<|endoftext|>",
34
+ "pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
35
+ "split_special_tokens": false,
36
+ "unk_token": "<unk>",
37
+ "video_token": "<|video_pad|>",
38
+ "vision_bos_token": "<|vision_start|>",
39
+ "vision_eos_token": "<|vision_end|>",
40
+ "chat_template": "{%- if enable_thinking is not defined -%}\n {%- set enable_thinking = false -%}\n{%- endif -%}\n{%- macro render_content(content, is_system_content=false) -%}\n {%- if content is string -%}\n {{- content -}}\n {%- elif content is iterable and content is not mapping -%}\n {%- set ns = namespace(parts=[]) -%}\n {%- for item in content -%}\n {%- if 'image' in item or 'image_url' in item or item.type == 'image' -%}\n {%- if is_system_content -%}\n {{- raise_exception('System message cannot contain images.') -}}\n {%- endif -%}\n {%- set ns.parts = ns.parts + ['<|image_pad|>'] -%}\n {%- elif 'video' in item or item.type == 'video' -%}\n {%- if is_system_content -%}\n {{- raise_exception('System message cannot contain videos.') -}}\n {%- endif -%}\n {%- set ns.parts = ns.parts + ['<|video_pad|>'] -%}\n {%- elif 'text' in item -%}\n {%- set ns.parts = ns.parts + [item.text] -%}\n {%- else -%}\n {{- raise_exception('Unexpected item type in content.') -}}\n {%- endif -%}\n {%- endfor -%}\n {{- ns.parts | join('\\n') -}}\n {%- elif content is none or content is undefined -%}\n {{- '' -}}\n {%- else -%}\n {{- raise_exception('Unexpected content type.') -}}\n {%- endif -%}\n{%- endmacro -%}\n{%- if not messages %}\n {{- raise_exception('No messages provided.') }}\n{%- endif %}\n{%- if tools and tools is iterable and tools is not mapping %}\n {{- '<|im_start|>system\\n' }}\n {{- \"# Tools\\n\\nYou have access to the following functions:\\n\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\" }}\n {{- '\\n\\nIf you choose to call a function ONLY reply in the following format with NO suffix:\\n\\n<tool_call>\\n<function=example_function_name>\\n<parameter=example_parameter_1>\\nvalue_1\\n</parameter>\\n<parameter=example_parameter_2>\\nThis is the value for the second parameter\\nthat can span\\nmultiple lines\\n</parameter>\\n</function>\\n</tool_call>\\n\\n<IMPORTANT>\\nReminder:\\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\\n- Required parameters MUST be specified\\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\\n</IMPORTANT>' }}\n {%- if messages[0].role == 'system' %}\n {%- set content = render_content(messages[0].content, true)|trim %}\n {%- if content %}\n {{- '\\n\\n' + content }}\n {%- endif %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {%- set content = render_content(messages[0].content, true)|trim %}\n {{- '<|im_start|>system\\n' + content + '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}\n{%- for message in messages[::-1] %}\n {%- set index = (messages|length - 1) - loop.index0 %}\n {%- if ns.multi_step_tool and message.role == \"user\" %}\n {%- set content = render_content(message.content)|trim %}\n {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}\n {%- set ns.multi_step_tool = false %}\n {%- set ns.last_query_index = index %}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if ns.multi_step_tool %}\n {{- raise_exception('No user query found in messages.') }}\n{%- endif %}\n{%- for message in messages %}\n {%- set content = render_content(message.content)|trim %}\n {%- if message.role == \"system\" %}\n {%- if not loop.first %}\n {{- raise_exception('System message must be at the beginning.') }}\n {%- endif %}\n {%- elif message.role == \"user\" %}\n {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {%- set reasoning_content = '' %}\n {%- if message.reasoning_content is string %}\n {%- set reasoning_content = message.reasoning_content %}\n {%- else %}\n {%- if '</think>' in content %}\n {%- set reasoning_content = content.split('</think>')[0].rstrip('\\n').split('<think>')[-1].lstrip('\\n') %}\n {%- set content = content.split('</think>')[-1].lstrip('\\n') %}\n {%- endif %}\n {%- endif %}\n {%- set reasoning_content = reasoning_content|trim %}\n {%- if loop.index0 > ns.last_query_index %}\n {{- '<|im_start|>' + message.role + '\\n<think>\\n' + reasoning_content + '\\n</think>\\n\\n' + content }}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {%- if loop.first %}\n {%- if content|trim %}\n {{- '\\n\\n<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- else %}\n {{- '<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- endif %}\n {%- else %}\n {{- '\\n<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- endif %}\n {%- if tool_call.arguments is defined %}\n {%- for args_name, args_value in tool_call.arguments|items %}\n {{- '<parameter=' + args_name + '>\\n' }}\n {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}\n {{- args_value }}\n {{- '\\n</parameter>\\n' }}\n {%- endfor %}\n {%- endif %}\n {{- '</function>\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.previtem and loop.previtem.role != \"tool\" %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- content }}\n {{- '\\n</tool_response>' }}\n {%- if not loop.last and loop.nextitem.role != \"tool\" %}\n {{- '<|im_end|>\\n' }}\n {%- elif loop.last %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- else %}\n {{- raise_exception('Unexpected message role.') }}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n {%- if enable_thinking is defined and enable_thinking is false %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- else %}\n {{- '<think>\\n' }}\n {%- endif %}\n{%- endif %}\n",
41
+ "tokenizer_class": "Qwen2Tokenizer"
42
+ }
minicpmv4_6_vision_448.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c1cb971418dfe32ad4af06634c994e3f94720b5c24cb901393b9242e7e908e20
3
+ size 609776421
model.embed_tokens.weight.bfloat16.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a0f4077f6d1946e32888542927e12b8772a49f3a8b93622f7670320149911b69
3
+ size 508096512
post_config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "enable_temperature" : false,
3
+ "temperature" : 0.9,
4
+
5
+ "enable_repetition_penalty" : false,
6
+ "repetition_penalty" : 1.2,
7
+ "penalty_window" : 20,
8
+
9
+ "enable_top_p_sampling" : false,
10
+ "top_p" : 0.8,
11
+
12
+ "enable_top_k_sampling" : false,
13
+ "top_k" : 10
14
+ }
python/infer_axmodel.py ADDED
@@ -0,0 +1,701 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import atexit
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import re
7
+ from dataclasses import dataclass
8
+ from typing import List, Optional, Sequence, Tuple
9
+
10
+ import numpy as np
11
+ from axengine import InferenceSession
12
+ from ml_dtypes import bfloat16
13
+ from transformers import AutoProcessor
14
+
15
+
16
+ def resolve_default_hf_model() -> str:
17
+ base_dir = os.path.dirname(__file__)
18
+ candidates = [
19
+ os.path.join(base_dir, "minicpm_v46_tokenizer"),
20
+ os.path.join(base_dir, "MiniCPM-V-4.6"),
21
+ os.path.join(base_dir, "MiniCPM-V-4.6-GPTQ"),
22
+ ]
23
+ for path in candidates:
24
+ if os.path.exists(os.path.join(path, "config.json")):
25
+ return path
26
+ return candidates[0]
27
+
28
+
29
+ DEFAULT_HF_MODEL = resolve_default_hf_model()
30
+
31
+
32
+ def release_ax_inference_session(session):
33
+ inner = getattr(session, "_sess", None)
34
+ unload = getattr(inner, "_unload", None)
35
+ if not callable(unload):
36
+ return
37
+
38
+ try:
39
+ unload()
40
+ except Exception as exc:
41
+ print(f"[WARN] Failed to unload axengine session cleanly: {exc}")
42
+ finally:
43
+ try:
44
+ inner._unload = lambda: None
45
+ except Exception:
46
+ pass
47
+
48
+
49
+ def bf16_zeros(shape: Sequence[int]) -> np.ndarray:
50
+ return np.zeros(tuple(shape), dtype=bfloat16)
51
+
52
+
53
+ def dtype_from_axengine(dtype) -> np.dtype:
54
+ name = str(dtype).lower()
55
+ if "bfloat16" in name or "bf16" in name:
56
+ return bfloat16
57
+ if "float32" in name or "fp32" in name:
58
+ return np.float32
59
+ if "float16" in name or "fp16" in name:
60
+ return np.float16
61
+ if "uint32" in name or "u32" in name:
62
+ return np.uint32
63
+ raise ValueError(f"Unsupported axengine dtype: {dtype}")
64
+
65
+
66
+ def tensor_digest(arr: np.ndarray) -> str:
67
+ arr = np.asarray(arr)
68
+ if arr.dtype == bfloat16:
69
+ raw = arr.view(np.uint16).tobytes()
70
+ else:
71
+ raw = arr.tobytes()
72
+ return hashlib.sha256(raw).hexdigest()[:16]
73
+
74
+
75
+ def tensor_stats(arr: np.ndarray) -> str:
76
+ arr32 = np.asarray(arr, dtype=np.float32)
77
+ finite = bool(np.isfinite(arr32).all())
78
+ if finite:
79
+ return (
80
+ f"finite=True hash={tensor_digest(np.asarray(arr))} "
81
+ f"sum={float(arr32.sum()):.6f} max={float(arr32.max()):.6f} min={float(arr32.min()):.6f}"
82
+ )
83
+ return (
84
+ f"finite=False hash={tensor_digest(np.asarray(arr))} "
85
+ f"nans={int(np.isnan(arr32).sum())} infs={int(np.isinf(arr32).sum())}"
86
+ )
87
+
88
+
89
+ def ensure_finite(name: str, arr: np.ndarray):
90
+ arr32 = np.asarray(arr, dtype=np.float32)
91
+ if not np.isfinite(arr32).all():
92
+ raise RuntimeError(f"{name} is non-finite: {tensor_stats(np.asarray(arr))}")
93
+
94
+
95
+ def load_text_config(hf_model: str) -> Tuple[dict, int]:
96
+ with open(os.path.join(hf_model, "config.json"), "r", encoding="utf-8") as f:
97
+ cfg = json.load(f)
98
+ text_cfg = cfg["text_config"]
99
+ eos_token_id = cfg.get("eos_token_id")
100
+ if eos_token_id is None:
101
+ eos_token_id = text_cfg.get("eos_token_id")
102
+ if isinstance(eos_token_id, list):
103
+ eos_token_id = eos_token_id[0]
104
+ return text_cfg, int(eos_token_id if eos_token_id is not None else 248044)
105
+
106
+
107
+ @dataclass
108
+ class LayerFiles:
109
+ layer_paths: List[str]
110
+ post_path: str
111
+
112
+
113
+ def detect_layer_files(model_dir: str, max_layers: Optional[int] = None) -> LayerFiles:
114
+ names = os.listdir(model_dir)
115
+ layer_pattern = re.compile(r"^(?P<prefix>.*)_p(?P<prefill>\d+)_l(?P<idx>\d+)_together\.axmodel$")
116
+ decode_layer_pattern = re.compile(r"^(?P<prefix>.*)_l(?P<idx>\d+)\.axmodel$")
117
+ post_pattern = re.compile(r"^(?P<prefix>.*)_post\.axmodel$")
118
+
119
+ prefix_map = {}
120
+ for name in names:
121
+ m = layer_pattern.match(name)
122
+ if not m:
123
+ m = decode_layer_pattern.match(name)
124
+ if not m:
125
+ continue
126
+ prefix = m.group("prefix")
127
+ idx = int(m.group("idx"))
128
+ prefix_map.setdefault(prefix, []).append((idx, name))
129
+
130
+ if not prefix_map:
131
+ raise FileNotFoundError(f"No layer axmodel found under {model_dir}")
132
+
133
+ prefix = max(prefix_map.items(), key=lambda kv: len(kv[1]))[0]
134
+ layer_items = sorted(prefix_map[prefix], key=lambda it: it[0])
135
+ if max_layers is not None:
136
+ layer_items = layer_items[: max_layers]
137
+ layer_paths = [os.path.join(model_dir, name) for _, name in layer_items]
138
+
139
+ post_name = None
140
+ for name in names:
141
+ m = post_pattern.match(name)
142
+ if m and m.group("prefix") == prefix:
143
+ post_name = name
144
+ break
145
+ if post_name is None:
146
+ raise FileNotFoundError(f"No post axmodel found for prefix {prefix} under {model_dir}")
147
+
148
+ return LayerFiles(layer_paths=layer_paths, post_path=os.path.join(model_dir, post_name))
149
+
150
+
151
+ class MiniCPMTextAxModelRunner:
152
+ def __init__(
153
+ self,
154
+ hf_model: str,
155
+ axmodel_dir: str,
156
+ embed_bin: Optional[str],
157
+ max_layers: Optional[int],
158
+ kv_cache_len: int = 255,
159
+ ):
160
+ self.hf_model = hf_model
161
+ self.axmodel_dir = axmodel_dir
162
+ self.text_cfg, self.eos_token_id = load_text_config(hf_model)
163
+ self.hidden_size = int(self.text_cfg["hidden_size"])
164
+ self.vocab_size = int(self.text_cfg["vocab_size"])
165
+ self.kv_cache_len = int(kv_cache_len)
166
+ self.layer_types = list(self.text_cfg.get("layer_types") or [])
167
+ self.num_attention_heads = int(self.text_cfg["num_attention_heads"])
168
+ self.num_key_value_heads = int(self.text_cfg["num_key_value_heads"])
169
+ self.head_dim = int(self.text_cfg.get("head_dim") or (self.hidden_size // self.num_attention_heads))
170
+ self.full_attn_kv_dim = self.num_key_value_heads * self.head_dim
171
+
172
+ self.processor = AutoProcessor.from_pretrained(hf_model, trust_remote_code=True)
173
+ self.layer_files = detect_layer_files(axmodel_dir, max_layers=max_layers)
174
+
175
+ if embed_bin is None:
176
+ candidate = os.path.join(axmodel_dir, "model.embed_tokens.weight.bfloat16.bin")
177
+ if not os.path.exists(candidate):
178
+ raise FileNotFoundError(
179
+ "Embedding bin not found under axmodel_dir, please pass --embed-bin explicitly"
180
+ )
181
+ embed_bin = candidate
182
+ self.embed_bin = embed_bin
183
+ self.embed_matrix = np.memmap(embed_bin, mode="r", dtype=np.uint16).view(bfloat16).reshape(
184
+ self.vocab_size, self.hidden_size
185
+ )
186
+
187
+ self.decoder_sessions = [InferenceSession(path) for path in self.layer_files.layer_paths]
188
+ self.post_session = None
189
+ self._closed = False
190
+ atexit.register(self.close)
191
+
192
+ self.layer_decode_input_shapes = []
193
+ self.layer_decode_input_dtypes = []
194
+ self.layer_prefill_input_shapes = []
195
+ self.layer_prefill_input_dtypes = []
196
+ self.layer_decode_output_names = []
197
+ self.layer_prefill_output_names = []
198
+ for layer_idx, session in enumerate(self.decoder_sessions):
199
+ decode_input_shapes = {x.name: tuple(x.shape) for x in session.get_inputs(shape_group=0)}
200
+ decode_input_dtypes = {x.name: dtype_from_axengine(x.dtype) for x in session.get_inputs(shape_group=0)}
201
+ # Some decode-only AX650 exports hide `indices` / `mask` from get_inputs(),
202
+ # but axengine still validates them as required runtime inputs.
203
+ decode_input_shapes.setdefault("indices", (1, 1))
204
+ decode_input_dtypes.setdefault("indices", np.uint32)
205
+ decode_input_shapes.setdefault("mask", (1, 1))
206
+ decode_input_dtypes.setdefault("mask", bfloat16)
207
+ if layer_idx < len(self.layer_types) and self.layer_types[layer_idx] == "full_attention":
208
+ decode_input_shapes["K_cache"] = (1, self.kv_cache_len, self.full_attn_kv_dim)
209
+ decode_input_shapes["V_cache"] = (1, self.kv_cache_len, self.full_attn_kv_dim)
210
+ self.layer_decode_input_shapes.append(decode_input_shapes)
211
+ self.layer_decode_input_dtypes.append(decode_input_dtypes)
212
+ self.layer_decode_output_names.append([x.name for x in session.get_outputs(shape_group=0)])
213
+ prefill_shape_groups = []
214
+ prefill_dtype_groups = []
215
+ prefill_output_groups = []
216
+ for shape_group in range(1, 64):
217
+ try:
218
+ prefill_inputs = session.get_inputs(shape_group=shape_group)
219
+ prefill_shape_groups.append({x.name: tuple(x.shape) for x in prefill_inputs})
220
+ prefill_dtype_groups.append({x.name: dtype_from_axengine(x.dtype) for x in prefill_inputs})
221
+ prefill_output_groups.append([x.name for x in session.get_outputs(shape_group=shape_group)])
222
+ except Exception:
223
+ break
224
+ self.layer_prefill_input_shapes.append(prefill_shape_groups)
225
+ self.layer_prefill_input_dtypes.append(prefill_dtype_groups)
226
+ self.layer_prefill_output_names.append(prefill_output_groups)
227
+ self.decode_input_shapes = self.layer_decode_input_shapes[0]
228
+ self.decode_input_dtypes = self.layer_decode_input_dtypes[0]
229
+ self.prefill_input_shapes = (
230
+ self.layer_prefill_input_shapes[0][0]
231
+ if self.layer_prefill_input_shapes and self.layer_prefill_input_shapes[0]
232
+ else {}
233
+ )
234
+ self.prefill_input_dtypes = (
235
+ self.layer_prefill_input_dtypes[0][0]
236
+ if self.layer_prefill_input_dtypes and self.layer_prefill_input_dtypes[0]
237
+ else {}
238
+ )
239
+ self.prefill_len = int(self.prefill_input_shapes["input"][1]) if "input" in self.prefill_input_shapes else 0
240
+ self.decode_output_names = self.layer_decode_output_names[0]
241
+ self.prefill_output_names = (
242
+ self.layer_prefill_output_names[0][0]
243
+ if self.layer_prefill_output_names and self.layer_prefill_output_names[0]
244
+ else []
245
+ )
246
+ self.hidden_dtype = self.decode_input_dtypes["input"]
247
+
248
+ def close(self):
249
+ if self._closed:
250
+ return
251
+ for session in getattr(self, "decoder_sessions", []):
252
+ release_ax_inference_session(session)
253
+ if getattr(self, "post_session", None) is not None:
254
+ release_ax_inference_session(self.post_session)
255
+ self.decoder_sessions = []
256
+ self.post_session = None
257
+ self._closed = True
258
+
259
+ def tokenize_prompt(self, prompt: str) -> List[int]:
260
+ messages = [{"role": "user", "content": [{"type": "text", "text": prompt}]}]
261
+ inputs = self.processor.apply_chat_template(
262
+ messages,
263
+ tokenize=True,
264
+ add_generation_prompt=True,
265
+ return_dict=True,
266
+ return_tensors="np",
267
+ )
268
+ return inputs["input_ids"][0].astype(np.int64).tolist()
269
+
270
+ def decode_tokens(self, token_ids: Sequence[int]) -> str:
271
+ return self.processor.decode(
272
+ list(token_ids),
273
+ skip_special_tokens=False,
274
+ clean_up_tokenization_spaces=False,
275
+ )
276
+
277
+ def embed_token(self, token_id: int) -> np.ndarray:
278
+ return np.asarray(self.embed_matrix[int(token_id)], dtype=self.hidden_dtype).reshape(1, 1, self.hidden_size)
279
+
280
+ def alloc_layer_states(self) -> Tuple[List[np.ndarray], List[np.ndarray]]:
281
+ k_states = [
282
+ np.zeros(shapes["K_cache"], dtype=dtypes["K_cache"])
283
+ for shapes, dtypes in zip(self.layer_decode_input_shapes, self.layer_decode_input_dtypes)
284
+ ]
285
+ v_states = [
286
+ np.zeros(shapes["V_cache"], dtype=dtypes["V_cache"])
287
+ for shapes, dtypes in zip(self.layer_decode_input_shapes, self.layer_decode_input_dtypes)
288
+ ]
289
+ return k_states, v_states
290
+
291
+ @staticmethod
292
+ def make_feed(shapes: dict, values: dict) -> dict:
293
+ return {name: value for name, value in values.items() if name in shapes}
294
+
295
+ def is_linear_layer(self, layer_idx: int) -> bool:
296
+ return layer_idx >= len(self.layer_types) or self.layer_types[layer_idx] != "full_attention"
297
+
298
+ def prefill_history_capacity(self, shapes: dict) -> int:
299
+ if not shapes:
300
+ return -1
301
+ input_len = int(shapes.get("input", (1, self.prefill_len))[1])
302
+ mask_shape = shapes.get("mask")
303
+ if mask_shape is not None and len(mask_shape) == 3:
304
+ return max(0, int(mask_shape[-1]) - input_len)
305
+ k_shape = shapes.get("K_cache")
306
+ if k_shape is not None and len(k_shape) >= 2:
307
+ return int(k_shape[1])
308
+ return 0
309
+
310
+ def select_prefill_shape_group(self, layer_idx: int, history_len: int) -> int:
311
+ groups = self.layer_prefill_input_shapes[layer_idx]
312
+ if not groups:
313
+ raise RuntimeError(f"layer {layer_idx} has no prefill shape_group")
314
+ if history_len <= 0:
315
+ return 1
316
+
317
+ candidates = []
318
+ for offset, shapes in enumerate(groups):
319
+ cap = self.prefill_history_capacity(shapes)
320
+ if cap >= history_len:
321
+ candidates.append((cap, offset + 1))
322
+ if candidates:
323
+ return min(candidates)[1]
324
+
325
+ # Linear-attention layers may prune reusable warm groups. Reuse the
326
+ # largest available group when its state shape is independent of history.
327
+ return max(range(1, len(groups) + 1), key=lambda gid: self.prefill_history_capacity(groups[gid - 1]))
328
+
329
+ def inspect(self):
330
+ print("hf_model:", self.hf_model)
331
+ print("axmodel_dir:", self.axmodel_dir)
332
+ print("embed_bin:", self.embed_bin)
333
+ print("num_layers:", len(self.decoder_sessions))
334
+ print("hidden_size:", self.hidden_size)
335
+ print("vocab_size:", self.vocab_size)
336
+ print("eos_token_id:", self.eos_token_id)
337
+ print("kv_cache_len:", self.kv_cache_len)
338
+ print("prefill_len:", self.prefill_len)
339
+ print("decode_inputs:", sorted((k, v, str(self.decode_input_dtypes[k])) for k, v in self.decode_input_shapes.items()))
340
+ print("decode_outputs:", [(x.name, tuple(x.shape), str(x.dtype)) for x in self.decoder_sessions[0].get_outputs(0)])
341
+ unique_k_shapes = sorted({tuple(spec["K_cache"]) for spec in self.layer_decode_input_shapes})
342
+ unique_v_shapes = sorted({tuple(spec["V_cache"]) for spec in self.layer_decode_input_shapes})
343
+ print("decode_k_cache_shapes:", unique_k_shapes)
344
+ print("decode_v_cache_shapes:", unique_v_shapes)
345
+ if self.prefill_input_shapes:
346
+ print("prefill_group_count_layer0:", len(self.layer_prefill_input_shapes[0]))
347
+ unique_prefill_k_shapes = sorted(
348
+ {
349
+ tuple(spec["K_cache"])
350
+ for groups in self.layer_prefill_input_shapes
351
+ for spec in groups
352
+ if "K_cache" in spec
353
+ }
354
+ )
355
+ unique_prefill_v_shapes = sorted(
356
+ {
357
+ tuple(spec["V_cache"])
358
+ for groups in self.layer_prefill_input_shapes
359
+ for spec in groups
360
+ if "V_cache" in spec
361
+ }
362
+ )
363
+ unique_prefill_mask_shapes = sorted(
364
+ {
365
+ tuple(spec["mask"])
366
+ for groups in self.layer_prefill_input_shapes
367
+ for spec in groups
368
+ if "mask" in spec
369
+ }
370
+ )
371
+ print("prefill_inputs_layer0:", sorted((k, v, str(self.prefill_input_dtypes[k])) for k, v in self.prefill_input_shapes.items()))
372
+ print("prefill_k_cache_shapes:", unique_prefill_k_shapes)
373
+ print("prefill_v_cache_shapes:", unique_prefill_v_shapes)
374
+ print("prefill_mask_shapes:", unique_prefill_mask_shapes)
375
+ print("prefill_outputs:", [(x.name, tuple(x.shape), str(x.dtype)) for x in self.decoder_sessions[0].get_outputs(1)])
376
+ else:
377
+ print("prefill_inputs: []")
378
+ print("prefill_outputs: []")
379
+ post_session = self.get_post_session()
380
+ print("post_inputs:", [(x.name, tuple(x.shape), str(x.dtype)) for x in post_session.get_inputs()])
381
+ print("post_outputs:", [(x.name, tuple(x.shape), str(x.dtype)) for x in post_session.get_outputs()])
382
+
383
+ def get_post_session(self):
384
+ if self.post_session is None:
385
+ self.post_session = InferenceSession(self.layer_files.post_path)
386
+ return self.post_session
387
+
388
+ def run_prefill(self, token_ids: Sequence[int], verbose: bool = False, return_states: bool = False):
389
+ k_states, v_states = self.alloc_layer_states()
390
+ last_hidden = None
391
+ for start in range(0, len(token_ids), self.prefill_len):
392
+ chunk_ids = token_ids[start : start + self.prefill_len]
393
+ chunk_len = len(chunk_ids)
394
+ data = np.zeros((1, self.prefill_len, self.hidden_size), dtype=self.hidden_dtype)
395
+ data[0, :chunk_len, :] = np.asarray(
396
+ self.embed_matrix[np.asarray(chunk_ids, dtype=np.int64)], dtype=self.hidden_dtype
397
+ )
398
+
399
+ for layer_idx, session in enumerate(self.decoder_sessions):
400
+ if self.is_linear_layer(layer_idx):
401
+ out = np.zeros_like(data)
402
+ for j in range(chunk_len):
403
+ hidden = data[:, j : j + 1, :]
404
+ hidden = self.run_single_layer_decode_step(
405
+ layer_idx,
406
+ hidden,
407
+ start + j,
408
+ k_states,
409
+ v_states,
410
+ verbose=False,
411
+ )
412
+ out[:, j : j + 1, :] = hidden
413
+ data = out
414
+ if verbose:
415
+ print(
416
+ f"prefill chunk={start // self.prefill_len} layer={layer_idx} "
417
+ f"linear_decode_replay tokens={chunk_len} {tensor_stats(data)}"
418
+ )
419
+ continue
420
+
421
+ shape_group = self.select_prefill_shape_group(layer_idx, start)
422
+ layer_shapes = self.layer_prefill_input_shapes[layer_idx][shape_group - 1]
423
+ layer_dtypes = self.layer_prefill_input_dtypes[layer_idx][shape_group - 1]
424
+ if not layer_shapes:
425
+ raise RuntimeError(f"layer {layer_idx} has no prefill shape_group={shape_group}")
426
+ history_cap = self.prefill_history_capacity(layer_shapes)
427
+ history_len = min(start, history_cap)
428
+ indices = None
429
+ if "indices" in layer_shapes:
430
+ indices = np.zeros(layer_shapes["indices"], dtype=layer_dtypes["indices"])
431
+ indices.reshape(-1)[:chunk_len] = np.arange(start, start + chunk_len, dtype=np.uint32)
432
+ mask = None
433
+ if "mask" in layer_shapes:
434
+ if self.layer_types[layer_idx] == "full_attention" and len(layer_shapes["mask"]) == 3:
435
+ mask = np.full(layer_shapes["mask"], -65536.0, dtype=np.float32)
436
+ for q in range(chunk_len):
437
+ mask[:, q, : history_len + q + 1] = 0.0
438
+ else:
439
+ mask = np.zeros(layer_shapes["mask"], dtype=np.float32)
440
+ mask.reshape(-1)[:chunk_len] = 1.0
441
+ mask = mask.astype(layer_dtypes["mask"])
442
+
443
+ k_feed = k_states[layer_idx]
444
+ v_feed = v_states[layer_idx]
445
+ if self.layer_types[layer_idx] == "full_attention":
446
+ k_feed = np.zeros(layer_shapes["K_cache"], dtype=layer_dtypes["K_cache"])
447
+ v_feed = np.zeros(layer_shapes["V_cache"], dtype=layer_dtypes["V_cache"])
448
+ if history_len > 0:
449
+ k_feed[:, :history_len, :] = k_states[layer_idx][:, :history_len, :]
450
+ v_feed[:, :history_len, :] = v_states[layer_idx][:, :history_len, :]
451
+ outputs = session.run(
452
+ None,
453
+ self.make_feed(
454
+ layer_shapes,
455
+ {
456
+ "K_cache": k_feed,
457
+ "V_cache": v_feed,
458
+ **({"indices": indices} if indices is not None else {}),
459
+ "input": data.astype(layer_dtypes["input"], copy=False),
460
+ **({"mask": mask} if mask is not None else {}),
461
+ },
462
+ ),
463
+ shape_group=shape_group,
464
+ )
465
+ output_map = dict(zip(self.layer_prefill_output_names[layer_idx][shape_group - 1], outputs))
466
+ k_out = output_map.get("K_cache_out")
467
+ if k_out is not None:
468
+ if self.layer_types[layer_idx] == "full_attention":
469
+ k_states[layer_idx][:, start : start + chunk_len, :] = k_out[:, :chunk_len, :]
470
+ else:
471
+ k_states[layer_idx] = k_out
472
+ v_out = output_map.get("V_cache_out")
473
+ if v_out is not None:
474
+ if self.layer_types[layer_idx] == "full_attention":
475
+ v_states[layer_idx][:, start : start + chunk_len, :] = v_out[:, :chunk_len, :]
476
+ else:
477
+ v_states[layer_idx] = v_out
478
+ data = output_map["output"]
479
+ ensure_finite(f"prefill layer {layer_idx} output", data)
480
+ if verbose:
481
+ print(
482
+ f"prefill chunk={start // self.prefill_len} layer={layer_idx} "
483
+ f"shape_group={shape_group} history_len={history_len} {tensor_stats(data)}"
484
+ )
485
+ last_hidden = data[:, chunk_len - 1 : chunk_len, :]
486
+ if return_states:
487
+ return k_states, v_states, last_hidden
488
+ return last_hidden
489
+
490
+ def run_single_layer_decode_step(
491
+ self,
492
+ layer_idx: int,
493
+ hidden: np.ndarray,
494
+ position: int,
495
+ k_states: List[np.ndarray],
496
+ v_states: List[np.ndarray],
497
+ verbose: bool = False,
498
+ ) -> np.ndarray:
499
+ session = self.decoder_sessions[layer_idx]
500
+ layer_shapes = self.layer_decode_input_shapes[layer_idx]
501
+ layer_dtypes = self.layer_decode_input_dtypes[layer_idx]
502
+ indices = None
503
+ if "indices" in layer_shapes:
504
+ indices = np.zeros(layer_shapes["indices"], dtype=layer_dtypes["indices"])
505
+ indices.reshape(-1)[0] = position
506
+ mask = None
507
+ if "mask" in layer_shapes:
508
+ if self.layer_types[layer_idx] == "full_attention" and len(layer_shapes["mask"]) == 3:
509
+ mask = np.full(layer_shapes["mask"], -65536.0, dtype=np.float32)
510
+ valid_past = min(position, layer_shapes["mask"][-1] - 1)
511
+ if valid_past > 0:
512
+ mask[:, :, :valid_past] = 0.0
513
+ mask[:, :, -1:] = 0.0
514
+ else:
515
+ mask = np.ones(layer_shapes["mask"], dtype=np.float32)
516
+ mask = mask.astype(layer_dtypes["mask"])
517
+ outputs = session.run(
518
+ None,
519
+ self.make_feed(
520
+ layer_shapes,
521
+ {
522
+ "K_cache": k_states[layer_idx],
523
+ "V_cache": v_states[layer_idx],
524
+ **({"indices": indices} if indices is not None else {}),
525
+ "input": hidden,
526
+ **({"mask": mask} if mask is not None else {}),
527
+ },
528
+ ),
529
+ shape_group=0,
530
+ )
531
+ output_map = dict(zip(self.layer_decode_output_names[layer_idx], outputs))
532
+ k_out = output_map.get("K_cache_out")
533
+ if k_out is not None:
534
+ if self.layer_types[layer_idx] == "full_attention" and k_states[layer_idx].shape != k_out.shape:
535
+ pos_end = position + k_out.shape[1]
536
+ k_states[layer_idx][:, position:pos_end, :] = k_out
537
+ else:
538
+ k_states[layer_idx] = k_out
539
+ v_out = output_map.get("V_cache_out")
540
+ if v_out is not None:
541
+ if self.layer_types[layer_idx] == "full_attention" and v_states[layer_idx].shape != v_out.shape:
542
+ pos_end = position + v_out.shape[1]
543
+ v_states[layer_idx][:, position:pos_end, :] = v_out
544
+ else:
545
+ v_states[layer_idx] = v_out
546
+ out = output_map["output"]
547
+ ensure_finite(f"decode step={position} layer={layer_idx} output", out)
548
+ if verbose:
549
+ print(f"decode step={position} layer={layer_idx} {tensor_stats(out)}")
550
+ return out
551
+
552
+ def run_decode_step(
553
+ self,
554
+ hidden: np.ndarray,
555
+ position: int,
556
+ k_states: List[np.ndarray],
557
+ v_states: List[np.ndarray],
558
+ verbose: bool = False,
559
+ ) -> np.ndarray:
560
+ data = hidden
561
+ for layer_idx in range(len(self.decoder_sessions)):
562
+ data = self.run_single_layer_decode_step(layer_idx, data, position, k_states, v_states, verbose=verbose)
563
+ return data
564
+
565
+ def decode_replay_prompt(
566
+ self,
567
+ token_ids: Sequence[int],
568
+ limit_prompt_tokens: Optional[int] = None,
569
+ verbose: bool = False,
570
+ ) -> Tuple[List[np.ndarray], List[np.ndarray], np.ndarray]:
571
+ if limit_prompt_tokens is not None:
572
+ token_ids = token_ids[:limit_prompt_tokens]
573
+ k_states, v_states = self.alloc_layer_states()
574
+ last_hidden = None
575
+ for pos, token_id in enumerate(token_ids):
576
+ last_hidden = self.run_decode_step(self.embed_token(int(token_id)), pos, k_states, v_states, verbose=verbose)
577
+ return k_states, v_states, last_hidden
578
+
579
+ def run_post(self, hidden: np.ndarray) -> np.ndarray:
580
+ logits = self.get_post_session().run(None, {"input": hidden})[0]
581
+ ensure_finite("post logits", logits)
582
+ return logits
583
+
584
+ def greedy_next_token(self, hidden: np.ndarray) -> int:
585
+ logits = self.run_post(hidden)
586
+ logits = np.asarray(logits, dtype=np.float32).reshape(-1)
587
+ return int(np.argmax(logits))
588
+
589
+ def generate(self, token_ids: Sequence[int], max_new_tokens: int, prompt_mode: str, verbose: bool = False):
590
+ if prompt_mode == "prefill":
591
+ k_states, v_states, last_hidden = self.run_prefill(token_ids, verbose=verbose, return_states=True)
592
+ else:
593
+ k_states, v_states, last_hidden = self.decode_replay_prompt(token_ids, verbose=verbose)
594
+ prompt_len = len(token_ids)
595
+ generated = []
596
+ for step in range(max_new_tokens):
597
+ next_token = self.greedy_next_token(last_hidden)
598
+ generated.append(next_token)
599
+ print(f"gen step={step} token_id={next_token} piece={self.decode_tokens([next_token])!r}")
600
+ if next_token == self.eos_token_id:
601
+ break
602
+ last_hidden = self.run_decode_step(
603
+ self.embed_token(next_token),
604
+ prompt_len + step,
605
+ k_states,
606
+ v_states,
607
+ verbose=verbose,
608
+ )
609
+
610
+ print("generated_ids:", generated)
611
+ print("generated_text:", self.decode_tokens(generated))
612
+
613
+
614
+ def parse_args():
615
+ parser = argparse.ArgumentParser(
616
+ description="MiniCPM-V-4.6 AX650 text-only axmodel Python runner/debugger"
617
+ )
618
+ parser.add_argument("--hf-model", default=DEFAULT_HF_MODEL, help="Tokenizer/config path for AutoProcessor")
619
+ parser.add_argument("--axmodel-dir", required=True, help="Compiled axmodel directory")
620
+ parser.add_argument("--embed-bin", default=None, help="Embedding bf16 bin path")
621
+ parser.add_argument(
622
+ "--mode",
623
+ default="inspect",
624
+ choices=["inspect", "prefill", "decode_replay", "generate"],
625
+ help="Execution mode",
626
+ )
627
+ parser.add_argument("--prompt", default="你好,请做一个简短自我介绍。", help="User prompt")
628
+ parser.add_argument("--prompt-file", default=None, help="Read user prompt from a UTF-8 text file")
629
+ parser.add_argument("--max-layers", type=int, default=None, help="Only load the first N decoder layers")
630
+ parser.add_argument(
631
+ "--limit-prompt-tokens",
632
+ type=int,
633
+ default=None,
634
+ help="Only consume the first N prompt tokens in decode_replay mode",
635
+ )
636
+ parser.add_argument(
637
+ "--prompt-mode",
638
+ default="decode_replay",
639
+ choices=["decode_replay", "prefill"],
640
+ help="How to consume the prompt before generation",
641
+ )
642
+ parser.add_argument("--max-new-tokens", type=int, default=16, help="Generation length for --mode generate")
643
+ parser.add_argument("--kv-cache-len", type=int, default=255, help="Decode KV cache length used at compile time")
644
+ parser.add_argument("--verbose", action="store_true", help="Print per-layer tensor stats")
645
+ return parser.parse_args()
646
+
647
+
648
+ def main():
649
+ args = parse_args()
650
+ if args.prompt_file:
651
+ with open(args.prompt_file, "r", encoding="utf-8") as f:
652
+ args.prompt = f.read()
653
+ runner = MiniCPMTextAxModelRunner(
654
+ hf_model=args.hf_model,
655
+ axmodel_dir=args.axmodel_dir,
656
+ embed_bin=args.embed_bin,
657
+ max_layers=args.max_layers,
658
+ kv_cache_len=args.kv_cache_len,
659
+ )
660
+ try:
661
+ token_ids = runner.tokenize_prompt(args.prompt)
662
+ print("prompt_token_count:", len(token_ids))
663
+ print("prompt_token_ids:", token_ids)
664
+ print("prompt_template_repr:", runner.decode_tokens(token_ids).encode("unicode_escape").decode())
665
+
666
+ if args.mode == "inspect":
667
+ runner.inspect()
668
+ return
669
+
670
+ if args.mode == "prefill":
671
+ hidden = runner.run_prefill(token_ids, verbose=args.verbose)
672
+ print("prefill_last_hidden:", tensor_stats(hidden))
673
+ logits = runner.run_post(hidden)
674
+ print("post_logits:", tensor_stats(logits))
675
+ print("greedy_next_token:", runner.greedy_next_token(hidden))
676
+ return
677
+
678
+ if args.mode == "decode_replay":
679
+ _, _, hidden = runner.decode_replay_prompt(
680
+ token_ids,
681
+ limit_prompt_tokens=args.limit_prompt_tokens,
682
+ verbose=args.verbose,
683
+ )
684
+ print("decode_replay_last_hidden:", tensor_stats(hidden))
685
+ logits = runner.run_post(hidden)
686
+ print("post_logits:", tensor_stats(logits))
687
+ print("greedy_next_token:", runner.greedy_next_token(hidden))
688
+ return
689
+
690
+ runner.generate(
691
+ token_ids,
692
+ max_new_tokens=args.max_new_tokens,
693
+ prompt_mode=args.prompt_mode,
694
+ verbose=args.verbose,
695
+ )
696
+ finally:
697
+ runner.close()
698
+
699
+
700
+ if __name__ == "__main__":
701
+ main()
python/infer_torch.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+
3
+ import torch
4
+ from transformers import AutoModelForImageTextToText, AutoProcessor
5
+
6
+
7
+ DEFAULT_MODEL_PATH = (
8
+ "/data/tmp/yongqiang/nfs/auto_model_deployment/"
9
+ "Minicpm-V-4.6-hf-original/MiniCPM-V-4.6"
10
+ )
11
+
12
+
13
+ def resolve_dtype(name: str):
14
+ if name == "auto":
15
+ return "auto"
16
+ return {
17
+ "float32": torch.float32,
18
+ "float16": torch.float16,
19
+ "bfloat16": torch.bfloat16,
20
+ }[name]
21
+
22
+
23
+ def resolve_device(name: str):
24
+ if name != "auto":
25
+ return name
26
+ return "cuda:0" if torch.cuda.is_available() else "cpu"
27
+
28
+
29
+ def main():
30
+ parser = argparse.ArgumentParser(description="MiniCPM-V-4.6 official torch text-only inference")
31
+ parser.add_argument("--model-path", default=DEFAULT_MODEL_PATH)
32
+ parser.add_argument("--prompt", default="你好,请做一个简短自我介绍。")
33
+ parser.add_argument("--device", default="auto")
34
+ parser.add_argument("--dtype", default="auto", choices=["auto", "float32", "float16", "bfloat16"])
35
+ parser.add_argument("--attn-implementation", default="eager")
36
+ parser.add_argument("--max-new-tokens", type=int, default=64)
37
+ parser.add_argument("--do-sample", action="store_true")
38
+ args = parser.parse_args()
39
+
40
+ device = resolve_device(args.device)
41
+ dtype = resolve_dtype(args.dtype)
42
+
43
+ processor = AutoProcessor.from_pretrained(args.model_path)
44
+
45
+ load_kwargs = {
46
+ "attn_implementation": args.attn_implementation,
47
+ "device_map": device,
48
+ }
49
+ if dtype != "auto":
50
+ load_kwargs["dtype"] = dtype
51
+
52
+ model = AutoModelForImageTextToText.from_pretrained(args.model_path, **load_kwargs).eval()
53
+
54
+ messages = [
55
+ {
56
+ "role": "user",
57
+ "content": [
58
+ {"type": "text", "text": args.prompt},
59
+ ],
60
+ }
61
+ ]
62
+
63
+ inputs = processor.apply_chat_template(
64
+ messages,
65
+ tokenize=True,
66
+ add_generation_prompt=True,
67
+ return_dict=True,
68
+ return_tensors="pt",
69
+ ).to(model.device)
70
+
71
+ prompt_text = processor.tokenizer.decode(inputs.input_ids[0], skip_special_tokens=False)
72
+ print("model_path:", args.model_path)
73
+ print("device:", model.device)
74
+ print("input_ids.shape:", tuple(inputs.input_ids.shape))
75
+ print("input_ids:", inputs.input_ids[0].tolist())
76
+ print("prompt_template_repr:", prompt_text.encode("unicode_escape").decode())
77
+
78
+ with torch.inference_mode():
79
+ generated_ids = model.generate(
80
+ **inputs,
81
+ max_new_tokens=args.max_new_tokens,
82
+ do_sample=args.do_sample,
83
+ )
84
+
85
+ trimmed_ids = generated_ids[0, inputs.input_ids.shape[-1] :]
86
+ output_text = processor.decode(trimmed_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)
87
+
88
+ print("generated_ids:", trimmed_ids.tolist())
89
+ print("output_text:", output_text)
90
+
91
+
92
+ if __name__ == "__main__":
93
+ main()
python/minicpm_v46_tokenizer/.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
python/minicpm_v46_tokenizer/README.md ADDED
@@ -0,0 +1,498 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ pipeline_tag: image-text-to-text
4
+ tags:
5
+ - minicpm-v
6
+ - multimodal
7
+ - On-Device Model
8
+ - lightweight
9
+ ---
10
+
11
+ A Pocket-Sized MLLM for Ultra-Efficient Image and Video Understanding on Your Phone
12
+
13
+ [GitHub](https://github.com/OpenBMB/MiniCPM-o) | [CookBook](https://github.com/OpenSQZ/MiniCPM-V-CookBook) | [Demo](https://huggingface.co/spaces/openbmb/MiniCPM-V-4.6-Demo) |
14
+ [Feishu (Lark)](https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/feishu_qrcode.png)
15
+
16
+ ## MiniCPM-V 4.6
17
+
18
+ **MiniCPM-V 4.6** is our most edge-deployment-friendly model to date. The model is built based on SigLIP2-400M and the Qwen3.5-0.8B LLM. It inherits the strong single-image, multi-image, and video understanding capabilities of MiniCPM-V family, while significantly improving computation efficiency. It also introduces mixed 4x/16x visual token compression. Notable features of MiniCPM-V 4.6 include:
19
+
20
+ - 🔥 **Leading Foundation Capability.**
21
+ MiniCPM-V 4.6 scores 13 on the Artificial Analysis Intelligence Index benchmark, outperforming Qwen3.5-0.8B's score of 10 with 19x fewer token cost, and Qwen3.5-0.8B-Thinking's score of 11 with 43x fewer token cost. It also surpasses the larger Ministral 3 3B (score of 11).
22
+
23
+ - 💪 **Strong Multimodal Capability.**
24
+ MiniCPM-V 4.6 outperforms Qwen3.5-0.8B on most vision-language understanding tasks, and reaches Qwen3.5 2B-level capability on many benchmarks including OpenCompass, RefCOCO, HallusionBench, MUIRBench, and OCRBench.
25
+ - 🚀 **Ultra-Efficient Architecture.**
26
+ Based on the latest technique in [LLaVA-UHD v4](https://github.com/THUMAI-Lab/LLaVA-UHD-v4), MiniCPM-V 4.6 reduces the visual encoding computation FLOPs by more than 50%. It enables MiniCPM-V 4.6 to achieve better efficiency to even smaller models, achieving ~1.5x token throughput compared to Qwen3.5-0.8B.
27
+ It also supports mixed 4x/16x visual token compression rate, allowing flexible switching between accuracy and speed.
28
+ - 📱 **Broad Mobile Platform Coverage.**
29
+ MiniCPM-V 4.6 can be deployed across all three mainstream mobile platforms — iOS, Android, and HarmonyOS. With every edge adaptation code open-sourced, developers can reproduce the on-device experience in [just a few steps](#deploy-minicpm-v-46-on-ios-android-and-harmonyos-platforms).
30
+ - 🛠️ **Developer Friendly.**
31
+ MiniCPM-V 4.6 is adapted to [inference frameworks](#inference-and-training) such as vLLM, SGLang, llama.cpp, Ollama, and supports [fine-tuning ecosystems](#inference-and-training) such as SWIFT and LLaMA-Factory. Developers can quickly customize models for new domains and tasks on consumer-grade GPUs. We provide multiple quantized variants across GGUF, BNB, AWQ, and GPTQ formats.
32
+
33
+
34
+ ### Evaluation <!-- omit in toc -->
35
+
36
+ **Overall Performance (Instruct)**
37
+
38
+ <p align="center">
39
+ <img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/instruct.png" width="90%"></img>
40
+ </p>
41
+
42
+
43
+ <details>
44
+ <summary>Click to view MiniCPM-V 4.6-Thinking performance.</summary>
45
+
46
+
47
+ <p align="center">
48
+ <img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/thinking.png" width="90%"></img>
49
+ </p>
50
+
51
+
52
+ </details>
53
+
54
+
55
+ <details>
56
+ <summary>Click to view MiniCPM-V 4.6 inference efficiency results.</summary>
57
+
58
+
59
+ **High-Concurrency Throughput**
60
+
61
+ <p align="center">
62
+ <img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/throughput.png" width="60%"></img>
63
+ </p>
64
+
65
+ **Single Request TTFT (ms)**
66
+
67
+ <p align="center">
68
+ <img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/ttft.png" width="60%"></img>
69
+ </p>
70
+
71
+
72
+ </details>
73
+
74
+
75
+ ### Examples <!-- omit in toc -->
76
+
77
+ #### Overall
78
+
79
+ <div align="center">
80
+ <a href="https://www.youtube.com/watch?v=Ch5UG1FoysM"><img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/video_play.png" width="70%"></a>
81
+ </div>
82
+
83
+ MiniCPM-V 4.6 can be deployed across three mainstream end-side platforms — **iOS, Android and HarmonyOS**. The clips below are raw screen recordings on phone devices without edition.
84
+
85
+ <table align="center">
86
+ <tr>
87
+ <td align="center"><b>iPhone</b><br><sub>iPhone 17 Pro Max</sub></td>
88
+ <td align="center"><b>Android</b><br><sub>Redmi K70</sub></td>
89
+ <td align="center"><b>HarmonyOS</b><br><sub>HUAWEI nova 14</sub></td>
90
+ </tr>
91
+ <tr>
92
+ <td align="center"><img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/v46_iphone_en_handwriting.gif" width="100%"/></td>
93
+ <td align="center"><img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/v46_android_en_refraction.gif" width="100%"/></td>
94
+ <td align="center"><img src="https://raw.githubusercontent.com/openbmb/MiniCPM-V/main/assets/minicpmv4.6/v46_harmonyos_en_ticket.gif" width="100%"/></td>
95
+ </tr>
96
+ </table>
97
+
98
+
99
+ ### Usages
100
+
101
+ #### Inference with Transformers <!-- omit in toc -->
102
+ ##### Installation <!-- omit in toc -->
103
+
104
+ ```bash
105
+ pip install "transformers[torch]>=5.7.0" torchvision torchcodec
106
+ ```
107
+
108
+ > **Note on CUDA compatibility:** `torchcodec` (used for video decoding) may have compatibility issues with certain CUDA versions. For example, `torch>=2.11` bundles CUDA 13.1 by default, while environments with CUDA 12.x may encounter errors such as `RuntimeError: Could not load libtorchcodec`. Two workarounds:
109
+ >
110
+ > 1. **Replace `torchcodec` with `PyAV`** — supports both image and video inference without CUDA version constraints:
111
+ > ```bash
112
+ > pip install "transformers[torch]>=5.7.0" torchvision av
113
+ > ```
114
+ > 2. **Pin the CUDA version** when installing torch to match your environment (e.g. CUDA 12.8):
115
+ > ```bash
116
+ > pip install "transformers>=5.7.0" torchvision torchcodec --index-url https://download.pytorch.org/whl/cu128
117
+ > ```
118
+
119
+ ##### Load Model <!-- omit in toc -->
120
+
121
+ ```python
122
+ from transformers import AutoModelForImageTextToText, AutoProcessor
123
+
124
+ model_id = "openbmb/MiniCPM-V-4.6"
125
+
126
+ processor = AutoProcessor.from_pretrained(model_id)
127
+ model = AutoModelForImageTextToText.from_pretrained(
128
+ model_id, torch_dtype="auto", device_map="auto"
129
+ )
130
+
131
+ # Flash Attention 2 is recommended for better acceleration and memory saving,
132
+ # especially in multi-image and video scenarios.
133
+ # model = AutoModelForImageTextToText.from_pretrained(
134
+ # model_id,
135
+ # torch_dtype=torch.bfloat16,
136
+ # attn_implementation="flash_attention_2",
137
+ # device_map="auto",
138
+ # )
139
+ ```
140
+
141
+ ##### Image Inference <!-- omit in toc -->
142
+
143
+ ```python
144
+ messages = [
145
+ {
146
+ "role": "user",
147
+ "content": [
148
+ {"type": "image", "url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/refract.png"},
149
+ {"type": "text", "text": "What causes this phenomenon?"},
150
+ ],
151
+ }
152
+ ]
153
+
154
+ downsample_mode = "16x" # Using `downsample_mode="4x"` for Finer Detail
155
+
156
+ inputs = processor.apply_chat_template(
157
+ messages, tokenize=True, add_generation_prompt=True,
158
+ return_dict=True, return_tensors="pt",
159
+ downsample_mode=downsample_mode,
160
+ max_slice_nums=36,
161
+ ).to(model.device)
162
+
163
+ generated_ids = model.generate(**inputs, downsample_mode=downsample_mode, max_new_tokens=512)
164
+ generated_ids_trimmed = [
165
+ out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
166
+ ]
167
+ output_text = processor.batch_decode(
168
+ generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
169
+ )
170
+ print(output_text[0])
171
+ ```
172
+
173
+ ##### Video Inference <!-- omit in toc -->
174
+
175
+ ```python
176
+ messages = [
177
+ {
178
+ "role": "user",
179
+ "content": [
180
+ {"type": "video", "url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/football.mp4"},
181
+ {"type": "text", "text": "Describe this video in detail. Follow the timeline and focus on on-screen text, interface changes, main actions, and scene changes."},
182
+ ],
183
+ }
184
+ ]
185
+
186
+ downsample_mode = "16x" # Using `downsample_mode="4x"` for Finer Detail
187
+
188
+ inputs = processor.apply_chat_template(
189
+ messages, tokenize=True, add_generation_prompt=True,
190
+ return_dict=True, return_tensors="pt",
191
+ downsample_mode=downsample_mode,
192
+ max_num_frames=128,
193
+ stack_frames=1,
194
+ max_slice_nums=1,
195
+ use_image_id=False,
196
+ ).to(model.device)
197
+
198
+ generated_ids = model.generate(**inputs, downsample_mode=downsample_mode, max_new_tokens=2048)
199
+ generated_ids_trimmed = [
200
+ out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
201
+ ]
202
+ output_text = processor.batch_decode(
203
+ generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
204
+ )
205
+ print(output_text[0])
206
+ ```
207
+
208
+ ##### Advanced Parameters <!-- omit in toc -->
209
+
210
+ You can customize image/video processing by passing additional parameters to `apply_chat_template`:
211
+
212
+ | Parameter | Default | Applies to | Description |
213
+ |-----------|---------|------------|-------------|
214
+ | `downsample_mode` | `"16x"` | Image & Video | Visual token downsampling. `"16x"` merges tokens for efficiency; `"4x"` keeps 4× more tokens for finer detail. Must also be passed to `generate()`. |
215
+ | `max_slice_nums` | `9` | Image & Video | Maximum number of slices when splitting a high-resolution image. Higher values preserve more detail for large images. Recommended: `36` for image, `1` for video. |
216
+ | `max_num_frames` | `128` | Video only | Maximum number of main frames sampled from the video. |
217
+ | `stack_frames` | `1` | Video only | Total sample points per second. `1` = main frame only (no stacking). `N` (N>1) = 1 main frame + N−1 sub-frames per second; the sub-frames are composited into a grid image and interleaved with main frames. Recommended setting is `1` for short videos, and `3` or `5` for long videos. |
218
+ | `use_image_id` | `True` | Image & Video | Whether to prepend `<image_id>N</image_id>` tags before each image/frame placeholder. Set `True` for image, `False` for video. |
219
+
220
+ > **Note:** `downsample_mode` must be passed to **both** `apply_chat_template` (for correct placeholder count) and `generate` (for the vision encoder). All other parameters only need to be passed to `apply_chat_template`.
221
+
222
+ ##### Serving with `transformers serve` <!-- omit in toc -->
223
+
224
+ Hugging Face Transformers includes a lightweight OpenAI-compatible server for quick testing and moderate-load deployment.
225
+
226
+ ```bash
227
+ pip install "transformers[serving]>=5.7.0"
228
+ ```
229
+
230
+ Start the server:
231
+
232
+ ```bash
233
+ transformers serve openbmb/MiniCPM-V-4.6 --port 8000 --host 0.0.0.0 --continuous-batching
234
+ ```
235
+
236
+ Send a request:
237
+
238
+ ```bash
239
+ curl -s http://localhost:8000/v1/chat/completions \
240
+ -H 'Content-Type: application/json' \
241
+ -d '{
242
+ "model": "openbmb/MiniCPM-V-4.6",
243
+ "messages": [{
244
+ "role": "user",
245
+ "content": [
246
+ {"type": "image_url", "image_url": {"url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/refract.png"}},
247
+ {"type": "text", "text": "What causes this phenomenon?"}
248
+ ]
249
+ }]
250
+ }'
251
+ ```
252
+
253
+ Tool calling example:
254
+
255
+ ```bash
256
+ curl -s http://localhost:8000/v1/chat/completions -H 'Content-Type: application/json' -d '{
257
+ "model": "openbmb/MiniCPM-V-4.6",
258
+ "messages": [{"role": "user", "content": [
259
+ {"type": "text", "text": "the weather of Beijing"}
260
+ ]}],
261
+ "tools": [{
262
+ "type": "function",
263
+ "function": {
264
+ "name": "get_weather",
265
+ "description": "Get the current weather for a given location",
266
+ "parameters": {
267
+ "type": "object",
268
+ "properties": {
269
+ "location": {"type": "string", "description": "City name"}
270
+ },
271
+ "required": ["location"]
272
+ }
273
+ }
274
+ }]
275
+ }'
276
+ ```
277
+
278
+ The model returns a natural-language explanation followed by a structured <tool_call> block embedded in the content field. Note that a dedicated tool call parser for this format has not yet been added to the transformers library, so the tool calls need to be extracted manually via regex for now.
279
+
280
+ ```
281
+ {
282
+ "id": "f4f09c7d-8045-4cb1-ade9-07aa5dee637d",
283
+ "choices": [
284
+ {
285
+ "finish_reason": "stop",
286
+ "index": 0,
287
+ "message": {
288
+ "content": "I need to check the current weather for Beijing, so I will call the get_weather function.\n\n<tool_call>\n<function=get_weather>\n<parameter=location>\nBeijing\n</parameter>\n</function>\n</tool_call>",
289
+ "role": "assistant"
290
+ }
291
+ }
292
+ ],
293
+ "created": 1778748859,
294
+ "model": "openbmb/MiniCPM-V-4.6@main",
295
+ "object": "chat.completion",
296
+ "usage": {
297
+ "completion_tokens": 47,
298
+ "prompt_tokens": 283,
299
+ "total_tokens": 330
300
+ }
301
+ }
302
+ ```
303
+
304
+ #### Handling Escaped Newlines in Model Outputs <!-- omit in toc -->
305
+
306
+ In some cases, the model might output escaped newline characters `\n` as string literals instead of actual newlines. To render the text correctly, especially in UI layers, you can use the following utility function. This function carefully replaces literal `\n` with real newlines while protecting scenarios where `\n` has specific semantic meaning.
307
+
308
+ **Utility Function:**
309
+
310
+ ```python
311
+ import re
312
+
313
+ _PATTERN = re.compile(
314
+ r'(```[\s\S]*?```' # fenced code blocks
315
+ r'|`[^`]+`' # inline code
316
+ r'|\$\$[\s\S]*?\$\$' # display math
317
+ r'|\$[^$]+\$' # inline math
318
+ r'|\\\([\s\S]*?\\\)' # \(...\)
319
+ r'|\\\[[\s\S]*?\\\]' # \[...\]
320
+ r')'
321
+ r'|(?<!\\)(?:\\r\\n|\\[nr])'
322
+ )
323
+
324
+ def normalize_response_text(text: str) -> str:
325
+ """
326
+ Lightweight post-processing: Converts literal '\\n' to actual newlines,
327
+ while protecting code blocks, inline code, and LaTeX commands.
328
+ """
329
+ if not isinstance(text, str) or "\\" not in text:
330
+ return text
331
+ return _PATTERN.sub(lambda m: m.group(1) or '\n', text)
332
+ ```
333
+
334
+ #### Deploy MiniCPM-V 4.6 on iOS, Android, and HarmonyOS Platforms <!-- omit in toc -->
335
+
336
+ We have adapted MiniCPM-V 4.6 for deployment on **iOS, Android, and HarmonyOS** platforms, with **all edge adaptation code fully open-sourced**. Developers can reproduce the on-device experience in just a few steps. Visit our [edge deployment repository](https://github.com/OpenBMB/MiniCPM-V-edge-demo) for platform-specific build guides, or go to the [download page](https://github.com/OpenBMB/MiniCPM-V-edge-demo/blob/main/DOWNLOAD.md) to try pre-built apps directly.
337
+
338
+ <a id="inference-and-training"></a>
339
+ #### Use MiniCPM-V 4.6 in Other Inference and Training Frameworks <!-- omit in toc -->
340
+
341
+ MiniCPM-V 4.6 supports multiple inference and training frameworks. Below are quick-start commands for each. For full details, see our [Cookbook](https://github.com/OpenSQZ/MiniCPM-V-CookBook).
342
+
343
+ <details>
344
+ <summary><b>vLLM</b> — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/deployment/vllm/minicpm-v4_6_vllm.md">Full Guide</a></summary>
345
+
346
+ ```bash
347
+ vllm serve openbmb/MiniCPM-V-4.6 \
348
+ --port 8000 \
349
+ --enable-auto-tool-choice \
350
+ --tool-call-parser qwen3_coder \
351
+ --default-chat-template-kwargs '{"enable_thinking": false}'
352
+ ```
353
+
354
+ > **Note:** `--enable-auto-tool-choice` and `--tool-call-parser qwen3_coder` enable tool/function calling support. If you don't need tool use, you can omit these flags and simply run `vllm serve openbmb/MiniCPM-V-4.6`.
355
+
356
+ ```bash
357
+ curl -s http://localhost:8000/v1/chat/completions -H 'Content-Type: application/json' -d '{
358
+ "model": "openbmb/MiniCPM-V-4.6",
359
+ "messages": [{"role": "user", "content": [
360
+ {"type": "image_url", "image_url": {"url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/refract.png"}},
361
+ {"type": "text", "text": "What causes this phenomenon?"}
362
+ ]}]
363
+ }'
364
+ ```
365
+
366
+
367
+ Tool calling example:
368
+
369
+ ```bash
370
+ curl -s http://localhost:8000/v1/chat/completions -H 'Content-Type: application/json' -d '{
371
+ "model": "openbmb/MiniCPM-V-4.6",
372
+ "messages": [{"role": "user", "content": [
373
+ {"type": "text", "text": "北京的天气"}
374
+ ]}],
375
+ "tools": [{
376
+ "type": "function",
377
+ "function": {
378
+ "name": "get_weather",
379
+ "description": "Get the current weather for a given location",
380
+ "parameters": {
381
+ "type": "object",
382
+ "properties": {
383
+ "location": {"type": "string", "description": "City name"}
384
+ },
385
+ "required": ["location"]
386
+ }
387
+ }
388
+ }]
389
+ }'
390
+ ```
391
+
392
+ </details>
393
+
394
+ <details>
395
+ <summary><b>SGLang</b> — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/deployment/sglang/minicpm-v4_6_sglang.md">Full Guide</a></summary>
396
+
397
+ ```bash
398
+ python -m sglang.launch_server --model openbmb/MiniCPM-V-4.6 --port 30000
399
+ ```
400
+
401
+ ```bash
402
+ curl -s http://localhost:30000/v1/chat/completions -H 'Content-Type: application/json' -d '{
403
+ "model": "openbmb/MiniCPM-V-4.6",
404
+ "messages": [{"role": "user", "content": [
405
+ {"type": "image_url", "image_url": {"url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/refract.png"}},
406
+ {"type": "text", "text": "What causes this phenomenon?"}
407
+ ]}]
408
+ }'
409
+ ```
410
+
411
+ </details>
412
+
413
+ <details>
414
+ <summary><b>llama.cpp</b> — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/deployment/llama.cpp/minicpm-v4_6_llamacpp.md">Full Guide</a></summary>
415
+
416
+ ```bash
417
+ llama-server -m MiniCPM-V-4.6-Q4_K_M.gguf --port 8080
418
+ ```
419
+
420
+ ```bash
421
+ curl -s http://localhost:8080/v1/chat/completions -H 'Content-Type: application/json' -d '{
422
+ "model": "MiniCPM-V-4.6",
423
+ "messages": [{"role": "user", "content": [
424
+ {"type": "image_url", "image_url": {"url": "https://huggingface.co/datasets/openbmb/DemoCase/resolve/main/refract.png"}},
425
+ {"type": "text", "text": "What causes this phenomenon?"}
426
+ ]}]
427
+ }'
428
+ ```
429
+
430
+ </details>
431
+
432
+ <details>
433
+ <summary><b>Ollama</b> — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/deployment/ollama/minicpm-v4_6_ollama.md">Full Guide</a></summary>
434
+
435
+ ```bash
436
+ ollama run minicpm-v-4.6
437
+ ```
438
+
439
+ In the interactive session, paste an image path or URL directly to chat with the model.
440
+
441
+ </details>
442
+
443
+ <details>
444
+ <summary><b>LLaMA-Factory</b> (Fine-tuning) — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/finetune/llamafactory_minicpmv46.md">Full Guide</a></summary>
445
+
446
+ ```bash
447
+ llamafactory-cli train examples/train_lora/minicpmv4_6_lora_sft.yaml
448
+ ```
449
+
450
+ </details>
451
+
452
+ <details>
453
+ <summary><b>ms-swift</b> (Fine-tuning) — <a href="https://github.com/OpenSQZ/MiniCPM-V-CookBook/blob/main/finetune/swift_minicpmv46.md">Full Guide</a></summary>
454
+
455
+ ```bash
456
+ swift sft --model_type minicpm-v-4_6 --dataset <your-dataset>
457
+ ```
458
+
459
+ </details>
460
+
461
+ ## License
462
+
463
+ #### Model License
464
+ * The MiniCPM-o/V model weights and code are open-sourced under the [Apache-2.0](https://github.com/OpenBMB/MiniCPM-V/blob/main/LICENSE) license.
465
+
466
+ #### Statement
467
+ * As MLLMs, MiniCPM-o/V models generate content by learning a large number of multimodal corpora, but they cannot comprehend, express personal opinions, or make value judgements. Anything generated by MiniCPM-o/V models does not represent the views and positions of the model developers
468
+ * We will not be liable for any problems arising from the use of MiniCPM-o/V models, including but not limited to data security issues, risk of public opinion, or any risks and problems arising from the misdirection, misuse, dissemination, or misuse of the model.
469
+
470
+
471
+ ## Technical Reports and Key Techniques Papers
472
+
473
+ 👏 Welcome to explore key techniques of MiniCPM-o/V and other multimodal projects of our team:
474
+
475
+ **Technical Reports:** [MiniCPM-o 4.5](https://huggingface.co/papers/2604.27393) | [MiniCPM-V 4.5](https://arxiv.org/abs/2509.18154) | [MiniCPM-o 2.6](https://openbmb.notion.site/MiniCPM-o-2-6-A-GPT-4o-Level-MLLM-for-Vision-Speech-and-Multimodal-Live-Streaming-on-Your-Phone-185ede1b7a558042b5d5e45e6b237da9) | [MiniCPM-Llama3-V 2.5](https://arxiv.org/abs/2408.01800) | [MiniCPM-V 2.0](https://openbmb.vercel.app/minicpm-v-2)
476
+
477
+ **Other Multimodal Projects:** [VisCPM](https://github.com/OpenBMB/VisCPM/tree/main) | [RLPR](https://github.com/OpenBMB/RLPR) | [RLHF-V](https://github.com/RLHF-V/RLHF-V) | [LLaVA-UHD](https://github.com/thunlp/LLaVA-UHD) | [RLAIF-V](https://github.com/RLHF-V/RLAIF-V) | [LLaVA-UHD-v4](https://arxiv.org/abs/2605.08985 )
478
+
479
+
480
+ ## Citation <!-- omit in toc -->
481
+
482
+ If you find our model/code/paper helpful, please consider citing our papers 📝 and staring us ⭐️!
483
+
484
+ ```bib
485
+ @proceedings{yu2025minicpmv45cookingefficient,
486
+ title={MiniCPM-V 4.5: Cooking Efficient MLLMs via Architecture, Data, and Training Recipe},
487
+ author={Tianyu Yu and Zefan Wang and Chongyi Wang and Fuwei Huang and Wenshuo Ma and Zhihui He and Tianchi Cai and Weize Chen and Yuxiang Huang and Yuanqian Zhao and others},
488
+ year={2025},
489
+ url={https://arxiv.org/abs/2509.18154},
490
+ }
491
+
492
+ @article{yao2024minicpm,
493
+ title={MiniCPM-V: A GPT-4V Level MLLM on Your Phone},
494
+ author={Yao, Yuan and Yu, Tianyu and Zhang, Ao and Wang, Chongyi and Cui, Junbo and Zhu, Hongji and Cai, Tianchi and Li, Haoyu and Zhao, Weilin and He, Zhihui and others},
495
+ journal={arXiv preprint arXiv:2408.01800},
496
+ year={2024}
497
+ }
498
+ ```
python/minicpm_v46_tokenizer/chat_template.jinja ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- if enable_thinking is not defined -%}
2
+ {%- set enable_thinking = false -%}
3
+ {%- endif -%}
4
+ {%- macro render_content(content, is_system_content=false) -%}
5
+ {%- if content is string -%}
6
+ {{- content -}}
7
+ {%- elif content is iterable and content is not mapping -%}
8
+ {%- set ns = namespace(parts=[]) -%}
9
+ {%- for item in content -%}
10
+ {%- if 'image' in item or 'image_url' in item or item.type == 'image' -%}
11
+ {%- if is_system_content -%}
12
+ {{- raise_exception('System message cannot contain images.') -}}
13
+ {%- endif -%}
14
+ {%- set ns.parts = ns.parts + ['<|image_pad|>'] -%}
15
+ {%- elif 'video' in item or item.type == 'video' -%}
16
+ {%- if is_system_content -%}
17
+ {{- raise_exception('System message cannot contain videos.') -}}
18
+ {%- endif -%}
19
+ {%- set ns.parts = ns.parts + ['<|video_pad|>'] -%}
20
+ {%- elif 'text' in item -%}
21
+ {%- set ns.parts = ns.parts + [item.text] -%}
22
+ {%- else -%}
23
+ {{- raise_exception('Unexpected item type in content.') -}}
24
+ {%- endif -%}
25
+ {%- endfor -%}
26
+ {{- ns.parts | join('\n') -}}
27
+ {%- elif content is none or content is undefined -%}
28
+ {{- '' -}}
29
+ {%- else -%}
30
+ {{- raise_exception('Unexpected content type.') -}}
31
+ {%- endif -%}
32
+ {%- endmacro -%}
33
+ {%- if not messages %}
34
+ {{- raise_exception('No messages provided.') }}
35
+ {%- endif %}
36
+ {%- if tools and tools is iterable and tools is not mapping %}
37
+ {{- '<|im_start|>system\n' }}
38
+ {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
39
+ {%- for tool in tools %}
40
+ {{- "\n" }}
41
+ {{- tool | tojson }}
42
+ {%- endfor %}
43
+ {{- "\n</tools>" }}
44
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
45
+ {%- if messages[0].role == 'system' %}
46
+ {%- set content = render_content(messages[0].content, true)|trim %}
47
+ {%- if content %}
48
+ {{- '\n\n' + content }}
49
+ {%- endif %}
50
+ {%- endif %}
51
+ {{- '<|im_end|>\n' }}
52
+ {%- else %}
53
+ {%- if messages[0].role == 'system' %}
54
+ {%- set content = render_content(messages[0].content, true)|trim %}
55
+ {{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
56
+ {%- endif %}
57
+ {%- endif %}
58
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
59
+ {%- for message in messages[::-1] %}
60
+ {%- set index = (messages|length - 1) - loop.index0 %}
61
+ {%- if ns.multi_step_tool and message.role == "user" %}
62
+ {%- set content = render_content(message.content)|trim %}
63
+ {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
64
+ {%- set ns.multi_step_tool = false %}
65
+ {%- set ns.last_query_index = index %}
66
+ {%- endif %}
67
+ {%- endif %}
68
+ {%- endfor %}
69
+ {%- if ns.multi_step_tool %}
70
+ {{- raise_exception('No user query found in messages.') }}
71
+ {%- endif %}
72
+ {%- for message in messages %}
73
+ {%- set content = render_content(message.content)|trim %}
74
+ {%- if message.role == "system" %}
75
+ {%- if not loop.first %}
76
+ {{- raise_exception('System message must be at the beginning.') }}
77
+ {%- endif %}
78
+ {%- elif message.role == "user" %}
79
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
80
+ {%- elif message.role == "assistant" %}
81
+ {%- set reasoning_content = '' %}
82
+ {%- if message.reasoning_content is string %}
83
+ {%- set reasoning_content = message.reasoning_content %}
84
+ {%- else %}
85
+ {%- if '</think>' in content %}
86
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
87
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
88
+ {%- endif %}
89
+ {%- endif %}
90
+ {%- set reasoning_content = reasoning_content|trim %}
91
+ {%- if loop.index0 > ns.last_query_index %}
92
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
93
+ {%- else %}
94
+ {{- '<|im_start|>' + message.role + '\n' + content }}
95
+ {%- endif %}
96
+ {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
97
+ {%- for tool_call in message.tool_calls %}
98
+ {%- if tool_call.function is defined %}
99
+ {%- set tool_call = tool_call.function %}
100
+ {%- endif %}
101
+ {%- if loop.first %}
102
+ {%- if content|trim %}
103
+ {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
104
+ {%- else %}
105
+ {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
106
+ {%- endif %}
107
+ {%- else %}
108
+ {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
109
+ {%- endif %}
110
+ {%- if tool_call.arguments is defined %}
111
+ {%- for args_name, args_value in tool_call.arguments|items %}
112
+ {{- '<parameter=' + args_name + '>\n' }}
113
+ {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
114
+ {{- args_value }}
115
+ {{- '\n</parameter>\n' }}
116
+ {%- endfor %}
117
+ {%- endif %}
118
+ {{- '</function>\n</tool_call>' }}
119
+ {%- endfor %}
120
+ {%- endif %}
121
+ {{- '<|im_end|>\n' }}
122
+ {%- elif message.role == "tool" %}
123
+ {%- if loop.previtem and loop.previtem.role != "tool" %}
124
+ {{- '<|im_start|>user' }}
125
+ {%- endif %}
126
+ {{- '\n<tool_response>\n' }}
127
+ {{- content }}
128
+ {{- '\n</tool_response>' }}
129
+ {%- if not loop.last and loop.nextitem.role != "tool" %}
130
+ {{- '<|im_end|>\n' }}
131
+ {%- elif loop.last %}
132
+ {{- '<|im_end|>\n' }}
133
+ {%- endif %}
134
+ {%- else %}
135
+ {{- raise_exception('Unexpected message role.') }}
136
+ {%- endif %}
137
+ {%- endfor %}
138
+ {%- if add_generation_prompt %}
139
+ {{- '<|im_start|>assistant\n' }}
140
+ {%- if enable_thinking is defined and enable_thinking is false %}
141
+ {{- '<think>\n\n</think>\n\n' }}
142
+ {%- else %}
143
+ {{- '<think>\n' }}
144
+ {%- endif %}
145
+ {%- endif %}
python/minicpm_v46_tokenizer/config.json ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "MiniCPMV4_6ForConditionalGeneration"
4
+ ],
5
+ "bos_token_id": null,
6
+ "drop_vision_last_layer": false,
7
+ "eos_token_id": 248044,
8
+ "image_size": 1120,
9
+ "model_type": "minicpmv4_6",
10
+ "pad_token_id": null,
11
+ "tie_word_embeddings": true,
12
+ "transformers_version": "5.7.0",
13
+ "use_cache": true,
14
+ "vision_config": {
15
+ "attention_dropout": 0.0,
16
+ "hidden_act": "gelu_pytorch_tanh",
17
+ "hidden_size": 1152,
18
+ "image_size": 980,
19
+ "intermediate_size": 4304,
20
+ "layer_norm_eps": 1e-06,
21
+ "model_type": "minicpmv4_6_vision",
22
+ "num_attention_heads": 16,
23
+ "num_channels": 3,
24
+ "num_hidden_layers": 27,
25
+ "patch_size": 14
26
+ },
27
+ "text_config": {
28
+ "attention_bias": false,
29
+ "attention_dropout": 0.0,
30
+ "attn_output_gate": true,
31
+ "full_attention_interval": 4,
32
+ "head_dim": 256,
33
+ "hidden_act": "silu",
34
+ "hidden_size": 1024,
35
+ "initializer_range": 0.02,
36
+ "intermediate_size": 3584,
37
+ "layer_types": [
38
+ "linear_attention",
39
+ "linear_attention",
40
+ "linear_attention",
41
+ "full_attention",
42
+ "linear_attention",
43
+ "linear_attention",
44
+ "linear_attention",
45
+ "full_attention",
46
+ "linear_attention",
47
+ "linear_attention",
48
+ "linear_attention",
49
+ "full_attention",
50
+ "linear_attention",
51
+ "linear_attention",
52
+ "linear_attention",
53
+ "full_attention",
54
+ "linear_attention",
55
+ "linear_attention",
56
+ "linear_attention",
57
+ "full_attention",
58
+ "linear_attention",
59
+ "linear_attention",
60
+ "linear_attention",
61
+ "full_attention"
62
+ ],
63
+ "linear_conv_kernel_dim": 4,
64
+ "linear_key_head_dim": 128,
65
+ "linear_num_key_heads": 16,
66
+ "linear_num_value_heads": 16,
67
+ "linear_value_head_dim": 128,
68
+ "mamba_ssm_dtype": "float32",
69
+ "max_position_embeddings": 262144,
70
+ "mlp_only_layers": [],
71
+ "mtp_num_hidden_layers": 1,
72
+ "mtp_use_dedicated_embeddings": false,
73
+ "num_attention_heads": 8,
74
+ "num_hidden_layers": 24,
75
+ "num_key_value_heads": 2,
76
+ "partial_rotary_factor": 0.25,
77
+ "rms_norm_eps": 1e-06,
78
+ "rope_parameters": {
79
+ "partial_rotary_factor": 0.25,
80
+ "rope_theta": 10000000,
81
+ "rope_type": "default"
82
+ },
83
+ "vocab_size": 248094,
84
+ "model_type": "qwen3_5_text",
85
+ "tie_word_embeddings": true
86
+ },
87
+ "insert_layer_id": 6,
88
+ "image_token_id": 248056,
89
+ "video_token_id": 248057
90
+ }
python/minicpm_v46_tokenizer/generation_config.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 248045,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 248044,
6
+ 248046
7
+ ],
8
+ "temperature": 0.7,
9
+ "top_k": 0,
10
+ "top_p": 1.0,
11
+ "repetition_penalty": 1.0,
12
+ "transformers_version": "5.7.0"
13
+ }
python/minicpm_v46_tokenizer/preprocessor_config.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "image_processor_type": "MiniCPMV4_6ImageProcessor",
3
+ "processor_class": "MiniCPMV4_6Processor",
4
+ "max_slice_nums": 9,
5
+ "scale_resolution": 448,
6
+ "patch_size": 14,
7
+ "use_image_id": true,
8
+ "slice_mode": true,
9
+ "image_mean": [
10
+ 0.5,
11
+ 0.5,
12
+ 0.5
13
+ ],
14
+ "image_std": [
15
+ 0.5,
16
+ 0.5,
17
+ 0.5
18
+ ]
19
+ }
python/minicpm_v46_tokenizer/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:33861e37bb955af1e3f3061182b820f347eba2b9c2c1011c82794bf0d6e77b54
3
+ size 19992481
python/minicpm_v46_tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "audio_bos_token": "<|audio_start|>",
4
+ "audio_eos_token": "<|audio_end|>",
5
+ "audio_token": "<|audio_pad|>",
6
+ "backend": "tokenizers",
7
+ "bos_token": "<|im_start|>",
8
+ "clean_up_tokenization_spaces": false,
9
+ "eos_token": "<|im_end|>",
10
+ "errors": "replace",
11
+ "extra_special_tokens": {
12
+ "image_token": "<|image_pad|>",
13
+ "video_token": "<|video_pad|>",
14
+ "image_start_token": "<image>",
15
+ "image_end_token": "</image>",
16
+ "slice_start_token": "<slice>",
17
+ "slice_end_token": "</slice>",
18
+ "image_id_start_token": "<image_id>",
19
+ "image_id_end_token": "</image_id>"
20
+ },
21
+ "image_token": "<|image_pad|>",
22
+ "is_local": true,
23
+ "model_max_length": 262144,
24
+ "model_specific_special_tokens": {
25
+ "audio_bos_token": "<|audio_start|>",
26
+ "audio_eos_token": "<|audio_end|>",
27
+ "audio_token": "<|audio_pad|>",
28
+ "image_token": "<|image_pad|>",
29
+ "video_token": "<|video_pad|>",
30
+ "vision_bos_token": "<|vision_start|>",
31
+ "vision_eos_token": "<|vision_end|>"
32
+ },
33
+ "pad_token": "<|endoftext|>",
34
+ "pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
35
+ "split_special_tokens": false,
36
+ "unk_token": "<unk>",
37
+ "video_token": "<|video_pad|>",
38
+ "vision_bos_token": "<|vision_start|>",
39
+ "vision_eos_token": "<|vision_end|>",
40
+ "chat_template": "{%- if enable_thinking is not defined -%}\n {%- set enable_thinking = false -%}\n{%- endif -%}\n{%- macro render_content(content, is_system_content=false) -%}\n {%- if content is string -%}\n {{- content -}}\n {%- elif content is iterable and content is not mapping -%}\n {%- set ns = namespace(parts=[]) -%}\n {%- for item in content -%}\n {%- if 'image' in item or 'image_url' in item or item.type == 'image' -%}\n {%- if is_system_content -%}\n {{- raise_exception('System message cannot contain images.') -}}\n {%- endif -%}\n {%- set ns.parts = ns.parts + ['<|image_pad|>'] -%}\n {%- elif 'video' in item or item.type == 'video' -%}\n {%- if is_system_content -%}\n {{- raise_exception('System message cannot contain videos.') -}}\n {%- endif -%}\n {%- set ns.parts = ns.parts + ['<|video_pad|>'] -%}\n {%- elif 'text' in item -%}\n {%- set ns.parts = ns.parts + [item.text] -%}\n {%- else -%}\n {{- raise_exception('Unexpected item type in content.') -}}\n {%- endif -%}\n {%- endfor -%}\n {{- ns.parts | join('\\n') -}}\n {%- elif content is none or content is undefined -%}\n {{- '' -}}\n {%- else -%}\n {{- raise_exception('Unexpected content type.') -}}\n {%- endif -%}\n{%- endmacro -%}\n{%- if not messages %}\n {{- raise_exception('No messages provided.') }}\n{%- endif %}\n{%- if tools and tools is iterable and tools is not mapping %}\n {{- '<|im_start|>system\\n' }}\n {{- \"# Tools\\n\\nYou have access to the following functions:\\n\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\" }}\n {{- '\\n\\nIf you choose to call a function ONLY reply in the following format with NO suffix:\\n\\n<tool_call>\\n<function=example_function_name>\\n<parameter=example_parameter_1>\\nvalue_1\\n</parameter>\\n<parameter=example_parameter_2>\\nThis is the value for the second parameter\\nthat can span\\nmultiple lines\\n</parameter>\\n</function>\\n</tool_call>\\n\\n<IMPORTANT>\\nReminder:\\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\\n- Required parameters MUST be specified\\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\\n</IMPORTANT>' }}\n {%- if messages[0].role == 'system' %}\n {%- set content = render_content(messages[0].content, true)|trim %}\n {%- if content %}\n {{- '\\n\\n' + content }}\n {%- endif %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {%- set content = render_content(messages[0].content, true)|trim %}\n {{- '<|im_start|>system\\n' + content + '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}\n{%- for message in messages[::-1] %}\n {%- set index = (messages|length - 1) - loop.index0 %}\n {%- if ns.multi_step_tool and message.role == \"user\" %}\n {%- set content = render_content(message.content)|trim %}\n {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}\n {%- set ns.multi_step_tool = false %}\n {%- set ns.last_query_index = index %}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if ns.multi_step_tool %}\n {{- raise_exception('No user query found in messages.') }}\n{%- endif %}\n{%- for message in messages %}\n {%- set content = render_content(message.content)|trim %}\n {%- if message.role == \"system\" %}\n {%- if not loop.first %}\n {{- raise_exception('System message must be at the beginning.') }}\n {%- endif %}\n {%- elif message.role == \"user\" %}\n {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {%- set reasoning_content = '' %}\n {%- if message.reasoning_content is string %}\n {%- set reasoning_content = message.reasoning_content %}\n {%- else %}\n {%- if '</think>' in content %}\n {%- set reasoning_content = content.split('</think>')[0].rstrip('\\n').split('<think>')[-1].lstrip('\\n') %}\n {%- set content = content.split('</think>')[-1].lstrip('\\n') %}\n {%- endif %}\n {%- endif %}\n {%- set reasoning_content = reasoning_content|trim %}\n {%- if loop.index0 > ns.last_query_index %}\n {{- '<|im_start|>' + message.role + '\\n<think>\\n' + reasoning_content + '\\n</think>\\n\\n' + content }}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {%- if loop.first %}\n {%- if content|trim %}\n {{- '\\n\\n<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- else %}\n {{- '<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- endif %}\n {%- else %}\n {{- '\\n<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- endif %}\n {%- if tool_call.arguments is defined %}\n {%- for args_name, args_value in tool_call.arguments|items %}\n {{- '<parameter=' + args_name + '>\\n' }}\n {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}\n {{- args_value }}\n {{- '\\n</parameter>\\n' }}\n {%- endfor %}\n {%- endif %}\n {{- '</function>\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.previtem and loop.previtem.role != \"tool\" %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- content }}\n {{- '\\n</tool_response>' }}\n {%- if not loop.last and loop.nextitem.role != \"tool\" %}\n {{- '<|im_end|>\\n' }}\n {%- elif loop.last %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- else %}\n {{- raise_exception('Unexpected message role.') }}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n {%- if enable_thinking is defined and enable_thinking is false %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- else %}\n {{- '<think>\\n' }}\n {%- endif %}\n{%- endif %}\n",
41
+ "tokenizer_class": "Qwen2Tokenizer"
42
+ }
qwen3_5_text_p128_l0_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3a9ecabeb93b62bf2e26a305c148bcc3a7a2d9fe39ba2ab05e091e284081b64b
3
+ size 27682449
qwen3_5_text_p128_l10_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e6402aa3504b0b4778042e2e23b56765f326666a18598485dd40931e026516eb
3
+ size 27682081
qwen3_5_text_p128_l11_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4a0609ea0ed4a14e3873cd8d6db4b8efa370b31a6beb3e0ca9e21be54334e14d
3
+ size 24273573
qwen3_5_text_p128_l12_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:855333dd407cbaec53bd34cdfccaaf369e9c881c4e8912e5216f5ca2ccb4156c
3
+ size 27682081
qwen3_5_text_p128_l13_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:800b592829ebfdd4e797a568217a07afbd0501ce65e159561db908996572cfc3
3
+ size 27682177
qwen3_5_text_p128_l14_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:feacb5c876ff67054bb9a50233956241cd7cedc035b5ce7ba059a033441c6a61
3
+ size 27682081
qwen3_5_text_p128_l15_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0decfb1327156025b6e98a09b03ee353a2ba960db73ba00274e3b500ce486023
3
+ size 24273573
qwen3_5_text_p128_l16_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:484a2738f1b30aeaeeb65f1534cfc8ed8ac8f282e8bcbbd0744f1b2cd0895f23
3
+ size 27682081
qwen3_5_text_p128_l17_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e1191dd1abceb45c2eb6a5557c681bb3c28b4bfe3f03fe26c4a7931a3e63b3fe
3
+ size 27682081
qwen3_5_text_p128_l18_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3686669d283b26a766f976654614a79b0a2f6ce3861397eb7eff9640d8cbd7c2
3
+ size 27682081
qwen3_5_text_p128_l19_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8bfecc5dacdf943bd5c41d8344f60c85301702afeddfc05dc69595544334dce3
3
+ size 24273573
qwen3_5_text_p128_l1_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c318f65070ec6456f256640f424327d557407e4fa89b6e46f8ec79077bc5cf67
3
+ size 27682353
qwen3_5_text_p128_l20_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2c3974b9cbacc63d02474dc6bebef7cec276a427f38c6db47ffa8a3ca905ea69
3
+ size 27682081
qwen3_5_text_p128_l21_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8c828b6e0243e02f7c564875ccdacbfee6275272aef8f5d2a48ac51648b51104
3
+ size 27682081
qwen3_5_text_p128_l22_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8bb656a1c19f4e47cc6f757b8afdc976d94f7716fb636468bcd01b36ad43971a
3
+ size 27682081
qwen3_5_text_p128_l23_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cdcfa32c4b8c3c6a52141814eff13c8497a064c0cfdb38811433ad8905b63495
3
+ size 24273573
qwen3_5_text_p128_l2_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:553dc8a2c4825eed7c435b393853c4757e0fdbda198be70622eaf0a002ca5d3a
3
+ size 27682065
qwen3_5_text_p128_l3_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ccfbe80d6db2ffd8611650ee29a7c582da9bc92a7f6423b0d1bccb432dfb3462
3
+ size 24273493
qwen3_5_text_p128_l4_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b59af8b13cc09897634f780511a2bf5a3125190339e4ebf5325158ee892e05ef
3
+ size 27682065
qwen3_5_text_p128_l5_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:07b87fef59116bb884f56f7f7f624662535961a22c4038b86a82c309dc53f0f5
3
+ size 27682161
qwen3_5_text_p128_l6_together.axmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2cc549f5c19ff7b25184ec0bf2d97015a5340f191af15ec95c1ec51cf37574fe
3
+ size 27682193