"""Deploy Gemma 4 31B IT as an OpenAI-compatible vLLM server on Modal. Usage ----- modal deploy src/alien_obfuscator/modal_serve.py # deploy permanently modal run src/alien_obfuscator/modal_serve.py # test locally After deployment, set the URL in your .env:: MODAL_API_URL=https://YOUR_WORKSPACE--modal-gemma-serve.modal.run """ import json import subprocess import time import urllib.error import urllib.request from pathlib import Path from typing import Any import aiohttp from aiohttp import ClientTimeout import modal import yaml _PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent _LOCAL_CONFIG_PATH = _PROJECT_ROOT / "config.yaml" _IMAGE_CONFIG_PATH = Path("/opt/config.yaml") for _config_path in (_IMAGE_CONFIG_PATH, _LOCAL_CONFIG_PATH): if _config_path.exists(): _cfg = yaml.safe_load(_config_path.read_text(encoding="utf-8")) break else: _msg = f"config.yaml not found at {_IMAGE_CONFIG_PATH} or {_LOCAL_CONFIG_PATH}" raise FileNotFoundError(_msg) MODEL_NAME: str = _cfg["backends"]["modal"]["default_model"] SCALEDOWN_WINDOW_MINUTES: int = _cfg["backends"]["modal"]["scaledown_window_minutes"] N_GPU = 1 MINUTES = 60 VLLM_PORT = 8000 FAST_BOOT = False vllm_image = ( modal.Image.from_registry("nvidia/cuda:12.9.0-devel-ubuntu24.04", add_python="3.12") .entrypoint([]) .uv_pip_install("vllm==0.21.0") .add_local_file(str(_LOCAL_CONFIG_PATH), "/opt/config.yaml", copy=True) .env( { "HF_XET_HIGH_PERFORMANCE": "1", "VLLM_LOG_STATS_INTERVAL": "1", } ) ) hf_cache_vol = modal.Volume.from_name("huggingface-cache", create_if_missing=True) vllm_cache_vol = modal.Volume.from_name("vllm-cache", create_if_missing=True) app = modal.App("modal-gemma") @app.function( image=vllm_image, gpu=f"H200:{N_GPU}", scaledown_window=SCALEDOWN_WINDOW_MINUTES * MINUTES, timeout=10 * MINUTES, volumes={ "/root/.cache/huggingface": hf_cache_vol, "/root/.cache/vllm": vllm_cache_vol, }, secrets=[modal.Secret.from_name("hf-token")], ) @modal.concurrent(max_inputs=100) @modal.web_server(port=VLLM_PORT, startup_timeout=10 * MINUTES) def serve() -> None: cmd = [ "vllm", "serve", MODEL_NAME, "--served-model-name", MODEL_NAME, "gemma-4-31b", "--host", "0.0.0.0", "--port", str(VLLM_PORT), "--uvicorn-log-level=info", "--async-scheduling", ] cmd += ["--enforce-eager" if FAST_BOOT else "--no-enforce-eager"] cmd += ["--tensor-parallel-size", str(N_GPU)] cmd += [ "--limit-mm-per-prompt", f"'{json.dumps({'image': 0, 'video': 0, 'audio': 0})}'", "--enable-auto-tool-choice", "--reasoning-parser", "gemma4", "--tool-call-parser", "gemma4", ] print(*cmd) subprocess.Popen(" ".join(cmd), shell=True) _warm_up() def _warm_up() -> None: """Poll /health then send a short warm-up request to trigger JIT compilation. After vLLM starts, the first real request triggers Triton kernel JIT compilation (~2-3 s extra latency). Sending a trivial prompt during startup absorbs this one-time cost so end-users never see it. """ health_url = f"http://0.0.0.0:{VLLM_PORT}/health" for i in range(300): try: with urllib.request.urlopen(health_url) as resp: if resp.status == 200: print(f"vLLM healthy after {i * 2}s") break except (urllib.error.URLError, ConnectionRefusedError, OSError): pass time.sleep(2) else: print("Warning: vLLM did not become healthy within 10 minutes") return warmup_payload = json.dumps( { "model": MODEL_NAME, "messages": [{"role": "user", "content": "Hi"}], "max_tokens": 5, } ).encode() warmup_req = urllib.request.Request( f"http://0.0.0.0:{VLLM_PORT}/v1/chat/completions", data=warmup_payload, headers={"Content-Type": "application/json"}, ) try: with urllib.request.urlopen(warmup_req, timeout=120) as resp: print(f"Warm-up complete (status {resp.status})") except Exception as e: print(f"Warm-up request failed: {e}") @app.local_entrypoint() async def test(test_timeout: int = 15 * MINUTES) -> None: """Health-check the server and send a test prompt.""" url = await serve.get_web_url.aio() messages: list[dict[str, str]] = [ {"role": "user", "content": "Say hello in pirate speak."}, ] async with aiohttp.ClientSession(base_url=url) as session: print(f"Running health check for server at {url}") async with session.get("/health", timeout=ClientTimeout(total=test_timeout - 1 * MINUTES)) as resp: up = resp.status == 200 assert up, f"Failed health check for server at {url}" print(f"Healthy: {url}") print(f"Sending test prompt to {url}:") await _send_request(session, "gemma-4-31b", messages) async def _send_request(session: aiohttp.ClientSession, model: str, messages: list[dict[str, str]]) -> None: payload: dict[str, Any] = { "messages": messages, "model": model, "stream": True, } payload["chat_template_kwargs"] = {"enable_thinking": True} headers = { "Content-Type": "application/json", "Accept": "text/event-stream", } async with session.post("/v1/chat/completions", json=payload, headers=headers) as resp: async for raw in resp.content: resp.raise_for_status() line = raw.decode().strip() if not line or line == "data: [DONE]": continue if line.startswith("data: "): line = line[len("data: ") :] chunk = json.loads(line) assert chunk["object"] == "chat.completion.chunk" delta = chunk["choices"][0]["delta"] content = delta.get("content") or delta.get("reasoning") or delta.get("reasoning_content") if content: print(content, end="") else: print("\n", chunk) print()