Add MiniCPM5-2B cookbook (#38295)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
zijiexia
2026-09-07 21:30:17 +08:00
committed by GitHub
co-authored by Claude Opus 5
parent 62a4a6ea0e
commit e4008de757
6 changed files with 511 additions and 2 deletions
@@ -2,7 +2,6 @@
title: MiniCPM-V 4.6
metatags:
description: "Deploy OpenBMB MiniCPM-V 4.6 (Qwen3.5-style hybrid GDN backbone + NaViT vision encoder) on NVIDIA GPUs with SGLang — multimodal text + image + video, slicing for high-resolution images."
tag: NEW
---
## 1. Model Introduction
@@ -0,0 +1,220 @@
---
title: MiniCPM5-2B
description: "Deploy OpenBMB MiniCPM5-2B with SGLang — a 2.5B dense Llama-architecture on-device model with 131K context, thinking mode, XML tool calling and DSpark speculative decoding on H200, RTX PRO 6000, RTX 5090 and DGX Spark."
tag: NEW
---
## Deployment
<a id="install" />
<Accordion title="Install SGLang">
For all methods and hardware platforms, see the [official SGLang installation guide](../../../docs/get-started/install). The two paths below match the **Python / Docker** toggle in the command panel.
<Tabs>
<Tab title="Python (pip / uv)">
```bash Command
pip install --upgrade pip
pip install uv
uv pip install --prerelease=allow "sglang>=0.5.12"
```
Then run the **Python** output of the command panel below in that environment.
</Tab>
<Tab title="Docker">
```bash Command
docker pull lmsysorg/sglang:dev
```
For how to launch the image, see [Install → Method 3: Using Docker](../../../docs/get-started/install#method-3-using-docker). Substitute the inner `sglang serve ...` with what the command generator below produces.
</Tab>
</Tabs>
</Accordion>
Pick your card to generate the launch command. MiniCPM5-2B is a 2.5B dense model and runs single-GPU at TP=1 on every supported card, so the page ships one operating point per card plus an optional **DSpark** speculative-decoding row.
import { Deployment } from "/src/snippets/_deployment.jsx";
import { config } from "/src/snippets/configs/openbmb/minicpm5-2b.jsx";
import { benchmarks } from "/src/snippets/configs/openbmb/minicpm5-2b-benchmarks.jsx";
<Deployment config={config} benchmarks={benchmarks} />
<Note>
Speed numbers exist for the RTX 5090 and DGX Spark cells — a single card at
random 1024/1024, recorded as Mean. The H200 and RTX PRO 6000 cells are
pending measurement, and no accuracy numbers have been taken on any platform
yet. The DSpark overlay carries no speed numbers on any card.
</Note>
## Playground
The Playground is where you experiment with **SGLang features beyond the recipes above**. The Deploy panel emits this model's documented launch recipes; the Playground lets you turn on additional knobs on top of whichever cell the Deploy panel is currently showing.
import { Playground } from "/src/snippets/_playground.jsx";
<Playground config={config} />
## 1. Model Introduction
**MiniCPM5-2B** is the second model in OpenBMB's MiniCPM5 series, following MiniCPM5-1B. It is a dense 2B-class Transformer built for on-device assistants, local deployment, coding agents and tool-use workflows — scenarios where a compact model with a small deployment footprint is preferred. OpenBMB reports 2B-class open-source SOTA within its comparison set (average 53.9), with its clearest advantages in code reasoning, math reasoning, long-context understanding, tool use and agentic tasks.
The checkpoint uses the standard `LlamaForCausalLM` architecture — no custom kernels and no model-code fork — so SGLang loads it through the stock Llama path. It is 2,516,756,480 parameters (1,981,982,720 non-embedding) over 42 layers with GQA (16 query heads, 2 KV heads), and a native context length of 131,072 tokens. Post-training runs SFT → RL → On-Policy Distillation, which merges 16 RL expert models into the single released checkpoint. Weights are released under the [Apache-2.0](https://github.com/OpenBMB/MiniCPM/blob/main/LICENSE) license.
<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
<colgroup>
<col style={{width: "34%"}} />
<col style={{width: "22%"}} />
<col style={{width: "44%"}} />
</colgroup>
<thead>
<tr style={{borderBottom: "2px solid #d55816"}}>
<th style={{textAlign: "left", padding: "10px 12px", fontWeight: 700}}>Checkpoint</th>
<th style={{textAlign: "left", padding: "10px 12px", fontWeight: 700}}>Precision</th>
<th style={{textAlign: "left", padding: "10px 12px", fontWeight: 700}}>Role on this page</th>
</tr>
</thead>
<tbody>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}><a href="https://huggingface.co/openbmb/MiniCPM5-2B">openbmb/MiniCPM5-2B</a></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}>BF16</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}>The served model in every cell above (final release, post-trained with RL + OPD).</td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.05)"}}><a href="https://huggingface.co/openbmb/MiniCPM5-2B-DSpark">openbmb/MiniCPM5-2B-DSpark</a></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>BF16 draft</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Draft model loaded by the <strong>DSPARK</strong> row of the Deploy panel; not served on its own.</td>
</tr>
</tbody>
</table>
OpenBMB also publishes SFT-only, mid-training and base checkpoints, plus GGUF, MLX and GPTQ-Int4 exports for llama.cpp / Ollama / LM Studio / Apple Silicon. Those target other runtimes and are not part of the SGLang matrix above.
**Recommended generation:** `temperature=1.0`, `top_p=0.95` (informational — SGLang reads the checkpoint's `generation_config.json`; do not hardcode these in client code).
**Resources:** [HuggingFace](https://huggingface.co/openbmb/MiniCPM5-2B) · [ModelScope](https://www.modelscope.cn/models/OpenBMB/MiniCPM5-2B) · [GitHub](https://github.com/OpenBMB/MiniCPM) · [Tech report](https://arxiv.org/pdf/2506.07900).
## 2. Advanced Usage
### 2.1 Thinking Mode
Thinking is controlled by the chat template's `enable_thinking` flag, passed per request through `chat_template_kwargs`. The `qwen3` reasoning parser — already in every generated command, and toggleable from the **Parsers** card in the [Playground above](#playground) — splits the `<think>` segment into `message.reasoning_content` and leaves the final answer in `message.content`. Drop the flag and the thinking text stays inline in `content`, closing tag and all.
<Accordion title="Thinking On / Off (Python)">
```python Example
from openai import OpenAI
client = OpenAI(base_url="http://localhost:30000/v1", api_key="EMPTY")
# Thinking on (chat-template default).
resp = client.chat.completions.create(
model="openbmb/MiniCPM5-2B",
messages=[{"role": "user", "content": "What is 15% of 240?"}],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
)
msg = resp.choices[0].message
print("Reasoning:", getattr(msg, "reasoning_content", None))
print("Answer:", msg.content)
# Thinking off — a direct answer, no deliberation segment.
resp = client.chat.completions.create(
model="openbmb/MiniCPM5-2B",
messages=[{"role": "user", "content": "What is 15% of 240?"}],
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)
print("Without thinking:", resp.choices[0].message.content)
```
</Accordion>
<Accordion title="Example Output">
```text Output
Reasoning: We are asked: "What is 15% of 240?" This is a simple percentage calculation. To find 15% of 240, we multiply 240 by 15% (which is 0.15). So: 240 × 0.15 = 36. Alternatively, we can think of it as (15/100) × 240 = (15 × 240)/100 = 3600/100 = 36. So the answer is 36.
We need to respond in a helpful way. The user might be testing or seeking quick answer. Let's provide a clear response with explanation if needed, but since it's straightforward, we can just give the answer directly or briefly explain.
Answer:
15% of 240 is **36**.
To calculate:
\( 240 \times 0.15 = 36 \)
Reasoning: None
Answer: To find 15% of 240, multiply 240 by 15% (which is 0.15):
\[
240 \times 0.15 = 36
\]
So, 15% of 240 is **36**.
```
</Accordion>
### 2.2 Tool Calling
MiniCPM5-2B emits XML-style tool calls (`<function name="..."><param name="...">...</param></function>`), and SGLang's built-in `minicpm5` detector converts them to OpenAI-compatible `tool_calls`, parallel calls included. The parser is already in every command the Deploy panel generates; the **Tool Call Parser** chip in the Playground's **Parsers** card is an opt-out, not an opt-in.
<Accordion title="Tool Calling (Python)">
```python Example
from openai import OpenAI
client = OpenAI(base_url="http://localhost:30000/v1", api_key="EMPTY")
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"date": {"type": "string", "description": "YYYY-MM-DD"},
},
"required": ["city"],
},
},
}]
resp = client.chat.completions.create(
model="openbmb/MiniCPM5-2B",
messages=[{"role": "user", "content": "What is the weather in Beijing today?"}],
tools=tools,
)
msg = resp.choices[0].message
# With the qwen3 reasoning parser on, the thinking segment lands in
# `reasoning_content` and `content` may be empty on a tool-call turn.
print("Reasoning:", getattr(msg, "reasoning_content", None))
print("Content:", msg.content)
for call in (msg.tool_calls or []):
print("Tool:", call.function.name, call.function.arguments)
```
</Accordion>
<Accordion title="Example Output">
```text Output
Reasoning:
The user is asking about the weather in Beijing today. I have access to a get_weather function that can help with this. Let me call it to get the current weather for Beijing.
Content:
Tool: get_weather {"city": "Beijing"}
```
</Accordion>
+1 -1
View File
@@ -136,7 +136,7 @@ metatags:
<Card
title="OpenBMB"
mode="card"
href="/cookbook/autoregressive/OpenBMB/MiniCPM-V-4_6"
href="/cookbook/autoregressive/OpenBMB/MiniCPM5-2B"
img="/cards/logos/openbmb.png"
/>
<Card
+1
View File
@@ -1404,6 +1404,7 @@
{
"group": "OpenBMB",
"pages": [
"cookbook/autoregressive/OpenBMB/MiniCPM5-2B",
"cookbook/autoregressive/OpenBMB/MiniCPM-V-4_6"
]
},
@@ -0,0 +1,55 @@
// One entry per cell `match` tuple (same keys as the config cells).
//
// Only the RTX 5090 row carries numbers. They were measured on a single
// RTX 5090 32GB (`zijiexia-5090-1gpu`, `lmsysorg/sglang:dev` @ 30705c004c) with
// `python3 -m sglang.bench_serving`, random 1024/1024, against the exact cell
// command on this page — including `--mem-fraction-static 0.75
// --cuda-graph-max-bs 128`, which is what keeps concurrency 64 and 128
// graph-backed (see the model page's Configuration Tips).
//
// `tokens_per_sec_per_gpu` is total (input+output) per GPU = the measured
// output throughput x 2 at 1024/1024 on one GPU.
//
// Two operating points only: concurrency 1 (single-user latency) and 128 (the
// saturated end of the sweep). The intermediate 8 / 32 / 64 rows were measured
// but are not published here.
//
// The source runs did not restate a percentile, so the values are recorded as
// Mean (bench_serving's headline lines) rather than claimed as P50 — same
// convention as the LFM2.5 page. TTFT was not captured at concurrency 128, so
// that cell carries `null` rather than a back-filled guess.
//
// No accuracy rows: the only GSM8K numbers taken so far came from a custom
// chat-template harness, not the `sgl-eval run gsm8k` command the Reproduce
// modal would show, and the two are not comparable.
export const benchmarks = [
{
match: { hw: "rtx5090", variant: "default", quant: "bf16", nodes: "single" },
sglang_version: "dev @ 30705c004c",
latencyPercentile: "Mean",
speed: [
{ workload: { dataset: "random", isl: 1024, osl: 1024, max_concurrency: 1 },
ttft_ms: 34, tpot_ms: 4.0, tokens_per_sec_per_gpu: 496 },
{ workload: { dataset: "random", isl: 1024, osl: 1024, max_concurrency: 128 },
ttft_ms: null, tpot_ms: 11.8, tokens_per_sec_per_gpu: 19280 },
],
},
{
// Single DGX Spark (GB10), same image and commit as the RTX 5090 row.
// TTFT is Mean; at concurrency 64 the Median is 246 ms, because the first
// wave of requests all queue behind one prefill.
match: { hw: "dgx-spark", variant: "default", quant: "bf16", nodes: "single" },
sglang_version: "dev @ 30705c004c",
latencyPercentile: "Mean",
speed: [
{ workload: { dataset: "random", isl: 1024, osl: 1024, max_concurrency: 1 },
ttft_ms: 85, tpot_ms: 27.7, tokens_per_sec_per_gpu: 72 },
{ workload: { dataset: "random", isl: 1024, osl: 1024, max_concurrency: 64 },
ttft_ms: 1373, tpot_ms: 42.9, tokens_per_sec_per_gpu: 2892 },
],
},
// Pending — no numbers taken on these platforms yet.
{ match: { hw: "h200", variant: "default", quant: "bf16", nodes: "single" } },
{ match: { hw: "rtx6000", variant: "default", quant: "bf16", nodes: "single" } },
];
@@ -0,0 +1,234 @@
// Single `export const config` literal — no spreads/calls/IIFE (Mintlify re-evals at hydration).
// Cells are denormalized: no `--nnodes`/`--node-rank`/`--dist-init-addr`/`--host`/`--port` literals — engine injects them.
//
// MiniCPM5-2B: 2.5B dense `LlamaForCausalLM` (42 layers, GQA 16Q/2KV, 131072
// context). Standard architecture — no custom kernels and no model-code fork —
// so every supported card runs it single-GPU at TP=1 with the stock backend.
// That leaves one recipe per card, hence a single variant / quantization /
// strategy / node option and no parallelism flags in any cell.
//
// Recipes are the OpenBMB model card's SGLang commands, rewritten from
// `python -m sglang.launch_server` to `sglang serve`, plus the parser pair the
// model needs to be usable through the OpenAI API. Both are baked into every
// cell, so the Parsers card in the Playground reads as an opt-OUT:
// --tool-call-parser minicpm5 the model emits XML-style
// `<function name="f"><param name="p">v</param></function>`; without the
// detector `tool_calls` comes back None and the XML lands in `content`.
// --reasoning-parser qwen3 the chat template is Qwen-style
// (`<|im_start|>` + `<think>`) and there is no `minicpm5` reasoning
// detector, so `qwen3` is the one that applies; without it `</think>`
// leaks into `content`.
//
// DSpark is the separately published draft checkpoint
// (openbmb/MiniCPM5-2B-DSpark). It is orthogonal to the card grid, so it is an
// overlay row rather than a match dim. No DSpark speed numbers are published:
// the speedup tracks acceptance length, which moves with the prompt
// distribution, and a random-token dataset inflates it above real traffic.
export const config = {
modelName: "MiniCPM5-2B",
supportedHardware: ["h200", "rtx6000", "rtx5090", "dgx-spark"],
// RTX PRO 6000 and RTX 5090 (SM120 / Blackwell workstation + desktop) are not
// datacenter parts, so the shared HARDWARE_CATALOG in _deployment.jsx does not
// carry them. Ids/labels match the DeepSeek-V4 and Qwen3.8-27B configs.
hardware: [
{ id: "rtx6000", label: "RTX PRO 6000", vram: "96GB", vendor: "blackwell" },
{ id: "rtx5090", label: "RTX 5090", vram: "32GB", vendor: "blackwell" },
],
matchDims: [
{ id: "variant", title: "Model Variant", options: [
{ id: "default", label: "Default" },
] },
{ id: "quant", title: "Quantization", options: [
{ id: "bf16", label: "BF16" },
] },
{ id: "nodes", title: "Nodes", options: [
{ id: "single", label: "Single Node" },
] },
],
overlayDims: [
{
id: "spec",
title: "Speculative Decoding",
default: "none",
options: [
{ id: "none", label: "None" },
{
id: "dspark", label: "DSPARK",
// Verbatim from the model card's DSpark command, including
// `--trust-remote-code`: the base checkpoint is plain Llama and does
// not need it, the draft checkpoint's config does. gamma = 7, so the
// verify window is 8 tokens.
flags: [
"--trust-remote-code",
"--speculative-algorithm DSPARK",
"--speculative-draft-model-path openbmb/MiniCPM5-2B-DSpark",
"--speculative-dspark-block-size 7",
],
},
],
},
],
modelNames: {
"default|bf16": "openbmb/MiniCPM5-2B",
},
placeholders: {
HOST_IP: { target: "command", label: "Bind host", default: "0.0.0.0" },
PORT: { target: "command", label: "Bind port", default: "30000" },
HF_TOKEN: { target: "command", label: "HF token (Docker)", default: "<your-hf-token>" },
CURL_HOST: { target: "curl", label: "Server host", default: "localhost" },
CURL_PORT: { target: "curl", label: "Server port", default: "30000" },
},
curl: `curl http://{{CURL_HOST}}:{{CURL_PORT}}/v1/chat/completions \\
-H 'Content-Type: application/json' \\
-d '{ "model": "{{MODEL_NAME}}", "messages": [{"role":"user","content":"Who are you? Please briefly introduce yourself."}] }'`,
// Reproduce command for the Benchmark card's "⚡ Reproduce" modal. No
// `accuracy` entry: the page carries no accuracy numbers yet.
benchmarkCommands: {
speed:
`python3 -m sglang.bench_serving \\
--backend sglang \\
--host {{CURL_HOST}} --port {{CURL_PORT}} \\
--model {{MODEL_NAME}} \\
--dataset-name {{DATASET}} \\
--random-input-len {{ISL}} --random-output-len {{OSL}} \\
--num-prompts {{NUM_PROMPTS}} --max-concurrency {{MAX_CONCURRENCY}} \\
--flush-cache`,
numPromptsByConc: { 1: 10, 128: 512 },
},
// MiniCPM5 support (the `minicpm5` tool-call parser and the DSPARK draft
// worker) ships in the SGLang dev image.
dockerImages: {
h200: "lmsysorg/sglang:dev",
rtx6000: "lmsysorg/sglang:dev",
rtx5090: "lmsysorg/sglang:dev",
"dgx-spark": "lmsysorg/sglang:dev",
},
// Pre-selects the issue template's `model` field on "Submit verified cell".
github: {
cookbookModel: "openbmb/MiniCPM5-2B",
},
playgroundFeatures: {
// The model fits one GPU on every supported card, so TP=1 is the verified
// shape; TP=2 is exposed for experimentation only.
attention: {
knobs: [
{ id: "tp", label: "TP", values: [null, 1, 2] },
],
},
// ----- Card: "Parsers" -----
// Opt-OUT: both flags are already in every cell, so the handler derives
// each chip as on and strips the flag when one is toggled off. The
// reasoning slug is `qwen3`, not `minicpm5` — see the header note.
parsers: {
items: [
{ id: "reasoning", label: "Reasoning Parser", flag: "--reasoning-parser qwen3" },
{ id: "toolCall", label: "Tool Call Parser", flag: "--tool-call-parser minicpm5" },
],
},
// ----- Card: "Speculative Decoding" -----
// Same DSpark flags as the Deploy panel's overlay row, so the two paths
// compose an identical command.
speculative: {
options: [
{ id: "current", label: "Inherited from base" },
{ id: "off", label: "Off (greedy)" },
{ id: "dspark", label: "DSpark",
flags: ["--trust-remote-code",
"--speculative-algorithm DSPARK",
"--speculative-draft-model-path openbmb/MiniCPM5-2B-DSpark",
"--speculative-dspark-block-size 7"] },
],
},
},
// One recipe per card — the model card's SGLang launch line, plus the
// `minicpm5` tool-call parser it recommends for agent workloads and the
// `qwen3` reasoning parser its `<think>` template needs.
cells: [
{
match: { hw: "h200", variant: "default", quant: "bf16", nodes: "single" },
verified: true,
env: [],
flags: [
"--model-path {{MODEL_NAME}}",
"--reasoning-parser qwen3",
"--tool-call-parser minicpm5",
"--host {{HOST_IP}}",
"--port {{PORT}}",
],
},
{
// Verification round still open on this card. `verificationStatus` alone,
// with NO `verified: true` baseline: the boolean is what the Playground
// reads for its own badge, so leaving it on would make the Playground
// claim "Verified" while the Deploy panel says the round is in progress.
match: { hw: "rtx6000", variant: "default", quant: "bf16", nodes: "single" },
// Flat string, not a predicate: this cell is in-progress with or without
// the DSPARK overlay, so there is nothing for the selection to switch on.
verificationStatus: "in-progress",
env: [],
flags: [
"--model-path {{MODEL_NAME}}",
"--reasoning-parser qwen3",
"--tool-call-parser minicpm5",
"--host {{HOST_IP}}",
"--port {{PORT}}",
],
},
{
// The 32GB card is the one where the default KV pool starves decode
// CUDA-graph capture: with defaults the pool takes 473,718 tokens / 19 GB
// and leaves 4.6 GB, so capture stops around bs=48 and every larger batch
// runs eager (3810 tok/s at concurrency 64). The pair below gives back
// 4% of the pool -- still hugely oversized for a 2.5B model -- and keeps
// batches up to 128 graph-backed (7454 tok/s at the same concurrency).
// The two flags go together: raising the cap without freeing the memory
// just lets SGLang clamp capture back down.
match: { hw: "rtx5090", variant: "default", quant: "bf16", nodes: "single" },
verified: true,
env: [],
flags: [
"--model-path {{MODEL_NAME}}",
"--reasoning-parser qwen3",
"--tool-call-parser minicpm5",
"--mem-fraction-static 0.75",
"--cuda-graph-max-bs 128",
"--host {{HOST_IP}}",
"--port {{PORT}}",
],
},
{
// GB10 has no discrete VRAM, so `mem_get_info()` reports all 128GB of
// unified system memory and the default fraction claims ~89GB for KV --
// leaving ~5GB for the OS, which kills the node during warmup with no
// traceback and no OOMKilled event. 0.30 is required, not tuning; it
// still leaves a 658k-token pool.
match: { hw: "dgx-spark", variant: "default", quant: "bf16", nodes: "single" },
verified: true,
env: [],
flags: [
"--model-path {{MODEL_NAME}}",
"--reasoning-parser qwen3",
"--tool-call-parser minicpm5",
"--mem-fraction-static 0.30",
"--cuda-graph-max-bs 128",
"--host {{HOST_IP}}",
"--port {{PORT}}",
],
},
],
};