Module neuralops.compression_engine.cr_compressor

CR (Column-Row / Interpolative Decomposition) Compressor. Decomposes W ≈ C @ P, where C are actual columns of W. Registered in COMPRESSOR_REGISTRY as 'cr' and 'cr_compressor'.

Functions

def cr_compressor_factory(model: nn.Module,
comp_config: Any | None = None,
rank: int | None = None,
**kwargs: Any) ‑> torch.nn.modules.module.Module | tuple[torch.nn.modules.module.Module, dict[str, typing.Any]]
Expand source code
@COMPRESSOR_REGISTRY.register("cr")
@COMPRESSOR_REGISTRY.register("cr_factory")
@COMPRESSOR_REGISTRY.register("cr_compressor_factory")
def cr_compressor_factory(
    model: nn.Module,
    comp_config: Any | None = None,
    rank: int | None = None,
    **kwargs: Any,
) -> nn.Module | tuple[nn.Module, dict[str, Any]]:
    """Factory function for CR compressor."""
    effective_rank = kwargs.pop(
        "rank",
        rank or _cfg_get(comp_config, "cr_rank", _cfg_get(comp_config, "rank", 64)),
    )
    max_error = kwargs.pop(
        "max_error",
        _cfg_get(comp_config, "cr_max_error", _cfg_get(comp_config, "max_error", 0.20)),
    )
    fallback = kwargs.pop(
        "fallback_to_original", _cfg_get(comp_config, "fallback_to_original", True)
    )
    target_attention_only = kwargs.pop(
        "target_attention_only",
        _cfg_get(
            comp_config,
            "cr_target_attention_only",
            _cfg_get(comp_config, "target_attention_only", False),
        ),
    )
    return_stats = kwargs.pop("return_stats", kwargs.pop("return_tuple", False))
    compressor = CRCompressor(
        model,
        rank=effective_rank,
        max_error=max_error,
        fallback_to_original=fallback,
        target_attention_only=target_attention_only,
        cfg=comp_config,
        **kwargs,
    )
    compressed_model, stats = compressor.compress()
    try:
        compressed_model.compression_stats = stats
    except Exception:
        pass
    if return_stats:
        return compressed_model, stats
    return compressed_model

Factory function for CR compressor.

Classes

