pipV1 / services /modal_flux.py
Itsjustamit's picture
some updates
cd40a43 verified
"""
Modal serverless GPU client for Flux/SDXL image generation.
Handles: Open-source image generation models on demand.
"""
import os
from typing import Optional
import httpx
import base64
# Note: For full Modal integration, you'd deploy a Modal app.
# This client calls a deployed Modal endpoint or falls back to HuggingFace.
class ModalFluxClient:
"""Modal-powered Flux/SDXL image generation for Pip."""
# New HuggingFace router API (the old api-inference.huggingface.co is deprecated)
HF_ROUTER_URL = "https://router.huggingface.co"
# Router endpoints for different models via fal.ai
ROUTER_ENDPOINTS = {
"flux": "/fal-ai/fal-ai/flux/schnell",
"flux_dev": "/fal-ai/fal-ai/flux/dev",
}
# Legacy models (for Modal deployment)
MODELS = {
"flux": "black-forest-labs/FLUX.1-schnell",
"sdxl_lightning": "ByteDance/SDXL-Lightning",
"sdxl": "stabilityai/stable-diffusion-xl-base-1.0",
}
def __init__(self):
self.hf_token = os.getenv("HF_TOKEN")
self.modal_endpoint = os.getenv("MODAL_FLUX_ENDPOINT") # If deployed
self.available = bool(self.hf_token) or bool(self.modal_endpoint)
if not self.available:
print("⚠️ HuggingFace/Modal: No tokens found - image generation limited")
def is_available(self) -> bool:
"""Check if the client is available."""
return self.available
async def generate_image(
self,
prompt: str,
model: str = "flux"
) -> Optional[str]:
"""
Generate image using Flux or SDXL via Modal/HuggingFace Router.
Returns base64 encoded image.
"""
# Try Modal endpoint first if available
if self.modal_endpoint:
result = await self._generate_via_modal(prompt, model)
if result:
return result
# Try new HuggingFace router API (primary method)
result = await self._generate_via_hf_router(prompt, model)
if result:
return result
# Final fallback - return None
print(f"All image generation methods failed for model: {model}")
return None
async def _generate_via_modal(self, prompt: str, model: str) -> Optional[str]:
"""
Call deployed Modal function for image generation.
"""
try:
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
self.modal_endpoint,
json={"prompt": prompt, "model": model}
)
if response.status_code == 200:
data = response.json()
return data.get("image_base64")
except Exception as e:
print(f"Modal generation error: {e}")
return None
async def _generate_via_hf_router(
self,
prompt: str,
model: str = "flux"
) -> Optional[str]:
"""
Generate image via new HuggingFace Router API (fal.ai backend).
This is the current working method as of 2025.
"""
try:
# Get router endpoint for model
endpoint = self.ROUTER_ENDPOINTS.get(model, self.ROUTER_ENDPOINTS["flux"])
url = f"{self.HF_ROUTER_URL}{endpoint}"
headers = {}
if self.hf_token:
headers["Authorization"] = f"Bearer {self.hf_token}"
# New API uses 'prompt' not 'inputs'
payload = {"prompt": prompt}
async with httpx.AsyncClient(timeout=120.0) as client:
response = await client.post(
url,
headers=headers,
json=payload
)
if response.status_code == 200:
data = response.json()
# New format returns {"images": [{"url": "...", "content_type": "..."}], ...}
if "images" in data and data["images"]:
image_info = data["images"][0]
# Image could be URL or base64
if isinstance(image_info, dict):
if "url" in image_info:
# Download image from URL and convert to base64
img_response = await client.get(image_info["url"])
if img_response.status_code == 200:
return base64.b64encode(img_response.content).decode('utf-8')
elif "b64_json" in image_info:
return image_info["b64_json"]
elif isinstance(image_info, str):
# Direct base64 string
return image_info
print(f"HF Router unexpected response format: {list(data.keys())}")
else:
print(f"HF Router API error: {response.status_code} - {response.text[:200]}")
except Exception as e:
print(f"HF Router generation error: {e}")
return None
async def generate_fast(self, prompt: str) -> Optional[str]:
"""
Use fastest available model (SDXL-Lightning).
"""
return await self.generate_image(prompt, model="sdxl_lightning")
async def generate_artistic(self, prompt: str) -> Optional[str]:
"""
Use Flux for more artistic, dreamlike results.
"""
return await self.generate_image(prompt, model="flux")
# Modal app definition for deployment (optional)
# Run with: modal deploy services/modal_flux.py
MODAL_APP_CODE = '''
import modal
app = modal.App("pip-flux-generator")
# Define the image with required dependencies
flux_image = modal.Image.debian_slim().pip_install(
"diffusers",
"transformers",
"accelerate",
"torch",
"safetensors"
)
@app.function(
image=flux_image,
gpu="A10G",
timeout=300,
)
def generate_flux_image(prompt: str) -> bytes:
"""Generate image using Flux on Modal GPU."""
import torch
from diffusers import FluxPipeline
pipe = FluxPipeline.from_pretrained(
"black-forest-labs/FLUX.1-schnell",
torch_dtype=torch.bfloat16
)
pipe.to("cuda")
image = pipe(
prompt,
guidance_scale=0.0,
num_inference_steps=4,
max_sequence_length=256,
).images[0]
# Convert to bytes
import io
buf = io.BytesIO()
image.save(buf, format="PNG")
return buf.getvalue()
@app.local_entrypoint()
def main(prompt: str = "a serene lake at sunset"):
image_bytes = generate_flux_image.remote(prompt)
with open("output.png", "wb") as f:
f.write(image_bytes)
print("Image saved to output.png")
'''