Spaces:
Sleeping
Sleeping
File size: 5,882 Bytes
5625297 55cb0af 14a7432 5625297 14a7432 b1c7862 14a7432 177ec96 14a7432 55cb0af 14a7432 55cb0af 14a7432 594145e 14a7432 37508d7 14a7432 47c1654 721c9d9 14a7432 721c9d9 37508d7 14a7432 721c9d9 37508d7 5625297 14a7432 721c9d9 14a7432 721c9d9 14a7432 721c9d9 194f537 14a7432 7fbf37f 721c9d9 14a7432 7fbf37f 31f684f 14a7432 31f684f 5625297 14a7432 5625297 11bf437 14a7432 11bf437 5cc96b8 bcc62dd 14a7432 | 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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | #!/usr/bin/env python3
import json
import logging
import os
import subprocess
import time
from typing import Any, List
import gradio as gr
import httpx
import yaml
log = logging.getLogger("glmocr_selfhosted_space")
logging.basicConfig(level=logging.INFO)
MODEL_ID = os.getenv("GLMOCR_MODEL_ID", "zai-org/GLM-OCR")
OCR_API_PORT = int(os.getenv("OCR_API_PORT", "8080"))
OCR_API_BASE = f"http://127.0.0.1:{OCR_API_PORT}"
VLLM_MAX_MODEL_LEN = os.getenv("VLLM_MAX_MODEL_LEN", "8192")
VLLM_GPU_MEMORY_UTIL = os.getenv("VLLM_GPU_MEMORY_UTIL", "0.92")
VLLM_EXTRA_ARGS = os.getenv("VLLM_EXTRA_ARGS", "").strip()
_parser = None
_vllm_proc = None
def _start_vllm_if_needed() -> None:
global _vllm_proc
if _vllm_proc is not None and _vllm_proc.poll() is None:
return
cmd: List[str] = [
"python",
"-m",
"vllm.entrypoints.openai.api_server",
"--model",
MODEL_ID,
"--served-model-name",
"glm-ocr",
"--port",
str(OCR_API_PORT),
"--max-model-len",
VLLM_MAX_MODEL_LEN,
"--gpu-memory-utilization",
VLLM_GPU_MEMORY_UTIL,
]
if VLLM_EXTRA_ARGS:
cmd.extend(VLLM_EXTRA_ARGS.split())
log.info("Starting vLLM server: %s", " ".join(cmd))
_vllm_proc = subprocess.Popen(cmd)
deadline = time.time() + 420
last_err: str = ""
while time.time() < deadline:
if _vllm_proc.poll() is not None:
raise RuntimeError("vLLM server exited early. Check Space logs for details.")
try:
resp = httpx.get(f"{OCR_API_BASE}/v1/models", timeout=8.0)
if resp.status_code == 200:
log.info("vLLM is ready.")
return
except Exception as e:
last_err = str(e)
time.sleep(3)
raise RuntimeError(f"Timed out waiting for vLLM startup. Last error: {last_err}")
def _configure_glmocr_to_keep_header_footer() -> None:
import glmocr
config_path = os.path.join(os.path.dirname(glmocr.__file__), "config.yaml")
with open(config_path, "r", encoding="utf-8") as f:
cfg = yaml.safe_load(f) or {}
pipeline = cfg.setdefault("pipeline", {})
maas = pipeline.setdefault("maas", {})
maas["enabled"] = False
ocr_api = pipeline.setdefault("ocr_api", {})
ocr_api["api_host"] = "127.0.0.1"
ocr_api["api_port"] = OCR_API_PORT
ocr_api["model"] = "glm-ocr"
ocr_api["api_path"] = "/v1/chat/completions"
ocr_api["api_mode"] = "openai"
ocr_api["verify_ssl"] = False
layout = pipeline.setdefault("layout", {})
label_task_mapping = layout.setdefault("label_task_mapping", {})
text_labels = set(label_task_mapping.get("text", []) or [])
abandon_labels = set(label_task_mapping.get("abandon", []) or [])
skip_labels = set(label_task_mapping.get("skip", []) or [])
text_labels.update({"header", "footer"})
abandon_labels.discard("header")
abandon_labels.discard("footer")
# Keep image-only decorative zones out of OCR by default.
# If you want logo OCR too, move these to text_labels.
skip_labels.update({"header_image", "footer_image"})
abandon_labels.discard("header_image")
abandon_labels.discard("footer_image")
label_task_mapping["text"] = sorted(text_labels)
label_task_mapping["abandon"] = sorted(abandon_labels)
label_task_mapping["skip"] = sorted(skip_labels)
with open(config_path, "w", encoding="utf-8") as f:
yaml.safe_dump(cfg, f, sort_keys=False)
def get_parser():
global _parser
if _parser is None:
_start_vllm_if_needed()
_configure_glmocr_to_keep_header_footer()
from glmocr import GlmOcr
_parser = GlmOcr()
return _parser
def _extract_md(result: Any) -> str:
if result is None:
return ""
if isinstance(result, list):
chunks = []
for item in result:
md = getattr(item, "markdown_result", "")
if md:
chunks.append(str(md).strip())
return "\n\n---page-separator---\n\n".join([c for c in chunks if c]).strip()
md = getattr(result, "markdown_result", "")
return str(md).strip() if md else ""
def _extract_json(result: Any) -> str:
if result is None:
return "{}"
if isinstance(result, list):
payload = [getattr(item, "json_result", None) for item in result]
return json.dumps(payload, ensure_ascii=False, indent=2)
return json.dumps(getattr(result, "json_result", None), ensure_ascii=False, indent=2)
def run_ocr(file_obj):
if file_obj is None:
return "Please upload a file.", "{}"
path = file_obj.name if hasattr(file_obj, "name") else str(file_obj)
try:
parser = get_parser()
result = parser.parse(path)
md = _extract_md(result) or "(No content)"
jr = _extract_json(result)
return md, jr
except Exception as e:
import traceback
log.exception("run_ocr failed: %s", e)
return f"Error: {e}\n\n{traceback.format_exc()}", "{}"
def build_demo():
with gr.Blocks(title="GLM-OCR Self-hosted (Header/Footer enabled)") as demo:
gr.Markdown("# GLM-OCR Self-hosted (Header/Footer enabled)")
gr.Markdown(
"Runs a local vLLM server inside the Space and configures GLM-OCR "
"to include `header` and `footer` regions in OCR output."
)
file_in = gr.File(
label="Upload PDF or image",
file_types=[".pdf", ".png", ".jpg", ".jpeg", ".tiff", ".bmp"],
)
run_btn = gr.Button("Run OCR", variant="primary")
out_md = gr.Textbox(label="Markdown", lines=30)
out_json = gr.Textbox(label="JSON (layout details)", lines=20)
run_btn.click(fn=run_ocr, inputs=file_in, outputs=[out_md, out_json])
return demo
if __name__ == "__main__":
build_demo().launch()
|