class CRCompressor (model: nn.Module | None = None,
rank: int = 64,
max_error: float = 0.2,
fallback_to_original: bool = True,
max_retries: int = 2,
target_attention_only: bool = False,
cfg: Any | None = None,
target_layers: list[str] | None = None,
**kwargs: Any)
Expand source code
@COMPRESSOR_REGISTRY.register("cr_compressor")
class CRCompressor(BaseCompressor):
    """
    CR (Column-Row / Interpolative Decomposition) Compressor.
    Decomposes linear layers into skeleton column bases and interpolative projections.

    Args:
        model: PyTorch model.
        rank: Rank parameter (number of skeleton columns).
        max_error: Maximum relative Frobenius reconstruction error (default 0.20).
        fallback_to_original: If True, keep original layer when error exceeds max_error (default True).
        max_retries: Number of rank increase retries before fallback (default 2).
        target_attention_only: If True, restrict compression to attention projections only.
        cfg: Optional configuration object.
        target_layers: Optional target layer filter.
    """

    def __init__(
        self,
        model: nn.Module | None = None,
        rank: int = 64,
        max_error: float = 0.20,
        fallback_to_original: bool = True,
        max_retries: int = 2,
        target_attention_only: bool = False,
        cfg: Any | None = None,
        target_layers: list[str] | None = None,
        **kwargs: Any,
    ) -> None:
        super().__init__(model if model is not None else nn.Identity(), name="cr", **kwargs)
        c_rank = _cfg_get(cfg, "cr_rank", _cfg_get(cfg, "rank", rank))
        c_max_error = _cfg_get(cfg, "cr_max_error", _cfg_get(cfg, "max_error", max_error))
        c_fallback = _cfg_get(cfg, "fallback_to_original", fallback_to_original)
        c_target_attn = _cfg_get(
            cfg,
            "cr_target_attention_only",
            _cfg_get(cfg, "target_attention_only", target_attention_only),
        )

        if c_max_error is None or c_max_error == 0.20:
            c_max_error = adaptive_max_error(c_rank, 768, 768, "distilbert")

        self.rank = int(c_rank) if c_rank is not None else 64
        self.max_error = float(c_max_error)
        self.fallback_to_original = bool(c_fallback)
        self.max_retries = max(0, int(max_retries))
        self.target_attention_only = bool(c_target_attn)
        self.cfg = cfg
        self.target_layers = target_layers or _cfg_get(cfg, "target_layers", None)
        self.compressed_layers: list[str] = []
        self.fallback_layers: list[str] = []
        self.layer_errors: dict[str, float] = {}
        self.layer_ranks: dict[str, int] = {}

    def _is_target_module(self, name: str, module: nn.Module) -> bool:
        """Filter candidate layers based on classification head exclusion and target_attention_only."""
        cls_name = module.__class__.__name__.lower()
        has_base = hasattr(module, "base_layer")
        base_cls = module.base_layer.__class__.__name__.lower() if has_base else ""

        is_linear = isinstance(module, nn.Linear) or "linear" in cls_name
        is_conv1d = "conv1d" in cls_name or hasattr(module, "nf")
        is_lora = has_base and (
            isinstance(module.base_layer, nn.Linear)
            or "conv1d" in base_cls
            or hasattr(module.base_layer, "nf")
        )

        if not (is_linear or is_conv1d or is_lora):
            return False

        if isinstance(module, CRLinear) or (has_base and isinstance(module.base_layer, CRLinear)):
            return False

        name_lower = name.lower()
        if any(
            skip in name_lower
            for skip in [
                "classifier",
                "score",
                "lm_head",
                "pooler",
                "embed",
                "logits_processor",
                "pre_classifier",
                "head",
            ]
        ):
            return False

        if self.target_attention_only:
            ffn_patterns = [
                "ffn",
                "mlp",
                "lin1",
                "lin2",
                "intermediate",
                "gate_proj",
                "up_proj",
                "down_proj",
                "c_fc",
            ]
            if any(p in name_lower for p in ffn_patterns):
                return False
            if "output.dense" in name_lower and "attention" not in name_lower:
                return False
            attn_patterns = [
                "attn",
                "attention",
                "q_proj",
                "k_proj",
                "v_proj",
                "o_proj",
                "out_lin",
                "q_lin",
                "k_lin",
                "v_lin",
                "c_attn",
                "c_proj",
                "query",
                "key",
                "value",
                "in_proj_qkv",
                "out_proj",
            ]
            if not any(p in name_lower for p in attn_patterns):
                return False

        if self.target_layers and not any(t in name for t in self.target_layers):
            return False

        return True

    def compress(self, model: nn.Module | None = None) -> tuple[nn.Module, dict[str, Any]]:
        """Compress linear layers using interpolative decomposition."""
        if model is not None:
            self.model = model
            self._original_params = self._count_params(self.model)

        self.compressed_layers = []
        self.fallback_layers = []
        self.layer_errors = {}
        self.layer_ranks = {}

        total_orig_decomp_params = 0
        total_comp_decomp_params = 0

        for name, module in list(self.model.named_modules()):
            if not self._is_target_module(name, module):
                if self.target_attention_only:
                    is_lin_like = (
                        isinstance(module, nn.Linear)
                        or "conv1d" in module.__class__.__name__.lower()
                        or hasattr(module, "base_layer")
                    )
                    if is_lin_like:
                        for p in module.parameters():
                            p.requires_grad = False
                continue

            target_mod = getattr(module, "base_layer", getattr(module, "linear", module))
            cls_name = module.__class__.__name__.lower()
            is_conv1d = "conv1d" in cls_name or hasattr(module, "nf") or hasattr(target_mod, "nf")
            (hasattr(module, "lora_A") and module.lora_A is not None) or hasattr(
                module, "base_layer"
            )

            raw_w = target_mod.weight.data
            orig_w = raw_w.t().float() if is_conv1d else raw_w.float()
            out_f, in_f = orig_w.shape
            max_rank = min(out_f, in_f)

            if max_rank < 16:
                continue

            # Parameter reduction check: r * (in_f + out_f) must be less than in_f * out_f
            target_r = min(self.rank, max_rank)
            if target_r * (in_f + out_f) >= 0.85 * (in_f * out_f):
                target_r = max(1, int(0.65 * (in_f * out_f) / (in_f + out_f)))

            cur_r = target_r
            best_cr_layer = None
            best_err = float("inf")
            best_r = cur_r
            norm_orig = torch.norm(orig_w, p="fro").clamp_min(1e-8)

            for _ in range(self.max_retries + 1):
                try:
                    candidate = CRLinear.from_linear(target_mod, rank=cur_r)
                    recon_w = candidate.get_weight().float()
                    err = (torch.norm(orig_w - recon_w, p="fro") / norm_orig).item()
                    if err < best_err:
                        best_err = err
                        best_cr_layer = candidate
                        best_r = cur_r

                    if err <= self.max_error:
                        break

                    next_r = min(int(cur_r * 1.5), max_rank - 1)
                    if next_r <= cur_r or next_r * (in_f + out_f) >= 0.85 * (in_f * out_f):
                        break
                    cur_r = next_r
                except Exception as ex:
                    logger.debug("CR decomposition error on %s with rank %d: %s", name, cur_r, ex)
                    break

            if best_err > self.max_error:
                logger.warning(
                    "CRCompressor: Layer %s error %.4f exceeded max_error %.4f",
                    name,
                    best_err,
                    self.max_error,
                )
                if self.fallback_to_original:
                    self.fallback_layers.append(name)
                    # Freeze fallback layers during recovery
                    for p in module.parameters():
                        p.requires_grad = False
                    continue

            if best_cr_layer is not None:
                new_layer = best_cr_layer
                compressed_layer = new_layer
                # Preserve existing LoRA adapter weights if the source module has them
                if (
                    hasattr(module, "lora_A")
                    and hasattr(module, "lora_B")
                    and module.lora_A is not None
                    and module.lora_B is not None
                ):
                    src_lA = getattr(module.lora_A, "weight", module.lora_A)
                    src_lB = getattr(module.lora_B, "weight", module.lora_B)
                    if hasattr(new_layer, "lora_A") and new_layer.lora_A is not None:
                        if hasattr(src_lA, "data") and src_lA.shape == new_layer.lora_A.shape:
                            new_layer.lora_A.data.copy_(src_lA.data)
                    if hasattr(new_layer, "lora_B") and new_layer.lora_B is not None:
                        if hasattr(src_lB, "data") and src_lB.shape == new_layer.lora_B.shape:
                            new_layer.lora_B.data.copy_(src_lB.data)

                if hasattr(module, "base_layer"):
                    module.base_layer = compressed_layer  # put compressed layer inside wrapper
                    # module itself (LoRALinear wrapper) remains the replacement
                    new_module = module
                else:
                    new_module = compressed_layer

                if hasattr(module, "base_layer"):
                    for p_name, p in module.named_parameters():
                        if any(t in p_name.lower() for t in ("lora_a", "lora_b", "adapter")):
                            p.requires_grad = True
                else:
                    if name == "":
                        self.model = new_module
                    else:
                        replace_module(self.model, name, new_module)

                self.compressed_layers.append(name)
                self.layer_errors[name] = float(best_err)
                self.layer_ranks[name] = int(best_r)
                total_orig_decomp_params += in_f * out_f
                total_comp_decomp_params += best_r * (in_f + out_f)

        self._compressed_params = self._count_params(self.model)
        stats = self.get_compression_stats()
        avg_err = (
            (sum(self.layer_errors.values()) / len(self.layer_errors)) if self.layer_errors else 0.0
        )
        effective_param_ratio = (
            total_comp_decomp_params / total_orig_decomp_params
            if total_orig_decomp_params > 0
            else 1.0
        )
        stats.update(
            {
                "compressed_layers": self.compressed_layers,
                "compressed_layer_count": len(self.compressed_layers),
                "fallback_layers": self.fallback_layers,
                "fallback_layer_count": len(self.fallback_layers),
                "layer_errors": self.layer_errors,
                "layer_ranks": self.layer_ranks,
                "avg_reconstruction_error": avg_err,
                "max_error": self.max_error,
                "fallback_to_original": self.fallback_to_original,
                "cr_rank": self.rank,
                "target_attention_only": self.target_attention_only,
                "effective_parameter_ratio": round(effective_param_ratio, 4),
                "used_interpolative_decomposition_count": len(self.compressed_layers),
            }
        )
        logger.info(
            "Compressor cr finished: %d compressed, %d fallback, avg_err=%.4f, max_err_threshold=%.4f",
            len(self.compressed_layers),
            len(self.fallback_layers),
            avg_err,
            self.max_error,
        )
        return self.model, stats

    def compress_linear(self, linear: nn.Linear | Any) -> CRLinear:
        """Compress a single linear layer into CRLinear."""
        return CRLinear.from_linear(linear, rank=self.rank)

