Module neuralops.benchmarks.run_whitened_svd

Functions

def cholesky_whitened_svd(W: torch.Tensor, X: torch.Tensor, rank: int, eps: float = 0.0001) ‑> torch.Tensor
Expand source code
def cholesky_whitened_svd(
    W: torch.Tensor, X: torch.Tensor, rank: int, eps: float = 1e-4
) -> torch.Tensor:
    """Computes Cholesky-whitened SVD on weight matrix W using activation covariance X^T X."""
    X = X.float()
    cov = (X.t() @ X) / max(1, X.shape[0]) + eps * torch.eye(X.shape[1])
    try:
        L = torch.linalg.cholesky(cov)
    except Exception:
        L = torch.diag(torch.sqrt(torch.diag(cov) + eps))
    W_f = W.float()
    W_tilde = W_f @ L
    U, S, Vt = torch.linalg.svd(W_tilde, full_matrices=False)
    r = min(rank, S.shape[0])
    W_approx = (U[:, :r] * S[:r]) @ Vt[:r, :]
    try:
        W_out = W_approx @ torch.linalg.inv(L)
    except Exception:
        W_out = W_approx
    return W_out.to(W.dtype)

Computes Cholesky-whitened SVD on weight matrix W using activation covariance X^T X.

def collect_activations(model: torch.nn.modules.module.Module, dataloader: Any, num_samples: int = 512) ‑> dict[str, torch.Tensor]
Expand source code
def collect_activations(
    model: torch.nn.Module, dataloader: Any, num_samples: int = 512
) -> dict[str, torch.Tensor]:
    """Collects input activations for all Linear layers using forward hooks."""
    activations: dict[str, list[torch.Tensor]] = {}
    hooks = []

    def get_hook(name):
        def hook(module, inp, out):
            if inp and isinstance(inp[0], torch.Tensor):
                x = inp[0].detach().cpu().reshape(-1, inp[0].shape[-1])
                if name not in activations:
                    activations[name] = []
                activations[name].append(x)

        return hook

    for name, mod in model.named_modules():
        if isinstance(mod, torch.nn.Linear):
            hooks.append(mod.register_forward_hook(get_hook(name)))

    count = 0
    with torch.no_grad():
        for batch in dataloader:
            device = next(model.parameters()).device
            inp = {k: v.to(device) for k, v in batch.items() if isinstance(v, torch.Tensor)}
            model(**inp)
            count += next(iter(inp.values())).shape[0]
            if count >= num_samples:
                break

    for h in hooks:
        h.remove()

    return {k: torch.cat(v, dim=0)[:num_samples] for k, v in activations.items()}

Collects input activations for all Linear layers using forward hooks.

def compress_whitened(model_path, dataset_name, rank, num_samples, output_dir)
Expand source code
def compress_whitened(model_path, dataset_name, rank, num_samples, output_dir):
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = AutoModelForSequenceClassification.from_pretrained(model_path).to(device)

    # Load dataset with fallback to local glue_data
    local_path = Path(f"neuralops/data/glue_data/{dataset_name}")
    if not local_path.exists():
        local_path = Path(f"PROJECT_ROOT/neuralops/data/glue_data/{dataset_name}")

    if local_path.exists():
        ds_dict = load_from_disk(str(local_path))
        ds = ds_dict["train"]
    else:
        try:
            ds = load_dataset(dataset_name, split="train[:2000]")
        except Exception:
            ds = load_dataset("glue", dataset_name, split="train[:2000]")

    def tokenize(batch):
        key = (
            "text"
            if "text" in batch
            else ("sentence" if "sentence" in batch else list(batch.keys())[0])
        )
        return tokenizer(batch[key], padding="max_length", truncation=True, max_length=128)

    ds = ds.map(tokenize, batched=True)
    ds.set_format(type="torch", columns=["input_ids", "attention_mask"])

    dataloader = DataLoader(ds, batch_size=32)

    print("Collecting activations...", flush=True)
    activations = collect_activations(model, dataloader, num_samples)

    print(
        f"Compressing Linear layers with Cholesky-whitened SVD (target rank={rank})...",
        flush=True,
    )
    for name, module in model.named_modules():
        if isinstance(module, torch.nn.Linear) and name in activations:
            # Skip classifier head to preserve classification logits structure
            if any(h in name.lower() for h in ("classifier", "score", "pre_classifier")):
                continue
            W = module.weight.data.cpu()
            X = activations[name]
            W_compressed = cholesky_whitened_svd(W, X, rank)
            module.weight.data = W_compressed.to(
                device=module.weight.device, dtype=module.weight.dtype
            )
            print(f"  Compressed {name}: {W.shape} -> rank {rank}", flush=True)

    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)
    model.save_pretrained(output_dir)
    tokenizer.save_pretrained(output_dir)
    print(f"Successfully saved whitened model to {output_dir}", flush=True)