Image-Text-to-Text
Transformers
Safetensors
Latin
qwen3_5
ocr
htr
handwritten-text-recognition
manuscripts
palaeography
catmus
medieval-latin
qwen3.5
vision-language
conversational
Instructions to use wjbmattingly/comma-qwen-3.5-0.8b-full-33k with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use wjbmattingly/comma-qwen-3.5-0.8b-full-33k with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="wjbmattingly/comma-qwen-3.5-0.8b-full-33k") 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("wjbmattingly/comma-qwen-3.5-0.8b-full-33k") model = AutoModelForMultimodalLM.from_pretrained("wjbmattingly/comma-qwen-3.5-0.8b-full-33k", 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 wjbmattingly/comma-qwen-3.5-0.8b-full-33k with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "wjbmattingly/comma-qwen-3.5-0.8b-full-33k" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "wjbmattingly/comma-qwen-3.5-0.8b-full-33k", "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/wjbmattingly/comma-qwen-3.5-0.8b-full-33k
- SGLang
How to use wjbmattingly/comma-qwen-3.5-0.8b-full-33k 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 "wjbmattingly/comma-qwen-3.5-0.8b-full-33k" \ --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": "wjbmattingly/comma-qwen-3.5-0.8b-full-33k", "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 "wjbmattingly/comma-qwen-3.5-0.8b-full-33k" \ --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": "wjbmattingly/comma-qwen-3.5-0.8b-full-33k", "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 wjbmattingly/comma-qwen-3.5-0.8b-full-33k with Docker Model Runner:
docker model run hf.co/wjbmattingly/comma-qwen-3.5-0.8b-full-33k
comma-qwen-3.5-0.8b-full-33k: continued full fine-tune on 33,112 CATMuS pages (cer_nfd 0.1240)
Browse files- .gitattributes +1 -0
- README.md +187 -0
- chat_template.jinja +154 -0
- config.json +104 -0
- generation_config.json +10 -0
- model.safetensors +3 -0
- processor_config.json +60 -0
- prompt.txt +102 -0
- tokenizer.json +3 -0
- tokenizer_config.json +33 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* 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
|
|
|
|
|
|
| 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
|
README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model: Qwen/Qwen3.5-0.8B
|
| 4 |
+
library_name: transformers
|
| 5 |
+
datasets:
|
| 6 |
+
- comma-project/deep-jsonl
|
| 7 |
+
language:
|
| 8 |
+
- la
|
| 9 |
+
pipeline_tag: image-text-to-text
|
| 10 |
+
tags:
|
| 11 |
+
- ocr
|
| 12 |
+
- htr
|
| 13 |
+
- handwritten-text-recognition
|
| 14 |
+
- manuscripts
|
| 15 |
+
- palaeography
|
| 16 |
+
- catmus
|
| 17 |
+
- medieval-latin
|
| 18 |
+
- qwen3.5
|
| 19 |
+
- vision-language
|
| 20 |
+
metrics:
|
| 21 |
+
- cer
|
| 22 |
+
- wer
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
# comma-qwen-3.5-0.8b-full-33k
|
| 26 |
+
|
| 27 |
+
`Qwen/Qwen3.5-0.8B` fine-tuned to produce **CATMuS-compliant, line-by-line transcriptions**
|
| 28 |
+
of medieval Latin manuscript pages.
|
| 29 |
+
|
| 30 |
+
Given a page image, the model returns one line of text for each physical written
|
| 31 |
+
line, in reading order. CATMuS is a *graphemic* standard: the sequence of letters
|
| 32 |
+
and signs as written, reduced to the modern Latin alphabet, with no editorial
|
| 33 |
+
intervention. The output is not a translation and not a normalised reading.
|
| 34 |
+
|
| 35 |
+
This is a **full fine-tune** — a standalone checkpoint, no adapter and no `peft` dependency at inference.
|
| 36 |
+
|
| 37 |
+
**This is a continued fine-tune.** It starts from `comma-qwen-3.5-0.8b-full (9,906 pages)` and trains further on new pages, so the *base* column below is that earlier model rather than a stock checkpoint — the gain shown is what the additional data bought, measured within one job.
|
| 38 |
+
|
| 39 |
+
- **Training data** — 33,112 pages from [`comma-project/deep-jsonl`](https://huggingface.co/datasets/comma-project/deep-jsonl)
|
| 40 |
+
- **Evaluation** — 243 held-out pages, each a different hand, none seen in training
|
| 41 |
+
- **Demo** — [comma-qwen-3.5-demo](https://huggingface.co/spaces/wjbmattingly/comma-qwen-3.5-demo)
|
| 42 |
+
|
| 43 |
+
## Results
|
| 44 |
+
|
| 45 |
+
Greedy decoding, 3072 max new tokens, 2048 visual tokens per page,
|
| 46 |
+
batch 4. "Base" is the previous **comma-qwen-3.5-0.8b-full (9,906 pages)**, scored on the same pages in the same job — a base number from another job on another
|
| 47 |
+
day is not a control.
|
| 48 |
+
|
| 49 |
+
| metric | base | **this model** |
|
| 50 |
+
|---|---|---|
|
| 51 |
+
| CER (NFD) | 0.1458 | **0.1240** |
|
| 52 |
+
| CER (raw code points) | 0.1859 | 0.1651 |
|
| 53 |
+
| WER (NFD) | 0.4029 | 0.3584 |
|
| 54 |
+
| line recall (NFD) | 0.1672 | 0.1883 |
|
| 55 |
+
| CER macro (NFD) | 0.1318 | 0.1385 |
|
| 56 |
+
| degenerate pages | 0.0041 | 0.0000 |
|
| 57 |
+
| truncated pages | 0.0165 | 0.0123 |
|
| 58 |
+
|
| 59 |
+
CER/WER are micro-averaged (total edits ÷ total reference characters), so a long
|
| 60 |
+
page outweighs a short one; the macro mean is given alongside because a gap
|
| 61 |
+
between them says the errors are concentrated. `line_recall` is the share of
|
| 62 |
+
reference lines reproduced exactly and in order — for a line-by-line CATMuS
|
| 63 |
+
target that is the number a palaeographer looks at first.
|
| 64 |
+
|
| 65 |
+
## Usage
|
| 66 |
+
|
| 67 |
+
```python
|
| 68 |
+
import torch
|
| 69 |
+
from PIL import Image
|
| 70 |
+
from transformers import AutoModelForImageTextToText, AutoProcessor
|
| 71 |
+
|
| 72 |
+
REPO = "wjbmattingly/comma-qwen-3.5-0.8b-full-33k"
|
| 73 |
+
BASE = "Qwen/Qwen3.5-0.8B"
|
| 74 |
+
|
| 75 |
+
processor = AutoProcessor.from_pretrained(REPO)
|
| 76 |
+
model = AutoModelForImageTextToText.from_pretrained(
|
| 77 |
+
REPO, dtype=torch.bfloat16, device_map="cuda"
|
| 78 |
+
)
|
| 79 |
+
model.eval()
|
| 80 |
+
|
| 81 |
+
# The prompt is the 11.6k-character CATMuS rule set the model was trained under.
|
| 82 |
+
# Serving a different prompt serves a different task: the rules are what the
|
| 83 |
+
# target obeys, so the model was taught to read them as part of the input.
|
| 84 |
+
prompt = open("prompt.txt", encoding="utf-8").read()
|
| 85 |
+
|
| 86 |
+
image = Image.open("page.jpg").convert("RGB")
|
| 87 |
+
messages = [{
|
| 88 |
+
"role": "user",
|
| 89 |
+
"content": [{"type": "image"}, {"type": "text", "text": prompt}],
|
| 90 |
+
}]
|
| 91 |
+
|
| 92 |
+
# `enable_thinking=False` is not optional. With no kwarg, Qwen3.5 0.8B/2B leave
|
| 93 |
+
# the thinking block CLOSED and 4B/9B leave it OPEN -- and a model trained on
|
| 94 |
+
# pure transcription that is handed an open block will think instead of
|
| 95 |
+
# transcribing, emitting prose until the token budget runs out.
|
| 96 |
+
text = processor.apply_chat_template(
|
| 97 |
+
messages, tokenize=False, add_generation_prompt=True, enable_thinking=False
|
| 98 |
+
)
|
| 99 |
+
assert text.count("<think>") == text.count("</think>"), "thinking block left open"
|
| 100 |
+
|
| 101 |
+
inputs = processor(text=[text], images=[[image]], return_tensors="pt").to(model.device)
|
| 102 |
+
|
| 103 |
+
with torch.inference_mode():
|
| 104 |
+
out = model.generate(
|
| 105 |
+
**inputs,
|
| 106 |
+
do_sample=False, # greedy is the published control
|
| 107 |
+
repetition_penalty=1.1, # the measured serving default; 1.0 reproduces the table
|
| 108 |
+
max_new_tokens=3072,
|
| 109 |
+
eos_token_id=[processor.tokenizer.eos_token_id],
|
| 110 |
+
pad_token_id=processor.tokenizer.pad_token_id,
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
print(processor.tokenizer.decode(out[0][inputs["input_ids"].shape[1]:],
|
| 114 |
+
skip_special_tokens=True).strip())
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
`prompt.txt` is in the
|
| 118 |
+
[demo Space](https://huggingface.co/spaces/wjbmattingly/comma-qwen-3.5-demo/blob/main/prompt.txt).
|
| 119 |
+
|
| 120 |
+
### The image budget travels with the checkpoint
|
| 121 |
+
|
| 122 |
+
This repo's `processor_config.json` is already capped to **2048 visual
|
| 123 |
+
tokens** per page (2,097,152 pixels after smart-resize, one token per
|
| 124 |
+
32×32 block), which is what training and scoring used. Load the processor from
|
| 125 |
+
this repo rather than from the base model: the library default is 16,777,216
|
| 126 |
+
pixels — 16k visual tokens for one page — and serving at that budget shows the
|
| 127 |
+
model a page at a resolution it never saw.
|
| 128 |
+
|
| 129 |
+
## Training
|
| 130 |
+
|
| 131 |
+
| | |
|
| 132 |
+
|---|---|
|
| 133 |
+
| tuning | full fine-tune |
|
| 134 |
+
| trainable parameters | 852,985,920 of 852,985,920 (100.0%) |
|
| 135 |
+
| epochs | 2 |
|
| 136 |
+
| effective batch | 8 |
|
| 137 |
+
| learning rate | 5e-05 |
|
| 138 |
+
| max sequence length | 8192 |
|
| 139 |
+
| visual tokens per page | 2048 |
|
| 140 |
+
| precision | bf16 |
|
| 141 |
+
| hardware | NVIDIA RTX PRO 6000 Blackwell Server Edition |
|
| 142 |
+
| wall time | 38.9 h |
|
| 143 |
+
|
| 144 |
+
`max_length` never truncates: every sample is measured first and over-budget
|
| 145 |
+
ones are dropped, because a truncated completion teaches the model to stop
|
| 146 |
+
mid-transcription and a truncated prompt removes the rules the target obeys.
|
| 147 |
+
|
| 148 |
+
## Limitations
|
| 149 |
+
|
| 150 |
+
- **Nothing under 0.02 CER is a result.** Four seeds of one configuration in this
|
| 151 |
+
project gave CER 0.1271 / 0.1334 / 0.1370 / 0.1485 — mean 0.1365, sd 0.0090.
|
| 152 |
+
bf16 training is nondeterministic across nodes and greedy decoding amplifies a
|
| 153 |
+
sub-millivolt logit difference into a different token. Treat differences
|
| 154 |
+
smaller than ~0.02 as ties.
|
| 155 |
+
- **Greedy is deterministic given identical batching, but not batch-size
|
| 156 |
+
invariant.** Re-scoring the same pages at batch 1 instead of 4 changes almost
|
| 157 |
+
every prediction and moves aggregate CER by ±0.013 for the smallest model here.
|
| 158 |
+
Any comparison across a batch-size change is invalid.
|
| 159 |
+
- **Quote `cer_nfd`, not `cer`.** `ũ` (U+0169) and `u` + combining tilde are one
|
| 160 |
+
glyph, and 77% of training targets mix the two forms — so an edit distance over
|
| 161 |
+
raw code points charges two edits for a difference that is not on the page,
|
| 162 |
+
about 16% of all edit operations. Every checkpoint is biased toward one form
|
| 163 |
+
and training does not fix it (the targets are mixed, so there is no signal to
|
| 164 |
+
fit), which means raw `cer` partly scores which Unicode form a tokenizer
|
| 165 |
+
prefers. NFD and not NFC: `t̃ r̃ m̃ p̃ c̃ q̃` have no precomposed form.
|
| 166 |
+
- **The remaining error is mostly convention, not reading.** On the best run of
|
| 167 |
+
this project, word-division spaces are ~17% of all edit operations and
|
| 168 |
+
allographs another ~5%, against ~1% for genuine letter confusion (`r`↔`s`,
|
| 169 |
+
`f`↔`s`). Both are decisions about the transcription convention. Do not read a
|
| 170 |
+
CER at this level as a statement about how well the model reads the script.
|
| 171 |
+
- **Latin, and this convention.** Trained on Latin-dominant manuscripts under the
|
| 172 |
+
CATMuS graphemic standard. deep-jsonl uses the full MUFI superscript-letter
|
| 173 |
+
repertoire (`qͥ` qui, `qͣ` qua, `qͦ` quo) where coarser conventions flatten almost
|
| 174 |
+
everything to a tilde, so scoring this model against a corpus transcribed more
|
| 175 |
+
coarsely costs 1–2% CER for reasons unrelated to reading.
|
| 176 |
+
- **Blank and non-text pages** return the literal string `VLM-NO-TEXT`.
|
| 177 |
+
|
| 178 |
+
## Citation
|
| 179 |
+
|
| 180 |
+
```bibtex
|
| 181 |
+
@misc{comma_qwen35,
|
| 182 |
+
title = {comma-qwen-3.5: CATMuS transcription models for medieval Latin manuscripts},
|
| 183 |
+
author = {Mattingly, William J. B.},
|
| 184 |
+
year = {2026},
|
| 185 |
+
url = {https://huggingface.co/wjbmattingly/comma-qwen-3.5-0.8b-full-33k}
|
| 186 |
+
}
|
| 187 |
+
```
|
chat_template.jinja
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- set image_count = namespace(value=0) %}
|
| 2 |
+
{%- set video_count = namespace(value=0) %}
|
| 3 |
+
{%- macro render_content(content, do_vision_count, is_system_content=false) %}
|
| 4 |
+
{%- if content is string %}
|
| 5 |
+
{{- content }}
|
| 6 |
+
{%- elif content is iterable and content is not mapping %}
|
| 7 |
+
{%- for item in content %}
|
| 8 |
+
{%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
|
| 9 |
+
{%- if is_system_content %}
|
| 10 |
+
{{- raise_exception('System message cannot contain images.') }}
|
| 11 |
+
{%- endif %}
|
| 12 |
+
{%- if do_vision_count %}
|
| 13 |
+
{%- set image_count.value = image_count.value + 1 %}
|
| 14 |
+
{%- endif %}
|
| 15 |
+
{%- if add_vision_id %}
|
| 16 |
+
{{- 'Picture ' ~ image_count.value ~ ': ' }}
|
| 17 |
+
{%- endif %}
|
| 18 |
+
{{- '<|vision_start|><|image_pad|><|vision_end|>' }}
|
| 19 |
+
{%- elif 'video' in item or item.type == 'video' %}
|
| 20 |
+
{%- if is_system_content %}
|
| 21 |
+
{{- raise_exception('System message cannot contain videos.') }}
|
| 22 |
+
{%- endif %}
|
| 23 |
+
{%- if do_vision_count %}
|
| 24 |
+
{%- set video_count.value = video_count.value + 1 %}
|
| 25 |
+
{%- endif %}
|
| 26 |
+
{%- if add_vision_id %}
|
| 27 |
+
{{- 'Video ' ~ video_count.value ~ ': ' }}
|
| 28 |
+
{%- endif %}
|
| 29 |
+
{{- '<|vision_start|><|video_pad|><|vision_end|>' }}
|
| 30 |
+
{%- elif 'text' in item %}
|
| 31 |
+
{{- item.text }}
|
| 32 |
+
{%- else %}
|
| 33 |
+
{{- raise_exception('Unexpected item type in content.') }}
|
| 34 |
+
{%- endif %}
|
| 35 |
+
{%- endfor %}
|
| 36 |
+
{%- elif content is none or content is undefined %}
|
| 37 |
+
{{- '' }}
|
| 38 |
+
{%- else %}
|
| 39 |
+
{{- raise_exception('Unexpected content type.') }}
|
| 40 |
+
{%- endif %}
|
| 41 |
+
{%- endmacro %}
|
| 42 |
+
{%- if not messages %}
|
| 43 |
+
{{- raise_exception('No messages provided.') }}
|
| 44 |
+
{%- endif %}
|
| 45 |
+
{%- if tools and tools is iterable and tools is not mapping %}
|
| 46 |
+
{{- '<|im_start|>system\n' }}
|
| 47 |
+
{{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
|
| 48 |
+
{%- for tool in tools %}
|
| 49 |
+
{{- "\n" }}
|
| 50 |
+
{{- tool | tojson }}
|
| 51 |
+
{%- endfor %}
|
| 52 |
+
{{- "\n</tools>" }}
|
| 53 |
+
{{- '\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>' }}
|
| 54 |
+
{%- if messages[0].role == 'system' %}
|
| 55 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 56 |
+
{%- if content %}
|
| 57 |
+
{{- '\n\n' + content }}
|
| 58 |
+
{%- endif %}
|
| 59 |
+
{%- endif %}
|
| 60 |
+
{{- '<|im_end|>\n' }}
|
| 61 |
+
{%- else %}
|
| 62 |
+
{%- if messages[0].role == 'system' %}
|
| 63 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 64 |
+
{{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
|
| 65 |
+
{%- endif %}
|
| 66 |
+
{%- endif %}
|
| 67 |
+
{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
|
| 68 |
+
{%- for message in messages[::-1] %}
|
| 69 |
+
{%- set index = (messages|length - 1) - loop.index0 %}
|
| 70 |
+
{%- if ns.multi_step_tool and message.role == "user" %}
|
| 71 |
+
{%- set content = render_content(message.content, false)|trim %}
|
| 72 |
+
{%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
|
| 73 |
+
{%- set ns.multi_step_tool = false %}
|
| 74 |
+
{%- set ns.last_query_index = index %}
|
| 75 |
+
{%- endif %}
|
| 76 |
+
{%- endif %}
|
| 77 |
+
{%- endfor %}
|
| 78 |
+
{%- if ns.multi_step_tool %}
|
| 79 |
+
{{- raise_exception('No user query found in messages.') }}
|
| 80 |
+
{%- endif %}
|
| 81 |
+
{%- for message in messages %}
|
| 82 |
+
{%- set content = render_content(message.content, true)|trim %}
|
| 83 |
+
{%- if message.role == "system" %}
|
| 84 |
+
{%- if not loop.first %}
|
| 85 |
+
{{- raise_exception('System message must be at the beginning.') }}
|
| 86 |
+
{%- endif %}
|
| 87 |
+
{%- elif message.role == "user" %}
|
| 88 |
+
{{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
|
| 89 |
+
{%- elif message.role == "assistant" %}
|
| 90 |
+
{%- set reasoning_content = '' %}
|
| 91 |
+
{%- if message.reasoning_content is string %}
|
| 92 |
+
{%- set reasoning_content = message.reasoning_content %}
|
| 93 |
+
{%- else %}
|
| 94 |
+
{%- if '</think>' in content %}
|
| 95 |
+
{%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
|
| 96 |
+
{%- set content = content.split('</think>')[-1].lstrip('\n') %}
|
| 97 |
+
{%- endif %}
|
| 98 |
+
{%- endif %}
|
| 99 |
+
{%- set reasoning_content = reasoning_content|trim %}
|
| 100 |
+
{%- if loop.index0 > ns.last_query_index %}
|
| 101 |
+
{{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
|
| 102 |
+
{%- else %}
|
| 103 |
+
{{- '<|im_start|>' + message.role + '\n' + content }}
|
| 104 |
+
{%- endif %}
|
| 105 |
+
{%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
|
| 106 |
+
{%- for tool_call in message.tool_calls %}
|
| 107 |
+
{%- if tool_call.function is defined %}
|
| 108 |
+
{%- set tool_call = tool_call.function %}
|
| 109 |
+
{%- endif %}
|
| 110 |
+
{%- if loop.first %}
|
| 111 |
+
{%- if content|trim %}
|
| 112 |
+
{{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 113 |
+
{%- else %}
|
| 114 |
+
{{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 115 |
+
{%- endif %}
|
| 116 |
+
{%- else %}
|
| 117 |
+
{{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 118 |
+
{%- endif %}
|
| 119 |
+
{%- if tool_call.arguments is defined %}
|
| 120 |
+
{%- for args_name, args_value in tool_call.arguments|items %}
|
| 121 |
+
{{- '<parameter=' + args_name + '>\n' }}
|
| 122 |
+
{%- 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 %}
|
| 123 |
+
{{- args_value }}
|
| 124 |
+
{{- '\n</parameter>\n' }}
|
| 125 |
+
{%- endfor %}
|
| 126 |
+
{%- endif %}
|
| 127 |
+
{{- '</function>\n</tool_call>' }}
|
| 128 |
+
{%- endfor %}
|
| 129 |
+
{%- endif %}
|
| 130 |
+
{{- '<|im_end|>\n' }}
|
| 131 |
+
{%- elif message.role == "tool" %}
|
| 132 |
+
{%- if loop.previtem and loop.previtem.role != "tool" %}
|
| 133 |
+
{{- '<|im_start|>user' }}
|
| 134 |
+
{%- endif %}
|
| 135 |
+
{{- '\n<tool_response>\n' }}
|
| 136 |
+
{{- content }}
|
| 137 |
+
{{- '\n</tool_response>' }}
|
| 138 |
+
{%- if not loop.last and loop.nextitem.role != "tool" %}
|
| 139 |
+
{{- '<|im_end|>\n' }}
|
| 140 |
+
{%- elif loop.last %}
|
| 141 |
+
{{- '<|im_end|>\n' }}
|
| 142 |
+
{%- endif %}
|
| 143 |
+
{%- else %}
|
| 144 |
+
{{- raise_exception('Unexpected message role.') }}
|
| 145 |
+
{%- endif %}
|
| 146 |
+
{%- endfor %}
|
| 147 |
+
{%- if add_generation_prompt %}
|
| 148 |
+
{{- '<|im_start|>assistant\n' }}
|
| 149 |
+
{%- if enable_thinking is defined and enable_thinking is true %}
|
| 150 |
+
{{- '<think>\n' }}
|
| 151 |
+
{%- else %}
|
| 152 |
+
{{- '<think>\n\n</think>\n\n' }}
|
| 153 |
+
{%- endif %}
|
| 154 |
+
{%- endif %}
|
config.json
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"Qwen3_5ForConditionalGeneration"
|
| 4 |
+
],
|
| 5 |
+
"dtype": "bfloat16",
|
| 6 |
+
"eos_token_id": 248046,
|
| 7 |
+
"image_token_id": 248056,
|
| 8 |
+
"model_type": "qwen3_5",
|
| 9 |
+
"pad_token_id": 248044,
|
| 10 |
+
"text_config": {
|
| 11 |
+
"attention_bias": false,
|
| 12 |
+
"attention_dropout": 0.0,
|
| 13 |
+
"attn_output_gate": true,
|
| 14 |
+
"bos_token_id": null,
|
| 15 |
+
"dtype": "bfloat16",
|
| 16 |
+
"eos_token_id": 248044,
|
| 17 |
+
"full_attention_interval": 4,
|
| 18 |
+
"head_dim": 256,
|
| 19 |
+
"hidden_act": "silu",
|
| 20 |
+
"hidden_size": 1024,
|
| 21 |
+
"initializer_range": 0.02,
|
| 22 |
+
"intermediate_size": 3584,
|
| 23 |
+
"layer_types": [
|
| 24 |
+
"linear_attention",
|
| 25 |
+
"linear_attention",
|
| 26 |
+
"linear_attention",
|
| 27 |
+
"full_attention",
|
| 28 |
+
"linear_attention",
|
| 29 |
+
"linear_attention",
|
| 30 |
+
"linear_attention",
|
| 31 |
+
"full_attention",
|
| 32 |
+
"linear_attention",
|
| 33 |
+
"linear_attention",
|
| 34 |
+
"linear_attention",
|
| 35 |
+
"full_attention",
|
| 36 |
+
"linear_attention",
|
| 37 |
+
"linear_attention",
|
| 38 |
+
"linear_attention",
|
| 39 |
+
"full_attention",
|
| 40 |
+
"linear_attention",
|
| 41 |
+
"linear_attention",
|
| 42 |
+
"linear_attention",
|
| 43 |
+
"full_attention",
|
| 44 |
+
"linear_attention",
|
| 45 |
+
"linear_attention",
|
| 46 |
+
"linear_attention",
|
| 47 |
+
"full_attention"
|
| 48 |
+
],
|
| 49 |
+
"linear_conv_kernel_dim": 4,
|
| 50 |
+
"linear_key_head_dim": 128,
|
| 51 |
+
"linear_num_key_heads": 16,
|
| 52 |
+
"linear_num_value_heads": 16,
|
| 53 |
+
"linear_value_head_dim": 128,
|
| 54 |
+
"mamba_ssm_dtype": "float32",
|
| 55 |
+
"max_position_embeddings": 262144,
|
| 56 |
+
"mlp_only_layers": [],
|
| 57 |
+
"model_type": "qwen3_5_text",
|
| 58 |
+
"mtp_num_hidden_layers": 1,
|
| 59 |
+
"mtp_use_dedicated_embeddings": false,
|
| 60 |
+
"num_attention_heads": 8,
|
| 61 |
+
"num_hidden_layers": 24,
|
| 62 |
+
"num_key_value_heads": 2,
|
| 63 |
+
"pad_token_id": null,
|
| 64 |
+
"partial_rotary_factor": 0.25,
|
| 65 |
+
"rms_norm_eps": 1e-06,
|
| 66 |
+
"rope_parameters": {
|
| 67 |
+
"mrope_interleaved": true,
|
| 68 |
+
"mrope_section": [
|
| 69 |
+
11,
|
| 70 |
+
11,
|
| 71 |
+
10
|
| 72 |
+
],
|
| 73 |
+
"partial_rotary_factor": 0.25,
|
| 74 |
+
"rope_theta": 10000000,
|
| 75 |
+
"rope_type": "default"
|
| 76 |
+
},
|
| 77 |
+
"tie_word_embeddings": true,
|
| 78 |
+
"use_cache": true,
|
| 79 |
+
"vocab_size": 248320
|
| 80 |
+
},
|
| 81 |
+
"tie_word_embeddings": true,
|
| 82 |
+
"transformers_version": "5.15.0",
|
| 83 |
+
"use_cache": false,
|
| 84 |
+
"video_token_id": 248057,
|
| 85 |
+
"vision_config": {
|
| 86 |
+
"deepstack_visual_indexes": [],
|
| 87 |
+
"depth": 12,
|
| 88 |
+
"dtype": "bfloat16",
|
| 89 |
+
"hidden_act": "gelu_pytorch_tanh",
|
| 90 |
+
"hidden_size": 768,
|
| 91 |
+
"in_channels": 3,
|
| 92 |
+
"initializer_range": 0.02,
|
| 93 |
+
"intermediate_size": 3072,
|
| 94 |
+
"model_type": "qwen3_5_vision",
|
| 95 |
+
"num_heads": 12,
|
| 96 |
+
"num_position_embeddings": 2304,
|
| 97 |
+
"out_hidden_size": 1024,
|
| 98 |
+
"patch_size": 16,
|
| 99 |
+
"spatial_merge_size": 2,
|
| 100 |
+
"temporal_patch_size": 2
|
| 101 |
+
},
|
| 102 |
+
"vision_end_token_id": 248054,
|
| 103 |
+
"vision_start_token_id": 248053
|
| 104 |
+
}
|
generation_config.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_from_model_config": true,
|
| 3 |
+
"eos_token_id": [
|
| 4 |
+
248046,
|
| 5 |
+
248044
|
| 6 |
+
],
|
| 7 |
+
"pad_token_id": 248044,
|
| 8 |
+
"transformers_version": "5.15.0",
|
| 9 |
+
"use_cache": true
|
| 10 |
+
}
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:27542c7c65470285272b25f3d6ab1b479490eaaade84ef7d93ebe059d7a17a36
|
| 3 |
+
size 1706030528
|
processor_config.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"image_processor": {
|
| 3 |
+
"do_convert_rgb": true,
|
| 4 |
+
"do_normalize": true,
|
| 5 |
+
"do_rescale": true,
|
| 6 |
+
"do_resize": true,
|
| 7 |
+
"image_mean": [
|
| 8 |
+
0.5,
|
| 9 |
+
0.5,
|
| 10 |
+
0.5
|
| 11 |
+
],
|
| 12 |
+
"image_processor_type": "Qwen2VLImageProcessor",
|
| 13 |
+
"image_std": [
|
| 14 |
+
0.5,
|
| 15 |
+
0.5,
|
| 16 |
+
0.5
|
| 17 |
+
],
|
| 18 |
+
"merge_size": 2,
|
| 19 |
+
"patch_size": 16,
|
| 20 |
+
"resample": 3,
|
| 21 |
+
"rescale_factor": 0.00392156862745098,
|
| 22 |
+
"size": {
|
| 23 |
+
"longest_edge": 2097152,
|
| 24 |
+
"shortest_edge": 65536
|
| 25 |
+
},
|
| 26 |
+
"temporal_patch_size": 2
|
| 27 |
+
},
|
| 28 |
+
"processor_class": "Qwen3VLProcessor",
|
| 29 |
+
"video_processor": {
|
| 30 |
+
"do_convert_rgb": true,
|
| 31 |
+
"do_normalize": true,
|
| 32 |
+
"do_rescale": true,
|
| 33 |
+
"do_resize": true,
|
| 34 |
+
"do_sample_frames": true,
|
| 35 |
+
"fps": 2,
|
| 36 |
+
"image_mean": [
|
| 37 |
+
0.5,
|
| 38 |
+
0.5,
|
| 39 |
+
0.5
|
| 40 |
+
],
|
| 41 |
+
"image_std": [
|
| 42 |
+
0.5,
|
| 43 |
+
0.5,
|
| 44 |
+
0.5
|
| 45 |
+
],
|
| 46 |
+
"max_frames": 768,
|
| 47 |
+
"merge_size": 2,
|
| 48 |
+
"min_frames": 4,
|
| 49 |
+
"patch_size": 16,
|
| 50 |
+
"resample": 3,
|
| 51 |
+
"rescale_factor": 0.00392156862745098,
|
| 52 |
+
"return_metadata": false,
|
| 53 |
+
"size": {
|
| 54 |
+
"longest_edge": 25165824,
|
| 55 |
+
"shortest_edge": 4096
|
| 56 |
+
},
|
| 57 |
+
"temporal_patch_size": 2,
|
| 58 |
+
"video_processor_type": "Qwen3VLVideoProcessor"
|
| 59 |
+
}
|
| 60 |
+
}
|
prompt.txt
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
You are a palaeographer producing CATMuS-compliant training data from the attached page image.
|
| 2 |
+
|
| 3 |
+
Transcribe the page LINE BY LINE: output exactly one line of text for each physical written line on the page, in reading order, top to bottom (and for a page in columns, finish the left column before starting the right one).
|
| 4 |
+
|
| 5 |
+
CATMuS TRANSCRIPTION RULES
|
| 6 |
+
You MUST follow the CATMuS guidelines (Consistent Approaches to Transcribing ManuScripts) exactly. CATMuS is a GRAPHEMIC transcription standard for producing machine-learning training data: reproduce the sequence of letters and signs as written, reduced to the modern Latin alphabet, with NO editorial intervention.
|
| 7 |
+
|
| 8 |
+
A. GENERAL PRINCIPLES
|
| 9 |
+
A1. Transcribe what is on the page, grapheme by grapheme, in the order written. Never translate, modernize, correct, complete, or improve the text.
|
| 10 |
+
A2. Scribal errors, repetitions, and odd spellings MUST be reproduced as written. Never mark them, never add [sic].
|
| 11 |
+
A3. Never expand abbreviations. Never resolve them silently. See section D.
|
| 12 |
+
A4. Decoration, ornament, ruling, and stains are NOT text: do not transcribe them.
|
| 13 |
+
A5. Prefer Unicode's public domain and MUFI characters; the character table in section I is authoritative and takes priority over your own preference.
|
| 14 |
+
A6. Do not add any markup, tags, comments, notes, numbering, or explanations of your own.
|
| 15 |
+
|
| 16 |
+
B. LETTERS AND NUMBERS
|
| 17 |
+
B1. Transcribe alphabetic signs with the modern Latin alphabet. Letterform variants (allographs) are NOT distinguished.
|
| 18 |
+
B2. Long s (ſ) MUST be transcribed as s. Insular/other obsolete letterforms MUST be reduced to their modern Latin equivalent (e.g. ƞ as n).
|
| 19 |
+
B3. Capital letters MUST be preserved. Small caps MUST be rendered as ordinary uppercase. Lowercase MUST NOT be normalized to uppercase.
|
| 20 |
+
B3a. Case follows the script on the page: a line written in a display majuscule (rustic capitals, uncial, epigraphic capitals — typical of incipits and rubrics) MUST be transcribed in uppercase; a line written in minuscule MUST be transcribed in lowercase, whatever its size or ink.
|
| 21 |
+
B3b. Letters spaced widely apart for display MUST NOT be split into separate words: transcribe INNOCENTI, never IN NOCEN TI. Insert a space only where the source marks an actual word division.
|
| 22 |
+
B4. Italic, bold, and other stylistic variation MUST NOT be transcribed.
|
| 23 |
+
B5. Ramist letters: in medieval sources v MUST be transcribed as u, and j MUST be transcribed as i (they are allographic, not separate letters). In modern/printed sources follow the source's own usage.
|
| 24 |
+
B6. Ligatures MUST be split into their separate letters, EXCEPT the ampersand & [U+0026], which MUST be kept as &, and ß [U+00DF] in modern sources, which MUST be kept as ß.
|
| 25 |
+
B7. Numbers MUST be transcribed as they appear, Roman or Arabic. Keep the dots or other punctuation surrounding a numeral (e.g. .iiii.). Apply B5 to Roman numerals (uii, not vij).
|
| 26 |
+
B8. Old-style Roman numerals CIↃ and IↃ MUST be transcribed as M and D.
|
| 27 |
+
B9. Drop capitals and oversized/decorated initials MUST be transcribed as uppercase letters, and belong to their OWN line, not to the text line they introduce.
|
| 28 |
+
|
| 29 |
+
C. SEGMENTATION AND SPACING
|
| 30 |
+
C1. Word division MUST follow the source as closely as possible. Where the spacing is genuinely unclear, fall back on modernized word division using a single space [U+0020].
|
| 31 |
+
C2. Original agglutination MUST be kept and MUST NOT be given an apostrophe: write qil, not q'il.
|
| 32 |
+
C3. Doubled consonants marking elision MUST be kept: write arriva, not á riva.
|
| 33 |
+
C4. Locutions being lexicalized MUST be written as they appear (enchargier or en chargier — whichever the page shows).
|
| 34 |
+
C5. Hyphenation MUST be transcribed only where the source marks it, with - [U+002D], whatever its shape in the source. Never add a hyphen the source does not have. If the mark is repeated at the start of the next line, transcribe that one as - too.
|
| 35 |
+
C6. Diastoles — vertical or oblique strokes drawn between two letters to show they belong to different words — MUST be transcribed as / [U+002F].
|
| 36 |
+
C7. No double spaces. No space before a punctuation mark. One space after a punctuation mark, except after quotation marks and apostrophes.
|
| 37 |
+
C7a. Medieval scribes routinely leave a gap on BOTH sides of a dot or colon. You MUST NOT reproduce that gap: spacing is normalized, not imitated. Write opus est. ut and euanescãt: opus — NEVER opus est . ut, NEVER euanescãt : opus, NEVER est .ut. Every . : ; , ! ? touches the character before it and is followed by exactly one space (or by the end of the line).
|
| 38 |
+
C7b. Never use uppercase I or L inside an otherwise lowercase word: Basilea, never BasiIea. A tall undotted stroke inside a lowercase word is l.
|
| 39 |
+
|
| 40 |
+
D. ABBREVIATIONS
|
| 41 |
+
D1. Abbreviations MUST be reproduced and MUST NOT be expanded. Expansion is a separate task performed after transcription.
|
| 42 |
+
D2. Use the character whose SHAPE matches the mark on the page, and whose semantic value is correct: for p with a stroke through the descender use ꝑ [U+A751], never the Armenian ք [U+0554].
|
| 43 |
+
D3. A tilde or macron above letters MUST be transcribed as combining tilde [U+0303] on the letter it sits over: ẽ, õ, ũ, ñ, ã. A straight bar and a wavy stroke are the SAME sign in CATMuS: ALWAYS use combining tilde [U+0303] and NEVER combining macron [U+0304] — write scõrũ, never scōrū.
|
| 44 |
+
D4. When one tilde covers more than one element, either double it (ẽẽ = esse) or place it over the single most appropriate letter. Be consistent within a page.
|
| 45 |
+
D5. Superscript/overwritten letters MUST be transcribed as the matching COMBINING letter on the base letter (e.g. q + [U+0365] = qͥ, u + [U+036C] = uͬ), not as a normal letter and not as a caret sequence.
|
| 46 |
+
D6. Strike-through abbreviations MUST use the dedicated characters: đ ħ ł ꝑ ꝓ ꝙ ꝵ ẜ (section I).
|
| 47 |
+
D7. The Tironian et MUST be transcribed as ⁊ [U+204A]; an ampersand-shaped et as & [U+0026]. A tilde over either (⁊̃) is kept.
|
| 48 |
+
D8. Do not invent abbreviation characters. If no table character matches the mark, transcribe the letters that are actually written and place a combining tilde [U+0303] over the letter carrying the mark.
|
| 49 |
+
|
| 50 |
+
E. ACCENTS AND CEDILLAS
|
| 51 |
+
E1. Accents MUST be transcribed wherever they appear.
|
| 52 |
+
E2. A pointed/dotted i MUST be transcribed as a plain i — NEVER as í.
|
| 53 |
+
E3. An accent drawn out of place (common in cursive) MUST be transcribed over the letter it logically belongs to, not where it physically sits.
|
| 54 |
+
E4. Cedillas MUST be transcribed with the combining cedilla [U+0327]: ȩ (which in 10th–13th c. Latin usually stands for the ae diphthong).
|
| 55 |
+
|
| 56 |
+
F. PUNCTUATION
|
| 57 |
+
F1. Medieval sources: EVERY single dot, at any height (low, mid, high, punctus elevatus), MUST be transcribed as . [U+002E].
|
| 58 |
+
F2. Medieval sources: double dots and the punctus interrogativus MUST be transcribed as : [U+003A].
|
| 59 |
+
F3. The virgula suspensiva MUST be transcribed as / [U+002F] (same sign as the diastole).
|
| 60 |
+
F4. Modern/contemporary sources: transcribe punctuation exactly as it appears. All dashes, long or short, MUST be transcribed as - [U+002D]. Any quotation-mark-like character MUST be transcribed as " [U+0022]; any apostrophe-like character as ' [U+0027].
|
| 61 |
+
F5. Never add punctuation that is not on the page, and never delete punctuation that is.
|
| 62 |
+
|
| 63 |
+
G. FUNCTIONAL AND CORRECTION SIGNS
|
| 64 |
+
G1. Pilcrows and their variants (reverse pilcrow, ornamental paragraph marks) MUST be transcribed as ¶ [U+00B6], whatever their orientation. In modern documents a section sign is § [U+00A7].
|
| 65 |
+
G1a. A pilcrow belongs to the line it stands at the head of, followed by one space: "¶ Bonũ ergo est.". NEVER put a pilcrow on a line of its own.
|
| 66 |
+
G2. Insertion/omission carets MUST be transcribed as ‸ [U+2038].
|
| 67 |
+
G3. Reference marks (asterisks, ※ and similar) MUST be normalized to * [U+002A].
|
| 68 |
+
G4. Manicules MUST be transcribed as ☞ [U+261E].
|
| 69 |
+
G5. Text struck out or cancelled by the scribe MUST be kept and wrapped in ⟦ [U+27E6] and ⟧ [U+27E7]: ⟦chapelain⟧.
|
| 70 |
+
G5a. Mark ⟦ ⟧ ONLY where you can actually see strokes cancelling the letters. Never bracket a passage because it reads oddly, looks redundant, or is hard to make out.
|
| 71 |
+
G6. Text that cannot be read (damage, stain, erasure) MUST be marked with [ [U+005B] and ] [U+005D], optionally containing one dot per illegible letter: escr[]pt, or escr[..]pt.
|
| 72 |
+
G6a. Brackets NEVER contain a conjecture: write p[..]uintias, never p[ro]uintias, and never [s] for a letter you think was there. If you can read a letter, transcribe it; if you cannot, it is dots or nothing.
|
| 73 |
+
G7. An interlinear addition is its own text line, not part of the line above or below.
|
| 74 |
+
|
| 75 |
+
H. DETERMINISM
|
| 76 |
+
H1. Apply these rules mechanically. Given the same page you MUST produce the same output every time.
|
| 77 |
+
H2. Never guess at content you cannot see: use the [] convention of G6 instead.
|
| 78 |
+
H3. Never output an explanation, apology, confidence note, or alternative reading.
|
| 79 |
+
H4. Transcribe each line of the page exactly once, then move to the next line. NEVER repeat a line, a phrase, or a reference more times than it is written on the page.
|
| 80 |
+
H5. Stop as soon as you reach the last line on the page. If you notice yourself producing the same text again, stop immediately — a truncated transcription is far better than a repeated one.
|
| 81 |
+
H6. Read the page; do not continue a pattern. Never extend a series (of citations, numbers, or letters) beyond what is actually written.
|
| 82 |
+
|
| 83 |
+
I. CHARACTER TABLE (authoritative — prefer these characters)
|
| 84 |
+
Strike/bar abbreviations: đ [U+0111] · ħ [U+0127] · ł [U+0142] · ꝑ [U+A751] p-with-stroke · ꝓ [U+A753] p-with-flourish · ꝙ [U+A759] q-with-stroke · ꝵ [U+A775] -rum · ẜ [U+1E9C] long-s-with-stroke
|
| 85 |
+
Standalone abbreviation signs: ⁊ [U+204A] et · & [U+0026] et · ꝭ [U+A76D] -is · ꝯ [U+A76F] con- · ꝰ [U+A770] -us/-os (superscript) · ≈ [U+2248] esse · ÷ [U+00F7] est/id est · ∅ [U+2205] instans
|
| 86 |
+
Combining marks (placed on the preceding letter): ̃ [U+0303] tilde — nasalisation, suspension, contraction · ̾ [U+033E] vertical tilde — -r/-er · ̧ [U+0327] cedilla
|
| 87 |
+
Combining letters: ͣ [U+0363] a · ᷓ [U+1DD3] flattened open a · ͨ [U+0368] c · ͤ [U+0364] e · ͥ [U+0365] i · ͫ [U+036B] m · ͦ [U+0366] o · ͬ [U+036C] r · ᷤ [U+1DE4] s · ͭ [U+036D] t · ͯ [U+036F] x · ᷑ [U+1DD1] ur · ᷒ [U+1DD2] us
|
| 88 |
+
Punctuation and marks: . [U+002E] · : [U+003A] · / [U+002F] diastole & virgula · - [U+002D] hyphenation · ¶ [U+00B6] · § [U+00A7] · * [U+002A] · ‸ [U+2038] · ☞ [U+261E] · ⟦ ⟧ [U+27E6 U+27E7] struck out · [ ] [U+005B U+005D] illegible
|
| 89 |
+
Units: ℥ [U+2125] ounce · 𐆒 [U+10192] semuncia
|
| 90 |
+
|
| 91 |
+
J. LINE-LEVEL OUTPUT
|
| 92 |
+
J0. Before you write each line, apply the spacing normalization of rules C7–C7a: no space before . : ; , ? !, exactly one space after, and never two spaces in a row — however widely spaced the marks are on the page.
|
| 93 |
+
J1. One physical line on the page = one line of output. NEVER merge two written lines into one output line, and NEVER split one written line across two.
|
| 94 |
+
J2. Keep each line exactly as it ends on the page, including its hyphenation mark (rule C5). Do not rejoin a word broken across two lines.
|
| 95 |
+
J3. A drop capital or oversized initial is its own output line (rule B9).
|
| 96 |
+
J4. An interlinear addition is its own output line, placed after the line it sits above (rule G7).
|
| 97 |
+
J5. Marginalia, rubrics, running titles, page numbers, catchwords, and signature marks ARE text and MUST each be transcribed on their own line, in the position where a reader meets them (running title and page number first if they head the page, catchword last).
|
| 98 |
+
J6. Leave no blank lines between output lines.
|
| 99 |
+
J7. Do not number the lines. Do not add a heading. Do not wrap the output in quotes or code fences.
|
| 100 |
+
J8. If the page carries no text at all, return exactly: VLM-NO-TEXT
|
| 101 |
+
|
| 102 |
+
Return ONLY the transcription.
|
tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:06b9509352d2af50381ab2247e083b80d32d5c0aba91c272ca9ff729b6a0e523
|
| 3 |
+
size 19989325
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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": null,
|
| 8 |
+
"clean_up_tokenization_spaces": false,
|
| 9 |
+
"eos_token": "<|im_end|>",
|
| 10 |
+
"errors": "replace",
|
| 11 |
+
"image_token": "<|image_pad|>",
|
| 12 |
+
"is_local": true,
|
| 13 |
+
"local_files_only": true,
|
| 14 |
+
"model_max_length": 262144,
|
| 15 |
+
"model_specific_special_tokens": {
|
| 16 |
+
"audio_bos_token": "<|audio_start|>",
|
| 17 |
+
"audio_eos_token": "<|audio_end|>",
|
| 18 |
+
"audio_token": "<|audio_pad|>",
|
| 19 |
+
"image_token": "<|image_pad|>",
|
| 20 |
+
"video_token": "<|video_pad|>",
|
| 21 |
+
"vision_bos_token": "<|vision_start|>",
|
| 22 |
+
"vision_eos_token": "<|vision_end|>"
|
| 23 |
+
},
|
| 24 |
+
"pad_token": "<|endoftext|>",
|
| 25 |
+
"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+",
|
| 26 |
+
"processor_class": "Qwen3VLProcessor",
|
| 27 |
+
"split_special_tokens": false,
|
| 28 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 29 |
+
"unk_token": null,
|
| 30 |
+
"video_token": "<|video_pad|>",
|
| 31 |
+
"vision_bos_token": "<|vision_start|>",
|
| 32 |
+
"vision_eos_token": "<|vision_end|>"
|
| 33 |
+
}
|