CR (Column-Row / Interpolative Decomposition) Compressor. Decomposes linear layers into skeleton column bases and interpolative projections.

Args
-----=
model
PyTorch model.
rank
Rank parameter (number of skeleton columns).
max_error
Maximum relative Frobenius reconstruction error (default 0.20).
fallback_to_original
If True, keep original layer when error exceeds max_error (default True).
max_retries
Number of rank increase retries before fallback (default 2).
target_attention_only
If True, restrict compression to attention projections only.
cfg
Optional configuration object.
target_layers
Optional target layer filter.

Ancestors

Methods

def compress(self, model: nn.Module | None = None) ‑> tuple[torch.nn.modules.module.Module, dict[str, typing.Any]]
Expand source code
def compress(self, model: nn.Module | None = None) -> tuple[nn.Module, dict[str, Any]]:
    """Compress linear layers using interpolative decomposition."""
    if model is not None:
        self.model = model
        self._original_params = self._count_params(self.model)

    self.compressed_layers = []
    self.fallback_layers = []
    self.layer_errors = {}
    self.layer_ranks = {}

    total_orig_decomp_params = 0
    total_comp_decomp_params = 0

    for name, module in list(self.model.named_modules()):
        if not self._is_target_module(name, module):
            if self.target_attention_only:
                is_lin_like = (
                    isinstance(module, nn.Linear)
                    or "conv1d" in module.__class__.__name__.lower()
                    or hasattr(module, "base_layer")
                )
                if is_lin_like:
                    for p in module.parameters():
                        p.requires_grad = False
            continue

        target_mod = getattr(module, "base_layer", getattr(module, "linear", module))
        cls_name = module.__class__.__name__.lower()
        is_conv1d = "conv1d" in cls_name or hasattr(module, "nf") or hasattr(target_mod, "nf")
        (hasattr(module, "lora_A") and module.lora_A is not None) or hasattr(
            module, "base_layer"
        )

        raw_w = target_mod.weight.data
        orig_w = raw_w.t().float() if is_conv1d else raw_w.float()
        out_f, in_f = orig_w.shape
        max_rank = min(out_f, in_f)

        if max_rank < 16:
            continue

        # Parameter reduction check: r * (in_f + out_f) must be less than in_f * out_f
        target_r = min(self.rank, max_rank)
        if target_r * (in_f + out_f) >= 0.85 * (in_f * out_f):
            target_r = max(1, int(0.65 * (in_f * out_f) / (in_f + out_f)))

        cur_r = target_r
        best_cr_layer = None
        best_err = float("inf")
        best_r = cur_r
        norm_orig = torch.norm(orig_w, p="fro").clamp_min(1e-8)

        for _ in range(self.max_retries + 1):
            try:
                candidate = CRLinear.from_linear(target_mod, rank=cur_r)
                recon_w = candidate.get_weight().float()
                err = (torch.norm(orig_w - recon_w, p="fro") / norm_orig).item()
                if err < best_err:
                    best_err = err
                    best_cr_layer = candidate
                    best_r = cur_r

                if err <= self.max_error:
                    break

                next_r = min(int(cur_r * 1.5), max_rank - 1)
                if next_r <= cur_r or next_r * (in_f + out_f) >= 0.85 * (in_f * out_f):
                    break
                cur_r = next_r
            except Exception as ex:
                logger.debug("CR decomposition error on %s with rank %d: %s", name, cur_r, ex)
                break

        if best_err > self.max_error:
            logger.warning(
                "CRCompressor: Layer %s error %.4f exceeded max_error %.4f",
                name,
                best_err,
                self.max_error,
            )
            if self.fallback_to_original:
                self.fallback_layers.append(name)
                # Freeze fallback layers during recovery
                for p in module.parameters():
                    p.requires_grad = False
                continue

        if best_cr_layer is not None:
            new_layer = best_cr_layer
            compressed_layer = new_layer
            # Preserve existing LoRA adapter weights if the source module has them
            if (
                hasattr(module, "lora_A")
                and hasattr(module, "lora_B")
                and module.lora_A is not None
                and module.lora_B is not None
            ):
                src_lA = getattr(module.lora_A, "weight", module.lora_A)
                src_lB = getattr(module.lora_B, "weight", module.lora_B)
                if hasattr(new_layer, "lora_A") and new_layer.lora_A is not None:
                    if hasattr(src_lA, "data") and src_lA.shape == new_layer.lora_A.shape:
                        new_layer.lora_A.data.copy_(src_lA.data)
                if hasattr(new_layer, "lora_B") and new_layer.lora_B is not None:
                    if hasattr(src_lB, "data") and src_lB.shape == new_layer.lora_B.shape:
                        new_layer.lora_B.data.copy_(src_lB.data)

            if hasattr(module, "base_layer"):
                module.base_layer = compressed_layer  # put compressed layer inside wrapper
                # module itself (LoRALinear wrapper) remains the replacement
                new_module = module
            else:
                new_module = compressed_layer

            if hasattr(module, "base_layer"):
                for p_name, p in module.named_parameters():
                    if any(t in p_name.lower() for t in ("lora_a", "lora_b", "adapter")):
                        p.requires_grad = True
            else:
                if name == "":
                    self.model = new_module
                else:
                    replace_module(self.model, name, new_module)

            self.compressed_layers.append(name)
            self.layer_errors[name] = float(best_err)
            self.layer_ranks[name] = int(best_r)
            total_orig_decomp_params += in_f * out_f
            total_comp_decomp_params += best_r * (in_f + out_f)

    self._compressed_params = self._count_params(self.model)
    stats = self.get_compression_stats()
    avg_err = (
        (sum(self.layer_errors.values()) / len(self.layer_errors)) if self.layer_errors else 0.0
    )
    effective_param_ratio = (
        total_comp_decomp_params / total_orig_decomp_params
        if total_orig_decomp_params > 0
        else 1.0
    )
    stats.update(
        {
            "compressed_layers": self.compressed_layers,
            "compressed_layer_count": len(self.compressed_layers),
            "fallback_layers": self.fallback_layers,
            "fallback_layer_count": len(self.fallback_layers),
            "layer_errors": self.layer_errors,
            "layer_ranks": self.layer_ranks,
            "avg_reconstruction_error": avg_err,
            "max_error": self.max_error,
            "fallback_to_original": self.fallback_to_original,
            "cr_rank": self.rank,
            "target_attention_only": self.target_attention_only,
            "effective_parameter_ratio": round(effective_param_ratio, 4),
            "used_interpolative_decomposition_count": len(self.compressed_layers),
        }
    )
    logger.info(
        "Compressor cr finished: %d compressed, %d fallback, avg_err=%.4f, max_err_threshold=%.4f",
        len(self.compressed_layers),
        len(self.fallback_layers),
        avg_err,
        self.max_error,
    )
    return self.model, stats

