stable_diffusion.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. # https://arxiv.org/pdf/2112.10752.pdf
  2. # https://github.com/ekagra-ranjan/huggingface-blog/blob/main/stable_diffusion.md
  3. import tempfile
  4. from pathlib import Path
  5. import argparse
  6. from collections import namedtuple
  7. from typing import Dict, Any
  8. from PIL import Image
  9. import numpy as np
  10. from tinygrad import Device, GlobalCounters, dtypes, Tensor, TinyJit
  11. from tinygrad.helpers import Timing, Context, getenv, fetch, colored, tqdm
  12. from tinygrad.nn import Conv2d, GroupNorm
  13. from tinygrad.nn.state import torch_load, load_state_dict, get_state_dict
  14. from extra.models.clip import Closed, Tokenizer
  15. from extra.models.unet import UNetModel
  16. class AttnBlock:
  17. def __init__(self, in_channels):
  18. self.norm = GroupNorm(32, in_channels)
  19. self.q = Conv2d(in_channels, in_channels, 1)
  20. self.k = Conv2d(in_channels, in_channels, 1)
  21. self.v = Conv2d(in_channels, in_channels, 1)
  22. self.proj_out = Conv2d(in_channels, in_channels, 1)
  23. # copied from AttnBlock in ldm repo
  24. def __call__(self, x):
  25. h_ = self.norm(x)
  26. q,k,v = self.q(h_), self.k(h_), self.v(h_)
  27. # compute attention
  28. b,c,h,w = q.shape
  29. q,k,v = [x.reshape(b,c,h*w).transpose(1,2) for x in (q,k,v)]
  30. h_ = Tensor.scaled_dot_product_attention(q,k,v).transpose(1,2).reshape(b,c,h,w)
  31. return x + self.proj_out(h_)
  32. class ResnetBlock:
  33. def __init__(self, in_channels, out_channels=None):
  34. self.norm1 = GroupNorm(32, in_channels)
  35. self.conv1 = Conv2d(in_channels, out_channels, 3, padding=1)
  36. self.norm2 = GroupNorm(32, out_channels)
  37. self.conv2 = Conv2d(out_channels, out_channels, 3, padding=1)
  38. self.nin_shortcut = Conv2d(in_channels, out_channels, 1) if in_channels != out_channels else lambda x: x
  39. def __call__(self, x):
  40. h = self.conv1(self.norm1(x).swish())
  41. h = self.conv2(self.norm2(h).swish())
  42. return self.nin_shortcut(x) + h
  43. class Mid:
  44. def __init__(self, block_in):
  45. self.block_1 = ResnetBlock(block_in, block_in)
  46. self.attn_1 = AttnBlock(block_in)
  47. self.block_2 = ResnetBlock(block_in, block_in)
  48. def __call__(self, x):
  49. return x.sequential([self.block_1, self.attn_1, self.block_2])
  50. class Decoder:
  51. def __init__(self):
  52. sz = [(128, 256), (256, 512), (512, 512), (512, 512)]
  53. self.conv_in = Conv2d(4,512,3, padding=1)
  54. self.mid = Mid(512)
  55. arr = []
  56. for i,s in enumerate(sz):
  57. arr.append({"block":
  58. [ResnetBlock(s[1], s[0]),
  59. ResnetBlock(s[0], s[0]),
  60. ResnetBlock(s[0], s[0])]})
  61. if i != 0: arr[-1]['upsample'] = {"conv": Conv2d(s[0], s[0], 3, padding=1)}
  62. self.up = arr
  63. self.norm_out = GroupNorm(32, 128)
  64. self.conv_out = Conv2d(128, 3, 3, padding=1)
  65. def __call__(self, x):
  66. x = self.conv_in(x)
  67. x = self.mid(x)
  68. for l in self.up[::-1]:
  69. print("decode", x.shape)
  70. for b in l['block']: x = b(x)
  71. if 'upsample' in l:
  72. # https://pytorch.org/docs/stable/generated/torch.nn.functional.interpolate.html ?
  73. bs,c,py,px = x.shape
  74. x = x.reshape(bs, c, py, 1, px, 1).expand(bs, c, py, 2, px, 2).reshape(bs, c, py*2, px*2)
  75. x = l['upsample']['conv'](x)
  76. x.realize()
  77. return self.conv_out(self.norm_out(x).swish())
  78. class Encoder:
  79. def __init__(self):
  80. sz = [(128, 128), (128, 256), (256, 512), (512, 512)]
  81. self.conv_in = Conv2d(3,128,3, padding=1)
  82. arr = []
  83. for i,s in enumerate(sz):
  84. arr.append({"block":
  85. [ResnetBlock(s[0], s[1]),
  86. ResnetBlock(s[1], s[1])]})
  87. if i != 3: arr[-1]['downsample'] = {"conv": Conv2d(s[1], s[1], 3, stride=2, padding=(0,1,0,1))}
  88. self.down = arr
  89. self.mid = Mid(512)
  90. self.norm_out = GroupNorm(32, 512)
  91. self.conv_out = Conv2d(512, 8, 3, padding=1)
  92. def __call__(self, x):
  93. x = self.conv_in(x)
  94. for l in self.down:
  95. print("encode", x.shape)
  96. for b in l['block']: x = b(x)
  97. if 'downsample' in l: x = l['downsample']['conv'](x)
  98. x = self.mid(x)
  99. return self.conv_out(self.norm_out(x).swish())
  100. class AutoencoderKL:
  101. def __init__(self):
  102. self.encoder = Encoder()
  103. self.decoder = Decoder()
  104. self.quant_conv = Conv2d(8, 8, 1)
  105. self.post_quant_conv = Conv2d(4, 4, 1)
  106. def __call__(self, x):
  107. latent = self.encoder(x)
  108. latent = self.quant_conv(latent)
  109. latent = latent[:, 0:4] # only the means
  110. print("latent", latent.shape)
  111. latent = self.post_quant_conv(latent)
  112. return self.decoder(latent)
  113. def get_alphas_cumprod(beta_start=0.00085, beta_end=0.0120, n_training_steps=1000):
  114. betas = np.linspace(beta_start ** 0.5, beta_end ** 0.5, n_training_steps, dtype=np.float32) ** 2
  115. alphas = 1.0 - betas
  116. alphas_cumprod = np.cumprod(alphas, axis=0)
  117. return Tensor(alphas_cumprod)
  118. unet_params: Dict[str,Any] = {
  119. "adm_in_ch": None,
  120. "in_ch": 4,
  121. "out_ch": 4,
  122. "model_ch": 320,
  123. "attention_resolutions": [4, 2, 1],
  124. "num_res_blocks": 2,
  125. "channel_mult": [1, 2, 4, 4],
  126. "n_heads": 8,
  127. "transformer_depth": [1, 1, 1, 1],
  128. "ctx_dim": 768,
  129. "use_linear": False,
  130. }
  131. class StableDiffusion:
  132. def __init__(self):
  133. self.alphas_cumprod = get_alphas_cumprod()
  134. self.model = namedtuple("DiffusionModel", ["diffusion_model"])(diffusion_model = UNetModel(**unet_params))
  135. self.first_stage_model = AutoencoderKL()
  136. self.cond_stage_model = namedtuple("CondStageModel", ["transformer"])(transformer = namedtuple("Transformer", ["text_model"])(text_model = Closed.ClipTextTransformer()))
  137. def get_x_prev_and_pred_x0(self, x, e_t, a_t, a_prev):
  138. temperature = 1
  139. sigma_t = 0
  140. sqrt_one_minus_at = (1-a_t).sqrt()
  141. #print(a_t, a_prev, sigma_t, sqrt_one_minus_at)
  142. pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt()
  143. # direction pointing to x_t
  144. dir_xt = (1. - a_prev - sigma_t**2).sqrt() * e_t
  145. x_prev = a_prev.sqrt() * pred_x0 + dir_xt
  146. return x_prev, pred_x0
  147. def get_model_output(self, unconditional_context, context, latent, timestep, unconditional_guidance_scale):
  148. # put into diffuser
  149. latents = self.model.diffusion_model(latent.expand(2, *latent.shape[1:]), timestep, unconditional_context.cat(context, dim=0))
  150. unconditional_latent, latent = latents[0:1], latents[1:2]
  151. e_t = unconditional_latent + unconditional_guidance_scale * (latent - unconditional_latent)
  152. return e_t
  153. def decode(self, x):
  154. x = self.first_stage_model.post_quant_conv(1/0.18215 * x)
  155. x = self.first_stage_model.decoder(x)
  156. # make image correct size and scale
  157. x = (x + 1.0) / 2.0
  158. x = x.reshape(3,512,512).permute(1,2,0).clip(0,1)*255
  159. return x.cast(dtypes.uint8) if Device.DEFAULT != "WEBGPU" else x
  160. def __call__(self, unconditional_context, context, latent, timestep, alphas, alphas_prev, guidance):
  161. e_t = self.get_model_output(unconditional_context, context, latent, timestep, guidance)
  162. x_prev, _ = self.get_x_prev_and_pred_x0(latent, e_t, alphas, alphas_prev)
  163. #e_t_next = get_model_output(x_prev)
  164. #e_t_prime = (e_t + e_t_next) / 2
  165. #x_prev, pred_x0 = get_x_prev_and_pred_x0(latent, e_t_prime, index)
  166. return x_prev.realize()
  167. # ** ldm.models.autoencoder.AutoencoderKL (done!)
  168. # 3x512x512 <--> 4x64x64 (16384)
  169. # decode torch.Size([1, 4, 64, 64]) torch.Size([1, 3, 512, 512])
  170. # section 4.3 of paper
  171. # first_stage_model.encoder, first_stage_model.decoder
  172. # ** ldm.modules.diffusionmodules.openaimodel.UNetModel
  173. # this is what runs each time to sample. is this the LDM?
  174. # input: 4x64x64
  175. # output: 4x64x64
  176. # model.diffusion_model
  177. # it has attention?
  178. # ** ldm.modules.encoders.modules.FrozenCLIPEmbedder
  179. # cond_stage_model.transformer.text_model
  180. if __name__ == "__main__":
  181. default_prompt = "a horse sized cat eating a bagel"
  182. parser = argparse.ArgumentParser(description='Run Stable Diffusion', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  183. parser.add_argument('--steps', type=int, default=5, help="Number of steps in diffusion")
  184. parser.add_argument('--prompt', type=str, default=default_prompt, help="Phrase to render")
  185. parser.add_argument('--out', type=str, default=Path(tempfile.gettempdir()) / "rendered.png", help="Output filename")
  186. parser.add_argument('--noshow', action='store_true', help="Don't show the image")
  187. parser.add_argument('--fp16', action='store_true', help="Cast the weights to float16")
  188. parser.add_argument('--timing', action='store_true', help="Print timing per step")
  189. parser.add_argument('--seed', type=int, help="Set the random latent seed")
  190. parser.add_argument('--guidance', type=float, default=7.5, help="Prompt strength")
  191. args = parser.parse_args()
  192. Tensor.no_grad = True
  193. model = StableDiffusion()
  194. # load in weights
  195. load_state_dict(model, torch_load(fetch('https://huggingface.co/CompVis/stable-diffusion-v-1-4-original/resolve/main/sd-v1-4.ckpt', 'sd-v1-4.ckpt'))['state_dict'], strict=False)
  196. if args.fp16:
  197. for k,v in get_state_dict(model).items():
  198. if k.startswith("model"):
  199. v.replace(v.cast(dtypes.float16).realize())
  200. # run through CLIP to get context
  201. tokenizer = Tokenizer.ClipTokenizer()
  202. prompt = Tensor([tokenizer.encode(args.prompt)])
  203. context = model.cond_stage_model.transformer.text_model(prompt).realize()
  204. print("got CLIP context", context.shape)
  205. prompt = Tensor([tokenizer.encode("")])
  206. unconditional_context = model.cond_stage_model.transformer.text_model(prompt).realize()
  207. print("got unconditional CLIP context", unconditional_context.shape)
  208. # done with clip model
  209. del model.cond_stage_model
  210. timesteps = list(range(1, 1000, 1000//args.steps))
  211. print(f"running for {timesteps} timesteps")
  212. alphas = model.alphas_cumprod[Tensor(timesteps)]
  213. alphas_prev = Tensor([1.0]).cat(alphas[:-1])
  214. # start with random noise
  215. if args.seed is not None: Tensor.manual_seed(args.seed)
  216. latent = Tensor.randn(1,4,64,64)
  217. @TinyJit
  218. def run(model, *x): return model(*x).realize()
  219. # this is diffusion
  220. with Context(BEAM=getenv("LATEBEAM")):
  221. for index, timestep in (t:=tqdm(list(enumerate(timesteps))[::-1])):
  222. GlobalCounters.reset()
  223. t.set_description("%3d %3d" % (index, timestep))
  224. with Timing("step in ", enabled=args.timing, on_exit=lambda _: f", using {GlobalCounters.mem_used/1e9:.2f} GB"):
  225. tid = Tensor([index])
  226. latent = run(model, unconditional_context, context, latent, Tensor([timestep]), alphas[tid], alphas_prev[tid], Tensor([args.guidance]))
  227. if args.timing: Device[Device.DEFAULT].synchronize()
  228. del run
  229. # upsample latent space to image with autoencoder
  230. x = model.decode(latent)
  231. print(x.shape)
  232. # save image
  233. im = Image.fromarray(x.numpy().astype(np.uint8, copy=False))
  234. print(f"saving {args.out}")
  235. im.save(args.out)
  236. # Open image.
  237. if not args.noshow: im.show()
  238. # validation!
  239. if args.prompt == default_prompt and args.steps == 5 and args.seed == 0 and args.guidance == 7.5:
  240. ref_image = Tensor(np.array(Image.open(Path(__file__).parent / "stable_diffusion_seed0.png")))
  241. distance = (((x - ref_image).cast(dtypes.float) / ref_image.max())**2).mean().item()
  242. assert distance < 3e-4, colored(f"validation failed with {distance=}", "red")
  243. print(colored(f"output validated with {distance=}", "green"))