NaserNajeh commited on
Commit
de4eda6
·
verified ·
1 Parent(s): e3842dc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +44 -56
app.py CHANGED
@@ -1,58 +1,48 @@
1
- import os, io, json, traceback
2
  import gradio as gr
3
  import fitz # PyMuPDF
4
  from PIL import Image
 
5
 
6
- import spaces # مطلوب ل ZeroGPU
7
- from huggingface_hub import hf_hub_download
8
 
9
- # ===== إعدادات النموذج =====
10
- HOROOF_MODEL_NAME = os.environ.get("HOROOF_MODEL", "NaserNajeh/Horoof")
 
11
 
12
- # تحميل كسول لتقليل زمن الإقلاع
13
  _model = None
14
- _tokenizer = None
15
- _image_processor = None
16
-
17
- def _load_clean_config(repo_id: str):
18
- """تحميل config.json وإزالة أي quantization_config لتفادي bitsandbytes."""
19
- from transformers import AutoConfig
20
- try:
21
- cfg_path = hf_hub_download(repo_id=repo_id, filename="config.json")
22
- with open(cfg_path, "r", encoding="utf-8") as f:
23
- cfg_json = json.load(f)
24
- # أزل أي مفاتيح قد تفرض bnb
25
- cfg_json.pop("quantization_config", None)
26
- cfg_json.pop("load_in_4bit", None)
27
- cfg_json.pop("load_in_8bit", None)
28
- return AutoConfig.from_dict(cfg_json)
29
- except Exception:
30
- # احتياطي: لو فشل، خذ الإعدادات الافتراضية
31
- return AutoConfig.from_pretrained(repo_id)
32
-
33
- def load_horoof():
34
- """تحميل نموذج Horoof (Qwen2-VL) على الـGPU عند أول استدعاء فقط، بدون bitsandbytes/torchvision."""
35
- global _model, _tokenizer, _image_processor
36
  if _model is not None:
37
  return
38
  try:
39
  import torch
40
- from transformers import Qwen2VLForConditionalGeneration, AutoTokenizer, AutoImageProcessor
 
 
41
  if not torch.cuda.is_available():
42
  raise AssertionError("هذه النسخة تتطلب GPU (CUDA) مفعّل على الـSpace.")
43
 
44
- # إعدادات خفيفة: نمنع أي كمّية كي لا يُستدعى bitsandbytes
45
- cfg = _load_clean_config(HOROOF_MODEL_NAME)
46
 
47
- _tokenizer = AutoTokenizer.from_pretrained(HOROOF_MODEL_NAME, trust_remote_code=False)
48
- _image_processor = AutoImageProcessor.from_pretrained(HOROOF_MODEL_NAME, trust_remote_code=False)
49
-
50
- _model = Qwen2VLForConditionalGeneration.from_pretrained(
51
- HOROOF_MODEL_NAME,
52
- config=cfg,
53
- torch_dtype=torch.float16, # fp16 على الـGPU
54
  ).to("cuda")
55
 
 
 
 
 
 
56
  except Exception as e:
57
  raise RuntimeError(f"تعذّر تحميل النموذج: {e}")
58
 
@@ -70,12 +60,12 @@ def pdf_to_images(pdf_bytes: bytes, dpi: int = 220, max_pages: int = 0):
70
  doc.close()
71
  return pages_imgs
72
 
73
- def ocr_page_with_horoof(pil_img: Image.Image, max_new_tokens: int = 1200) -> str:
74
- """تشغيل Horoof على صورة صفحة واحدة (بدون quantization)."""
75
- load_horoof()
76
  import torch
77
 
78
- # رسالة محادثة متوافقة مع Qwen2-VL
79
  messages = [
80
  {
81
  "role": "user",
@@ -86,34 +76,32 @@ def ocr_page_with_horoof(pil_img: Image.Image, max_new_tokens: int = 1200) -> st
86
  }
87
  ]
88
 
