Image-Text-to-Text
Transformers
Safetensors
Vietnamese
English
qwen3_5
ocr
document-parsing
vietnamese
vllm
distillation
pruning
grounding
conversational
Instructions to use DuyTa/Peppercorn_Base with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use DuyTa/Peppercorn_Base with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="DuyTa/Peppercorn_Base") 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)# pip install -U transformers accelerate # Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("DuyTa/Peppercorn_Base") model = AutoModelForMultimodalLM.from_pretrained("DuyTa/Peppercorn_Base", 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=256) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use DuyTa/Peppercorn_Base with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "DuyTa/Peppercorn_Base" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "DuyTa/Peppercorn_Base", "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/DuyTa/Peppercorn_Base
- SGLang
How to use DuyTa/Peppercorn_Base 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 "DuyTa/Peppercorn_Base" \ --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": "DuyTa/Peppercorn_Base", "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 "DuyTa/Peppercorn_Base" \ --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": "DuyTa/Peppercorn_Base", "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 DuyTa/Peppercorn_Base with Docker Model Runner:
docker model run hf.co/DuyTa/Peppercorn_Base
File size: 5,624 Bytes
0909e6c | 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 | """Stop a page read that has started to loop, at serve time.
A looping page runs to max_tokens (4,096): on the proxy models those pages take a third or more of all
decode time, and the tokens after the loop starts are garbage that only raise CER. The guard watches
the text as it streams and reports where the loop began; the client then aborts the request (vLLM
frees its slot) and keeps the text up to the end of the first copy.
Two signals, both on the tail of the text:
period the tail is one unit of p characters repeated r times (exact), with p*r over a length that
grows for short units: dot leaders and "-----" rules are legitimate short repeats. Units with
table markup need far more (12 copies, 1,500 chars): empty rows and repeated header cells
are real table content.
lines the same line (stripped, at least `min_line` chars) repeated `line_reps` times in a row.
Tables and lists repeat structure, not whole identical lines.
Thresholds were set on eval v2 outputs (`python -m lightonocr_test.loop_guard tune ...`): no cut on a
page that stopped on its own (v1-ipo2), while catching most pages that hit the cap.
"""
from __future__ import annotations
import json
import sys
class LoopGuard:
def __init__(self, min_span: int = 240, short_unit: int = 12, short_span: int = 600,
table_span: int = 1500, table_reps: int = 12,
line_reps: int = 4, min_line: int = 12, every: int = 32):
self.min_span, self.short_unit, self.short_span = min_span, short_unit, short_span
self.table_span, self.table_reps = table_span, table_reps
self.line_reps, self.min_line, self.every = line_reps, min_line, every
self._checked = 0
def _period(self, t: str) -> int | None:
n = len(t)
for p in range(1, 401):
span = self.short_span if p <= self.short_unit else self.min_span
if 3 * p > n or span > n:
continue
unit = t[n - p:]
r = 1
while (r + 1) * p <= n and t[n - (r + 1) * p:n - r * p] == unit:
r += 1
need = 3
if "<t" in unit or "</t" in unit: # empty rows, repeated header cells: tables repeat legitimately
span, need = max(span, self.table_span), self.table_reps
if r >= need and r * p >= span:
return n - r * p + p # keep one copy of the unit
return None
def _lines(self, t: str) -> int | None:
lines = t.split("\n")
if len(lines) < self.line_reps + 1:
return None
body = lines[:-1] # the last line may still be growing
last = body[-1].strip()
if len(last) < self.min_line:
return None
r = 1
while r < len(body) and body[-1 - r].strip() == last:
r += 1
if r >= self.line_reps:
keep = "\n".join(lines[:len(body) - r + 1])
return len(keep)
return None
def check(self, text: str) -> int | None:
"""Cut position if `text` ends in a loop, else None. Cheap to call on every streamed chunk:
it only looks again after `every` new characters."""
if len(text) - self._checked < self.every:
return None
self._checked = len(text)
tail_start = max(0, len(text) - 4000)
tail = text[tail_start:]
for f in (self._period, self._lines):
c = f(tail)
if c is not None:
return tail_start + c
return None
def scan(text: str, step: int = 16, **kw) -> int | None:
"""Replay a finished output in `step`-character chunks, as a stream would arrive."""
g = LoopGuard(**kw)
for end in range(step, len(text) + step, step):
c = g.check(text[:end])
if c is not None:
return c
return None
def tune(paths: list[str]) -> None:
"""Per eval file: cuts on pages that stopped on their own (false alarms) and on capped pages,
characters saved, and CER before/after cutting (needs the references: eval_by_length sets)."""
from rapidfuzz.distance import Levenshtein
from .eval_by_length import sets, texts
refs = {}
for name, rows in sets(["v2"]).items():
for k, (_, ref, meta) in enumerate(rows):
refs[(name, k)] = (ref, meta)
for p in paths:
rows = [json.loads(l) for l in open(p, encoding="utf-8")]
fa = caught = capped = 0
chars = saved = 0
cer0 = cer1 = 0.0
n = 0
for r in rows:
h = r.get("hyp") or ""
c = scan(h)
chars += len(h)
if r["capped"]:
capped += 1
caught += c is not None
elif c is not None:
fa += 1
key = (r["set"], r["k"])
if key in refs:
ref, meta = refs[key]
R, H0 = texts(ref, meta, h)
_, H1 = texts(ref, meta, h[:c] if c is not None else h)
cer0 += Levenshtein.normalized_distance(R, H0)
cer1 += Levenshtein.normalized_distance(R, H1)
n += 1
if c is not None:
saved += len(h) - c
print(f"{p}: pages {len(rows)} | false cuts {fa} of {len(rows) - capped} stopped pages | "
f"caught {caught} of {capped} capped | chars saved {100 * saved / max(1, chars):.1f}% | "
f"CER {cer0 / max(1, n):.4f} -> {cer1 / max(1, n):.4f}")
if __name__ == "__main__":
if sys.argv[1] == "tune":
tune(sys.argv[2:])
|