Image-Text-to-Text
Transformers
Safetensors
Tibetan
paddleocr_vl
ocr
tibetan
vision-language
paddleocr-vl
pecha
conversational
Instructions to use BDRC/tibetan-ocr with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use BDRC/tibetan-ocr with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="BDRC/tibetan-ocr") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("BDRC/tibetan-ocr") model = AutoModelForMultimodalLM.from_pretrained("BDRC/tibetan-ocr", device_map="auto") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] inputs = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use BDRC/tibetan-ocr with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "BDRC/tibetan-ocr" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "BDRC/tibetan-ocr", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/BDRC/tibetan-ocr
- SGLang
How to use BDRC/tibetan-ocr 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 "BDRC/tibetan-ocr" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "BDRC/tibetan-ocr", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'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 "BDRC/tibetan-ocr" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "BDRC/tibetan-ocr", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use BDRC/tibetan-ocr with Docker Model Runner:
docker model run hf.co/BDRC/tibetan-ocr
File size: 10,882 Bytes
b1d6115 50506eb b1d6115 50506eb b1d6115 50506eb b1d6115 50506eb b1d6115 50506eb b1d6115 8d18014 b1d6115 5e82f0e b1d6115 5e82f0e cc59d47 5e82f0e b1d6115 5e82f0e 50506eb b1d6115 5e82f0e 50506eb 5e82f0e b1d6115 50506eb b1d6115 50506eb b1d6115 50506eb b1d6115 50506eb b1d6115 50506eb b1d6115 50506eb b1d6115 50506eb b1d6115 50506eb b1d6115 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | ---
license: apache-2.0
base_model: PaddlePaddle/PaddleOCR-VL-1.6
language:
- bo
tags:
- ocr
- tibetan
- vision-language
- paddleocr-vl
- pecha
pipeline_tag: image-text-to-text
library_name: transformers
metrics:
- cer
---
# Tibetan OCR — Yigdzin 1
<!-- Model card for BDRC/tibetan-ocr ("Yigdzin 1").
Weights: checkpoint elie_v8_coarse_grow26_ep2 (bec_mixed_elie_v8 mix, evaluation
benchmark pages held out of training via images_exclude_from_train.csv). -->
**A vision-first OCR model for Tibetan pecha** — modern publications, woodblock
prints, and manuscripts (uchen and u-med), including pecha layout and orthographic
shorthands. Built by the [Buddhist Digital Resource Center (BDRC)](https://bdrc.io)
and trained on the `bec_mixed_elie_v8` mix of curated real + synthetic pages, with
the **evaluation-benchmark pages held out of training**.
- **Repository:** `BDRC/tibetan-ocr` (display name **Yigdzin 1**)
- **Base model:** [`PaddlePaddle/PaddleOCR-VL-1.6`](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6) (Apache-2.0)
- **Live demo:** [ocr.bdrc.io](https://ocr.bdrc.io) · **Desktop app:** [buda-base/tibetan-ocr-app](https://github.com/buda-base/tibetan-ocr-app)
## TL;DR
On a 1,070-page hand-transcribed benchmark (production serving: vLLM + sequential
regime + DRY guard + temperature-retry), **median page CER is ~1.4%** and clean-page
(non-catastrophic) mean CER is **~4.0%**, with **0 hard repetition loops**. The
benchmark pages are held out of training. It is a *specialized* OCR model, not a
general chat VLM.
## Model details
| | |
|---|---|
| Architecture | PaddleOCR-VL-1.6 (vision encoder + depth-upscaled decoder) |
| Parameters | ~810M (~413M vision · ~360M 26-layer decoder · ~4M embeddings/head) |
| Vision share | ~51–59% of parameters ("big eyes, small mouth" — vision-first) |
| Tokenizer | Tibetan **unicode-stack** tokenizer, vocab **3,560** (pruned + BoCorpus-warmed) |
| Decoder growth | grow26: SOLAR-style depth-upscale 18L→26L, then annealed |
| Position regime | **sequential** image-token M-RoPE (1-D); `max_pixels` 1280 budget |
| Languages | Tibetan (`bo`); Tibetan script |
| License | Apache-2.0 (derivative of PaddleOCR-VL-1.6; upstream `NOTICE` retained) |
| Funder | Khyentse Foundation ("The BDRC Etext Corpus") |
## Intended use
- OCR of Tibetan pecha pages: **uchen** and **u-med** (dbu med) scripts across
**modern print, woodblock, and manuscript** sources.
- Batch OCR at corpus scale (BDRC is applying it to ~25M scanned pages).
**Out of scope:** general vision-language chat / VQA; non-Tibetan scripts; layout
analysis of illustrations, tables, or diagrams; line/region detection (this model
transcribes page or line crops it is given).
## How to use
Prompt (used at training and inference), rendered through the repo's
`chat_template.jinja`:
```
Extract all Tibetan text. Preserve line breaks.
```
This checkpoint uses the **native** PaddleOCR-VL architecture (`model_type:
paddleocr_vl`, `PaddleOCRVLForConditionalGeneration`) — **no `trust_remote_code`
needed**, but it requires **transformers ≥ 5.15** (native `paddleocr_vl` support)
or **vLLM ≥ 0.26**. Decode **greedy** (`temperature=0`).
```bash
pip install "transformers>=5.15" torch torchvision accelerate pillow
# torchvision is required by the PaddleOCR-VL image processor.
```
> **⚠ Position regime — required for correct output.** This model was trained in the
> **sequential** image-token M-RoPE regime (1-D positions), *not* the default "grid"
> regime. If the processor emits a non-zero `mm_token_type_ids`, structured pages
> loop badly (some pages regress from ~0.10 to ~0.62 CER). Serve it sequential:
> - **HF:** zero the mask before the forward —
> `inputs["mm_token_type_ids"] = torch.zeros_like(inputs["input_ids"])`.
> - **vLLM:** install the `vllm_paddleocr_seqpos` plugin and set
> `OCR_VLLM_IMAGE_TOKEN_POSITIONS=sequential`.
### HF transformers (reference / accuracy baseline)
```python
import torch
from PIL import Image
from transformers import AutoProcessor, AutoModelForImageTextToText
model_id = "BDRC/tibetan-ocr"
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForImageTextToText.from_pretrained(model_id, dtype="bfloat16", device_map="cuda")
image = Image.open("page.jpg").convert("RGB")
messages = [{"role": "user", "content": [
{"type": "image"},
{"type": "text", "text": "Extract all Tibetan text. Preserve line breaks."}]}]
prompt = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
inputs = processor(text=[prompt], images=[image], return_tensors="pt").to("cuda")
inputs["mm_token_type_ids"] = torch.zeros_like(inputs["input_ids"]) # sequential regime (required)
out = model.generate(**inputs, do_sample=False, max_new_tokens=4096)
print(processor.batch_decode(out[:, inputs["input_ids"].shape[1]:], skip_special_tokens=True)[0])
```
To add the **DRY** anti-loop guard under HF too (optional; `dry_logits_processor.py`
ships in this repo):
```python
from transformers import LogitsProcessorList
from dry_logits_processor import make_hf_dry_processor
prompt_len = inputs["input_ids"].shape[1]
out = model.generate(**inputs, do_sample=False, max_new_tokens=4096,
logits_processor=LogitsProcessorList([make_hf_dry_processor(
prompt_len=prompt_len, multiplier=0.8, base=1.75, allowed_length=12)]))
```
### vLLM (production)
Serve with **vLLM ≥ 0.26**. Two things matter for production quality:
**1. Sequential image-token positions (required).** Stock vLLM serves PaddleOCR-VL in
the *grid* regime and never reads `mm_token_type_ids`; this model needs the *sequential*
regime. A tiny vLLM plugin ships in this repo under `vllm_paddleocr_seqpos/`:
```bash
pip install "git+https://huggingface.co/BDRC/tibetan-ocr#subdirectory=vllm_paddleocr_seqpos"
export OCR_VLLM_IMAGE_TOKEN_POSITIONS=sequential # 'grid' / unset = no-op
```
Without it, structured (book/list) pages skip or merge lines and CER regresses badly.
**2. DRY anti-loop guard + temperature retry (recommended).** Greedy decoding runs
away into repetition on a small fraction of pages. The surgical fix is the **DRY**
("Don't Repeat Yourself") penalty: it ties an n-gram ban on corpus CER, zeroes hard
loops, and — unlike `repetition_penalty` / `no_repeat_ngram_size` — barely touches
clean pages or legitimate repetitive scripture (mantras, litanies). It ships here as
the self-contained `dry_logits_processor.py` (torch-only):
```python
from vllm import LLM, SamplingParams
from dry_logits_processor import DRYLogitsProcessor # ships in this repo
llm = LLM(model="BDRC/tibetan-ocr", logits_processors=[DRYLogitsProcessor]) # + seqpos env above
params = SamplingParams(temperature=0, max_tokens=4096, extra_args={
"dry_multiplier": 0.8, "dry_base": 1.75, "dry_allowed_length": 12, # production config, no breakers
})
```
For the pages that still loop under greedy+DRY, **re-decode at temperature and keep the
cleanest sample.** The strong signal is *how often DRY fired*: the measured knee is
**≥ 100 fires** — re-decode only those pages at `temperature=0.4`, `n=3` (DRY still on)
and pick the sample with the lowest leftover repetition. On this benchmark that is ~2%
of pages and clears the residual hard loops. `dry_logits_processor.py` writes
per-request fire counts (pass `dry_stats_id` / `dry_stats_path` in `extra_args`, read
them back with `load_dry_stats_dir`); `deploy/fast_inference/bench.py` in the training
repo is the reference implementation of the full greedy → fire-count gate →
temperature-retry loop.
Do **not** reach for aggressive `repetition_penalty` / `no_repeat_ngram_size`: they
"correct away" legitimate repeated scripture. DRY with the config above is the tested,
surgical choice.
## Training data
Curated mix (`bec_mixed_elie_v8`), assembled from BDRC alignment collections +
BoCorpus-rendered synthetic pages, with an easy→hard curriculum, u-med over-weighting,
and repetition-aware filtering. **The evaluation-benchmark pages are held out of
training** (`images_exclude_from_train.csv`). Released component datasets:
- Real transcriptions: [ALL-BDRC (ACIP Sungbum)](https://huggingface.co/datasets/BDRC/ALL-BDRC-alignments),
[PalriParkhang](https://huggingface.co/datasets/BDRC/palri-parkhang),
[Berkeley](https://huggingface.co/datasets/BDRC/berkeley),
[MonlamAI-transcriptions](https://huggingface.co/datasets/BDRC/monlamai-transcriptions),
[MonlamAI-handwritten](https://huggingface.co/datasets/BDRC/monlamai-handwritten),
[Stok](https://huggingface.co/datasets/openpecha/stok), [TibSchol](https://huggingface.co/datasets/BDRC/tibschol) *(gated)*.
- Synthetic: [Tibetan OCR synthetic v5](https://huggingface.co/datasets/BDRC/tibetan-ocr-synthetic).
## Evaluation
Evaluated on the BDRC hand-transcribed benchmark `20260315` (1,070 pages) in the
**production configuration** (vLLM + `vllm_paddleocr_seqpos` sequential regime + DRY
guard `mult=0.8 base=1.75 allowed_length=12` + temperature-retry `temp=0.4 n=3` on
pages with ≥100 DRY fires). Metrics from the benchmark's `compute_cer.py` (botok
normalization, whitespace stripping, tsheg folding, placeholder removal); pages with
CER > 50% are counted as *catastrophic* and reported separately.
| Metric (1,070 pages) | **Yigdzin 1** |
|---|---:|
| Median page CER | **0.0142** |
| Mean page CER (all) | 0.0628 |
| Catastrophic (>50%) share | 3.3% (35) |
| Clean mean CER (non-catastrophic) | **0.0404** |
| Clean mean SER | **0.0764** |
| Hard repetition loops | **0** |
Full method + the multi-system leaderboard:
**[BDRC Tibetan OCR benchmark](https://huggingface.co/datasets/BDRC/tibetan-ocr-benchmark)**
and the **[leaderboard](https://huggingface.co/spaces/BDRC/tibetan-ocr-leaderboard)**.
## Limitations & recommendations
- **Residual repetition loops** on a small fraction of pages. Production uses the
**DRY** guard + temperature retry (see How to use), which clears them (0 hard loops
on the benchmark) while leaving legitimate repetitive scripture (mantras) intact;
keep any anti-loop mitigation this gentle.
- **Line-break joins** on some dense (6-line) woodblock pecha.
- Struggles on illustrations, non-Tibetan text, and illegible/damaged pages.
## Citation
A comprehensive article is forthcoming (Springer *Language Resources and
Evaluation*). Until then, please cite the model and BDRC:
```bibtex
@misc{bdrc_tibetan_ocr_2026,
title = {Tibetan OCR (Yigdzin 1)},
author = {Roux, Elie and Werner, Eric},
year = {2026},
howpublished = {Buddhist Digital Resource Center, Hugging Face},
note = {https://huggingface.co/BDRC/tibetan-ocr}
}
```
## Acknowledgements
Built on [PaddleOCR-VL](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6)
(Apache-2.0). Funded by the **Khyentse Foundation**. Thanks to the BDRC, Dharmaduta,
and collaborating transcription teams.
|