Buckets:
| """Run reference inference for all TIPSv2 HuggingFace models.""" | |
| import datetime | |
| import gc | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| from PIL import Image | |
| from safetensors.torch import save_file | |
| from torchvision import transforms | |
| from transformers import AutoModel | |
| try: | |
| from sklearn.decomposition import PCA | |
| HAS_SKLEARN = True | |
| except ImportError: | |
| HAS_SKLEARN = False | |
| try: | |
| import matplotlib.pyplot as plt | |
| import matplotlib.cm as cm | |
| HAS_MATPLOTLIB = True | |
| except ImportError: | |
| HAS_MATPLOTLIB = False | |
| # Match TF32/cuDNN settings from transformers conftest.py so results are comparable | |
| if hasattr(torch.backends, "cuda"): | |
| torch.backends.cuda.matmul.allow_tf32 = False | |
| if hasattr(torch.backends.cudnn, "allow_tf32"): | |
| torch.backends.cudnn.allow_tf32 = False | |
| if hasattr(torch.backends.cudnn, "conv") and hasattr(torch.backends.cudnn.conv, "fp32_precision"): | |
| torch.backends.cudnn.conv.fp32_precision = "ieee" | |
| torch.set_printoptions(precision=5, sci_mode=False) | |
| SCRIPT_DIR = Path(__file__).parent | |
| MODELS = [ | |
| "google/tipsv2-b14", | |
| "google/tipsv2-l14", | |
| "google/tipsv2-so400m14", | |
| "google/tipsv2-g14", | |
| "google/tipsv2-b14-dpt", | |
| "google/tipsv2-l14-dpt", | |
| "google/tipsv2-so400m14-dpt", | |
| "google/tipsv2-g14-dpt", | |
| ] | |
| IMAGES = [ | |
| SCRIPT_DIR / "reference_images/coco_000000039769.jpg", # two cats on a sofa | |
| SCRIPT_DIR / "reference_images/depth_ade20k_00014.png", # outdoor scene (ADE20K) | |
| SCRIPT_DIR / "reference_images/nyuv2_living_room_01260.jpg", # indoor living room | |
| SCRIPT_DIR / "reference_images/pca_cph.jpeg", # city street (Copenhagen) | |
| SCRIPT_DIR / "reference_images/zeroseg_pascal_context_00049_image.png", # bus on street | |
| ] | |
| TEXT_QUERIES = ["two cats on a sofa", "a cat lying down", "a dog on a couch", "an empty room"] | |
| IMAGE_SIZE = 448 | |
| # Identity normalization matching run_image_encoder_inference.py — no ImageNet normalization for TIPSv2. | |
| IMAGE_MEAN = (0, 0, 0) | |
| IMAGE_STD = (1.0, 1.0, 1.0) | |
| def preprocess_image(path: Path, size: int = IMAGE_SIZE) -> torch.Tensor: | |
| transform = transforms.Compose([ | |
| transforms.Resize((size, size)), | |
| transforms.ToTensor(), | |
| transforms.Normalize(IMAGE_MEAN, IMAGE_STD), | |
| ]) | |
| return transform(Image.open(path).convert("RGB")).unsqueeze(0) | |
| def tprint(msg: str, log_file) -> None: | |
| # print(msg) | |
| log_file.write(msg + "\n") | |
| log_file.flush() | |
| def log_tensor(name: str, tensor: torch.Tensor, log_file) -> None: | |
| """Print tensor shape and a representative sample slice to stdout and log file.""" | |
| ndim = tensor.ndim | |
| tprint(f" {name}: shape={tuple(tensor.shape)}", log_file) | |
| if ndim >= 4: | |
| # 2D spatial tensor [batch, channel, H, W] — print 3x3 patch from first channel | |
| tprint(f" {name}[0, 0, :3, :3] =\n{tensor[0, 0, :3, :3]}", log_file) | |
| elif ndim == 3: | |
| if tensor.is_floating_point(): | |
| # Sequence-like [batch, seq, dim] — print first 5 features of first token | |
| tprint(f" {name}[0, 0, :5] = {tensor[0, 0, :5]}", log_file) | |
| else: | |
| # Post-argmax spatial [batch, H, W] — print 3x3 patch | |
| tprint(f" {name}[0, :3, :3] =\n{tensor[0, :3, :3]}", log_file) | |
| elif ndim == 2: | |
| if tensor.shape[1] == 1: | |
| # Column vector [queries, 1] — print all rows | |
| tprint(f" {name}[:5, 0] = {tensor[:5, 0]}", log_file) | |
| else: | |
| # [batch, dim] or [queries, dim] — print first 5 values of first row | |
| tprint(f" {name}[0, :5] = {tensor[0, :5]}", log_file) | |
| elif ndim == 1: | |
| tprint(f" {name}[:5] = {tensor[:5]}", log_file) | |
| else: | |
| tprint(f" {name} = {tensor.item()}", log_file) | |
| tprint("", log_file) | |
| def log_base_model_outputs( | |
| forward: dict, | |
| postprocess: dict, | |
| text_emb: torch.Tensor, | |
| text_norm: torch.Tensor, | |
| log_file, | |
| ) -> None: | |
| tprint("=== backbone outputs ===", log_file) | |
| log_tensor("cls_token", forward["cls_token"], log_file) | |
| log_tensor("register_tokens", forward["register_tokens"], log_file) | |
| log_tensor("patch_tokens", forward["patch_tokens"], log_file) | |
| log_tensor("vision_pooler_output", forward["vision_pooler_output"], log_file) | |
| tprint("=== postprocess ===", log_file) | |
| log_tensor("cls_token_norm", postprocess["cls_token_norm"], log_file) | |
| tprint(" [classification scores: pre-argmax]", log_file) | |
| log_tensor("classification_scores", postprocess["classification_scores"], log_file) | |
| tprint(" [classification: post-argmax]", log_file) | |
| log_tensor("predicted_class", postprocess["predicted_class"], log_file) | |
| tprint("=== text embeddings (shared across images) ===", log_file) | |
| log_tensor("text_pooler_output", text_emb, log_file) | |
| log_tensor("text_embeddings_norm", text_norm, log_file) | |
| tprint("=== base model logits ===", log_file) | |
| if "loss" in postprocess: | |
| log_tensor("loss", postprocess["loss"], log_file) | |
| log_tensor("logits_per_image", postprocess["logits_per_image"], log_file) | |
| log_tensor("logits_per_text", postprocess["logits_per_text"], log_file) | |
| def log_dpt_model_outputs(forward: dict, forward_postprocessed: dict, postprocess: dict, log_file) -> None: | |
| tprint("=== model outputs (native DPT resolution, no image_size) ===", log_file) | |
| log_tensor("depth", forward["depth"], log_file) | |
| log_tensor("normals", forward["normals"], log_file) | |
| tprint(" [segmentation logits: pre-argmax]", log_file) | |
| log_tensor("segmentation", forward["segmentation"], log_file) | |
| tprint("=== post-processed model outputs (upsampled to input size with image_size) ===", log_file) | |
| log_tensor("depth", forward_postprocessed["depth"], log_file) | |
| log_tensor("normals", forward_postprocessed["normals"], log_file) | |
| tprint(" [post-processed segmentation logits: pre-argmax]", log_file) | |
| log_tensor("segmentation", forward_postprocessed["segmentation"], log_file) | |
| tprint("=== postprocess ===", log_file) | |
| tprint(" [segmentation: post-argmax]", log_file) | |
| log_tensor("segmentation_labels", postprocess["segmentation_labels"], log_file) | |
| def visualize_base_model(forward: dict, postprocess: dict, out_dir: Path) -> None: | |
| if not HAS_SKLEARN or not HAS_MATPLOTLIB: | |
| return | |
| patch_tokens = forward["patch_tokens"][0].numpy() # sklearn requires numpy; (1024, D) | |
| grid = int(patch_tokens.shape[0] ** 0.5) | |
| patch_rgb = PCA(n_components=3, whiten=True).fit_transform(patch_tokens).reshape(grid, grid, 3) | |
| patch_rgb = 1 / (1 + np.exp(-2.0 * patch_rgb)) | |
| Image.fromarray((patch_rgb * 255).astype(np.uint8)).save(out_dir / "patch_tokens_pca.png") | |
| similarity = postprocess["classification_scores"] # (1, num_queries) | |
| fig, ax = plt.subplots(figsize=(10, 2)) | |
| im = ax.imshow(similarity.numpy(), cmap="RdYlGn", aspect="auto", vmin=-1, vmax=1) | |
| ax.set_yticks([]) | |
| ax.set_xticks(range(len(TEXT_QUERIES))) | |
| ax.set_xticklabels(TEXT_QUERIES, rotation=45, ha="right", fontsize=8) | |
| ax.set_title("Cls-Text Similarity") | |
| plt.colorbar(im, ax=ax, label="Cosine Similarity") | |
| plt.tight_layout() | |
| plt.savefig(out_dir / "text_similarity.png", dpi=100) | |
| plt.close() | |
| def visualize_dpt_model(forward: dict, postprocess: dict, out_dir: Path) -> None: | |
| if not HAS_MATPLOTLIB: | |
| return | |
| depth = forward["depth"][0, 0] # (H, W) | |
| depth_norm = (depth - depth.min()) / (depth.max() - depth.min() + 1e-8) | |
| Image.fromarray((depth_norm.numpy() * 255).astype(np.uint8)).save(out_dir / "depth.png") | |
| normals_vis = ((forward["normals"][0].permute(1, 2, 0) + 1) / 2).clamp(0, 1) # (H, W, 3) | |
| Image.fromarray((normals_vis.numpy() * 255).astype(np.uint8)).save(out_dir / "normals.png") | |
| seg_labels = postprocess["segmentation_labels"][0].numpy().astype(int) # matplotlib cmap requires numpy | |
| cmap = cm.get_cmap("tab20", 150) | |
| seg_colored = cmap((seg_labels % 150) / 150.0)[:, :, :3] | |
| Image.fromarray((seg_colored * 255).astype(np.uint8)).save(out_dir / "segmentation.png") | |
| def main() -> None: | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| print(f"Device: {device}") | |
| out_root = SCRIPT_DIR / "out" | |
| out_root.mkdir(exist_ok=True) | |
| now = datetime.datetime.now() | |
| date_str = now.strftime("%Y%m%d") | |
| time_str = now.strftime("%H%M%S") | |
| for model_id in MODELS: | |
| model_name = model_id.split("/")[-1] | |
| is_dpt = model_name.endswith("-dpt") | |
| model_dir = out_root / f"{date_str}_{time_str}_{model_name}_ref" | |
| print(f"\n=== {model_id} ===") | |
| model = AutoModel.from_pretrained(model_id, trust_remote_code=True) | |
| model.eval().to(device) | |
| if is_dpt: | |
| _orig_forwards = {} | |
| _orig_F_normalize = torch.nn.functional.normalize | |
| def _identity_normalize(input, p=2.0, dim=1, eps=1e-12, out=None): | |
| return input | |
| for head_name in ("depth_head", "segmentation_head"): | |
| head = getattr(model, head_name) | |
| orig_fwd = head.forward | |
| _orig_forwards[head_name] = orig_fwd | |
| head.forward = lambda inputs, image_size=None, _fwd=orig_fwd: _fwd(inputs) | |
| # Normals head: also skip F.normalize so the no-image_size output is pre-normalization logits | |
| _normals_orig_fwd = model.normals_head.forward | |
| _orig_forwards["normals_head"] = _normals_orig_fwd | |
| def _normals_forward_no_normalize(inputs, image_size=None, _fwd=_normals_orig_fwd): | |
| torch.nn.functional.normalize = _identity_normalize | |
| try: | |
| result = _fwd(inputs) | |
| finally: | |
| torch.nn.functional.normalize = _orig_F_normalize | |
| return result | |
| model.normals_head.forward = _normals_forward_no_normalize | |
| try: | |
| text_emb = None | |
| text_norm = None | |
| if not is_dpt: | |
| with torch.no_grad(): | |
| text_emb = model.encode_text(TEXT_QUERIES).detach().cpu() | |
| text_norm = F.normalize(text_emb, dim=-1) | |
| model_dir.mkdir(parents=True, exist_ok=True) | |
| save_file( | |
| {"text_pooler_output": text_emb, "text_embeddings_norm": text_norm}, | |
| model_dir / "text_embeddings.safetensors", | |
| ) | |
| for image_path in IMAGES: | |
| image_stem = image_path.stem | |
| out_dir = model_dir / image_stem | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| pixel_values = preprocess_image(image_path).to(device) | |
| with torch.no_grad(): | |
| if is_dpt: | |
| # Run without image_size (native DPT resolution, heads not upsampled) | |
| outputs = model(pixel_values) | |
| forward = { | |
| "depth": outputs.depth.detach().cpu().contiguous(), | |
| "normals": outputs.normals.detach().cpu().contiguous(), | |
| "segmentation": outputs.segmentation.detach().cpu().contiguous(), | |
| } | |
| # Restore original forwards and run with image_size (upsampled to input size) | |
| for head_name, orig_fwd in _orig_forwards.items(): | |
| getattr(model, head_name).forward = orig_fwd | |
| outputs_pp = model(pixel_values) | |
| forward_postprocessed = { | |
| "depth": outputs_pp.depth.detach().cpu().contiguous(), | |
| "normals": outputs_pp.normals.detach().cpu().contiguous(), | |
| "segmentation": outputs_pp.segmentation.detach().cpu().contiguous(), | |
| } | |
| postprocess = { | |
| "segmentation_labels": outputs_pp.segmentation.argmax(dim=1).detach().cpu().contiguous(), | |
| } | |
| else: | |
| image_out = model.encode_image(pixel_values) | |
| forward = { | |
| "cls_token": image_out.cls_token.detach().cpu(), | |
| "register_tokens": image_out.register_tokens.detach().cpu(), | |
| "patch_tokens": image_out.patch_tokens.detach().cpu(), | |
| "vision_pooler_output": image_out.cls_token[:, 0, :].detach().cpu(), | |
| } | |
| cls_norm = F.normalize(image_out.cls_token[:, 0, :], dim=-1).detach().cpu() | |
| classification_scores = cls_norm @ text_norm.T # (1, num_queries) | |
| temperature = model.config.temperature | |
| # Match transformers: logits_per_text = matmul(text_embeds, image_embeds.T) / temperature | |
| logits_per_image = cls_norm @ text_norm.T / temperature # (1, num_queries) | |
| logits_per_text = text_norm @ cls_norm.T / temperature # (num_queries, 1) | |
| postprocess = { | |
| "cls_token_norm": cls_norm, | |
| "classification_scores": classification_scores, | |
| "predicted_class": classification_scores.argmax(dim=-1), | |
| "logits_per_image": logits_per_image, | |
| "logits_per_text": logits_per_text, | |
| } | |
| save_file(forward, out_dir / "forward.safetensors") | |
| save_file(postprocess, out_dir / "postprocess.safetensors") | |
| if is_dpt: | |
| save_file(forward_postprocessed, out_dir / "forward_postprocessed.safetensors") | |
| log_path = out_dir / "inference_log.txt" | |
| with open(log_path, "w") as log_file: | |
| tprint(f"model: {model_id}", log_file) | |
| tprint(f"image: {image_path.name}", log_file) | |
| tprint(f"device: {device}", log_file) | |
| tprint("", log_file) | |
| if is_dpt: | |
| log_dpt_model_outputs(forward, forward_postprocessed, postprocess, log_file) | |
| else: | |
| log_base_model_outputs(forward, postprocess, text_emb, text_norm, log_file) | |
| if is_dpt: | |
| visualize_dpt_model(forward_postprocessed, postprocess, out_dir) | |
| else: | |
| visualize_base_model(forward, postprocess, out_dir) | |
| print(f" {image_stem} -> {out_dir}") | |
| except Exception as exc: | |
| print(f" ERROR: {exc}") | |
| finally: | |
| del model | |
| gc.collect() | |
| if device.type == "cuda": | |
| torch.cuda.empty_cache() | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 14.9 kB
- Xet hash:
- c0e7cfefcebda38175cb8903bde4e66fb74aa69015eb8936d21cce0aaf7d3fff
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.