fix: use MiniCPM-V-4.5 to resolve unsupported architecture on Space
Browse filesMiniCPM-V-4.6 has model_type 'minicpmv4_6' and requires transformers>=5.7.0,
which the Space's transformers 4.x can't load (ValueError: architecture not
recognized). Switch to MiniCPM-V-4.5 (model_type 'minicpmv'), which uses
trust_remote_code + model.chat() on transformers 4.x.
- redac/vision.py: MODEL_ID -> openbmb/MiniCPM-V-4_5; load/download weights
on CPU before entering the @spaces.GPU window so the 16GB download can't
exhaust the ZeroGPU time budget; deterministic decoding (sampling=False).
- requirements.txt: pin transformers>=4.49,<5; add timm.
- app.py: make local port configurable via GRADIO_SERVER_PORT (xray-anything
occupies 7860-7863).
Co-authored-by: OpenAI Codex <codex@openai.com>
- app.py +5 -2
- redac/vision.py +11 -4
- requirements.txt +2 -1
app.py
CHANGED
|
@@ -104,7 +104,7 @@ with gr.Blocks(title="Redac") as demo:
|
|
| 104 |
with gr.Tab("Image"):
|
| 105 |
gr.Markdown(
|
| 106 |
"Upload a document or ID image. A local vision model "
|
| 107 |
-
"(MiniCPM-V-4.
|
| 108 |
)
|
| 109 |
i_map = gr.State({})
|
| 110 |
with gr.Row():
|
|
@@ -136,4 +136,7 @@ with gr.Blocks(title="Redac") as demo:
|
|
| 136 |
|
| 137 |
|
| 138 |
if __name__ == "__main__":
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
with gr.Tab("Image"):
|
| 105 |
gr.Markdown(
|
| 106 |
"Upload a document or ID image. A local vision model "
|
| 107 |
+
"(MiniCPM-V-4.5) extracts the fields, then Redac redacts them."
|
| 108 |
)
|
| 109 |
i_map = gr.State({})
|
| 110 |
with gr.Row():
|
|
|
|
| 136 |
|
| 137 |
|
| 138 |
if __name__ == "__main__":
|
| 139 |
+
import os
|
| 140 |
+
|
| 141 |
+
port = int(os.environ.get("GRADIO_SERVER_PORT", "7860"))
|
| 142 |
+
demo.launch(server_name="0.0.0.0", server_port=port, theme=gr.themes.Soft())
|
redac/vision.py
CHANGED
|
@@ -1,9 +1,14 @@
|
|
| 1 |
-
"""Image -> text extraction with a local vision model (MiniCPM-V-4.
|
| 2 |
|
| 3 |
Runs in-Space on ZeroGPU via the @spaces.GPU decorator. The model reads a
|
| 4 |
document/ID image and returns the fields as plain text, which then flows
|
| 5 |
through the same PII detection + redaction core as typed text.
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
Local dev: set REDAC_MOCK=1 to skip the model entirely and return a canned
|
| 8 |
extraction, so the Gradio UI runs on a laptop with no GPU.
|
| 9 |
"""
|
|
@@ -13,7 +18,7 @@ from __future__ import annotations
|
|
| 13 |
import os
|
| 14 |
from functools import lru_cache
|
| 15 |
|
| 16 |
-
MODEL_ID = "openbmb/MiniCPM-V-
|
| 17 |
|
| 18 |
EXTRACTION_PROMPT = (
|
| 19 |
"You are a document data extractor. Read this image and transcribe ALL "
|
|
@@ -52,6 +57,7 @@ except Exception: # pragma: no cover - local fallback
|
|
| 52 |
|
| 53 |
@lru_cache(maxsize=1)
|
| 54 |
def _load_model():
|
|
|
|
| 55 |
import torch
|
| 56 |
from transformers import AutoModel, AutoTokenizer
|
| 57 |
|
|
@@ -67,10 +73,10 @@ def _load_model():
|
|
| 67 |
|
| 68 |
@_gpu
|
| 69 |
def _chat(image, prompt: str) -> str:
|
| 70 |
-
model, tokenizer = _load_model()
|
| 71 |
model = model.to("cuda")
|
| 72 |
msgs = [{"role": "user", "content": [image, prompt]}]
|
| 73 |
-
return model.chat(image=None, msgs=msgs, tokenizer=tokenizer)
|
| 74 |
|
| 75 |
|
| 76 |
def extract_text_from_image(image, prompt: str | None = None) -> str:
|
|
@@ -79,4 +85,5 @@ def extract_text_from_image(image, prompt: str | None = None) -> str:
|
|
| 79 |
return ""
|
| 80 |
if _is_mock():
|
| 81 |
return _MOCK_EXTRACTION
|
|
|
|
| 82 |
return _chat(image.convert("RGB"), prompt or EXTRACTION_PROMPT).strip()
|
|
|
|
| 1 |
+
"""Image -> text extraction with a local vision model (MiniCPM-V-4.5).
|
| 2 |
|
| 3 |
Runs in-Space on ZeroGPU via the @spaces.GPU decorator. The model reads a
|
| 4 |
document/ID image and returns the fields as plain text, which then flows
|
| 5 |
through the same PII detection + redaction core as typed text.
|
| 6 |
|
| 7 |
+
MiniCPM-V-4.5 (model_type "minicpmv") uses trust_remote_code + model.chat()
|
| 8 |
+
and runs on transformers 4.x. The weights are downloaded/loaded on CPU
|
| 9 |
+
*before* entering the GPU window so the 16GB download can't blow the
|
| 10 |
+
ZeroGPU time budget; the GPU call only moves the model to CUDA and generates.
|
| 11 |
+
|
| 12 |
Local dev: set REDAC_MOCK=1 to skip the model entirely and return a canned
|
| 13 |
extraction, so the Gradio UI runs on a laptop with no GPU.
|
| 14 |
"""
|
|
|
|
| 18 |
import os
|
| 19 |
from functools import lru_cache
|
| 20 |
|
| 21 |
+
MODEL_ID = "openbmb/MiniCPM-V-4_5"
|
| 22 |
|
| 23 |
EXTRACTION_PROMPT = (
|
| 24 |
"You are a document data extractor. Read this image and transcribe ALL "
|
|
|
|
| 57 |
|
| 58 |
@lru_cache(maxsize=1)
|
| 59 |
def _load_model():
|
| 60 |
+
"""Download + load weights on CPU. Cached. Called outside the GPU window."""
|
| 61 |
import torch
|
| 62 |
from transformers import AutoModel, AutoTokenizer
|
| 63 |
|
|
|
|
| 73 |
|
| 74 |
@_gpu
|
| 75 |
def _chat(image, prompt: str) -> str:
|
| 76 |
+
model, tokenizer = _load_model() # cached -> instant inside GPU window
|
| 77 |
model = model.to("cuda")
|
| 78 |
msgs = [{"role": "user", "content": [image, prompt]}]
|
| 79 |
+
return model.chat(image=None, msgs=msgs, tokenizer=tokenizer, sampling=False)
|
| 80 |
|
| 81 |
|
| 82 |
def extract_text_from_image(image, prompt: str | None = None) -> str:
|
|
|
|
| 85 |
return ""
|
| 86 |
if _is_mock():
|
| 87 |
return _MOCK_EXTRACTION
|
| 88 |
+
_load_model() # warm the cache on CPU before claiming the GPU
|
| 89 |
return _chat(image.convert("RGB"), prompt or EXTRACTION_PROMPT).strip()
|
requirements.txt
CHANGED
|
@@ -2,9 +2,10 @@ gradio==6.18.0
|
|
| 2 |
gliner>=0.2.13
|
| 3 |
torch
|
| 4 |
torchvision
|
| 5 |
-
transformers>=4.
|
| 6 |
accelerate
|
| 7 |
sentencepiece
|
|
|
|
| 8 |
pillow
|
| 9 |
huggingface_hub
|
| 10 |
spaces
|
|
|
|
| 2 |
gliner>=0.2.13
|
| 3 |
torch
|
| 4 |
torchvision
|
| 5 |
+
transformers>=4.49,<5
|
| 6 |
accelerate
|
| 7 |
sentencepiece
|
| 8 |
+
timm
|
| 9 |
pillow
|
| 10 |
huggingface_hub
|
| 11 |
spaces
|