diffusion_models / server.py
nanye's picture
- added support for google/ddpm-cifar10-32
785e85b
Raw History Blame Contribute Delete
5.44 kB
import base64
import binascii
import json
import mimetypes
import secrets
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from io import BytesIO
from pathlib import Path
from urllib.parse import urlparse
from generate import CHECKPOINTS, MODEL_ID, MODEL_SIZES, generate_images, is_model_loaded, load_model
ROOT = Path(__file__).resolve().parent
def encode_frame(image):
buffer = BytesIO()
image.save(buffer, format="JPEG", quality=86)
return base64.b64encode(buffer.getvalue()).decode("ascii")
def generate_frames(seed, checkpoints=CHECKPOINTS, initial_image=None, model_id=MODEL_ID):
yield {"type": "status", "status": "loading", "model": model_id}
try:
for step, image in generate_images(seed, checkpoints=checkpoints, initial_image=initial_image, model_id=model_id):
yield {"type": "progress", "step": step, "total": 1000}
if image is not None:
yield {"type": "frame", "step": step, "image": encode_frame(image)}
_, _, device = load_model(model_id=model_id)
yield {"type": "complete", "seed": seed, "device": device, "model": model_id}
except Exception as error:
yield {"type": "error", "message": str(error)}
class DiffusionHandler(BaseHTTPRequestHandler):
def send_bytes(self, status, content_type, body):
self.send_response(status)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
def do_GET(self):
path = urlparse(self.path).path
if path == "/api/health":
body = json.dumps({"model": MODEL_ID, "models": list(MODEL_SIZES), "loaded": is_model_loaded()}).encode()
self.send_bytes(200, "application/json", body)
return
relative_path = "index.html" if path == "/" else path.lstrip("/")
if relative_path not in {
"index.html",
"app.js",
"styles.css",
"assets/celeba_hq_validation_91_256.png",
"assets/cifar10_train_23_32.png",
}:
self.send_error(404)
return
file_path = ROOT / relative_path
content_type = mimetypes.guess_type(file_path.name)[0] or "application/octet-stream"
self.send_bytes(200, content_type, file_path.read_bytes())
def do_POST(self):
if urlparse(self.path).path != "/api/generate":
self.send_error(404)
return
try:
length = int(self.headers.get("Content-Length", "0"))
request = json.loads(self.rfile.read(length) or b"{}")
model_id = request.get("model", MODEL_ID)
if not isinstance(model_id, str) or model_id not in MODEL_SIZES:
raise ValueError("Unsupported model.")
seed = int(request.get("seed", secrets.randbelow(2**31)))
if seed < 0 or seed >= 2**63:
raise ValueError("Seed must be between 0 and 2^63 - 1.")
checkpoints = request.get("checkpoints", CHECKPOINTS)
if (
not isinstance(checkpoints, list)
or not 2 <= len(checkpoints) <= 12
or any(type(step) is not int or step < 0 or step > 1000 for step in checkpoints)
):
raise ValueError("Checkpoints must be a list of 2 to 12 integers from 0 to 1000.")
checkpoints = tuple(sorted(set(checkpoints)))
if checkpoints[0] != 0 or checkpoints[-1] != 1000:
raise ValueError("Checkpoints must include 0 and 1000.")
initial_image = None
encoded_image = request.get("initial_image")
if encoded_image is not None:
if not isinstance(encoded_image, str) or len(encoded_image) > 3_000_000:
raise ValueError("The forward-noise image is invalid or too large.")
from PIL import Image
encoded_image = encoded_image.partition(",")[2]
initial_image = Image.open(BytesIO(base64.b64decode(encoded_image, validate=True)))
initial_image.load()
except (ValueError, TypeError, json.JSONDecodeError, binascii.Error, OSError) as error:
self.send_bytes(400, "application/json", json.dumps({"error": str(error)}).encode())
return
self.send_response(200)
self.send_header("Content-Type", "application/x-ndjson; charset=utf-8")
self.send_header("Cache-Control", "no-store")
self.send_header("X-Accel-Buffering", "no")
self.end_headers()
try:
for event in generate_frames(seed, checkpoints, initial_image, model_id):
self.wfile.write((json.dumps(event) + "\n").encode())
self.wfile.flush()
except (BrokenPipeError, ConnectionResetError):
pass
def log_message(self, format_string, *args):
print(f"{self.address_string()} - {format_string % args}")
class DiffusionServer(ThreadingHTTPServer):
daemon_threads = True
if __name__ == "__main__":
server = DiffusionServer(("0.0.0.0", 8000), DiffusionHandler)
print(f"Diffusion Lab: http://{server.server_address[0]}:{server.server_address[1]}")
print(f"Model: {MODEL_ID} (loaded on first generation request)")
server.serve_forever()