CoolFace
Datasetpublic

ysn-rfd/text-dataset-tiny-code-script-py-format

USED of tahamajs/medicine_ds_persian for .parquet file USED of Alijafarixcs2/persian-it-llama2-2k for .parquet file USED of Abirate/english_quotes for .jsonl file NEW FILES (05/12/2025) NEW FILES (12/26/2025) NEW FILES (02/15/2026)

sourceHugging Faceapache-2.0updated 4mo agoView on Hugging Face
3likes1.7kdownloads
1"""
2sd_directml_tiled.py
3Tiled diffusion + VAE tiling + partial offload for torch-directml
4Requires:
5  pip install torch-directml diffusers transformers accelerate safetensors pillow
6Usage: just run. Edit MODEL_PATH and PROMPT below.
7"""
8import os
9import time
10import math
11import torch
12import torch_directml
13from diffusers import StableDiffusionPipeline, LCMScheduler
14from PIL import Image
15import numpy as np
16
17# -------------------------
18# User config (edit these)
19# -------------------------
20MODEL_PATH = r"C:\Users\ysnrfd\Desktop\torch_directml\ds_lcm.safetensors"
21OUT_IMAGE = "out_tiled_directml.png"
22PROMPT = "A cinematic landscape, sunrise, ultra-detailed"
23WIDTH = 768   # try 512/384/256 for lower VRAM; tiled helps but base size still matters
24HEIGHT = 512
25STEPS = 20
26GUIDANCE = 1
27SEED = 1234
28
29# Tile config (image-space pixels). We will compute latent tile size by dividing by vae_scale.
30# Keep tile_px reasonably small for lower memory (e.g., 256 or 128)
31TILE_PX = 256
32TILE_OVERLAP_PX = 64  # overlap to blend tiles and avoid seams
33
34# Precision and devices
35DML_DEVICE_NAME = torch_directml.default_device()
36DML_DEVICE = torch_directml.device(DML_DEVICE_NAME)
37CPU_DEVICE = torch.device("cpu")
38USE_FP16 = True  # try True first on DirectML
39
40# -------------------------
41# Helpers
42# -------------------------
43def get_latent_downscale(pipe):
44    """
45    Determine the downscale factor from image to latents (usually 8 for SD: image/8 = latent spatial dims)
46    We'll infer by comparing vae configs if possible, otherwise default 8.
47    """
48    try:
49        # many diffusers VAE have config with 'scaling_factor' or rely on encoder/decoder shapes; fallback to 8
50        return 8
51    except Exception:
52        return 8
53
54def to_device_partial(pipe, unet_device):
55    """
56    Move UNet to GPU (DirectML), move vae and text_encoder to CPU.
57    """
58    try:
59        pipe.unet.to(unet_device)
60    except Exception as e:
61        print("⚠️ couldn't move UNet to DML via .to():", e)
62    # put heavy but tolerable parts to CPU
63    try:
64        pipe.vae.to(CPU_DEVICE)
65    except Exception:
66        pass
67    try:
68        if hasattr(pipe, "text_encoder"):
69            pipe.text_encoder.to(CPU_DEVICE)
70    except Exception:
71        pass
72
73def apply_optimizations(pipe):
74    """
75    common diffusers optimizations
76    """
77    try:
78        if hasattr(pipe, "enable_attention_slicing"):
79            pipe.enable_attention_slicing()
80            print("🔧 attention slicing enabled")
81        if hasattr(pipe, "vae") and hasattr(pipe.vae, "enable_slicing"):
82            pipe.vae.enable_slicing()
83            print("🔧 VAE slicing enabled")
84        if hasattr(pipe, "set_progress_bar_config"):
85            pipe.set_progress_bar_config(disable=True)
86        # channels-last attempt
87        def ch_last(mod):
88            try:
89                for p in mod.parameters():
90                    if p.ndim == 4:
91                        p.data = p.data.contiguous(memory_format=torch.channels_last)
92            except Exception:
93                pass
94        for sub in [pipe.unet, getattr(pipe, "vae", None), getattr(pipe, "text_encoder", None)]:
95            if sub is not None:
96                ch_last(sub)
97    except Exception as e:
98        print("⚠️ apply_optimizations error:", e)
99
100def make_prompt_embedding(pipe, prompt, device):
101    """
102    Encode prompt into text embeddings on CPU (since text_encoder is on CPU).
103    Returns encoder_hidden_states on CPU or moved to DML only when UNet needs it.
104    """
105    # Use pipeline's tokenizer + text_encoder
106    tokenizer = pipe.tokenizer
107    text_encoder = pipe.text_encoder
108    inputs = tokenizer(prompt, padding="max_length", truncation=True, max_length=tokenizer.model_max_length, return_tensors="pt")
109    input_ids = inputs.input_ids.to(CPU_DEVICE)
110    with torch.no_grad():
111        # text_encoder is on CPU_device
112        encoder_hidden_states = text_encoder(input_ids)[0].to(CPU_DEVICE)
113    return encoder_hidden_states
114
115# -------------------------
116# tiled UNet inference helpers
117# -------------------------
118def slice_latent_to_tiles(latents, tile_px, overlap_px, vae_scale):
119    """
120    latents: tensor (b, c, H_lat, W_lat) in latent resolution
121    tile_px, overlap_px: in image pixels. Convert to latent pixels by dividing by vae_scale.
122    Returns list of tile boxes in latent coordinates: (y0,y1,x0,x1)
123    """
124    _, _, H_lat, W_lat = latents.shape
125    tile_lat = max(1, tile_px // vae_scale)
126    overlap_lat = max(0, overlap_px // vae_scale)
127    step = tile_lat - overlap_lat
128    tiles = []
129    y = 0
130    while y < H_lat:
131        y1 = min(y + tile_lat, H_lat)
132        y0 = max(0, y1 - tile_lat)
133        x = 0
134        while x < W_lat:
135            x1 = min(x + tile_lat, W_lat)
136            x0 = max(0, x1 - tile_lat)
137            tiles.append((y0, y1, x0, x1))
138            x += step
139        y += step
140    return tiles
141
142def merge_tile_predictions(pred_full, pred_tile, box):
143    """
144    Merge predicted noise for a tile into the full prediction tensor.
145    Use simple overwrite in this implementation (we can average in overlaps).
146    box = (y0,y1,x0,x1)
147    """
148    y0, y1, x0, x1 = box
149    pred_full[..., y0:y1, x0:x1] = pred_tile
150    return pred_full
151
152@torch.no_grad()
153def denoise_with_tiled_unet(pipe, latents, scheduler, encoder_hidden_states, guidance_scale, device_unet, tile_px, overlap_px, vae_scale):
154    """
155    Run denoising loop but at each UNet call split latents into tiles to reduce peak memory.
156    latents: initial latents (b, c, H_lat, W_lat) on CPU or DML? We'll ensure UNet gets tile tensor moved to DML.
157    scheduler: pipeline.scheduler (already configured)
158    encoder_hidden_states: text embeddings on CPU
159    device_unet: DML device object
160    """
161    pipe_unet = pipe.unet
162    timesteps = scheduler.timesteps
163    # prepare latent dtype / device: keep latents on CPU to save VRAM, move tiles to DML on demand
164    # but UNet expects latents on device_unet
165    for t in timesteps:
166        # prepare model_input as in diffusers: scale latents etc.
167        # mimic pipeline.prepare_latents behavior: here latents are already batched
168        # predict noise for each tile then merge
169        predicted_noise_full = torch.zeros_like(latents)  # keep on CPU to save VRAM
170        tiles = slice_latent_to_tiles(latents, tile_px, overlap_px, vae_scale)
171        for box in tiles:
172            y0, y1, x0, x1 = box
173            latent_tile = latent_tile.to(device_unet, dtype=pipe.unet.dtype)
174            # timestep tensor on device_unet
175            t_tensor = t_tensor.to(device_unet, dtype=pipe.unet.dtype)
176            # encoder_hidden_states may be on CPU: move to device_unet temporarily
177            enc = enc.to(device_unet, dtype=pipe.unet.dtype)
178            # classifier-free guidance split
179            latent_model_input = torch.cat([latent_tile] * 2) if guidance_scale > 1.0 else latent_tile
180            # scale model input if required by scheduler (some schedulers expect it)
181            # call unet
182            with torch.no_grad():
183                noise_pred = pipe_unet(latent_model_input, t_tensor, encoder_hidden_states=enc).sample
184            # guidance
185            if guidance_scale > 1.0:
186                eps_uncond, eps_cond = noise_pred.chunk(2)
187                noise_pred = eps_uncond + guidance_scale * (eps_cond - eps_uncond)
188            # move noise_pred back to CPU and merge
189            noise_pred = noise_pred.to(latents.device)
190            predicted_noise_full = merge_tile_predictions(predicted_noise_full, noise_pred, box)
191            # free DML memory by deleting temp vars
192            del latent_tile, t_tensor, enc, noise_pred, latent_model_input
193            torch.directml.empty_cache()  # try free DML memory (best-effort)
194        # scheduler step: update latents based on predicted noise (on CPU)
195        latents = scheduler.step(predicted_noise_full, t, latents)["prev_sample"]
196    return latents
197
198@torch.no_grad()
199def decode_latents_tiled(pipe, latents, tile_px, overlap_px, vae_scale):
200    """
201    Decode latents to image via VAE in tiles.
202    vae is on CPU (so keep tiles on CPU). We decode each tile and paste into final image with blending in overlaps.
203    Returns PIL Image.
204    """
205    vae = pipe.vae  # on CPU
206    # latents shape: (b, c, H_lat, W_lat)
207    b, c, H_lat, W_lat = latents.shape
208    assert b == 1, "batch>1 not supported in tiled decode"
209    tile_lat = max(1, tile_px // vae_scale)
210    overlap_lat = max(0, overlap_px // vae_scale)
211    step = tile_lat - overlap_lat
212    # compute final image size
213    img_h = H_lat * vae_scale
214    img_w = W_lat * vae_scale
215    # We'll decode tile-by-tile and paste into numpy array, using a weight map for blending
216    final_acc = np.zeros((img_h, img_w, 3), dtype=np.float32)
217    weight_acc = np.zeros((img_h, img_w, 1), dtype=np.float32)
218    tiles = slice_latent_to_tiles(latents, tile_px, overlap_px, vae_scale)
219    for box in tiles:
220        y0, y1, x0, x1 = box
221        latent_tile = latents[..., y0:y1, x0:x1].to(CPU_DEVICE)
222        # VAE decode expects latents scaled by vae.config.scaling maybe; use pipeline.decode_latents if present
223        try:
224            # use pipeline's decode_latents if exists (handles scaling)
225            decoded = pipe.decode_latents(latent_tile)
226            # decode_latents returns list or tensor; normalize to numpy HWC
227            if isinstance(decoded, list):
228                decoded = decoded[0]
229        except Exception:
230            # fallback using vae directly
231            with torch.no_grad():
232                decoded = vae.decode(latent_tile).sample
233        img_tile = decoded.cpu().permute(0,2,3,1).numpy()[0]  # HxWxC, usually in [-1,1]
234        # convert to 0..1
235        img_tile = (img_tile + 1.0) / 2.0
236        # compute pixel coords in final image
237        py0 = int(y0 * vae_scale)
238        py1 = int(y1 * vae_scale)
239        px0 = int(x0 * vae_scale)
240        px1 = int(x1 * vae_scale)
241        h_tile = py1 - py0
242        w_tile = px1 - px0
243        # ensure tile shape matches expected (sometimes rounding)
244        img_tile = Image.fromarray((np.clip(img_tile*255,0,255)).astype(np.uint8)).resize((w_tile,h_tile), resample=Image.LANCZOS)
245        img_tile = np.array(img_tile).astype(np.float32) / 255.0
246        # weight (rectangular) and blend
247        weight = np.ones((h_tile,w_tile,1), dtype=np.float32)
248        final_acc[py0:py1, px0:px1] += img_tile * weight
249        weight_acc[py0:py1, px0:px1] += weight
250        # free mem
251        del latent_tile, decoded, img_tile, weight
252    # normalize by weights
253    weight_acc = np.maximum(weight_acc, 1e-6)
254    final = final_acc / weight_acc
255    final = (np.clip(final,0,1)*255).astype(np.uint8)
256    pil = Image.fromarray(final)
257    return pil
258
259# -------------------------
260# Main flow
261# -------------------------
262def main():
263    print("DirectML device:", DML_DEVICE_NAME)
264    dtype = torch.float16 if USE_FP16 else torch.float32
265    print("Loading pipeline (dtype=%s)..." % dtype)
266    # load pipeline on CPU first
267    pipe = StableDiffusionPipeline.from_single_file(MODEL_PATH, torch_dtype=dtype, use_safetensors=True, safety_checker=None)
268    # apply scheduler LCM if available
269    try:
270        pipe.scheduler = LCMScheduler.from_config(pipe.scheduler.config)
271        print("LCMScheduler active")
272    except Exception:
273        print("LCMScheduler not available or failed to set (falling back to current scheduler)")
274
275    apply_optimizations(pipe)
276    # partial device placement
277    to_device_partial(pipe, DML_DEVICE)
278    # prepare prompt embeddings (on CPU)
279    encoder_hidden_states = make_prompt_embedding(pipe, PROMPT, CPU_DEVICE)
280    # prepare initial latents on CPU (normal pipeline uses random latents)
281    vae_scale = get_latent_downscale(pipe)
282    # latent dims
283    H_lat = HEIGHT // vae_scale
284    W_lat = WIDTH // vae_scale
285    latents = torch.randn((1, pipe.unet.in_channels, H_lat, W_lat), device=CPU_DEVICE, generator=torch.Generator(device=CPU_DEVICE).manual_seed(SEED))
286    # scheduler prepare (use pipeline methods if exist)
287    scheduler = pipe.scheduler
288    scheduler.set_timesteps(STEPS)
289    # scale initial noise if needed by scheduler
290    latents = latents * scheduler.init_noise_sigma
291    start = time.time()
292    print("Starting tiled denoising (this will be slower but memory-efficient)...")
293    latents = denoise_with_tiled_unet(pipe, latents, scheduler, encoder_hidden_states, GUIDANCE, DML_DEVICE, TILE_PX, TILE_OVERLAP_PX, vae_scale)
294    print("Denoising done. Decoding latents via tiled VAE (CPU)...")
295    img = decode_latents_tiled(pipe, latents, TILE_PX, TILE_OVERLAP_PX, vae_scale)
296    img.save(OUT_IMAGE)
297    elapsed = time.time() - start
298    print(f"Saved {OUT_IMAGE} (time: {elapsed:.1f}s)")
299
300if __name__ == "__main__":
301    main()
302