Compress linear layers using interpolative decomposition.

def compress_linear(self, linear: nn.Linear | Any) ‑> CRLinear
Expand source code
def compress_linear(self, linear: nn.Linear | Any) -> CRLinear:
    """Compress a single linear layer into CRLinear."""
    return CRLinear.from_linear(linear, rank=self.rank)

Compress a single linear layer into CRLinear.

Inherited members

class CRLinear (in_features: int,
out_features: int,
rank: int = 64,
bias: bool = True,
dtype: torch.dtype | None = None,
device: torch.device | None = None)
Expand source code
class CRLinear(nn.Module):
    """
    Interpolative Decomposition / Column-Row Factorized Linear Layer.
    W ≈ C @ P where:
    - C in R^{out_features x rank}: column skeleton from W
    - P in R^{rank x in_features}: interpolative projection matrix

    Forward pass:
    y = ((x @ P^T) @ C^T) + bias
    """

    def __init__(
        self,
        in_features: int,
        out_features: int,
        rank: int = 64,
        bias: bool = True,
        dtype: torch.dtype | None = None,
        device: torch.device | None = None,
    ) -> None:
        super().__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.nf = out_features  # Conv1D compatibility
        self.rank = max(1, min(rank, in_features, out_features))

        self.C = nn.Parameter(torch.empty(out_features, self.rank, dtype=dtype, device=device))
        self.P = nn.Parameter(torch.empty(self.rank, in_features, dtype=dtype, device=device))

        bound = 1.0 / math.sqrt(max(1, in_features))
        nn.init.uniform_(self.C, -bound, bound)
        nn.init.uniform_(self.P, -bound, bound)

        if bias:
            self.bias = nn.Parameter(torch.zeros(out_features, dtype=dtype, device=device))
        else:
            self.register_parameter("bias", None)

    @property
    def weight(self) -> torch.Tensor:
        """Reconstructed effective weight matrix (out_features, in_features)."""
        return torch.matmul(self.C, self.P)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """
        Sub-quadratic forward execution:
        y = ((x @ P^T) @ C^T) + bias
        """
        orig_shape = x.shape
        x_flat = x.reshape(-1, self.in_features)

        x_p = F.linear(x_flat, self.P)
        out = F.linear(x_p, self.C, self.bias)

        return out.view(*orig_shape[:-1], self.out_features)

    def get_weight(self) -> torch.Tensor:
        """Materialize full reconstructed weight matrix."""
        return torch.matmul(self.C, self.P)

    @classmethod
    def from_linear(cls, linear: nn.Linear | Any, rank: int = 64) -> CRLinear:
        """
        Perform Column Interpolative Decomposition on linear or Conv1D layer weight matrix.
        """
        target_mod = getattr(linear, "base_layer", getattr(linear, "linear", linear))
        raw_w = target_mod.weight.data
        bias = target_mod.bias.data if getattr(target_mod, "bias", None) is not None else None
        cls_name = target_mod.__class__.__name__.lower()
        wrapper_name = linear.__class__.__name__.lower()

        is_conv1d = (
            "conv1d" in cls_name
            or "conv1d" in wrapper_name
            or hasattr(target_mod, "nf")
            or hasattr(linear, "nf")
        )
        if is_conv1d:
            # Conv1D weights are [in_features, out_features] -> transpose to [out_features, in_features]
            W = raw_w.t().contiguous()
        else:
            W = raw_w.contiguous()

        out_f, in_f = W.shape
        r = max(1, min(rank, out_f, in_f))

        cr_layer = cls(
            in_features=in_f,
            out_features=out_f,
            rank=r,
            bias=bias is not None,
            dtype=W.dtype,
            device=W.device,
        )

        W_float = W.float()

        # Step 1: Select skeleton columns via SciPy QRCP or robust PyTorch pivoted selection
        col_idx = None
        try:
            import scipy.linalg

            W_np = W_float.cpu().numpy()
            _, _, piv = scipy.linalg.qr(W_np, mode="economic", pivoting=True)
            col_idx = torch.tensor(piv[:r], dtype=torch.long, device=W.device)
        except Exception as e:
            logger.debug("CRLinear pivoted QR failed; using greedy column selection: %s", e)

        if col_idx is None:
            # Pure PyTorch QRCP-equivalent greedy pivoted column selection
            list(range(in_f))
            selected_cols = []
            norms = torch.sum(W_float**2, dim=0)

            for _ in range(r):
                best_col_idx = int(torch.argmax(norms).item())
                selected_cols.append(best_col_idx)
                # Zero out selected norm
                norms[best_col_idx] = -1.0
                if len(selected_cols) >= r:
                    break
                # Orthogonalize remaining columns against selected column
                v = W_float[:, best_col_idx : best_col_idx + 1]
                v_norm_sq = torch.sum(v**2).clamp_min(1e-12)
                proj = torch.matmul(v.T, W_float) / v_norm_sq
                W_float = W_float - torch.matmul(v, proj)
                norms = torch.sum(W_float**2, dim=0)
                for sc in selected_cols:
                    norms[sc] = -1.0

            col_idx = torch.tensor(selected_cols, dtype=torch.long, device=W.device)

        W_float_orig = W.float()
        C_data = W_float_orig[:, col_idx]  # (out_f, r)

        # Step 2: Tikhonov regularized pseudo-inverse: (C^T C + eps * I)^{-1} C^T
        CtC = torch.matmul(C_data.T, C_data)  # (r, r)
        diag_mean = torch.trace(CtC) / max(1, r)
        eps = max(1e-4 * diag_mean.item(), 1e-4)
        reg_eye = eps * torch.eye(r, device=C_data.device, dtype=C_data.dtype)
        try:
            C_pinv = torch.linalg.solve(CtC + reg_eye, C_data.T)  # (r, out_f)
        except Exception:
            C_pinv = torch.linalg.pinv(C_data, rcond=1e-4)

        P_data = torch.matmul(C_pinv, W_float_orig)  # (r, in_f)

        # Step 3: Symmetrically normalize factor magnitudes to ensure FP16 stability
        norm_C = torch.norm(C_data, p="fro").clamp_min(1e-8)
        norm_P = torch.norm(P_data, p="fro").clamp_min(1e-8)
        scale = torch.sqrt(norm_C / norm_P)
        C_data = C_data / scale
        P_data = P_data * scale

        cr_layer.C.data.copy_(C_data.to(dtype=W.dtype))
        cr_layer.P.data.copy_(P_data.to(dtype=W.dtype))

        if bias is not None and cr_layer.bias is not None:
            cr_layer.bias.data.copy_(bias)

        return cr_layer

