ASTRALK commited on
Commit
be67303
·
verified ·
1 Parent(s): ef73896

Upload comic/modal_backend.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. comic/modal_backend.py +82 -0
comic/modal_backend.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Real Modal backends: Gemma (vLLM OpenAI endpoint) + FLUX (resident pipeline).
2
+
3
+ Config via env (set after deploying the two Modal apps in serve/):
4
+ COMIC_GEMMA_URL - base URL of the vLLM OpenAI server, ending in /v1
5
+ COMIC_GEMMA_MODEL - served model name (default "gemma-comic")
6
+ COMIC_GEMMA_KEY - token if the endpoint uses proxy-auth (else "EMPTY")
7
+ COMIC_FLUX_APP - Modal app name for FLUX (default comic-flux)
8
+ COMIC_FLUX_CLS - Modal class name (default FluxRenderer)
9
+
10
+ Pattern mirrors the wisdom2 reference deployment: an OpenAI client to the vLLM /v1
11
+ endpoint for the writer, and a modal.Cls handle for the artist. The generous timeout
12
+ lets the first call after scale-to-zero ride through the GPU cold boot.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+
19
+ from .backends import WriterBackend, ArtistBackend
20
+
21
+ DEFAULT_GEMMA_URL = "https://keshav-public07--comic-gemma-serve.modal.run/v1"
22
+ DEFAULT_GEMMA_MODEL = "gemma-comic"
23
+
24
+
25
+ class ModalWriter(WriterBackend):
26
+ def __init__(self, base_url=None, model=None, api_key=None,
27
+ temperature=0.9, max_tokens=16384, timeout=900):
28
+ from openai import OpenAI
29
+
30
+ base_url = base_url or os.environ.get("COMIC_GEMMA_URL", DEFAULT_GEMMA_URL)
31
+ self.model = model or os.environ.get("COMIC_GEMMA_MODEL", DEFAULT_GEMMA_MODEL)
32
+ self.temperature = temperature
33
+ # Bibles/panel batches are large JSON; give plenty of output room.
34
+ self.max_tokens = max_tokens
35
+ # 15 min: absorbs the ~850s first-deploy cold boot without timing out.
36
+ self._client = OpenAI(
37
+ base_url=base_url,
38
+ api_key=api_key or os.environ.get("COMIC_GEMMA_KEY", "EMPTY"),
39
+ timeout=timeout,
40
+ )
41
+
42
+ def chat(self, messages: list) -> str:
43
+ resp = self._client.chat.completions.create(
44
+ model=self.model,
45
+ messages=messages,
46
+ stream=False,
47
+ temperature=self.temperature,
48
+ max_tokens=self.max_tokens,
49
+ # Ask vLLM for a JSON object directly when the server supports it; the
50
+ # prompts also demand strict JSON, so this is belt-and-braces.
51
+ response_format={"type": "json_object"},
52
+ )
53
+ return resp.choices[0].message.content or ""
54
+
55
+ def warm(self) -> bool:
56
+ try:
57
+ self._client.models.list()
58
+ return True
59
+ except Exception:
60
+ return False
61
+
62
+
63
+ class ModalArtist(ArtistBackend):
64
+ def __init__(self, app=None, cls=None):
65
+ import modal
66
+
67
+ app = app or os.environ.get("COMIC_FLUX_APP", "comic-flux")
68
+ cls = cls or os.environ.get("COMIC_FLUX_CLS", "FluxRenderer")
69
+ Renderer = modal.Cls.from_name(app, cls)
70
+ self._obj = Renderer()
71
+
72
+ def render(self, prompt: str, seed: int = 0) -> bytes:
73
+ return self._obj.render.remote(prompt, seed)
74
+
75
+ def render_batch(self, prompts: list, seeds: list) -> list:
76
+ return self._obj.render_batch.remote(list(prompts), list(seeds))
77
+
78
+ def warm(self) -> bool:
79
+ try:
80
+ return bool(self._obj.warm.remote())
81
+ except Exception:
82
+ return False