Ornith-api / server.py
cloudunity's picture
Update server.py
244f1aa verified
Raw History Blame
5.3 kB
import io
import logging
import os
import threading
import uuid
import torch
from flask import Flask, jsonify, request, send_file, redirect
from flask_cors import CORS
os.environ["OMP_NUM_THREADS"] = "4"
os.environ["MKL_NUM_THREADS"] = "4"
torch.set_num_threads(4)
torch.set_num_interop_threads(1)
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
logger = logging.getLogger("sd-turbo-server")
DEFAULT_STEPS = int(os.environ.get("DEFAULT_STEPS", "4"))
DEFAULT_GUIDANCE = float(os.environ.get("DEFAULT_GUIDANCE", "1.0"))
DEFAULT_WIDTH = 768
DEFAULT_HEIGHT = 768
app = Flask(__name__)
CORS(app)
_pipeline = None
_pipeline_lock = threading.Lock()
_pipeline_load_error = None
def load_pipeline():
global _pipeline, _pipeline_load_error
from diffusers import AutoPipelineForText2Image
model_dir = os.environ.get("MODEL_LOCAL_DIR", "/app/models/sd-turbo")
logger.info("Loading pipeline from %s", model_dir)
try:
pipeline = AutoPipelineForText2Image.from_pretrained(
model_dir, torch_dtype=torch.float32, safety_checker=None
)
pipeline.to("cpu")
pipeline.unet = torch.quantization.quantize_dynamic(
pipeline.unet, {torch.nn.Linear}, dtype=torch.qint8
)
if hasattr(pipeline, "text_encoder"):
pipeline.text_encoder = torch.quantization.quantize_dynamic(
pipeline.text_encoder, {torch.nn.Linear}, dtype=torch.qint8
)
pipeline.set_progress_bar_config(disable=True)
_pipeline = pipeline
logger.info("Pipeline loaded and quantized successfully.")
except Exception as e:
_pipeline_load_error = str(e)
logger.error("Pipeline load failed: %s", e)
def run_generation(prompt, width, height, steps, guidance):
with _pipeline_lock:
result = _pipeline(
prompt=prompt,
num_inference_steps=steps,
guidance_scale=guidance,
width=width,
height=height,
)
return result.images[0]
@app.route("/")
def index():
return "SD Turbo Server - Use /image or /v1/images/generations"
@app.route("/health")
def health():
return jsonify({"status": "ok", "model": "sd-turbo"})
@app.route("/v1/models")
def list_models():
return jsonify({
"object": "list",
"data": [{"id": "sd-turbo", "object": "model", "owned_by": "stabilityai"}]
})
@app.route("/v1/images/generations", methods=["POST"])
def images_generations():
"""OpenAI-compatible endpoint that returns a direct image URL"""
if not request.is_json:
return jsonify({"error": {"message": "JSON required"}}), 400
data = request.get_json()
prompt = data.get("prompt")
if not prompt:
return jsonify({"error": {"message": "prompt is required"}}), 400
size = data.get("size", "768x768")
n = data.get("n", 1)
steps = int(data.get("num_inference_steps", DEFAULT_STEPS))
guidance = float(data.get("guidance_scale", DEFAULT_GUIDANCE))
if steps > 6: steps = 6
if guidance > 2.0: guidance = 1.5
width = DEFAULT_WIDTH
height = DEFAULT_HEIGHT
if "x" in size:
try:
w, h = map(int, size.split("x"))
width, height = min(w, 768), min(h, 768)
except:
pass
request_id = uuid.uuid4().hex[:8]
logger.info(f"[{request_id}] OpenAI gen - prompt={prompt[:60]}... size={width}x{height}")
try:
image = run_generation(prompt, width, height, steps, guidance)
# Save to a temporary in-memory route so we can return a URL
img_id = uuid.uuid4().hex[:12]
# For simplicity we return a direct /image URL with the same prompt
image_url = f"https://cloudunity-sdturbolumi.hf.space/image?prompt={prompt.replace(' ', '+')}&width={width}&height={height}&steps={steps}&guidance={guidance}"
return jsonify({
"created": int(time.time()),
"data": [{"url": image_url}]
})
except Exception as e:
logger.exception("Generation failed")
return jsonify({"error": {"message": str(e)}}), 500
@app.route("/image", methods=["GET"])
def generate_image():
prompt = request.args.get("prompt", "").replace("+", " ")
if not prompt:
return "Missing prompt", 400
try:
width = int(request.args.get("width", DEFAULT_WIDTH))
height = int(request.args.get("height", DEFAULT_HEIGHT))
steps = int(request.args.get("steps", DEFAULT_STEPS))
guidance = float(request.args.get("guidance", DEFAULT_GUIDANCE))
except ValueError:
return "Invalid parameters", 400
if width > 768: width = 768
if height > 768: height = 768
if steps > 6: steps = 6
if guidance > 2.0: guidance = 1.0
try:
img = run_generation(prompt, width, height, steps, guidance)
buf = io.BytesIO()
img.save(buf, format="PNG")
buf.seek(0)
return send_file(buf, mimetype="image/png")
except Exception as e:
logger.exception("Image generation failed")
return str(e), 500
if __name__ == "__main__":
load_pipeline()
port = int(os.environ.get("PORT", "7860"))
app.run(host="0.0.0.0", port=port, threaded=True)