This model is a
QLoRA fine-tuned LoRA adapter for
google/gemma-3-4b-it,
optimized for
Arabic legal document OCR and structured information
extraction. It analyzes document images and returns a structured
JSON output.
For best OCR accuracy, preprocess images (resize, grayscale, contrast
enhance) before sending them to the model:
1import base64
2from io import BytesIO
3from PIL import Image, ImageEnhance
4
5def preprocess_image(image_path, max_width=1024, do_enhance=True, return_base64=False):
6 image = Image.open(image_path)
7 gray_image = image.convert('L')
8
9 if gray_image.width > max_width:
10 ratio = max_width / float(gray_image.width)
11 new_height = int(gray_image.height * ratio)
12 gray_image = gray_image.resize((max_width, new_height), Image.LANCZOS)
13
14 if do_enhance:
15 enhancer = ImageEnhance.Contrast(gray_image)
16 gray_image = enhancer.enhance(1.5)
17
18 if return_base64:
19 buffered = BytesIO()
20 gray_image.save(buffered, format="JPEG", optimize=True, quality=95)
21 img_str = base64.b64encode(buffered.getvalue()).decode('utf-8')
22 return f"data:image/jpeg;base64,{img_str}"
23
24 return gray_image
1import torch
2import json_repair
3from PIL import Image
4from transformers import AutoProcessor, Gemma3ForConditionalGeneration
5from peft import PeftModel
6
7BASE_MODEL = "google/gemma-3-4b-it"
8ADAPTER = "ahmedyasser006/arabic-legal-ocr-gemma-3-4b-qlora"
9
10processor = AutoProcessor.from_pretrained(BASE_MODEL)
11
12base_model = Gemma3ForConditionalGeneration.from_pretrained(
13 BASE_MODEL, dtype="auto", device_map="auto"
14)
15model = PeftModel.from_pretrained(base_model, ADAPTER)
16model.eval()
17
18image = preprocess_image("document.jpg", return_base64=False)
19
20task_1_message = """
21You are a professional OCR Details Extractor.
22Your rule to extract: the page markdown content in addition to the structural_elements of the document.
23Extract the final output into a json format.
24Do not generate any introduction or conclusion.
25""".strip()
26
27
28task_2_message = """
29You are a professional OCR Details Extractor.
30Your rule to extract the: document_classification, source, physical_properties, official_marks, signatures_authorization, routing_distribution, attachments_references, condition_notes and confidence_quality of the document.
31Extract the final output into a json format.
32Do not generate any introduction or conclusion.
33""".strip()
34
35
36messages = [
37 {
38 "role": "system",
39 "content": [
40 {
41 "type": "text",
42 "text": "You are a helpful assistant."
43 }
44 ],
45 },
46 {
47 "role": "user",
48 "content": [
49 {
50 "type": "image",
51 "image": image,
52 },
53 {
54 "type": "text",
55 "text": task_1_message,
56 },
57 ],
58 },
59]
60
61
62inputs = processor.apply_chat_template(
63 messages,
64 tokenize=True,
65 add_generation_prompt=True,
66 return_dict=True,
67 return_tensors="pt",
68)
69
70
71# Move tensor inputs to the model device
72inputs = {
73 key: value.to(model.device)
74 if hasattr(value, "to")
75 else value
76 for key, value in inputs.items()
77}
78
79
80input_length = inputs["input_ids"].shape[-1]
81
82
83with torch.inference_mode():
84 outputs = model.generate(
85 **inputs,
86 max_new_tokens=512,
87 do_sample=False,
88 )
89
90
91generated_tokens = outputs[0,input_length:]
92
93
94result = processor.decode(
95 generated_tokens,
96 skip_special_tokens=True,
97)
98
99# Robust JSON parsing, even if the model output isn't perfectly formed
100json_data = json_repair.loads(result)
101print(result)
vLLM can load LoRA adapters directly, so you can serve this adapter on
top of the base model without merging weights:
1vllm serve google/gemma-3-4b-it \
2 --enable-lora \
3 --lora-modules arabic-legal-ocr=ahmedyasser006/arabic-legal-ocr-gemma-3-4b-qlora \
4 --dtype bfloat16 --gpu_memory_utilization 0.8 \
5 --enable-chunked-prefill \
6 --allowed-local-media-path "/workspace/"
1from openai import OpenAI
2import json_repair
3
4client = OpenAI(api_key="any", base_url="http://localhost:8000/v1")
5
6b64_image = preprocess_image("document.jpg", return_base64=True)
7
8response = client.chat.completions.create(
9 model="arabic-legal-ocr",
10 messages=[{"role": "user", "content": [
11 {"type": "image_url", "image_url": {"url": b64_image}},
12 {"type": "text", "text": "Extract details to JSON."}
13 ]}]
14)
15
16structured_output = json_repair.loads(response.choices[0].message.content)
17print(structured_output)
1{
2 "classification": { "type": null, "category": null, "language": "Arabic" },
3 "source": { "authority": null, "document_number": null, "primary_date": null },
4 "content": { "subject": null, "full_text": "", "tables": [], "legal_articles": [] },
5 "quality": { "confidence": null, "manual_review": false }
6}
The exact schema depends on the document image (see prompt for the full
field set).
1arabic-legal-ocr-gemma-3-4b-qlora/
2├── README.md
3├── adapter_config.json
4├── adapter_model.safetensors ← final adapter, use for inference
5├── tokenizer.model / tokenizer_config.json / ...
6├── train_results.json / eval_results.json / trainer_state.json
7└── last-checkpoint/ ← training-resume checkpoint only
8 ├── adapter_model.safetensors
9 ├── optimizer.pt / scheduler.pt / scaler.pt / rng_state*.pth
10 └── trainer_state.json
This adapter uses the Gemma license associated with the base model.
Review the license and terms of use of google/gemma-3-4b-it before
using or distributing this model.