Interpolative Decomposition / Column-Row Factorized Linear Layer. W ≈ C @ P where: - C in R^{out_features x rank}: column skeleton from W - P in R^{rank x in_features}: interpolative projection matrix

Forward pass: y = ((x @ P^T) @ C^T) + bias

Initialize internal Module state, shared by both nn.Module and ScriptModule.

Ancestors

  • torch.nn.modules.module.Module

Static methods

def from_linear(linear: nn.Linear | Any, rank: int = 64) ‑> CRLinear

Perform Column Interpolative Decomposition on linear or Conv1D layer weight matrix.

Instance variables

prop weight : torch.Tensor
Expand source code
@property
def weight(self) -> torch.Tensor:
    """Reconstructed effective weight matrix (out_features, in_features)."""
    return torch.matmul(self.C, self.P)

Reconstructed effective weight matrix (out_features, in_features).

Methods

def forward(self, x: torch.Tensor) ‑> torch.Tensor
Expand source code
def forward(self, x: torch.Tensor) -> torch.Tensor:
    """
    Sub-quadratic forward execution:
    y = ((x @ P^T) @ C^T) + bias
    """
    orig_shape = x.shape
    x_flat = x.reshape(-1, self.in_features)

    x_p = F.linear(x_flat, self.P)
    out = F.linear(x_p, self.C, self.bias)

    return out.view(*orig_shape[:-1], self.out_features)

Sub-quadratic forward execution: y = ((x @ P^T) @ C^T) + bias

def get_weight(self) ‑> torch.Tensor
Expand source code
def get_weight(self) -> torch.Tensor:
    """Materialize full reconstructed weight matrix."""
    return torch.matmul(self.C, self.P)

Materialize full reconstructed weight matrix.