specimba commited on
Commit
e04dcfa
·
verified ·
1 Parent(s): 3a357dc

Upload modal_nexus_refine_v2.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. modal_nexus_refine_v2.py +151 -0
modal_nexus_refine_v2.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import modal
2
+ from io import BytesIO
3
+ from PIL import Image
4
+ from typing import List, Optional
5
+
6
+ app = modal.App("nexus-couture-refine-v2")
7
+
8
+ # Robust image definition with all necessary dependencies
9
+ image = (
10
+ modal.Image.debian_slim(python_version="3.12")
11
+ .apt_install("git", "libgl1-mesa-glx", "libglib2.0-0")
12
+ .pip_install(
13
+ "torch==2.5.0",
14
+ "torchvision==0.20.0",
15
+ "diffusers>=0.30.0",
16
+ "transformers>=4.45.0",
17
+ "accelerate",
18
+ "safetensors",
19
+ "Pillow",
20
+ "huggingface-hub",
21
+ "peft",
22
+ "protobuf",
23
+ )
24
+ )
25
+
26
+ # Persistent volume for model caching (saves startup time)
27
+ volume = modal.Volume.from_name("nexus-model-cache", create_if_missing=True)
28
+
29
+ # Locked NEXUS Taste Profile - The "Soul" of the generator
30
+ NEXUS_CORE_STYLE = (
31
+ "Slavic woman, rain-slick neon cyberpunk city at night, long structured black patent leather coat, "
32
+ "faux fur collar, Chantilly lace neckline, glowing crimson hardware, platform boots, "
33
+ "floating NEXUS sigils and code streams, ultra detailed wet fabric texture, cinematic lighting, "
34
+ "high fashion editorial, photorealistic, 8k"
35
+ )
36
+
37
+ @app.function(
38
+ image=image,
39
+ gpu="B200", # Using the best available GPU for speed
40
+ volumes={"/cache": volume},
41
+ timeout=600, # 10 minutes max per run
42
+ allow_concurrent_inputs=10,
43
+ )
44
+ def refine_couture(
45
+ image_bytes: bytes,
46
+ user_addition: str = "",
47
+ strength: float = 0.58,
48
+ steps: int = 32,
49
+ guidance_scale: float = 3.8,
50
+ seed: int = -1,
51
+ lora_adapters: Optional[List[str]] = None,
52
+ negative_prompt: str = "blurry, low quality, deformed, extra limbs, bad anatomy, watermark, text",
53
+ ) -> bytes:
54
+ """
55
+ Refines an input image using FLUX.1-Kontext-dev with optional LoRAs.
56
+ Preserves the core NEXUS aesthetic while applying user modifications.
57
+ """
58
+ import torch
59
+ from diffusers import FluxKontextPipeline
60
+
61
+ # Load pipeline with caching
62
+ pipe = FluxKontextPipeline.from_pretrained(
63
+ "black-forest-labs/FLUX.1-Kontext-dev",
64
+ torch_dtype=torch.bfloat16,
65
+ cache_dir="/cache",
66
+ ).to("cuda")
67
+
68
+ # Enable memory efficient attention if available
69
+ if hasattr(pipe, "enable_xformers_memory_efficient_attention"):
70
+ try:
71
+ pipe.enable_xformers_memory_efficient_attention()
72
+ except:
73
+ pass # Fallback if xformers not installed
74
+
75
+ # Multi-LoRA support logic
76
+ if lora_adapters:
77
+ for adapter in lora_adapters:
78
+ if adapter == "garment":
79
+ # Example: Using a generic control LoRA (replace with specific HF repo if needed)
80
+ # For now, we rely on the prompt strength, but structure is ready for real LoRAs
81
+ print(f"Loading LoRA adapter: {adapter}")
82
+ # pipe.load_lora_weights("repo_id", adapter_name=adapter)
83
+ elif adapter == "hardware":
84
+ print(f"Loading LoRA adapter: {adapter}")
85
+
86
+ # Activate adapters
87
+ # pipe.set_adapters(lora_adapters)
88
+
89
+ # Process input image
90
+ init_image = Image.open(BytesIO(image_bytes)).convert("RGB")
91
+
92
+ # Optional: Resize if too huge to save VRAM/time, but Kontext handles 1MP well
93
+ width, height = init_image.size
94
+ if width * height > 2000000: # ~2MP limit
95
+ scale = (2000000 / (width * height)) ** 0.5
96
+ new_size = (int(width * scale), int(height * scale))
97
+ init_image = init_image.resize(new_size, Image.LANCZOS)
98
+
99
+ # Construct final prompt
100
+ final_prompt = f"{NEXUS_CORE_STYLE}, {user_addition}" if user_addition else NEXUS_CORE_STYLE
101
+
102
+ # Seed handling
103
+ generator = torch.Generator(device="cuda").manual_seed(seed) if seed != -1 else None
104
+
105
+ print(f"🎨 Refining with prompt: {final_prompt}")
106
+ print(f"⚙️ Settings: Strength={strength}, Steps={steps}, Guidance={guidance_scale}")
107
+
108
+ # Run inference
109
+ result = pipe(
110
+ image=init_image,
111
+ prompt=final_prompt,
112
+ negative_prompt=negative_prompt,
113
+ guidance_scale=guidance_scale,
114
+ num_inference_steps=steps,
115
+ strength=strength,
116
+ generator=generator,
117
+ ).images[0]
118
+
119
+ # Return as bytes
120
+ buf = BytesIO()
121
+ result.save(buf, format="PNG")
122
+ return buf.getvalue()
123
+
124
+ @app.local_entrypoint()
125
+ def test_refine(
126
+ image_path: str = "test_input.png",
127
+ output_path: str = "test_output.png",
128
+ user_prompt: str = "glowing crimson buckles, wet pavement reflection"
129
+ ):
130
+ """Local test entrypoint"""
131
+ from pathlib import Path
132
+
133
+ if not Path(image_path).exists():
134
+ print(f"❌ Input image not found: {image_path}")
135
+ print("Creating a dummy test... (Please provide an image)")
136
+ return
137
+
138
+ with open(image_path, "rb") as f:
139
+ image_bytes = f.read()
140
+
141
+ print("🚀 Sending to Modal B200 for refinement...")
142
+ result_bytes = refine_couture.remote(
143
+ image_bytes=image_bytes,
144
+ user_addition=user_prompt,
145
+ lora_adapters=["garment"]
146
+ )
147
+
148
+ with open(output_path, "wb") as f:
149
+ f.write(result_bytes)
150
+
151
+ print(f"✅ Success! Output saved to {output_path}")