8BitStudio commited on
Commit
668ebc0
·
verified ·
1 Parent(s): c2ef5a0

Upload Aniimage_2_Interactive_Colab.ipynb

Browse files
Files changed (1) hide show
  1. Aniimage_2_Interactive_Colab.ipynb +296 -0
Aniimage_2_Interactive_Colab.ipynb ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Aniimage-2 Image generator\n",
8
+ "\n",
9
+ "This notebook runs [`8BitStudio/Aniimage-2`](https://huggingface.co/8BitStudio/Aniimage-2) with a Gradio interface. It manually assembles the repository's UNet with the matching VAE and CLIP text encoder because the repository is not packaged as a complete Diffusers pipeline.\n",
10
+ "\n",
11
+ "## Before running\n",
12
+ "\n",
13
+ "1. In Colab choose **Runtime → Change runtime type → T4 GPU** (or a better GPU).\n",
14
+ "2. Run every cell in order. The first model load downloads several gigabytes and can take a few minutes.\n",
15
+ "3. Open the Gradio link printed by the final cell. Generated images are also saved under `/content/aniimage2_outputs`.\n",
16
+ "\n",
17
+ "The defaults follow the model card: 512×512, DPM++ SDE Karras, 50 steps, v-prediction, zero-terminal-SNR, CLIP penultimate layer, and CFG rescale 0.7. The default negative prompt includes `NSFW` because omitting it may produce NSFW images.\n",
18
+ "\n",
19
+ "If you want to generate NSFW images, remove the `NSFW` text in the negative prompt. \n",
20
+ "\n",
21
+ "If your results look \"glitched\", try lowering your CFG to around 7 or 6.5"
22
+ ]
23
+ },
24
+ {
25
+ "cell_type": "code",
26
+ "execution_count": null,
27
+ "metadata": {},
28
+ "outputs": [],
29
+ "source": [
30
+ "!pip -q install -U \"diffusers>=0.37.1\" \"transformers>=4.46,<5\" accelerate safetensors huggingface_hub \"gradio>=5,<7\" \"click>=8.2\" \"Pillow<11.0.0\""
31
+ ]
32
+ },
33
+ {
34
+ "cell_type": "code",
35
+ "execution_count": null,
36
+ "metadata": {},
37
+ "outputs": [],
38
+ "source": [
39
+ "import json\n",
40
+ "import random\n",
41
+ "from pathlib import Path\n",
42
+ "\n",
43
+ "import gradio as gr\n",
44
+ "import numpy as np\n",
45
+ "import torch\n",
46
+ "from PIL import Image\n",
47
+ "from diffusers import (\n",
48
+ " AutoencoderKL,\n",
49
+ " DDIMScheduler,\n",
50
+ " DPMSolverMultistepScheduler,\n",
51
+ " EulerAncestralDiscreteScheduler,\n",
52
+ " EulerDiscreteScheduler,\n",
53
+ " UNet2DConditionModel,\n",
54
+ ")\n",
55
+ "from huggingface_hub import hf_hub_download\n",
56
+ "from transformers import CLIPTextModel, CLIPTokenizer\n",
57
+ "\n",
58
+ "REPO_ID = \"8BitStudio/Aniimage-2\"\n",
59
+ "CLIP_ID = \"openai/clip-vit-large-patch14\"\n",
60
+ "OUTPUT_DIR = Path(\"/content/aniimage2_outputs\")\n",
61
+ "OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n",
62
+ "\n",
63
+ "if not torch.cuda.is_available():\n",
64
+ " raise RuntimeError(\"No GPU detected. In Colab select Runtime > Change runtime type > T4 GPU, then run again.\")\n",
65
+ "\n",
66
+ "DEVICE = \"cuda\"\n",
67
+ "DTYPE = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16\n",
68
+ "torch.backends.cuda.matmul.allow_tf32 = True\n",
69
+ "torch.backends.cudnn.allow_tf32 = True\n",
70
+ "print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n",
71
+ "print(f\"Model dtype: {DTYPE}\")\n"
72
+ ]
73
+ },
74
+ {
75
+ "cell_type": "code",
76
+ "execution_count": null,
77
+ "metadata": {},
78
+ "outputs": [],
79
+ "source": [
80
+ "# Download the small model metadata first so every component matches training.\n",
81
+ "config_path = hf_hub_download(REPO_ID, \"Aniimage-2/model_config.json\")\n",
82
+ "with open(config_path, \"r\", encoding=\"utf-8\") as f:\n",
83
+ " MODEL_CONFIG = json.load(f)\n",
84
+ "\n",
85
+ "VAE_ID = MODEL_CONFIG[\"vae\"]\n",
86
+ "GUIDANCE_RESCALE = float(MODEL_CONFIG.get(\"guidance_rescale\", 0.7))\n",
87
+ "\n",
88
+ "print(\"Loading Aniimage-2 UNet...\")\n",
89
+ "unet = UNet2DConditionModel.from_pretrained(\n",
90
+ " REPO_ID,\n",
91
+ " subfolder=\"Aniimage-2/unet\",\n",
92
+ " torch_dtype=DTYPE,\n",
93
+ " low_cpu_mem_usage=True,\n",
94
+ ").to(DEVICE).eval()\n",
95
+ "unet.requires_grad_(False)\n",
96
+ "\n",
97
+ "print(f\"Loading VAE: {VAE_ID}...\")\n",
98
+ "vae = AutoencoderKL.from_pretrained(VAE_ID, torch_dtype=DTYPE).to(DEVICE).eval()\n",
99
+ "vae.requires_grad_(False)\n",
100
+ "vae.enable_slicing()\n",
101
+ "\n",
102
+ "print(f\"Loading text encoder: {CLIP_ID}...\")\n",
103
+ "tokenizer = CLIPTokenizer.from_pretrained(CLIP_ID)\n",
104
+ "text_encoder = CLIPTextModel.from_pretrained(CLIP_ID, torch_dtype=DTYPE).to(DEVICE).eval()\n",
105
+ "text_encoder.requires_grad_(False)\n",
106
+ "\n",
107
+ "# Aniimage-2 was trained using CLIP's penultimate transformer layer.\n",
108
+ "clip_inner = getattr(text_encoder, \"text_model\", text_encoder)\n",
109
+ "clip_inner.encoder.layers = torch.nn.ModuleList(list(clip_inner.encoder.layers[:-1]))\n",
110
+ "\n",
111
+ "print(\"Aniimage-2 is loaded and ready.\")\n"
112
+ ]
113
+ },
114
+ {
115
+ "cell_type": "code",
116
+ "execution_count": null,
117
+ "metadata": {},
118
+ "outputs": [],
119
+ "source": [
120
+ "def make_scheduler(name):\n",
121
+ " base = dict(\n",
122
+ " num_train_timesteps=int(MODEL_CONFIG.get(\"num_train_timesteps\", 1000)),\n",
123
+ " beta_schedule=MODEL_CONFIG.get(\"beta_schedule\", \"scaled_linear\"),\n",
124
+ " prediction_type=MODEL_CONFIG.get(\"prediction_type\", \"v_prediction\"),\n",
125
+ " rescale_betas_zero_snr=bool(MODEL_CONFIG.get(\"zero_terminal_snr\", True)),\n",
126
+ " timestep_spacing=MODEL_CONFIG.get(\"timestep_spacing\", \"trailing\"),\n",
127
+ " )\n",
128
+ " if name == \"DPM++ SDE Karras\":\n",
129
+ " return DPMSolverMultistepScheduler(\n",
130
+ " **base, algorithm_type=\"sde-dpmsolver++\", solver_order=2, use_karras_sigmas=True\n",
131
+ " )\n",
132
+ " if name == \"DPM++ 2M Karras\":\n",
133
+ " return DPMSolverMultistepScheduler(\n",
134
+ " **base, algorithm_type=\"dpmsolver++\", solver_order=2, use_karras_sigmas=True\n",
135
+ " )\n",
136
+ " if name == \"Euler a\":\n",
137
+ " return EulerAncestralDiscreteScheduler(**base)\n",
138
+ " if name == \"Euler\":\n",
139
+ " return EulerDiscreteScheduler(**base)\n",
140
+ " if name == \"DDIM\":\n",
141
+ " return DDIMScheduler(**base, clip_sample=False, set_alpha_to_one=False)\n",
142
+ " raise ValueError(f\"Unknown scheduler: {name}\")\n",
143
+ "\n",
144
+ "\n",
145
+ "@torch.inference_mode()\n",
146
+ "def encode_prompts(prompt, negative_prompt):\n",
147
+ " tokens = tokenizer(\n",
148
+ " [negative_prompt or \"\", prompt],\n",
149
+ " padding=\"max_length\",\n",
150
+ " max_length=tokenizer.model_max_length,\n",
151
+ " truncation=True,\n",
152
+ " return_tensors=\"pt\",\n",
153
+ " )\n",
154
+ " return text_encoder(tokens.input_ids.to(DEVICE))[0]\n",
155
+ "\n",
156
+ "\n",
157
+ "def rescale_cfg(noise_cfg, noise_text, amount):\n",
158
+ " dims = tuple(range(1, noise_cfg.ndim))\n",
159
+ " std_text = noise_text.std(dim=dims, keepdim=True)\n",
160
+ " std_cfg = noise_cfg.std(dim=dims, keepdim=True).clamp_min(1e-6)\n",
161
+ " noise_rescaled = noise_cfg * (std_text / std_cfg)\n",
162
+ " return amount * noise_rescaled + (1.0 - amount) * noise_cfg\n",
163
+ "\n",
164
+ "\n",
165
+ "@torch.inference_mode()\n",
166
+ "def generate_one(prompt, negative_prompt, scheduler_name, steps, cfg_scale, seed):\n",
167
+ " scheduler = make_scheduler(scheduler_name)\n",
168
+ " scheduler.set_timesteps(int(steps), device=DEVICE)\n",
169
+ " embeddings = encode_prompts(prompt, negative_prompt)\n",
170
+ "\n",
171
+ " generator = torch.Generator(device=DEVICE).manual_seed(int(seed))\n",
172
+ " latent_size = int(MODEL_CONFIG.get(\"image_size\", 512)) // 8\n",
173
+ " latents = torch.randn(\n",
174
+ " (1, int(unet.config.in_channels), latent_size, latent_size),\n",
175
+ " generator=generator, device=DEVICE, dtype=torch.float32,\n",
176
+ " ) * scheduler.init_noise_sigma\n",
177
+ "\n",
178
+ " for timestep in scheduler.timesteps:\n",
179
+ " latent_input = torch.cat([latents, latents], dim=0)\n",
180
+ " latent_input = scheduler.scale_model_input(latent_input, timestep)\n",
181
+ " with torch.autocast(\"cuda\", dtype=DTYPE):\n",
182
+ " prediction = unet(latent_input, timestep, encoder_hidden_states=embeddings).sample\n",
183
+ " pred_negative, pred_text = prediction.chunk(2)\n",
184
+ " prediction = pred_negative + float(cfg_scale) * (pred_text - pred_negative)\n",
185
+ " prediction = rescale_cfg(prediction, pred_text, GUIDANCE_RESCALE)\n",
186
+ " latents = scheduler.step(prediction, timestep, latents).prev_sample\n",
187
+ "\n",
188
+ " scaled = (latents / vae.config.scaling_factor).to(dtype=DTYPE)\n",
189
+ " with torch.autocast(\"cuda\", dtype=DTYPE):\n",
190
+ " image = vae.decode(scaled).sample\n",
191
+ " image = (image.float() / 2 + 0.5).clamp(0, 1)\n",
192
+ " array = (image[0].permute(1, 2, 0).cpu().numpy() * 255).round().astype(np.uint8)\n",
193
+ " return Image.fromarray(array)\n",
194
+ "\n",
195
+ "\n",
196
+ "def generate_gallery(prompt, negative_prompt, scheduler_name, steps, cfg_scale, seed, randomize_seed, image_count, progress=gr.Progress()):\n",
197
+ " prompt = (prompt or \"\").strip()\n",
198
+ " if not prompt:\n",
199
+ " raise gr.Error(\"Enter a prompt first.\")\n",
200
+ "\n",
201
+ " count = int(image_count)\n",
202
+ " base_seed = random.randint(0, 2**31 - 1) if randomize_seed or int(seed) < 0 else int(seed)\n",
203
+ " images, records = [], []\n",
204
+ "\n",
205
+ " for index in range(count):\n",
206
+ " used_seed = (base_seed + index) % (2**31)\n",
207
+ " progress(index / count, desc=f\"Generating image {index + 1} of {count}\")\n",
208
+ " image = generate_one(prompt, negative_prompt, scheduler_name, steps, cfg_scale, used_seed)\n",
209
+ " path = OUTPUT_DIR / f\"aniimage2_{used_seed}.png\"\n",
210
+ " image.save(path)\n",
211
+ " images.append(image)\n",
212
+ " records.append(f\"- Seed `{used_seed}` — `{path}`\")\n",
213
+ "\n",
214
+ " progress(1.0, desc=\"Done\")\n",
215
+ " details = \"### Results\\n\" + \"\\n\".join(records)\n",
216
+ " return images, details, base_seed\n"
217
+ ]
218
+ },
219
+ {
220
+ "cell_type": "code",
221
+ "execution_count": null,
222
+ "metadata": {},
223
+ "outputs": [],
224
+ "source": [
225
+ "DEFAULT_NEGATIVE = (\n",
226
+ " \"NSFW, low quality, ugly, blurry, distorted, deformed, bad anatomy, bad proportions, \"\n",
227
+ " \"extra limbs, missing limbs, watermark, text, signature, washed out, flat colors, \"\n",
228
+ " \"manga panel, disfigured, poorly drawn, jpeg artifacts, cropped, out of frame\"\n",
229
+ ")\n",
230
+ "\n",
231
+ "with gr.Blocks(title=\"Aniimage-2 Generator\", theme=gr.themes.Soft()) as demo:\n",
232
+ " gr.Markdown(\n",
233
+ " \"# Aniimage-2 Interactive Generator\\n\"\n",
234
+ " \"Use a short plain-English prompt for best results. Images are 512×512.\"\n",
235
+ " )\n",
236
+ " with gr.Row():\n",
237
+ " with gr.Column(scale=2):\n",
238
+ " prompt = gr.Textbox(\n",
239
+ " label=\"Prompt\",\n",
240
+ " value=\"A smiling anime girl with red hair and a school uniform\",\n",
241
+ " lines=3,\n",
242
+ " )\n",
243
+ " negative = gr.Textbox(label=\"Negative prompt\", value=DEFAULT_NEGATIVE, lines=4)\n",
244
+ " generate_button = gr.Button(\"Generate\", variant=\"primary\")\n",
245
+ " with gr.Column(scale=1):\n",
246
+ " scheduler = gr.Dropdown(\n",
247
+ " [\"DPM++ SDE Karras\", \"DPM++ 2M Karras\", \"Euler a\", \"Euler\", \"DDIM\"],\n",
248
+ " value=\"DPM++ SDE Karras\", label=\"Scheduler\",\n",
249
+ " )\n",
250
+ " steps = gr.Slider(10, 80, value=50, step=1, label=\"Steps\")\n",
251
+ " cfg = gr.Slider(1.0, 15.0, value=7.5, step=0.1, label=\"CFG scale\")\n",
252
+ " seed = gr.Number(value=-1, precision=0, label=\"Seed (-1 = random)\")\n",
253
+ " randomize = gr.Checkbox(value=True, label=\"Randomize seed\")\n",
254
+ " count = gr.Slider(1, 4, value=1, step=1, label=\"Number of images\")\n",
255
+ "\n",
256
+ " gallery = gr.Gallery(label=\"Generated images\", columns=2, object_fit=\"contain\", height=620)\n",
257
+ " result_info = gr.Markdown()\n",
258
+ "\n",
259
+ " generate_button.click(\n",
260
+ " fn=generate_gallery,\n",
261
+ " inputs=[prompt, negative, scheduler, steps, cfg, seed, randomize, count],\n",
262
+ " outputs=[gallery, result_info, seed],\n",
263
+ " concurrency_limit=1,\n",
264
+ " )\n",
265
+ "\n",
266
+ "demo.queue(max_size=8)\n",
267
+ "print(\"Interface created. Run the final cell to launch it.\")\n"
268
+ ]
269
+ },
270
+ {
271
+ "cell_type": "code",
272
+ "execution_count": null,
273
+ "metadata": {},
274
+ "outputs": [],
275
+ "source": [
276
+ "demo.launch(share=True, show_error=True)\n"
277
+ ]
278
+ }
279
+ ],
280
+ "metadata": {
281
+ "accelerator": "GPU",
282
+ "colab": {
283
+ "name": "Aniimage_2_Interactive_Colab.ipynb",
284
+ "provenance": []
285
+ },
286
+ "kernelspec": {
287
+ "display_name": "Python 3",
288
+ "name": "python3"
289
+ },
290
+ "language_info": {
291
+ "name": "python"
292
+ }
293
+ },
294
+ "nbformat": 4,
295
+ "nbformat_minor": 5
296
+ }