Download df from don0726/Token: direct link, hf CLI and curl.
- Browser
- Download file 7.08 kB
-
https://huggingface.co/spaces/don0726/Token/resolve/main/df
- Command line
-
hf download hf://spaces/don0726/Token/df
-
curl -L -o df https://huggingface.co/spaces/don0726/Token/resolve/main/df
7.08 kB
| """ | |
| DeepSeek-OCR Gradio Space (CPU) | |
| ------------------------------- | |
| Upload an image and run it through deepseek-ai/DeepSeek-OCR using the | |
| built-in "Free OCR" prompt mode (plain text extraction, no markdown/ | |
| grounding boxes). A "Markdown" mode is also included since it's a | |
| one-line prompt swap. | |
| This version runs on CPU only (no CUDA, no flash-attention). The model | |
| stays in bfloat16, matching hardcoded dtype casts inside its own custom | |
| code — PyTorch supports bfloat16 tensors on CPU, just without hardware | |
| acceleration, so it works but is slow. DeepSeek-OCR is a vision-language | |
| model, so expect anywhere from ~30s to a few minutes per image depending | |
| on resolution preset and CPU. Use the "Tiny" or "Small" resolution | |
| presets for faster (if lower-fidelity) results. | |
| """ | |
| import os | |
| import re | |
| import shutil | |
| import tempfile | |
| import gradio as gr | |
| import torch | |
| from transformers import AutoModel, AutoTokenizer | |
| MODEL_NAME = "deepseek-ai/DeepSeek-OCR" | |
| # Force CPU regardless of what's visible to torch. | |
| os.environ["CUDA_VISIBLE_DEVICES"] = "" | |
| DEVICE = "cpu" | |
| # --------------------------------------------------------------------------- | |
| # Model loading (once, at startup) | |
| # --------------------------------------------------------------------------- | |
| print("Loading tokenizer...") | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True) | |
| print("Loading model on CPU (this can take a while, and first run also " | |
| "downloads several GB of weights)...") | |
| model = AutoModel.from_pretrained( | |
| MODEL_NAME, | |
| trust_remote_code=True, | |
| use_safetensors=True, | |
| ) | |
| # NOTE: don't cast to float32 here. DeepSeek-OCR's own forward pass hardcodes | |
| # casting image inputs to bfloat16 internally (independent of the model's | |
| # actual dtype), so the model's weights must also be bfloat16 or you get a | |
| # "Input type (BFloat16) and bias type (float) should be the same" crash. | |
| # PyTorch CPU does support bfloat16 tensors/ops (just without hardware | |
| # acceleration), so this works, it's just slow. | |
| model = model.eval().to(DEVICE).to(torch.bfloat16) | |
| # --------------------------------------------------------------------------- | |
| # DeepSeek-OCR's own `model.infer()` method has `.cuda()` calls hardcoded | |
| # directly in its custom modeling code (independent of what device the model | |
| # itself was loaded on), so it crashes on CPU-only hardware with | |
| # "Found no NVIDIA driver". Since the model and all its tensors already live | |
| # on CPU here, we monkey-patch `.cuda()` into a no-op that just returns the | |
| # tensor/module unchanged — this makes those hardcoded calls harmless instead | |
| # of failing. | |
| if not torch.cuda.is_available(): | |
| torch.Tensor.cuda = lambda self, *args, **kwargs: self | |
| torch.nn.Module.cuda = lambda self, *args, **kwargs: self | |
| # Preset resolution modes exposed by the model's own infer() signature | |
| SIZE_PRESETS = { | |
| "Tiny (512, fastest)": dict(base_size=512, image_size=512, crop_mode=False), | |
| "Small (640)": dict(base_size=640, image_size=640, crop_mode=False), | |
| "Base (1024, high quality)": dict(base_size=1024, image_size=1024, crop_mode=False), | |
| "Large (1280, best quality)": dict(base_size=1280, image_size=1280, crop_mode=False), | |
| "Gundam (1024/640 crop, best for documents)": dict(base_size=1024, image_size=640, crop_mode=True), | |
| } | |
| TASK_PROMPTS = { | |
| "Free OCR (plain text)": "<image>\nFree OCR. ", | |
| "Markdown (structure + tables)": "<image>\n<|grounding|>Convert the document to markdown. ", | |
| } | |
| def run_ocr(image, task_choice, size_choice, progress=gr.Progress(track_tqdm=True)): | |
| if image is None: | |
| return "Please upload an image first." | |
| prompt = TASK_PROMPTS[task_choice] | |
| preset = SIZE_PRESETS[size_choice] | |
| # model.infer() reads the image from a file path and writes its result to | |
| # output_path. Point both at a temp dir. | |
| tmp_dir = tempfile.mkdtemp(prefix="deepseek_ocr_") | |
| image_path = os.path.join(tmp_dir, "input.png") | |
| image.convert("RGB").save(image_path) | |
| try: | |
| with torch.no_grad(): | |
| # IMPORTANT: model.infer()'s own source code only `return`s the | |
| # extracted text when save_results=True (or test_compress/ | |
| # eval_mode=True); with all three False/default it computes | |
| # everything but returns None. So we must pass save_results=True | |
| # and then read the text back from the file it writes | |
| # (output_path/result.mmd) rather than trusting the return value. | |
| model.infer( | |
| tokenizer, | |
| prompt=prompt, | |
| image_file=image_path, | |
| output_path=tmp_dir, | |
| base_size=preset["base_size"], | |
| image_size=preset["image_size"], | |
| crop_mode=preset["crop_mode"], | |
| save_results=True, | |
| ) | |
| result_file = os.path.join(tmp_dir, "result.mmd") | |
| if not os.path.exists(result_file): | |
| return ("No output file was produced. Try a different resolution " | |
| "preset, or check the Space logs for errors.") | |
| with open(result_file, "r", encoding="utf-8") as f: | |
| text = f.read() | |
| # Free OCR output is plain text already; Markdown/grounding output | |
| # can contain <|ref|>...<|/ref|> and <|det|>[[...]]<|/det|> tags — | |
| # strip those so the box shows clean readable text either way. | |
| text = re.sub(r"<\|ref\|>.*?<\|/ref\|>", "", text, flags=re.DOTALL) | |
| text = re.sub(r"<\|det\|>\[\[.*?\]\]<\|/det\|>", "", text, flags=re.DOTALL) | |
| text = text.strip() | |
| return text if text else "No text was detected in this image." | |
| finally: | |
| shutil.rmtree(tmp_dir, ignore_errors=True) | |
| with gr.Blocks(title="DeepSeek-OCR (CPU)") as demo: | |
| gr.Markdown( | |
| """ | |
| # DeepSeek-OCR — Free OCR (CPU) | |
| Upload an image (a photo, screenshot, scanned page, etc.) and extract | |
| its text using [deepseek-ai/DeepSeek-OCR](https://huggingface.co/deepseek-ai/DeepSeek-OCR). | |
| ⚠️ Running on **CPU** — this is noticeably slower than GPU. Start with | |
| the "Tiny" or "Small" resolution preset if you want faster results. | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| image_input = gr.Image(type="pil", label="Upload image") | |
| task_dropdown = gr.Dropdown( | |
| choices=list(TASK_PROMPTS.keys()), | |
| value="Free OCR (plain text)", | |
| label="Task", | |
| ) | |
| size_dropdown = gr.Dropdown( | |
| choices=list(SIZE_PRESETS.keys()), | |
| value="Gundam (1024/640 crop, best for documents)", | |
| label="Resolution mode (lower = faster on CPU)", | |
| ) | |
| run_button = gr.Button("Run OCR", variant="primary") | |
| with gr.Column(): | |
| output_text = gr.Textbox( | |
| label="Extracted text", lines=25 | |
| ) | |
| run_button.click( | |
| fn=run_ocr, | |
| inputs=[image_input, task_dropdown, size_dropdown], | |
| outputs=output_text, | |
| ) | |
| if __name__ == "__main__": | |
| demo.queue().launch() |