Instructions to use Gabriel/diffu-minicpm-ocr-htr-dspark with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Gabriel/diffu-minicpm-ocr-htr-dspark with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="Gabriel/diffu-minicpm-ocr-htr-dspark", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Gabriel/diffu-minicpm-ocr-htr-dspark", trust_remote_code=True, device_map="auto") - MiniCPM-OCR
How to use Gabriel/diffu-minicpm-ocr-htr-dspark with MiniCPM-OCR:
pip install "minicpm-ocr[all]" huggingface_hub # Deploy Gabriel/diffu-minicpm-ocr-htr-dspark as a recognition service before running the Python example: # https://github.com/OpenBMB/MiniCPM-OCR/blob/main/docs/vllm_deploy_en.md # vLLM: load the model above; use --port 8080, # --served-model-name minicpm-ocr and --max-model-len 40960. # Layout detection uses cuda:0; recognition runs on the deployed service.
from importlib.resources import files from pathlib import Path from minicpmvocr import load_config, pipelined_run from huggingface_hub import snapshot_download layout_model = snapshot_download("PaddlePaddle/PP-DocLayoutV3") cfg = load_config( str(files("minicpmvocr").joinpath("configs/minicpmvocr.yaml")), overrides={ "input": {"file_path": str(Path("demo.png").resolve())}, "engine": { "type": "vllm", "server_url": "http://127.0.0.1:8080", "model_name": "minicpm-ocr", "max_model_len": 40960, }, "layout": {"model_dir": layout_model, "device": "cuda:0"}, # Render PDFs in-process so this example also works without a main guard. "pipeline": {"overlap_layout": True, "render_workers": 1}, }, ) pipelined_run(cfg) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use Gabriel/diffu-minicpm-ocr-htr-dspark with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "Gabriel/diffu-minicpm-ocr-htr-dspark" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Gabriel/diffu-minicpm-ocr-htr-dspark", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/Gabriel/diffu-minicpm-ocr-htr-dspark
- SGLang
How to use Gabriel/diffu-minicpm-ocr-htr-dspark with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "Gabriel/diffu-minicpm-ocr-htr-dspark" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Gabriel/diffu-minicpm-ocr-htr-dspark", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "Gabriel/diffu-minicpm-ocr-htr-dspark" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Gabriel/diffu-minicpm-ocr-htr-dspark", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use Gabriel/diffu-minicpm-ocr-htr-dspark with Docker Model Runner:
docker model run hf.co/Gabriel/diffu-minicpm-ocr-htr-dspark
diffu-minicpm-ocr-htr-dspark
A DSpark draft (speculative decoding) head for diffu-minicpm-ocr-htr, the Swedish handwriting reader of diffu. It proposes up to 15 tokens at a time for the reader to verify, so the reader decodes faster and, decoding greedily, writes the same text. It is trained the way OpenBMB trained MiniCPM-OCR-1.0-DSpark, but on this reader's own answers.
Training
- Data: the reader's own answers to 250,000 training lines and 50,000 training regions of GT-Lejonet (February 2026), each a seeded draw.
- Recipe: 3 online epochs, with vLLM serving the reader on one RTX PRO 6000 Blackwell and the trainer on another.
- Checkpoint: epoch 2, the best on held-out answers.
The files are in the speculators format (config.json, its config.py
and the weights), and the config names Gabriel/diffu-minicpm-ocr-htr as the verifier.
Setup
The draft runs inside vLLM, beside the reader. It fits this reader only: a draft learns its target's own answers.
You need Linux, an NVIDIA GPU with about 20 GB free, and uv. MiniCPM-OCR needs a vLLM nightly from 29 October 2026 or later (vllm-project/vllm#64105) with transformers 5.22.1; this is the build the draft was trained and tested with:
uv venv --python 3.12 htr && source htr/bin/activate
echo "transformers==5.22.1" > overrides.txt
uv pip install --override overrides.txt openai \
"vllm @ https://wheels.vllm.ai/fa3b9d1377219247060d3c8b49eae199c9e7e154/vllm-0.33.0rc1.dev234%2Bgfa3b9d137-cp38-abi3-manylinux_2_28_x86_64.whl"
On an ARM machine (such as a DGX Spark), use the ..._aarch64.whl of the same commit. While the repos are private,
log in first with hf auth login.
Use
Serve the reader with the draft:
vllm serve Gabriel/diffu-minicpm-ocr-htr --served-model-name htr --max-model-len 8192 \
--limit-mm-per-prompt '{"image": 4, "video": 0}' \
--mm-processor-kwargs '{"downsample_mode": "4x", "max_slice_nums": 9}' --trust-request-mm-kwargs \
--default-chat-template-kwargs '{"enable_thinking": false}' \
--speculative-config '{"method": "dspark", "model": "Gabriel/diffu-minicpm-ocr-htr-dspark", "num_speculative_tokens": 15, "enable_adaptive_verification": false}'
Then ask it exactly as you ask the reader alone: one line or text-region image and the prompt Text Recognition:,
decoded greedily.
import base64
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="-")
image = base64.b64encode(open("line.jpg", "rb").read()).decode()
reply = client.chat.completions.create(
model="htr",
messages=[{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image}"}},
{"type": "text", "text": "Text Recognition:"}]}],
max_tokens=512, # 4096 for a text region
temperature=0.01, top_p=0.001, extra_body={"top_k": 1, "repetition_penalty": 1.0},
)
print(reply.choices[0].message.content)
Leave out --speculative-config to serve the reader alone. To check that the draft is used, read vLLM's
spec_decode_num_* counters at http://localhost:8000/metrics. They rise with every request, and the acceptance
length is 1 + accepted tokens / drafts.
The reader is a hybrid model (linear-attention layers), so with a draft vLLM keeps one copy of each request's recurrent state per draft token (speculators: throughput vs. interactivity): each request takes more memory, and fewer fit in a batch. Measure with and without the draft at the concurrency you read at.
Evaluation
On held-out answers: 8.65 tokens accepted a step on average, acceptance rate 85.1%.
Speed
Measured on 9 November 2026 on the same machine, one RTX PRO 6000 Blackwell per model, with the vLLM build under Setup, prefix caching off and greedy decoding. The test data is the first 1,000 of 5,000 test lines (whole volumes held out of training) and 200 dev regions. The reference is the Swedish Lion TrOCR (float32, greedy) on the same lines and GPU; it reads single lines only. Figures are lines or regions read per second.
| At a time | TrOCR | Reader alone | Reader + draft | Draft speedup | |
|---|---|---|---|---|---|
| Lines | 1 | 4.3 | 13.3 | 18.2 | 1.37ร |
| Lines | 64 (TrOCR: batch 32) | 6.5 | 74.6 | 82.2 | 1.10ร |
| Regions | 1 | โ | 1.95 | 4.08 | 2.09ร |
| Regions | 64 | โ | 12.4 | 13.2 | 1.07ร |
The acceptance length in vLLM is 7.5 on lines and 9.7 on regions, out of at most 16 (from vLLM's spec_decode counters).
The draft helps most when few requests run at once: reading a page while someone waits, or long regions. With 64
requests at a time the GPU is already busy, and the gain is about 10%.
Accuracy
The draft does not change what the reader writes. It only proposes tokens; the reader accepts or rejects each one.
| CER on the same text (OpenBMB's corpus CER) | TrOCR | Reader alone | Reader + draft |
|---|---|---|---|
| 1,000 test lines | 4.48% | 4.99% | 4.97% |
| 200 dev regions | โ | 4.28% | 4.27% |
964 of the 1,000 line readings with the draft are character-for-character identical to the reader's alone. The rest differ by GPU rounding, not by the draft: the reader alone gives the same rate (959 of 1,000 identical) when it reads the same lines one at a time and then 64 at a time. On these 1,000 lines TrOCR is the more accurate of the two readers. The reader's own card has the full comparison.
Training curves
runs/dspark-all/ holds the TensorBoard log of the training run: the loss, the acceptance length and each draft
position's accuracy every 10 steps, and the held-out validation after each epoch.
Licence
Apache-2.0, as its base model.
- Downloads last month
- 38
Model tree for Gabriel/diffu-minicpm-ocr-htr-dspark
Base model
openbmb/MiniCPM-V-4.6