multimodalart's picture
multimodalart HF Staff
Upload app.py with huggingface_hub
184af76 verified
Raw
History Blame Contribute Delete
5.17 kB
import spaces # MUST come before torch / any CUDA-touching import
import torch
from transformers import AutoProcessor, AutoModelForImageTextToText
import gradio as gr
from PIL import Image
import time
MODEL_ID = "KDLAI/KDL-Frontier-Parser-nano"
processor = AutoProcessor.from_pretrained(MODEL_ID, trust_remote_code=True)
model = AutoModelForImageTextToText.from_pretrained(
MODEL_ID,
dtype=torch.bfloat16,
attn_implementation="sdpa",
trust_remote_code=True,
).to("cuda").eval()
# Task prompts per the model card (note the leading newline).
TASK_PROMPTS = {
"Layout Detection": "\nLayout Detection:",
"Text Recognition": "\nText Recognition:",
"Table Recognition": "\nTable Recognition:",
"Formula Recognition": "\nFormula Recognition:",
"Image Analysis": "\nImage Analysis:",
}
CSS = """
#col-container { max-width: 1100px; margin: 0 auto; }
.dark .gradio-container { color: var(--body-text-color); }
"""
@spaces.GPU(duration=120)
def parse_document(image: Image.Image, task: str, custom_prompt: str) -> str:
"""Run the KDL-Frontier-Parser-nano VLM on a document image.
Args:
image: a page image (PNG/JPEG) of the document to parse.
task: one of Layout Detection, Text Recognition, Table Recognition,
Formula Recognition, Image Analysis.
custom_prompt: optional free-text prompt appended after the task
header; leave empty for the canonical task prompt.
Returns:
The model's parsed text (OTSL for tables, etc.).
"""
if image is None:
return "Please upload a document image."
if image.mode != "RGB":
image = image.convert("RGB")
if task == "Custom" and custom_prompt.strip():
prompt_text = custom_prompt
else:
prompt_text = TASK_PROMPTS.get(task, "\nText Recognition:")
if custom_prompt.strip():
prompt_text = prompt_text + " " + custom_prompt.strip()
messages = [
{
"role": "user",
"content": [
{"type": "image"},
{"type": "text", "text": prompt_text},
],
}
]
chat_text = processor.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False,
)
inputs = processor(
text=chat_text,
images=image,
return_tensors="pt",
padding=True,
).to("cuda")
t0 = time.perf_counter()
with torch.inference_mode():
output_ids = model.generate(
**inputs,
do_sample=False,
temperature=0.0,
top_k=1,
top_p=1.0,
max_new_tokens=4096,
)
elapsed = time.perf_counter() - t0
generated = output_ids[:, inputs["input_ids"].shape[1]:]
text = processor.batch_decode(
generated,
skip_special_tokens=False,
)[0]
footer = f"\n\n---\n*Inference: {elapsed:.1f}s · task: {task}*"
return text + footer
with gr.Blocks() as demo:
gr.Markdown(
"# KDL-Frontier-Parser-nano\n"
"A 1.2B-parameter document-parsing VLM by [KoreaDeep](https://www.koreadeep.com/). "
"Upload a page image, pick a task, and run. See the "
"[model card](https://huggingface.co/KDLAI/KDL-Frontier-Parser-nano) for details."
)
with gr.Row():
with gr.Column(scale=1):
image_in = gr.Image(type="pil", label="Document image", sources=["upload"])
task = gr.Radio(
choices=list(TASK_PROMPTS.keys()) + ["Custom"],
value="Layout Detection",
label="Task",
)
custom_prompt = gr.Textbox(
label="Custom prompt (optional)",
placeholder="Used only when Task = Custom; otherwise appended to the task header.",
lines=2,
)
run_btn = gr.Button("Run", variant="primary")
with gr.Column(scale=1):
output = gr.Textbox(label="Parsed output", lines=18)
with gr.Accordion("Tips", open=False):
gr.Markdown(
"- **Layout Detection** returns the page layout (regions + types).\n"
"- **Text Recognition** extracts text per region.\n"
"- **Table Recognition** returns OTSL.\n"
"- **Formula Recognition** parses formulas.\n"
"- **Image Analysis** describes figure content.\n"
"- Feed page images, not PDFs. Greedy decoding (temperature=0) is used."
)
gr.Examples(
examples=[
["examples/astronaut.jpg", "Layout Detection", ""],
["examples/astronaut.jpg", "Text Recognition", ""],
["examples/noodles.jpg", "Image Analysis", ""],
],
inputs=[image_in, task, custom_prompt],
outputs=output,
fn=parse_document,
cache_examples=True,
cache_mode="lazy",
)
run_btn.click(
fn=parse_document,
inputs=[image_in, task, custom_prompt],
outputs=output,
api_name="parse",
)
if __name__ == "__main__":
demo.launch(mcp_server=True, theme=gr.themes.Citrus(), css=CSS)