atharvda commited on
Commit
8dc839a
·
verified ·
1 Parent(s): 65a2634

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. Dockerfile +23 -0
  2. README.md +10 -3
  3. app.py +120 -0
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM pytorch/pytorch:2.5.1-cuda12.1-cudnn9-runtime
2
+
3
+ ENV HF_HOME=/tmp/hf \
4
+ HF_HUB_ENABLE_HF_TRANSFER=1 \
5
+ PYTHONUNBUFFERED=1 \
6
+ DEBIAN_FRONTEND=noninteractive
7
+
8
+ WORKDIR /app
9
+
10
+ RUN pip install --no-cache-dir \
11
+ "transformers>=4.52" \
12
+ accelerate \
13
+ "bitsandbytes>=0.43" \
14
+ sentencepiece \
15
+ hf_transfer \
16
+ fastapi \
17
+ "uvicorn[standard]" \
18
+ pillow
19
+
20
+ COPY app.py /app/app.py
21
+
22
+ EXPOSE 7860
23
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,17 @@
1
  ---
2
  title: Gemma Vision Judge
3
  emoji: 🔥
4
- colorFrom: green
5
- colorTo: pink
6
  sdk: docker
 
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
1
  ---
2
  title: Gemma Vision Judge
3
  emoji: 🔥
4
+ colorFrom: red
5
+ colorTo: purple
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
  ---
10
 
11
+ # Gemma Vision Judge
12
+
13
+ OpenAI-compatible vision endpoint serving
14
+ `llmfan46/gemma-3-12b-it-ultra-uncensored-heretic` in 4-bit, for the FLUX
15
+ feedback-loop judge. Endpoints: `/health`, `/v1/chat/completions`.
16
+
17
+ Temporary experiment Space — pause or delete when done to stop GPU billing.
app.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ OpenAI-compatible vision shim for an uncensored Gemma-3 12B judge.
3
+
4
+ Loads llmfan46/gemma-3-12b-it-ultra-uncensored-heretic in 4-bit (bitsandbytes)
5
+ and exposes POST /v1/chat/completions accepting text + image_url content, so the
6
+ FLUX pipeline's GemmaClient can talk to it unchanged.
7
+
8
+ Runs on a T4 (16 GB): 12B in nf4 ≈ 6-7 GB + vision tower + KV → comfortable.
9
+ """
10
+ import base64
11
+ import io
12
+ import os
13
+ import time
14
+ import urllib.request
15
+
16
+ import torch
17
+ from fastapi import FastAPI, Request
18
+ from fastapi.responses import JSONResponse
19
+ from PIL import Image
20
+
21
+ MODEL_ID = os.environ.get("MODEL_ID", "llmfan46/gemma-3-12b-it-ultra-uncensored-heretic")
22
+
23
+ app = FastAPI()
24
+ _proc = None
25
+ _model = None
26
+ _load_error = None
27
+
28
+
29
+ def _load():
30
+ """Lazy-load the model on first request (keeps startup/health fast)."""
31
+ global _proc, _model, _load_error
32
+ if _model is not None or _load_error is not None:
33
+ return
34
+ try:
35
+ from transformers import AutoProcessor, BitsAndBytesConfig, Gemma3ForConditionalGeneration
36
+ _proc = AutoProcessor.from_pretrained(MODEL_ID)
37
+ bnb = BitsAndBytesConfig(
38
+ load_in_4bit=True,
39
+ bnb_4bit_quant_type="nf4",
40
+ bnb_4bit_compute_dtype=torch.float16, # T4 has no bf16
41
+ bnb_4bit_use_double_quant=True,
42
+ )
43
+ _model = Gemma3ForConditionalGeneration.from_pretrained(
44
+ MODEL_ID, quantization_config=bnb, device_map="auto", torch_dtype=torch.float16,
45
+ )
46
+ _model.eval()
47
+ except Exception as e: # surfaced via /health and request errors
48
+ _load_error = f"{type(e).__name__}: {e}"
49
+
50
+
51
+ def _load_image(url: str) -> Image.Image:
52
+ if url.startswith("data:"):
53
+ b64 = url.split(",", 1)[1]
54
+ return Image.open(io.BytesIO(base64.b64decode(b64))).convert("RGB")
55
+ with urllib.request.urlopen(url, timeout=30) as r:
56
+ return Image.open(io.BytesIO(r.read())).convert("RGB")
57
+
58
+
59
+ def _to_gemma_messages(messages):
60
+ """Convert OpenAI messages (string or [parts]) into the transformers
61
+ multimodal chat format, inlining PIL images."""
62
+ conv = []
63
+ for m in messages:
64
+ role, content = m.get("role", "user"), m.get("content", "")
65
+ if isinstance(content, str):
66
+ conv.append({"role": role, "content": [{"type": "text", "text": content}]})
67
+ continue
68
+ parts = []
69
+ for p in content:
70
+ if p.get("type") == "text":
71
+ parts.append({"type": "text", "text": p.get("text", "")})
72
+ elif p.get("type") == "image_url":
73
+ parts.append({"type": "image", "image": _load_image(p["image_url"]["url"])})
74
+ conv.append({"role": role, "content": parts})
75
+ return conv
76
+
77
+
78
+ @app.get("/health")
79
+ def health():
80
+ return {"status": "ok", "model_loaded": _model is not None, "load_error": _load_error}
81
+
82
+
83
+ @app.post("/v1/chat/completions")
84
+ async def chat(req: Request):
85
+ _load()
86
+ if _load_error:
87
+ return JSONResponse({"error": _load_error}, status_code=500)
88
+ body = await req.json()
89
+ try:
90
+ conv = _to_gemma_messages(body.get("messages", []))
91
+ inputs = _proc.apply_chat_template(
92
+ conv, add_generation_prompt=True, tokenize=True,
93
+ return_dict=True, return_tensors="pt",
94
+ ).to(_model.device)
95
+ in_len = inputs["input_ids"].shape[1]
96
+ max_new = int(body.get("max_tokens", 512))
97
+ temp = float(body.get("temperature", 0.7))
98
+ with torch.inference_mode():
99
+ out = _model.generate(
100
+ **inputs, max_new_tokens=max_new,
101
+ do_sample=temp > 0, temperature=max(temp, 0.01),
102
+ )
103
+ gen = out[0][in_len:]
104
+ text = _proc.decode(gen, skip_special_tokens=True).strip()
105
+ return {
106
+ "id": "chatcmpl-gemma", "object": "chat.completion",
107
+ "created": int(time.time()), "model": MODEL_ID,
108
+ "choices": [{"index": 0, "message": {"role": "assistant", "content": text},
109
+ "finish_reason": "stop"}],
110
+ "usage": {"prompt_tokens": int(in_len), "completion_tokens": int(gen.shape[0]),
111
+ "total_tokens": int(in_len + gen.shape[0])},
112
+ }
113
+ except Exception as e:
114
+ return JSONResponse({"error": f"{type(e).__name__}: {e}"}, status_code=500)
115
+
116
+
117
+ @app.get("/")
118
+ def root():
119
+ return {"service": "gemma-vision-judge", "model": MODEL_ID,
120
+ "endpoints": ["/health", "/v1/chat/completions"]}