| import json |
| import os |
| import sys |
| from glob import glob |
| from io import BytesIO |
| from pathlib import Path |
|
|
| os.environ["HF_DATASETS_OFFLINE"] = "1" |
| os.environ["HF_METRICS_OFFLINE"] = "1" |
| os.environ["HF_MODULES_OFFLINE"] = "1" |
| os.environ["TRANSFORMERS_OFFLINE"] = "1" |
| os.environ["DIFFUSERS_OFFLINE"] = "1" |
| os.environ["HF_HUB_OFFLINE"] = "1" |
| os.environ.setdefault("ACCELERATE_MIXED_PRECISION", "bf16") |
|
|
| import torch |
| from torch.utils.data import DataLoader |
| from tqdm.auto import tqdm |
| from datasets import load_dataset |
| from PIL import Image |
| from torchvision import transforms |
| from transformers import AutoTokenizer |
| from safetensors.torch import load_file |
|
|
| from trainer.models.sana_preference_model import SanaPreferenceModel, SanaPreferenceModelConfig |
|
|
|
|
| |
| |
| |
| PROJECT_ROOT = Path("/g/data/rr81/LPO/lrm/lrm_sana").resolve() |
| DEFAULT_CKPT_REL = ( |
| "logs/lrm/reward_model/" |
| "step_sana_sana_sprint_0_6b_1024_variable-t_lr1e-5_step-8000_filter2_time951/" |
| "checkpoint-gstep100" |
| ) |
| CKPT_DIR = Path(os.environ.get("SANA_CKPT_DIR", str(PROJECT_ROOT / DEFAULT_CKPT_REL))).resolve() |
|
|
| BASE_SANA_ID = "Efficient-Large-Model/Sana_Sprint_0.6B_1024px_diffusers" |
| DATASET_NAME = "pickapic-anonymous/pickapic_v1" |
| SPLIT = "test_unique" |
| BATCH_SIZE = 1 |
| NUM_WORKERS = 2 |
| MAX_BATCHES = None |
|
|
| MAX_SEQUENCE_LENGTH = 300 |
| MAX_SEQUENCE_LENGTH_2 = 300 |
| IMAGE_SIZE = 1024 |
|
|
| os.chdir(PROJECT_ROOT) |
| if str(PROJECT_ROOT) not in sys.path: |
| sys.path.insert(0, str(PROJECT_ROOT)) |
|
|
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| print("Project root:", PROJECT_ROOT) |
| print("Python:", sys.executable) |
| print("Torch:", torch.__version__) |
| print("CUDA available:", torch.cuda.is_available()) |
| print("Device:", DEVICE) |
| print("Checkpoint dir:", CKPT_DIR) |
|
|
|
|
| |
| |
| |
| model_file = CKPT_DIR / "model.safetensors" |
| if not model_file.exists(): |
| raise FileNotFoundError(f"Missing model checkpoint file: {model_file}") |
|
|
| model_cfg = SanaPreferenceModelConfig( |
| pretrained_model_name_or_path=BASE_SANA_ID, |
| pretrained_vae_name_or_path="", |
| model_profile="sana_sprint_0_6b_1024", |
| max_sequence_length=MAX_SEQUENCE_LENGTH, |
| max_sequence_length_2=MAX_SEQUENCE_LENGTH_2, |
| image_size=IMAGE_SIZE, |
| ) |
|
|
| model = SanaPreferenceModel(model_cfg) |
| state = load_file(str(model_file)) |
| missing, unexpected = model.load_state_dict(state, strict=False) |
| model.to(DEVICE).eval() |
|
|
| print("Model loaded from checkpoint.") |
| print("state_dict keys:", len(state)) |
| print("missing keys:", len(missing)) |
| if missing: |
| print("missing sample:", missing[:10]) |
| print("unexpected keys:", len(unexpected)) |
| if unexpected: |
| print("unexpected sample:", unexpected[:10]) |
| print("logit_scale(exp):", float(model.logit_scale.exp().detach().cpu().item())) |
|
|
|
|
| |
| |
| |
| def features2probs(model_obj, text_features, image_0_features, image_1_features): |
| image_0_scores = model_obj.logit_scale.exp() * torch.diag( |
| torch.einsum("bd,cd->bc", text_features, image_0_features) |
| ) |
| image_1_scores = model_obj.logit_scale.exp() * torch.diag( |
| torch.einsum("bd,cd->bc", text_features, image_1_features) |
| ) |
| scores = torch.stack([image_0_scores, image_1_scores], dim=-1) |
| probs = torch.softmax(scores, dim=-1) |
| return probs[:, 0], probs[:, 1] |
|
|
|
|
| def get_features(model_obj, input_ids, input_ids_2, pixels_0_values, pixels_1_values, timesteps): |
| all_pixel_values = torch.cat([pixels_0_values, pixels_1_values], dim=0) |
| timesteps = timesteps.reshape(-1, 2) |
| timesteps = torch.cat([timesteps[:, 0], timesteps[:, 1]], dim=0) |
|
|
| text_features, all_image_features = model_obj( |
| text_input_ids=input_ids, |
| text_input_ids_2=input_ids_2, |
| image_inputs=all_pixel_values, |
| time_cond=timesteps, |
| ) |
| all_image_features = all_image_features / all_image_features.norm(dim=-1, keepdim=True) |
| text_features = text_features / text_features.norm(dim=-1, keepdim=True) |
| image_0_features, image_1_features = all_image_features.chunk(2, dim=0) |
| return image_0_features, image_1_features, text_features |
|
|
|
|
| def load_dataset_split_like_sana(dataset_name: str, split: str): |
| offline_mode = os.getenv("HF_HUB_OFFLINE", "0").strip().lower() in {"1", "true", "yes", "on"} |
| if not offline_mode: |
| return load_dataset(dataset_name, split=split) |
|
|
| if "/" not in dataset_name: |
| return load_dataset(dataset_name, split=split) |
|
|
| org, name = dataset_name.split("/", 1) |
|
|
| cache_candidates = [] |
| for p in [ |
| os.getenv("HF_HUB_CACHE"), |
| os.getenv("HUGGINGFACE_HUB_CACHE"), |
| (os.path.join(os.getenv("HF_HOME"), "hub") if os.getenv("HF_HOME") else None), |
| os.path.expanduser("~/.cache/huggingface/hub"), |
| "/scratch/rr81/ma5430/.cache/huggingface/hub", |
| ]: |
| if p and p not in cache_candidates: |
| cache_candidates.append(p) |
|
|
| repo_cache_dirs = [ |
| os.path.join(cache_root, f"datasets--{org}--{name}") |
| for cache_root in cache_candidates |
| if os.path.isdir(os.path.join(cache_root, f"datasets--{org}--{name}")) |
| ] |
|
|
| for repo_cache_dir in repo_cache_dirs: |
| snapshot_dir = None |
| ref_main = os.path.join(repo_cache_dir, "refs", "main") |
| if os.path.isfile(ref_main): |
| revision = open(ref_main, "r", encoding="utf-8").read().strip() |
| candidate = os.path.join(repo_cache_dir, "snapshots", revision) |
| if os.path.isdir(candidate): |
| snapshot_dir = candidate |
|
|
| if snapshot_dir is None: |
| snapshots = sorted(glob(os.path.join(repo_cache_dir, "snapshots", "*"))) |
| if snapshots: |
| snapshot_dir = snapshots[-1] |
|
|
| if snapshot_dir is None: |
| continue |
|
|
| data_dir = os.path.join(snapshot_dir, "data") |
| if not os.path.isdir(data_dir): |
| continue |
|
|
| selected_split = split |
| parquet_files = sorted(glob(os.path.join(data_dir, f"{selected_split}-*.parquet"))) |
| if not parquet_files and split.startswith("validation"): |
| for alt_split in ("test_unique", "test"): |
| alt_files = sorted(glob(os.path.join(data_dir, f"{alt_split}-*.parquet"))) |
| if alt_files: |
| selected_split = alt_split |
| parquet_files = alt_files |
| print(f"Offline cache missing split '{split}', falling back to '{selected_split}'") |
| break |
|
|
| if parquet_files: |
| print( |
| f"Loading cached offline split '{selected_split}' from {len(parquet_files)} parquet shards\n" |
| f"cache={repo_cache_dir}" |
| ) |
| return load_dataset("parquet", data_files=parquet_files, split="train") |
|
|
| raise RuntimeError( |
| "Offline mode is enabled and cached parquet dataset was not found. " |
| f"Searched cache roots: {cache_candidates}. " |
| "Set HF_HUB_CACHE/HF_HOME to your predownloaded cache root or disable offline mode." |
| ) |
|
|
|
|
| image_transform = transforms.Compose( |
| [ |
| transforms.Resize((IMAGE_SIZE, IMAGE_SIZE), interpolation=transforms.InterpolationMode.BILINEAR), |
| transforms.CenterCrop(IMAGE_SIZE), |
| transforms.ToTensor(), |
| transforms.Normalize([0.5], [0.5]), |
| ] |
| ) |
|
|
| tokenizer = AutoTokenizer.from_pretrained(BASE_SANA_ID, subfolder="tokenizer") |
| try: |
| tokenizer_2 = AutoTokenizer.from_pretrained(BASE_SANA_ID, subfolder="tokenizer_2") |
| except Exception: |
| tokenizer_2 = None |
|
|
|
|
| def resolve_max_len(tok, requested): |
| m = getattr(tok, "model_max_length", None) |
| if m is None: |
| return requested |
| if m > 100000: |
| return requested |
| return min(requested, m) |
|
|
|
|
| max_len_1 = resolve_max_len(tokenizer, MAX_SEQUENCE_LENGTH) |
| max_len_2 = resolve_max_len(tokenizer_2, MAX_SEQUENCE_LENGTH_2) if tokenizer_2 is not None else max_len_1 |
|
|
| raw_test = load_dataset_split_like_sana(DATASET_NAME, SPLIT) |
| raw_test = raw_test.filter(lambda x: x["has_label"]) |
|
|
|
|
| def to_image(x): |
| if isinstance(x, dict): |
| x = x.get("bytes", x) |
| if isinstance(x, bytes): |
| x = Image.open(BytesIO(x)) |
| if isinstance(x, str): |
| x = Image.open(x) |
| return x.convert("RGB") |
|
|
|
|
| def preprocess_example(example): |
| caption = example["caption"] |
| input_ids = tokenizer( |
| caption, |
| max_length=max_len_1, |
| padding="max_length", |
| truncation=True, |
| add_special_tokens=True, |
| return_tensors="pt", |
| ).input_ids.squeeze(0) |
|
|
| if tokenizer_2 is not None: |
| input_ids_2 = tokenizer_2( |
| caption, |
| max_length=max_len_2, |
| padding="max_length", |
| truncation=True, |
| add_special_tokens=True, |
| return_tensors="pt", |
| ).input_ids.squeeze(0) |
| else: |
| input_ids_2 = input_ids.clone() |
|
|
| pixel_0 = image_transform(to_image(example["jpg_0"])) |
| pixel_1 = image_transform(to_image(example["jpg_1"])) |
|
|
| |
| timestep = torch.tensor([1, 1], dtype=torch.long) |
|
|
| return { |
| "input_ids": input_ids, |
| "input_ids_2": input_ids_2, |
| "pixel_values_0": pixel_0, |
| "pixel_values_1": pixel_1, |
| "label_0": torch.tensor(example["label_0"], dtype=torch.long), |
| "label_1": torch.tensor(example["label_1"], dtype=torch.long), |
| "timestep": timestep, |
| } |
|
|
|
|
| def collate_fn(batch): |
| return { |
| "input_ids": torch.stack([x["input_ids"] for x in batch], dim=0), |
| "input_ids_2": torch.stack([x["input_ids_2"] for x in batch], dim=0), |
| "pixel_values_0": torch.stack([x["pixel_values_0"] for x in batch], dim=0), |
| "pixel_values_1": torch.stack([x["pixel_values_1"] for x in batch], dim=0), |
| "label_0": torch.stack([x["label_0"] for x in batch], dim=0), |
| "label_1": torch.stack([x["label_1"] for x in batch], dim=0), |
| "timestep": torch.stack([x["timestep"] for x in batch], dim=0), |
| } |
|
|
|
|
| class EvalDataset(torch.utils.data.Dataset): |
| def __init__(self, hf_ds): |
| self.hf_ds = hf_ds |
|
|
| def __len__(self): |
| return len(self.hf_ds) |
|
|
| def __getitem__(self, idx): |
| return preprocess_example(self.hf_ds[idx]) |
|
|
|
|
| eval_ds = EvalDataset(raw_test) |
| loader = DataLoader( |
| eval_ds, |
| shuffle=False, |
| batch_size=BATCH_SIZE, |
| num_workers=NUM_WORKERS, |
| collate_fn=collate_fn, |
| ) |
|
|
|
|
| |
| |
| |
| all_correct = [] |
| num_batches = 0 |
|
|
| with torch.no_grad(): |
| for batch in tqdm(loader, desc=f"Evaluating {SPLIT}"): |
| num_batches += 1 |
|
|
| for k, v in list(batch.items()): |
| if torch.is_tensor(v): |
| batch[k] = v.to(DEVICE) |
|
|
| image_0_features, image_1_features, text_features = get_features( |
| model, |
| batch["input_ids"], |
| batch["input_ids_2"], |
| batch["pixel_values_0"], |
| batch["pixel_values_1"], |
| batch["timestep"], |
| ) |
|
|
| image_0_probs, image_1_probs = features2probs(model, text_features, image_0_features, image_1_features) |
|
|
| agree_on_0 = (image_0_probs > image_1_probs) * batch["label_0"] |
| agree_on_1 = (image_0_probs < image_1_probs) * batch["label_1"] |
| is_correct = (agree_on_0 + agree_on_1).detach().cpu() |
| all_correct.append(is_correct) |
|
|
| if MAX_BATCHES is not None and num_batches >= MAX_BATCHES: |
| break |
|
|
| correct_tensor = torch.cat(all_correct).float() if all_correct else torch.tensor([], dtype=torch.float32) |
| accuracy = float(correct_tensor.mean().item()) if correct_tensor.numel() > 0 else float("nan") |
| num_samples = int(correct_tensor.numel()) |
|
|
| metrics = { |
| "split": SPLIT, |
| "accuracy": accuracy, |
| "num_samples": num_samples, |
| f"{SPLIT}_accuracy": accuracy, |
| f"{SPLIT}_num_samples": num_samples, |
| "logit_scale": float(model.logit_scale.exp().detach().cpu().item()), |
| "evaluated_batches": num_batches, |
| "checkpoint": str(CKPT_DIR), |
| } |
|
|
| print(json.dumps(metrics, indent=2)) |
|
|