89
- # قالب المحادثة كنص (بدون تقطيع) ثم نقاطّع
90
- prompt = _tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
91
 
92
- # مدخلات الصورة + النص
93
- vision_inputs = _image_processor(images=pil_img, return_tensors="pt")
94
- text_inputs = _tokenizer([prompt], return_tensors="pt")
95
- inputs = {**vision_inputs, **text_inputs}
96
- inputs = {k: (v.to("cuda") if hasattr(v, "to") else v) for k, v in inputs.items()}
97
 
98
  # توليد
99
- output_ids = _model.generate(**inputs, max_new_tokens=max_new_tokens)
100
- text = _tokenizer.batch_decode(output_ids, skip_special_tokens=True)[0]
 
101
  return (text or "").strip()
102
 
103
- @spaces.GPU # ضروري ل ZeroGPU: يجعل الاستدعاء يحجز GPU
104
  def ocr_pdf(pdf_file, dpi, limit_pages):
105
  """الدالة الرئيسة التي يستدعيها Gradio."""
106
  if pdf_file is None:
107
  return "لم يتم رفع ملف."
108
  try:
109
  pdf_bytes = pdf_file.read() if hasattr(pdf_file, "read") else pdf_file
110
- limit = int(limit_pages) if limit_pages else 1 # صفحة واحدة افتراضًا للاختبار
111
  pages = pdf_to_images(pdf_bytes, dpi=int(dpi), max_pages=limit)
112
  if not pages:
113
  return "لا توجد صفحات."
114
  out = []
115
  for idx, img in pages:
116
- txt = ocr_page_with_horoof(img)
117
  out.append(f"--- صفحة {idx} ---\n{txt}")
118
  return "\n\n".join(out)
119
  except AssertionError as ae:
@@ -123,9 +111,9 @@ def ocr_pdf(pdf_file, dpi, limit_pages):
123
  return f"حدث خطأ: {repr(e)}"
124
 
125
  with gr.Blocks(title="Horoof OCR (ZeroGPU)") as demo:
126
- gr.Markdown("### Horoof OCR على ZeroGPU (Qwen2-VL) بدون bitsandbytes/torchvision.")
127
  pdf_in = gr.File(label="ارفع ملف PDF", file_types=[".pdf"], type="binary")
128
- dpi = gr.Slider(150, 300, value=220, step=10, label="دقة التحويل (DPI)")
129
  limit_pages = gr.Number(value=1, precision=0, label="عدد الصفحات (للاختبار؛ زِد لاحقًا)")
130
  run_btn = gr.Button("بدء التحويل")
131
  out = gr.Textbox(label="النص المستخرج", lines=24)
 
1
+ import os, io, traceback
2
  import gradio as gr
3
  import fitz # PyMuPDF
4
  from PIL import Image
5
+ import spaces # ل ZeroGPU
6
 
7
+ # تعطيل torchvision داخل Transformers (لمنع AutoVideoProcessor)
8
+ os.environ["TRANSFORMERS_NO_TORCHVISION"] = "1"
9
 
10
+ # إعدادات النماذج
11
+ BASE_MODEL = os.environ.get("BASE_MODEL", "Qwen/Qwen2-VL-2B-Instruct")
12
+ HOROOF_ADAPTER = os.environ.get("HOROOF_MODEL", "NaserNajeh/Horoof")
13
 
 
14
  _model = None
15
+ _processor = None # AutoProcessor من الـBase Model
16
+
17
+ def load_model_merged():
18
+ """
19
+ 1) نحمل Base Qwen2-VL-2B-Instruct على GPU (fp16).
20
+ 2) نركب لورا Horoof (PEFT) على الـBase.
21
+ 3) ندمج الأوزان merge_and_unload لتفادي أي تبعية لـ bitsandbytes.
22
+ """
23
+ global _model, _processor
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  if _model is not None:
25
  return
26
  try:
27
  import torch
28
+ from transformers import Qwen2VLForConditionalGeneration, AutoProcessor
29
+ from peft import PeftModel
30
+
31
  if not torch.cuda.is_available():
