pikadiffusion / lowram.py
simonko912's picture
Upload lowram.py
29dc8e3 verified
Raw
History Blame Contribute Delete
8.39 kB
print("importing gc")
import gc
print("gc imported")
print("importing os")
import os
print("os imported")
print("importing torch")
import torch
print("torch imported")
print("importing pillow")
from PIL import Image
print("imported pillow")
print("importing clip")
from transformers import CLIPTokenizer, CLIPTextModel
print("clip imported")
print("importing diffusers")
from diffusers import UNet2DConditionModel, DDPMScheduler
print("imported diffusers")
print("importing safetensors")
from safetensors.torch import load_file
print("safetensors imported")
# ============================================================
# SETTINGS
# ============================================================
MODEL_PATH = "model.safetensors"
CLIP_NAME = "openai/clip-vit-base-patch32"
DEVICE = torch.device("cpu")
IMAGE_SIZE = 128
# ============================================================
# RAM CLEANUP
# ============================================================
def free_ram():
gc.collect()
# Make sure PyTorch releases unused CPU allocations
try:
torch._C._malloc_trim(0)
except Exception:
pass
# ============================================================
# CREATE UNET ARCHITECTURE
# ============================================================
def create_unet():
return UNet2DConditionModel(
sample_size=128,
in_channels=3,
out_channels=3,
layers_per_block=2,
block_out_channels=(
128,
256,
512,
512
),
down_block_types=(
"DownBlock2D",
"DownBlock2D",
"CrossAttnDownBlock2D",
"DownBlock2D"
),
up_block_types=(
"UpBlock2D",
"CrossAttnUpBlock2D",
"UpBlock2D",
"UpBlock2D"
),
cross_attention_dim=512
)
# ============================================================
# CLIP STAGE
# ============================================================
def encode_prompt(prompt):
print("Loading CLIP tokenizer...")
tokenizer = CLIPTokenizer.from_pretrained(
CLIP_NAME
)
print("Loading CLIP...")
text_encoder = CLIPTextModel.from_pretrained(
CLIP_NAME
)
text_encoder.eval()
# --------------------------------------------------------
# INT8 dynamic quantization
#
# This converts Linear layers to INT8 weights.
# It works on CPU and doesn't require CUDA/bitsandbytes.
# --------------------------------------------------------
print("Quantizing CLIP Linear layers to INT8...")
text_encoder = torch.ao.quantization.quantize_dynamic(
text_encoder,
{
torch.nn.Linear
},
dtype=torch.qint8
)
tokens = tokenizer(
prompt,
padding="max_length",
max_length=77,
truncation=True,
return_tensors="pt"
)
print("Running CLIP...")
with torch.no_grad():
text = text_encoder(
**tokens
).last_hidden_state
# --------------------------------------------------------
# CLIP IS NOW COMPLETELY UNLOADED
# --------------------------------------------------------
del text_encoder
del tokenizer
del tokens
free_ram()
print("CLIP unloaded.")
return text
# ============================================================
# UNET STAGE
# ============================================================
def load_unet():
print("Creating UNet...")
unet = create_unet()
print("Loading UNet weights...")
state = load_file(
MODEL_PATH,
device="cpu"
)
unet.load_state_dict(
state
)
del state
free_ram()
unet.eval()
# --------------------------------------------------------
# INT8 dynamic quantization
#
# IMPORTANT:
# This quantizes Linear layers, NOT Conv2d layers.
# Your UNet contains lots of Conv2d weights, so this
# alone won't make the entire 129M parameter model 8-bit.
# --------------------------------------------------------
print("Quantizing UNet Linear layers to INT8...")
unet = torch.ao.quantization.quantize_dynamic(
unet,
{
torch.nn.Linear
},
dtype=torch.qint8
)
free_ram()
print("UNet loaded.")
return unet
# ============================================================
# GENERATION
# ============================================================
def generate(
prompt,
steps=250,
seed=0
):
# --------------------------------------------------------
# SEED
# --------------------------------------------------------
if seed == 0:
seed = torch.randint(
0,
2**32 - 1,
(1,),
dtype=torch.int64
).item()
seed = int(seed)
print(
f"Seed: {seed}"
)
# --------------------------------------------------------
# STAGE 1:
# CLIP ONLY
# --------------------------------------------------------
print()
print("========== CLIP ==========")
text = encode_prompt(
prompt
)
print(
f"Text embedding shape: {tuple(text.shape)}"
)
# --------------------------------------------------------
# At this point CLIP is completely gone.
#
# Only the ~77x512 embedding remains.
# --------------------------------------------------------
free_ram()
# --------------------------------------------------------
# STAGE 2:
# UNET ONLY
# --------------------------------------------------------
print()
print("========== UNET ==========")
unet = load_unet()
scheduler = DDPMScheduler(
num_train_timesteps=1000,
beta_schedule="scaled_linear",
prediction_type="epsilon"
)
generator = torch.Generator(
device="cpu"
).manual_seed(
seed
)
image = torch.randn(
(1, 3, IMAGE_SIZE, IMAGE_SIZE),
generator=generator,
device="cpu"
)
scheduler.set_timesteps(
int(steps)
)
# --------------------------------------------------------
# DIFFUSION
# --------------------------------------------------------
with torch.no_grad():
for i, t in enumerate(
scheduler.timesteps
):
print(
f"\rDiffusion step "
f"{i + 1}/{steps}",
end="",
flush=True
)
noise_pred = unet(
image,
t,
encoder_hidden_states=text
).sample
image = scheduler.step(
noise_pred,
t,
image
).prev_sample
print()
# --------------------------------------------------------
# CONVERT TO IMAGE
# --------------------------------------------------------
image = (
image
.clamp(-1, 1)
.add(1)
.div(2)
)
image = (
image[0]
.permute(1, 2, 0)
.cpu()
.numpy()
)
image = (
image * 255
).astype("uint8")
result = Image.fromarray(
image
)
# --------------------------------------------------------
# UNLOAD EVERYTHING
# --------------------------------------------------------
del unet
del scheduler
del text
del image
del noise_pred
free_ram()
print("UNet unloaded.")
return result, seed
# ============================================================
# MAIN
# ============================================================
if __name__ == "__main__":
prompt = input(
"Pokemon prompt: "
)
steps_input = input(
"Steps [250]: "
)
if steps_input.strip():
steps = int(steps_input)
else:
steps = 250
seed_input = input(
"Seed [0 = random]: "
)
if seed_input.strip():
seed = int(seed_input)
else:
seed = 0
result, used_seed = generate(
prompt,
steps,
seed
)
output_path = "generated.png"
result.save(
output_path
)
print()
print(
f"Saved: {output_path}"
)
print(
f"Seed: {used_seed}"
)