| 426 | } |
| 427 | |
| 428 | class OpenClipEncoder: |
| 429 | def __init__(self, dims:int, text_cfg:Dict, vision_cfg:Dict, **_): |
| 430 | self.visual = Open.ClipVisionTransformer(**vision_cfg) |
| 431 | |
| 432 | text = Open.ClipTextTransformer(**text_cfg) |
| 433 | self.transformer = text.transformer |
| 434 | self.token_embedding = text.token_embedding |
| 435 | self.positional_embedding = text.positional_embedding |
| 436 | self.ln_final = text.ln_final |
| 437 | self.text_projection = text.text_projection |
| 438 | |
| 439 | self.attn_mask = Tensor.full((77, 77), float("-inf")).triu(1).realize() |
| 440 | self.mean = Tensor([0.48145466, 0.45782750, 0.40821073]).reshape(-1, 1, 1) |
| 441 | self.std = Tensor([0.26862954, 0.26130258, 0.27577711]).reshape(-1, 1, 1) |
| 442 | |
| 443 | # TODO: |
| 444 | # Should be doable in pure tinygrad, would just require some work and verification. |
| 445 | # This is very desirable since it would allow for full generation->evaluation in a single JIT call. |
| 446 | def prepare_image(self, image) -> Tensor: |
| 447 | from PIL import Image |
| 448 | SIZE = 224 |
| 449 | w, h = image.size |
| 450 | scale = min(SIZE / h, SIZE / w) |
| 451 | image = image.resize((max(int(w*scale),SIZE),max(int(h*scale),SIZE)), Image.Resampling.BICUBIC) |
| 452 | w, h = image.size |
| 453 | if w > SIZE: |
| 454 | left = (w - SIZE) // 2 |
| 455 | image = image.crop((left, left+SIZE, 0, SIZE)) |
| 456 | elif h > SIZE: |
| 457 | top = (h - SIZE) // 2 |
| 458 | image = image.crop((0, SIZE, top, top+SIZE)) |
| 459 | |
| 460 | x = Tensor(np.array(image.convert('RGB')), device=self.std.device) |
| 461 | x = x.permute(2, 0, 1).cast(dtypes.float32) / 255.0 |
| 462 | return (x - self.mean) / self.std |
| 463 | |
| 464 | def encode_tokens(self, tokens:Tensor) -> Tensor: |
| 465 | x = self.token_embedding(tokens) |
| 466 | x = x + self.positional_embedding |
| 467 | x = self.transformer(x, attn_mask=self.attn_mask) |
| 468 | x = self.ln_final(x) |
| 469 | x = x[Tensor.arange(x.shape[0], device=x.device), tokens.argmax(axis=-1)] |
| 470 | x = x @ self.text_projection |
| 471 | return x |
| 472 | |
| 473 | def get_clip_score(self, tokens:Tensor, image:Tensor) -> Tensor: |
| 474 | image_features: Tensor = self.visual(image) |
| 475 | image_features /= image_features.square().sum(-1, keepdim=True).sqrt() # Frobenius Norm |
| 476 | |
| 477 | text_features = self.encode_tokens(tokens) |
| 478 | text_features /= text_features.square().sum(-1, keepdim=True).sqrt() # Frobenius Norm |
| 479 | |
| 480 | return (image_features * text_features).sum(axis=-1) |
no outgoing calls
no test coverage detected
searching dependent graphs…