| """ |
| Train binary linear probe(s) on SAE feature activations of Llava at specified layers. |
| |
| Two-phase pipeline: |
| Phase 1 (one VLM pass + SAE): for each image generate caption, run forced |
| forward pass, extract hook activations, SAE encode + max-pool, and keep only |
| the pooled feature vectors in memory (one per hook point). No disk writes. |
| Phase 2: gather all pooled vectors from all ranks → train binary linear probes |
| per hook point/layer. All probes share the same positive/negative split. |
| |
| Dataset: any HF dataset with binary labels and train/validation splits. |
| Negatives: pre-split image IDs from {"train": [...], "validation": [...]} JSON file. |
| Saved probes: {save_dir}/{hook_type}/probe_{layer}.pth |
| """ |
|
|
| import argparse |
| import json |
| import os |
| from tqdm import tqdm |
|
|
| import torch as t |
| import torch.distributed as dist |
| from datasets import load_dataset |
| from PIL import Image |
| from sklearn.metrics import ( |
| confusion_matrix, f1_score, precision_score, recall_score, roc_auc_score, |
| ) |
| from transformers import AutoConfig, LlavaProcessor |
|
|
| from extra_materials.mechanistic_interp.interp_utils import * |
| from extra_materials.mechanistic_interp.probe.probing import train_binary_probe |
| from model.llava.hooked_llava import HookedSAELlavaConditionalGeneration |
| from sae.SAE_Tools import get_loader, get_sae_activations, load_sae_model |
|
|
| import wandb |
| wandb.init(project="Probe_SAE") |
|
|
|
|
| |
|
|
| def setup_distributed(): |
| if "RANK" in os.environ and "WORLD_SIZE" in os.environ: |
| rank = int(os.environ["RANK"]) |
| world_size = int(os.environ["WORLD_SIZE"]) |
| local_rank = int(os.environ["LOCAL_RANK"]) |
| dist.init_process_group(backend="nccl") |
| return rank, world_size, local_rank |
| return 0, 1, 0 |
|
|
|
|
| def cleanup_distributed(): |
| if dist.is_initialized(): |
| dist.destroy_process_group() |
|
|
|
|
| def gather_tensors(tensor: t.Tensor, world_size: int) -> t.Tensor: |
| """Gather variable-length tensors from all ranks onto rank 0.""" |
| if world_size == 1: |
| return tensor |
| local_size = t.tensor([tensor.shape[0]], device=tensor.device) |
| all_sizes = [t.zeros(1, dtype=t.long, device=tensor.device) for _ in range(world_size)] |
| dist.all_gather(all_sizes, local_size) |
| max_size = max(s.item() for s in all_sizes) |
| if tensor.shape[0] < max_size: |
| pad = list(tensor.shape) |
| pad[0] = max_size - tensor.shape[0] |
| tensor = t.cat([tensor, t.zeros(pad, dtype=tensor.dtype, device=tensor.device)]) |
| gathered = [t.zeros_like(tensor) for _ in range(world_size)] |
| dist.all_gather(gathered, tensor) |
| return t.cat([g[: int(s.item())] for g, s in zip(gathered, all_sizes)], dim=0) |
|
|
|
|
| |
|
|
| def reserve_gpu_memory(device, fraction: float = 0.90, verbose: bool = True) -> None: |
| """Claim VRAM in PyTorch's allocator so competing CUDA processes can't take it. |
| |
| Allocating then deleting a large buffer populates PyTorch's per-process cache. |
| Other processes' cudaMalloc calls see far less free VRAM and will fail instead |
| of competing at peak-memory moments. Never call torch.cuda.empty_cache() after |
| this — it releases cached blocks back to the driver and undoes the reservation. |
| Controlled via GPU_MEM_RESERVE_FRAC env var (default 0.90). |
| """ |
| if not str(device).startswith("cuda"): |
| return |
| total = t.cuda.get_device_properties(device).total_memory |
| already = t.cuda.memory_reserved(device) |
| to_alloc = max(int(total * fraction) - already, 0) |
| if to_alloc == 0: |
| if verbose: |
| print(f" [mem-reserve] already at {already/2**30:.1f}/{total/2**30:.1f} GB — skip") |
| return |
| try: |
| buf = t.empty(to_alloc // 2, dtype=t.int16, device=device) |
| del buf |
| reserved = t.cuda.memory_reserved(device) |
| if verbose: |
| print(f" [mem-reserve] {reserved/2**30:.1f}/{total/2**30:.1f} GB reserved " |
| f"({100*reserved/total:.0f}%) — others see ≤{(total-reserved)/2**30:.1f} GB free") |
| except t.cuda.OutOfMemoryError: |
| t.cuda.empty_cache() |
| print(f" [mem-reserve] WARNING: reservation failed — GPU already heavily loaded " |
| f"({t.cuda.memory_allocated(device)/2**30:.1f} GB in use)") |
|
|
|
|
| |
|
|
| _HOOK_SUFFIX = {"pre": "hook_resid_pre", "mid": "hook_resid_mid", "post": "hook_resid_post"} |
|
|
| def build_hook_points(layers: list, hook_type: str) -> list: |
| suffix = _HOOK_SUFFIX[hook_type] |
| return [f"model.language_model.layers.{layer}.{suffix}" for layer in layers] |
|
|
|
|
| |
|
|
| def phase1_cache(rank, world_size, model, processor, image_files, |
| hook_points, args, device, question, sae): |
| """ |
| Batch VLM forward passes; for each image: generate caption, forced forward, |
| SAE encode + max-pool activations. Returns {img_stem: {hp: pooled_vec}}. |
| """ |
| shard = image_files[rank::world_size] |
| processed = 0 |
|
|
| rank_cache = {} |
|
|
| batch_iter = range(0, len(shard), args.batch_size) |
| if rank == 0: |
| batch_iter = tqdm(batch_iter, desc="Phase1", total=len(batch_iter)) |
| for i in batch_iter: |
| batch_files = shard[i:i + args.batch_size] |
| images, valid_files = [], [] |
| for img_file in batch_files: |
| try: |
| img = Image.open(os.path.join(args.image_folder, img_file)).convert("RGB") |
| images.append(img) |
| valid_files.append(img_file) |
| except Exception: |
| continue |
| if not images: |
| continue |
|
|
| |
| gen_prompts = [f"USER: <image>\n{question}\nASSISTANT:"] * len(images) |
| gen_inputs = processor(images=images, text=gen_prompts, return_tensors="pt", padding=True).to(device) |
|
|
| with t.no_grad(): |
| gen_outputs = model.generate( |
| **gen_inputs, do_sample=False, num_beams=1, |
| use_cache=True, max_new_tokens=args.max_new_tokens, |
| ) |
|
|
| captions = processor.batch_decode(gen_outputs, skip_special_tokens=True) |
| del gen_inputs, gen_outputs |
|
|
| |
| forced_texts = [] |
| for cap in captions: |
| asst_text = cap.split("ASSISTANT:")[-1].strip() |
| forced_texts.append(f"USER: <image>\n{question}\nASSISTANT: {asst_text}") |
|
|
| act_buf: dict = {} |
| def make_hook(name): |
| def _fn(act: t.Tensor, hook): |
| act_buf[name] = act.detach().cpu() |
| return _fn |
|
|
| fwd_inputs = processor(images=images, text=forced_texts, return_tensors="pt", padding=True).to(device) |
| with t.no_grad(): |
| model.run_with_hooks( |
| fwd_inputs, |
| fwd_hooks=[(hp, make_hook(hp)) for hp in hook_points] |
| ) |
|
|
| attn_mask = fwd_inputs['attention_mask'].cpu() |
| seq_lens = attn_mask.sum(dim=1) |
| |
| input_ids_cpu = fwd_inputs['input_ids'].cpu() if args.dry_run else None |
| del fwd_inputs |
|
|
| |
| batch_acts_per_hp = {hp: [] for hp in hook_points} |
| for idx in range(len(valid_files)): |
| current_caption = captions[idx].split("ASSISTANT:")[-1].strip() |
| |
| |
| cap_len = len(processor.tokenizer(current_caption, |
| add_special_tokens=False)["input_ids"]) |
|
|
| if args.dry_run and idx == 0 and i == 0: |
| sl = input_ids_cpu[idx, seq_lens[idx] - cap_len : seq_lens[idx]] |
| decoded = processor.tokenizer.decode(sl, skip_special_tokens=True) |
| print(f" [dry-run] token slice (len={cap_len}): '{decoded}'") |
| print(f" [dry-run] expected caption: '{current_caption}'") |
|
|
| for hp in hook_points: |
| full = act_buf[hp][idx] |
| |
| |
| a = full[seq_lens[idx] - cap_len : seq_lens[idx]] |
| batch_acts_per_hp[hp].append(a) |
|
|
| act_buf.clear() |
|
|
| |
| for hp in hook_points: |
| if not batch_acts_per_hp[hp]: |
| continue |
| pooled_list = sae_encode_and_pool(batch_acts_per_hp[hp], sae, args.sae_batch, device) |
|
|
| |
| for img_file, pooled_vec in zip(valid_files, pooled_list): |
| stem = os.path.splitext(os.path.basename(img_file))[0] |
| if stem not in rank_cache: |
| rank_cache[stem] = {} |
| rank_cache[stem][hp] = pooled_vec |
|
|
| processed += len(valid_files) |
|
|
| if rank == 0: |
| print(f"Phase 1 complete — processed: {processed}") |
| if world_size > 1: |
| dist.barrier() |
|
|
| return rank_cache |
|
|
|
|
| |
|
|
| def sae_encode_and_pool(acts_list: list, sae, sae_batch: int, device) -> list: |
| """ |
| Encode variable-length (T_i, D) activations through the SAE in a single pass, |
| then max-pool over tokens per input using scatter_reduce. |
| Returns a list of (d_sae,) CPU vectors, one per input. |
| """ |
| lens = [a.shape[0] for a in acts_list] |
| flat = t.cat(acts_list, dim=0).unsqueeze(1).to(device) |
| loader = get_loader(flat, batch_size=min(sae_batch, flat.shape[0])) |
| topk_idxs, topk_vals = get_sae_activations(sae, loader, device, no_tqdm=True) |
| topk_idxs = topk_idxs.squeeze(1) |
| topk_vals = topk_vals.squeeze(1).float() |
|
|
| d_sae = sae.cfg.d_sae |
| pooled_list = [] |
| offset = 0 |
| for length in lens: |
| idxs = topk_idxs[offset:offset + length].reshape(-1) |
| vals = topk_vals[offset:offset + length].reshape(-1) |
| pooled = t.zeros(d_sae, dtype=t.float32, device=topk_vals.device) |
| pooled.scatter_reduce_(0, idxs, vals, reduce="amax", include_self=True) |
| pooled_list.append(pooled.cpu()) |
| offset += length |
| return pooled_list |
|
|
|
|
| def prepare_phase2_data(merged_cache, train_ids_labels, val_ids_labels, hook_points, verbose=False): |
| """ |
| Convert merged_cache: {img_id: {hp: pooled_vec}} → |
| {"train": {hp: (Tensor[N, d_sae], Tensor[N])}, "val": ...} |
| """ |
| if verbose: |
| cache_sample = list(merged_cache.keys())[:3] |
| train_sample = [img_id for img_id, _ in train_ids_labels[:3]] |
| val_sample = [img_id for img_id, _ in val_ids_labels[:3]] |
| train_matches = sum(1 for img_id, _ in train_ids_labels if img_id in merged_cache) |
| val_matches = sum(1 for img_id, _ in val_ids_labels if img_id in merged_cache) |
| print(f" [dry-run] cache ({len(merged_cache)}): {cache_sample}") |
| print(f" [dry-run] train_labels ({len(train_ids_labels)}) hits={train_matches}: {train_sample}") |
| print(f" [dry-run] val_labels ({len(val_ids_labels)}) hits={val_matches}: {val_sample}") |
|
|
| train_feats = {hp: [] for hp in hook_points} |
| train_lbls = {hp: [] for hp in hook_points} |
| val_feats = {hp: [] for hp in hook_points} |
| val_lbls = {hp: [] for hp in hook_points} |
|
|
| for img_id, label in train_ids_labels: |
| if img_id not in merged_cache: |
| continue |
| cached = merged_cache[img_id] |
| for hp in hook_points: |
| if hp in cached: |
| train_feats[hp].append(cached[hp]) |
| train_lbls[hp].append(label) |
|
|
| for img_id, label in val_ids_labels: |
| if img_id not in merged_cache: |
| continue |
| cached = merged_cache[img_id] |
| for hp in hook_points: |
| if hp in cached: |
| val_feats[hp].append(cached[hp]) |
| val_lbls[hp].append(label) |
|
|
| return { |
| "train": {hp: (t.stack(train_feats[hp]), t.tensor(train_lbls[hp])) |
| for hp in hook_points if train_feats[hp]}, |
| "val": {hp: (t.stack(val_feats[hp]), t.tensor(val_lbls[hp])) |
| for hp in hook_points if val_feats[hp]}, |
| } |
|
|
|
|
| def phase2_train_probes(rank, hook_points, layers, phase2_data, args, device): |
| if rank != 0: |
| return |
|
|
| if not args.dry_run: |
| os.makedirs(args.save_dir, exist_ok=True) |
| train_feats = phase2_data["train"] |
| val_feats = phase2_data["val"] |
|
|
| for hook_point, layer in zip(hook_points, layers): |
| if hook_point not in train_feats or hook_point not in val_feats: |
| print(f"Skipping layer {layer} ({hook_point}): no activations found.") |
| continue |
|
|
| print(f"\n{'='*64}") |
| print(f" Layer {layer} — {hook_point}") |
| print(f"{'='*64}") |
|
|
| train_data, train_label = train_feats[hook_point] |
| val_data, val_label = val_feats[hook_point] |
|
|
| probe = train_binary_probe( |
| train_data, train_label, |
| batch_size=args.probe_batch_size, |
| num_epochs=args.probe_epochs, |
| lr=args.probe_lr, |
| device=device, |
| ) |
|
|
| if args.dry_run: |
| print(f" [dry-run] skipping save") |
| else: |
| os.makedirs(os.path.join(args.save_dir, args.hook_type), exist_ok=True) |
| save_path = os.path.join(args.save_dir, args.hook_type, f"probe_{layer}.pth") |
| t.save(probe.state_dict(), save_path) |
| print(f" Saved → {save_path}") |
|
|
| with t.no_grad(): |
| val_logits = probe(val_data.to(device)) |
| val_preds = (val_logits > 0).float() |
| val_acc = (val_preds.squeeze(-1) == val_label.to(device)).float().mean().item() |
|
|
| |
| y_true = val_label.view(-1).cpu().numpy() |
| y_score = val_logits.squeeze(-1).float().cpu().numpy() |
| y_pred = val_preds.squeeze(-1).cpu().numpy() |
|
|
| try: |
| auc = roc_auc_score(y_true, y_score) |
| except ValueError: |
| auc = float("nan") |
| f1 = f1_score(y_true, y_pred, zero_division=0) |
| prec = precision_score(y_true, y_pred, zero_division=0) |
| rec = recall_score(y_true, y_pred, zero_division=0) |
| tn, fp, fn, tp = confusion_matrix(y_true, y_pred, labels=[0, 1]).ravel() |
|
|
| print(f"\n── Validation Results ─────────────────────────────") |
| print(f" Accuracy : {val_acc:.4f}") |
| print(f" AUC-ROC : {auc:.4f}") |
| print(f" F1 : {f1:.4f}") |
| print(f" Precision : {prec:.4f}") |
| print(f" Recall : {rec:.4f}") |
| print(f" TP={tp} TN={tn} FP={fp} FN={fn}") |
| print("───────────────────────────────────────────────────") |
|
|
|
|
| |
|
|
| def main(): |
| ap = argparse.ArgumentParser( |
| description="Train binary linear probe(s) on SAE feature activations " |
| "of Llava at specified hook points (two-phase: cache then train)." |
| ) |
|
|
| |
| ap.add_argument("--sae_ckpt", required=True, |
| help="Path to the SAE checkpoint (.ckpt).") |
| ap.add_argument("--model_name", default="llava-hf/llava-1.5-7b-hf") |
| ap.add_argument("--device_id", type=int, default=0, |
| help="GPU id (single-GPU mode only; ignored with torchrun).") |
| ap.add_argument("--dtype", default="bfloat16", |
| choices=["float32", "float16", "bfloat16"]) |
|
|
| |
| ap.add_argument("--image_folder", required=True, |
| help="Folder of images to process.") |
| ap.add_argument("--hf_dataset", default="pbcong/bathroom-toilet", |
| help="HF dataset with binary relation labels and train/validation splits.") |
| ap.add_argument("--id_col", default="image_id", |
| help="Column in the HF dataset holding the image filename stem.") |
| ap.add_argument("--probe_type", default="toilet", |
| help="Binary label column used as positives (label==1).") |
| ap.add_argument("--other_object", default=None, |
| help="Column name of a contrastive object in the HF dataset. " |
| "When set: positives = probe_type=1 (any other_object value); " |
| "HF negatives = other_object=1 & probe_type=0 (drawn from train/val splits). " |
| "Stacked on top of any --neg_jsonl negatives.") |
| ap.add_argument("--neg_jsonl", default=None, |
| help='JSON file: {"train": [...image_ids], "validation": [...image_ids]}. ' |
| 'Optional when --other_object is set.') |
|
|
| |
| ap.add_argument("--layers", type=int, nargs="+", default=None, |
| help="Layer indices; one probe trained per layer (e.g. --layers 10 15 20). " |
| "If omitted, trains on all layers (0 .. num_hidden_layers-1).") |
| ap.add_argument("--hook_type", default="mid", choices=["pre", "mid", "post"], |
| help="Residual stream position: pre (before attn), mid (after attn), post (after mlp).") |
|
|
| |
| ap.add_argument("--sae_batch", type=int, default=2048, |
| help="Sub-batch size for SAE processing.") |
|
|
| |
| ap.add_argument("--max_new_tokens", type=int, default=128) |
| ap.add_argument("--batch_size", type=int, default=32, |
| help="Batch size for Phase 1 image processing.") |
| ap.add_argument("--question", type=str, default="Describe this image.", |
| help='Question to ask about the image (used in prompt: "USER: <image>\n{question}\nASSISTANT:")') |
|
|
| |
| ap.add_argument("--probe_batch_size", type=int, default=256) |
| ap.add_argument("--probe_epochs", type=int, default=20) |
| ap.add_argument("--probe_lr", type=float, default=0.01) |
|
|
| |
| ap.add_argument("--save_dir", required=True, |
| help="Directory to save trained probe checkpoints.") |
|
|
| |
| ap.add_argument("--dry_run", action="store_true", |
| help="Smoke-test: process only --dry_run_images images and 3 probe epochs " |
| "to verify the full pipeline end-to-end before a real run.") |
| ap.add_argument("--dry_run_images", type=int, default=8, |
| help="Number of images to process in dry-run mode (default 8).") |
|
|
| args = ap.parse_args() |
|
|
| |
| rank, world_size, local_rank = setup_distributed() |
| is_distributed = world_size > 1 |
|
|
| dtype_map = {"float32": t.float32, "float16": t.float16, "bfloat16": t.bfloat16} |
| dtype = dtype_map[args.dtype] |
| device = ( |
| f"cuda:{local_rank}" if is_distributed |
| else (f"cuda:{args.device_id}" if t.cuda.is_available() else "cpu") |
| ) |
|
|
| if rank == 0: |
| print(f"GPUs: {world_size} | device: {device} | dtype: {dtype}") |
|
|
| |
| if not args.layers: |
| n_layers = AutoConfig.from_pretrained(args.model_name).text_config.num_hidden_layers |
| args.layers = list(range(n_layers)) |
| if rank == 0: |
| print(f"--layers not provided; defaulting to all {n_layers} layers.") |
|
|
| |
| hook_points = build_hook_points(args.layers, args.hook_type) |
| if rank == 0: |
| print(f"Hook points: {hook_points}") |
|
|
| if not args.other_object and not args.neg_jsonl: |
| ap.error("At least one of --other_object or --neg_jsonl must be provided for negatives.") |
|
|
| |
| train_ds = load_dataset(args.hf_dataset, split="train") |
| val_ds = load_dataset(args.hf_dataset, split="validation") |
| label_col = args.probe_type |
|
|
| |
| train_pos_ids = [row[args.id_col] for row in train_ds if row[label_col] == 1] |
| val_pos_ids = [row[args.id_col] for row in val_ds if row[label_col] == 1] |
|
|
| |
| if args.other_object: |
| train_hf_neg_ids = [row[args.id_col] for row in train_ds |
| if row[args.other_object] == 1 and row[label_col] == 0] |
| val_hf_neg_ids = [row[args.id_col] for row in val_ds |
| if row[args.other_object] == 1 and row[label_col] == 0] |
| else: |
| train_hf_neg_ids, val_hf_neg_ids = [], [] |
|
|
| |
| train_json_neg_ids, val_json_neg_ids = [], [] |
| if args.neg_jsonl: |
| with open(args.neg_jsonl) as f: |
| neg_split = json.load(f) |
| train_json_neg_ids = neg_split.get("train", []) |
| val_json_neg_ids = neg_split.get("validation", []) |
|
|
| train_neg_ids = train_hf_neg_ids + train_json_neg_ids |
| val_neg_ids = val_hf_neg_ids + val_json_neg_ids |
|
|
| |
| stem_to_file = {} |
| for root, dirs, files in os.walk(args.image_folder): |
| for fn in files: |
| if fn.lower().endswith((".jpg", ".jpeg", ".png", ".webp")): |
| stem = os.path.splitext(fn)[0] |
| |
| rel_path = os.path.relpath(os.path.join(root, fn), args.image_folder) |
| stem_to_file[stem] = rel_path |
|
|
| def ids_in_folder(ids): |
| |
| |
| return [os.path.splitext(os.path.basename(img_id))[0] |
| for img_id in ids |
| if os.path.splitext(os.path.basename(img_id))[0] in stem_to_file] |
|
|
| train_pos_ids = ids_in_folder(train_pos_ids) |
| val_pos_ids = ids_in_folder(val_pos_ids) |
| train_neg_ids = ids_in_folder(train_neg_ids) |
| val_neg_ids = ids_in_folder(val_neg_ids) |
|
|
| train_ids_labels = [(s, 1) for s in train_pos_ids] + [(s, 0) for s in train_neg_ids] |
| val_ids_labels = [(s, 1) for s in val_pos_ids] + [(s, 0) for s in val_neg_ids] |
|
|
| all_needed_ids = set(train_pos_ids + val_pos_ids + train_neg_ids + val_neg_ids) |
| all_needed_files = [stem_to_file[s] for s in all_needed_ids] |
|
|
| if args.dry_run: |
| |
| |
| |
| n = max(2, args.dry_run_images // 4) |
| dry_stems = set( |
| train_pos_ids[:n] + train_neg_ids[:n] + |
| val_pos_ids[:n] + val_neg_ids[:n] |
| ) |
| all_needed_files = [stem_to_file[s] for s in dry_stems][:args.dry_run_images] |
| args.probe_epochs = 3 |
| if rank == 0: |
| print(f"\n{'='*64}") |
| print(f" DRY RUN — up to {args.dry_run_images} images ({len(all_needed_files)} selected), 3 probe epochs") |
| print(f"{'='*64}\n") |
|
|
| if rank == 0: |
| print(f"Train : {len(train_pos_ids)} pos + {len(train_neg_ids)} neg " |
| f"({len(train_hf_neg_ids)} HF-contrastive + {len(train_json_neg_ids)} JSON)" |
| f" = {len(train_ids_labels)}") |
| print(f"Val : {len(val_pos_ids)} pos + {len(val_neg_ids)} neg " |
| f"({len(val_hf_neg_ids)} HF-contrastive + {len(val_json_neg_ids)} JSON)" |
| f" = {len(val_ids_labels)}") |
| print(f"Phase 1 images to process: {len(all_needed_files)}") |
|
|
| |
| model = HookedSAELlavaConditionalGeneration.from_pretrained(args.model_name) |
| processor = LlavaProcessor.from_pretrained(args.model_name) |
| model = model.to(device, dtype=dtype) |
| model.language_model = t.compile(model.language_model, dynamic=True) |
|
|
| |
| sae = load_sae_model(args.sae_ckpt, model_type="llava", hook_type="text", device=device) |
|
|
| |
| |
| _mem_frac = float(os.environ.get("GPU_MEM_RESERVE_FRAC", "0.90")) |
| if _mem_frac > 0: |
| reserve_gpu_memory(device, fraction=_mem_frac, verbose=(rank == 0)) |
|
|
| rank_cache = phase1_cache( |
| rank, world_size, model, processor, all_needed_files, |
| hook_points, args, device, args.question, sae |
| ) |
|
|
| |
| del model, sae |
| t.cuda.empty_cache() |
|
|
| |
| if is_distributed: |
| all_rank_caches = [None] * world_size |
| dist.all_gather_object(all_rank_caches, rank_cache) |
| else: |
| all_rank_caches = [rank_cache] |
|
|
| if rank == 0: |
| merged_cache = {} |
| for rc in all_rank_caches: |
| merged_cache.update(rc) |
|
|
| phase2_data = prepare_phase2_data(merged_cache, train_ids_labels, val_ids_labels, hook_points, |
| verbose=args.dry_run) |
| phase2_train_probes(rank, hook_points, args.layers, phase2_data, args, device) |
|
|
| cleanup_distributed() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|