32
  raise AssertionError("هذه النسخة تتطلب GPU (CUDA) مفعّل على الـSpace.")
33
 
34
+ # التحميل
35
+ _processor = AutoProcessor.from_pretrained(BASE_MODEL, trust_remote_code=False)
36
 
37
+ base = Qwen2VLForConditionalGeneration.from_pretrained(
38
+ BASE_MODEL, torch_dtype=torch.float16
 
 
 
 
 
39
  ).to("cuda")
40
 
41
+ # ركب لورا Horoof ثم دمجها
42
+ peft_model = PeftModel.from_pretrained(base, HOROOF_ADAPTER)
43
+ _model = peft_model.merge_and_unload() # نحصل على نموذج مدمج بلا تبعيات إضافية
44
+ _model.to("cuda")
45
+
46
  except Exception as e:
47
  raise RuntimeError(f"تعذّر تحميل النموذج: {e}")
48
 
 
60
  doc.close()
61
  return pages_imgs
62
 
63
+ def ocr_page_gpu(pil_img: Image.Image, max_new_tokens: int = 1200) -> str:
64
+ """OCR لصفحة واحدة باستخدام Qwen2-VL المدموج مع لورا Horoof."""
65
+ load_model_merged()
66
  import torch
67
 
68
+ # نبني رسالة محادثة متوافقة مع Qwen2-VL
69
  messages = [
70
  {
71
  "role": "user",
 
76
  }
77
  ]
78
 
79
+ # تحويل الرسائل إلى Prompt داخلي
80
+ prompt = _processor.apply_chat_template(messages, add_generation_prompt=True)
81
 
82
+ # تجهيز المدخلات (نمرر النص + الصورة)
83
+ inputs = _processor(text=[prompt], images=[pil_img], return_tensors="pt").to("cuda")
 
 
 
84
 
85
  # توليد
86
+ with torch.inference_mode():
87
+ output_ids = _model.generate(**inputs, max_new_tokens=max_new_tokens)
88
+ text = _processor.batch_decode(output_ids, skip_special_tokens=True)[0]
89
  return (text or "").strip()
90
 
91
+ @spaces.GPU # مهم ل ZeroGPU: يحجز GPU عند الاستدعاء
92
  def ocr_pdf(pdf_file, dpi, limit_pages):
93
  """الدالة الرئيسة التي يستدعيها Gradio."""
94
  if pdf_file is None:
95
  return "لم يتم رفع ملف."
96
  try:
97
  pdf_bytes = pdf_file.read() if hasattr(pdf_file, "read") else pdf_file
98
+ limit = int(limit_pages) if limit_pages else 1 # صفحة واحدة افتراضيًا للاختبار
99
  pages = pdf_to_images(pdf_bytes, dpi=int(dpi), max_pages=limit)
100
  if not pages:
101
  return "لا توجد صفحات."
102
  out = []
103
  for idx, img in pages:
104
+ txt = ocr_page_gpu(img)
105
  out.append(f"--- صفحة {idx} ---\n{txt}")
106
  return "\n\n".join(out)
107
  except AssertionError as ae:
 
111
  return f"حدث خطأ: {repr(e)}"
112
 
113
  with gr.Blocks(title="Horoof OCR (ZeroGPU)") as demo:
114
+ gr.Markdown("### Horoof OCR على ZeroGPU Qwen2-VL + LoRA (مُدمج أثناء التشغيل، بدون bitsandbytes).")
115
  pdf_in = gr.File(label="ارفع ملف PDF", file_types=[".pdf"], type="binary")
116
+ dpi = gr.Slider(150, 300, value=220, step=10, label="دقّة التحويل (DPI)")
117
  limit_pages = gr.Number(value=1, precision=0, label="عدد الصفحات (للاختبار؛ زِد لاحقًا)")
118
  run_btn = gr.Button("بدء التحويل")
119
  out = gr.Textbox(label="النص المستخرج", lines=24)