Module neuralops.compression_engine.lillama_compressor
LiLLaMA: Local Distillation via Activation Cross-Covariance SVD. Registered as 'lillama_class', 'lillama_compressor_class', 'lillama_factory' and 'lillama' / 'lillama_compressor' as factory aliases.
Formulation -----= C_YX = (1/N) Y^T X (cross-covariance) C_YX ≈ U_r Σ_r V_r^T (truncated SVD) W_distill = U_r Σ_r V_r^T (distilled weight)
Functions
def lillama_factory(model: Any, comp_config: Any = None, **kwargs: Any) ‑> Any-
Expand source code
@COMPRESSOR_REGISTRY.register("lillama") @COMPRESSOR_REGISTRY.register("lillama_compressor") @COMPRESSOR_REGISTRY.register("lillama_factory") def lillama_factory( model: Any, comp_config: Any = None, _depth: int = 0, **kwargs: Any, ) -> Any: """Factory function for LiLLaMA compressor.""" if _depth > 4: logger.warning("lillama_factory: recursion depth exceeded.") return model rank = _cfg_get( comp_config, "lillama_rank", _cfg_get(comp_config, "rank", kwargs.pop("rank", 32)), ) max_error = _cfg_get(comp_config, "max_error", kwargs.pop("max_error", 0.20)) fallback_to_original = _cfg_get( comp_config, "fallback_to_original", kwargs.pop("fallback_to_original", True), ) skip_attention = _cfg_get(comp_config, "skip_attention", kwargs.pop("skip_attention", False)) target_attention_only = _cfg_get( comp_config, "target_attention_only", kwargs.pop("target_attention_only", False), ) skip_filter = _cfg_get( comp_config, "skip_should_compress_filter", kwargs.pop("skip_should_compress_filter", False), ) min_layer_dim = _cfg_get(comp_config, "min_layer_dim", kwargs.pop("min_layer_dim", 8)) tokenizer = ( kwargs.pop("tokenizer", None) or getattr(model, "tokenizer", None) or _cfg_get(comp_config, "tokenizer", None) ) return_stats = kwargs.pop("return_stats", kwargs.pop("return_tuple", False)) compressor = LiLLaMACompressor( model, tokenizer=tokenizer, rank=rank, max_error=max_error, fallback_to_original=fallback_to_original, skip_attention=skip_attention, target_attention_only=target_attention_only, skip_should_compress_filter=skip_filter, min_layer_dim=min_layer_dim, comp_config=comp_config, **kwargs, ) compressed_model, stats = compressor.compress() if return_stats: return compressed_model, stats return compressed_modelFactory function for LiLLaMA compressor.
Classes
class LiLLaMADistiller (model: nn.Module,
tokenizer: Any | None = None,
rank: int = 32,
calibration_texts: list[str] | None = None,
calibration_loader: Any | None = None,
device: str | torch.device | None = None,
max_error: float = 0.2,
fallback_to_original: bool = True,
skip_attention: bool = False,
target_attention_only: bool = False,
target_modules: list[str] | None = None,
min_layer_dim: int = 8,
skip_should_compress_filter: bool = False,
max_calibration_batches: int = 8,
**kwargs: Any)-
Expand source code
@COMPRESSOR_REGISTRY.register("lillama_class") @COMPRESSOR_REGISTRY.register("lillama_compressor_class") @COMPRESSOR_REGISTRY.register("lillama_distiller_class") class LiLLaMADistiller(BaseCompressor): # LL-FIX-01: this is a local distillation / weight-rewriter, not standard factorized attention. is_custom_attention: bool = False category: str = "weight_rewriting" def __init__( self, model: nn.Module, tokenizer: Any | None = None, rank: int = 32, calibration_texts: list[str] | None = None, calibration_loader: Any | None = None, device: str | torch.device | None = None, max_error: float = 0.20, fallback_to_original: bool = True, skip_attention: bool = False, target_attention_only: bool = False, target_modules: list[str] | None = None, min_layer_dim: int = 8, skip_should_compress_filter: bool = False, max_calibration_batches: int = 8, **kwargs: Any, ) -> None: super().__init__(model, name="lillama", **kwargs) cfg = kwargs.get("comp_config", kwargs.get("cfg")) if cfg is not None: rank = _cfg_get(cfg, "lillama_rank", _cfg_get(cfg, "rank", rank)) max_error = _cfg_get(cfg, "max_error", max_error) fallback_to_original = _cfg_get(cfg, "fallback_to_original", fallback_to_original) skip_attention = _cfg_get(cfg, "skip_attention", skip_attention) target_attention_only = _cfg_get(cfg, "target_attention_only", target_attention_only) min_layer_dim = _cfg_get(cfg, "min_layer_dim", min_layer_dim) skip_should_compress_filter = _cfg_get( cfg, "skip_should_compress_filter", skip_should_compress_filter, ) max_calibration_batches = _cfg_get( cfg, "max_calibration_batches", max_calibration_batches ) # LL-FIX-11: tokenizer resolution. self.tokenizer = ( tokenizer or getattr(model, "tokenizer", None) or getattr(getattr(model, "config", None), "tokenizer", None) ) self.rank = max(1, int(rank)) self.calibration_texts = calibration_texts self.calibration_loader = calibration_loader self.max_error = float(max_error) self.fallback_to_original = bool(fallback_to_original) self.skip_attention = bool(skip_attention) self.target_attention_only = bool(target_attention_only) self.target_modules = target_modules self.min_layer_dim = max(1, int(min_layer_dim)) self.skip_should_compress_filter = bool(skip_should_compress_filter) self.max_calibration_batches = max(1, int(max_calibration_batches)) # LL-FIX-03: safe device. if device is not None: self.target_device = torch.device(device) else: self.target_device = _get_device(model) # LL-FIX-15: initialize all lists in __init__. self.compressed_layers: list[str] = [] self.fallback_layers: list[str] = [] self.layer_errors: dict[str, float] = {} self.layer_details: dict[str, Any] = {} self.stats: dict[str, Any] = {} self._compressed_params: int = 0 # ------------------------------------------------------------------ # Target check (LL-FIX-06) # ------------------------------------------------------------------ def _is_target_module(self, name: str, module: nn.Module, _debug: bool = False) -> bool: name_lower = name.lower() if any(part in name.split(".") for part in ("lora_A", "lora_B", "base_layer")): if _debug: logger.info(f" [skip] {name}: lora/base_layer part") return False # Skip already-compressed. cls_name = module.__class__.__name__ if cls_name in ( "LSCPrunedLoRALinear", "DynamicSVDLinear", "SVDLinear", "FactorisedLinear", "LowRankPlusSparseLinear", ): if _debug: logger.info(f" [skip] {name}: already compressed ({cls_name})") return False if any( skip in name_lower for skip in [ "classifier", "score", "lm_head", "pooler", "embedding", "router", "gate", ] ): if _debug: logger.info(f" [skip] {name}: skip keyword") return False target_mod = getattr(module, "base_layer", module) cls_lower = target_mod.__class__.__name__.lower() is_linear_like = ( isinstance(target_mod, nn.Linear) or "conv1d" in cls_lower or hasattr(target_mod, "nf") ) if not is_linear_like: if _debug: logger.info(f" [skip] {name}: not linear-like") return False # LL-FIX-09: allow bypassing framework filter. if not self.skip_should_compress_filter: try: if not should_compress_linear(self.model, name): if _debug: logger.info(f" [skip] {name}: should_compress_linear=False") return False except Exception as e: if _debug: logger.info(f" [skip] {name}: should_compress_linear raised {e}") return False # LL-FIX-10: filter tiny layers. w = getattr(target_mod, "weight", None) if w is None: if _debug: logger.info(f" [skip] {name}: weight None") return False if min(w.shape[0], w.shape[1]) < self.min_layer_dim: if _debug: logger.info(f" [skip] {name}: dim too small {tuple(w.shape)}") return False if self.target_attention_only: is_attn = any(k in name_lower for k in _ATTN_NAMES) if not is_attn: if _debug: logger.info(f" [skip] {name}: not attention") return False if self.skip_attention: attn_keywords = { "query", "key", "value", "q_lin", "k_lin", "v_lin", "q_proj", "k_proj", "v_proj", "out_lin", "out_proj", "dense", "o_proj", "c_attn", } last = name.split(".")[-1].lower() if last in attn_keywords or any(k in name_lower for k in ("self_attn", "attn.c_attn")): if _debug: logger.info(f" [skip] {name}: skip_attention") return False if self.target_modules is not None: ok = any(t in name for t in self.target_modules) if not ok and _debug: logger.info(f" [skip] {name}: not in target_modules") return ok if _debug: logger.info(f" [TARGET] {name}: shape={tuple(w.shape)}") return True # ------------------------------------------------------------------ # IO activation collection # ------------------------------------------------------------------ def _collect_io_activations( self, ) -> dict[str, tuple[torch.Tensor, torch.Tensor]]: """Collect input/output activations across target layers.""" self.model.eval() activations: dict[str, dict[str, list[torch.Tensor]]] = {} hooks = [] def make_hook(layer_name: str): def hook(module: nn.Module, inputs: tuple[Any, ...], output: Any): inp = inputs[0].detach() if inputs and isinstance(inputs[0], torch.Tensor) else None if isinstance(output, torch.Tensor): out = output.detach() elif ( isinstance(output, (tuple, list)) and output and isinstance(output[0], torch.Tensor) ): out = output[0].detach() else: out = None if inp is not None and out is not None: if layer_name not in activations: activations[layer_name] = {"in": [], "out": []} activations[layer_name]["in"].append(inp.cpu()) activations[layer_name]["out"].append(out.cpu()) return hook target_list = [ (name, module) for name, module in self.model.named_modules() if self._is_target_module(name, module) ] if not target_list: return {} for name, module in target_list: hooks.append(module.register_forward_hook(make_hook(name))) try: calib_inputs = get_calibration_tensors( self.model, tokenizer=self.tokenizer, calibration_loader=self.calibration_loader, calibration_texts=self.calibration_texts, device=self.target_device, ) batches = self._normalize_batches(calib_inputs) n_batches = min(len(batches), self.max_calibration_batches) with torch.no_grad(): for i in range(n_batches): batch = batches[i] try: if isinstance(batch, dict): batch = { k: (v.to(self.target_device) if isinstance(v, torch.Tensor) else v) for k, v in batch.items() } self.model(**batch) elif isinstance(batch, torch.Tensor): self.model(batch.to(self.target_device)) elif isinstance(batch, (list, tuple)): batch = [ (v.to(self.target_device) if isinstance(v, torch.Tensor) else v) for v in batch ] self.model(*batch) except Exception as e: logger.debug("Calibration batch %d failed: %s", i, e) except Exception as e: logger.warning("LiLLaMA: calibration forward raised: %s", e) finally: for h in hooks: h.remove() aggregated: dict[str, tuple[torch.Tensor, torch.Tensor]] = {} MAX_ACTIVATION_SAMPLES = 512 for name, data in activations.items(): if data["in"] and data["out"]: try: cat_in = torch.cat(data["in"], dim=0) cat_out = torch.cat(data["out"], dim=0) if cat_in.size(0) > MAX_ACTIVATION_SAMPLES: cat_in = cat_in[:MAX_ACTIVATION_SAMPLES] cat_out = cat_out[:MAX_ACTIVATION_SAMPLES] aggregated[name] = (cat_in, cat_out) except Exception as e: logger.debug("Activation concat failed for %s: %s", name, e) return aggregated @staticmethod def _normalize_batches(calib_inputs: Any) -> list[Any]: if calib_inputs is None: return [] if isinstance(calib_inputs, torch.Tensor): return [calib_inputs] if isinstance(calib_inputs, dict): return [calib_inputs] if isinstance(calib_inputs, (list, tuple)): if len(calib_inputs) == 0: return [] first = calib_inputs[0] if isinstance(first, (dict, torch.Tensor)): return list(calib_inputs) return [calib_inputs] return [calib_inputs] # ------------------------------------------------------------------ # SVD factorization helper # ------------------------------------------------------------------ def _factorize( self, W: torch.Tensor, rank: int, ) -> tuple[torch.Tensor, int]: """Return (W_recon, rank_used).""" try: U, S, Vh = torch.linalg.svd(W, full_matrices=False) total_energy = (S**2).sum().clamp(min=1e-12) cum_ratio = torch.cumsum(S**2, 0) / total_energy r = min(rank, len(S)) cum_e = cum_ratio[r - 1].item() if r > 0 else 0.0 if cum_e < 0.95: idx = torch.where(cum_ratio >= 0.95)[0] if len(idx) > 0: r = min(int(idx[0].item()) + 1, len(S)) W_recon = (U[:, :r] * S[:r].unsqueeze(0)) @ Vh[:r, :] return W_recon, r except Exception as e: logger.debug("LiLLaMA SVD factorization failed: %s", e) return W, 0 # ------------------------------------------------------------------ # Main compress # ------------------------------------------------------------------ def compress(self, **kwargs: Any) -> tuple[nn.Module, dict[str, Any]]: # LL-FIX-08: idempotence. if getattr(self.model, "_lillama_compressed", False): logger.warning("LiLLaMA: model already compressed; skipping.") try: return self.model, self.get_compression_stats() except Exception: return self.model, {} self.compressed_layers = [] self.fallback_layers = [] self.layer_errors = {} self.layer_details = {} activations_map = self._collect_io_activations() logger.info("LiLLaMA: %d target activation sets collected.", len(activations_map)) # LL-FIX-06: diagnostic if no targets. target_list = [ (name, module) for name, module in self.model.named_modules() if self._is_target_module(name, module) ] if not target_list: logger.warning("LiLLaMA: no compressible layers found. Running diagnostic...") n_scanned = 0 for name, module in self.model.named_modules(): n_scanned += 1 self._is_target_module(name, module, _debug=True) logger.warning("LiLLaMA diagnostic: %d modules scanned, 0 targets.", n_scanned) try: return self.model, self.get_compression_stats() except Exception: return self.model, {} logger.info("LiLLaMA: %d target layers identified.", len(target_list)) total_orig_params = 0 total_comp_params = 0 # Deduplicated parameter accounting. seen_param_ids: set = set() count_param_ids: set = set() for p in self.model.parameters(): if id(p) not in seen_param_ids: seen_param_ids.add(id(p)) total_orig_params += p.numel() def _count_params_of(mod: nn.Module): nonlocal total_comp_params for p in mod.parameters(recurse=False): if id(p) not in count_param_ids: count_param_ids.add(id(p)) total_comp_params += p.numel() for name, module in target_list: target_layer = getattr(module, "base_layer", module) raw_w = target_layer.weight.data is_conv1d = _is_conv1d(target_layer) device = raw_w.device dtype = target_layer.weight.dtype W = raw_w.t().contiguous().clone().float() if is_conv1d else raw_w.clone().float() d_out, d_in = W.shape used_cross_cov = False r_used = self.rank # LL-FIX-04: initialize W_new to W (safe default). W_new = W if name in activations_map: inp, out = activations_map[name] try: inp_flat = inp.reshape(-1, inp.shape[-1]).float() out_flat = out.reshape(-1, out.shape[-1]).float() if inp_flat.shape[-1] == d_in and out_flat.shape[-1] == d_out: C = torch.matmul(out_flat.t().contiguous(), inp_flat) / max( len(inp_flat), 1 ) W_new, r_used = self._factorize(C, self.rank) used_cross_cov = True else: W_new, r_used = self._factorize(W, self.rank) except Exception as e: logger.debug("Cross-covariance factorization failed for %s: %s", name, e) W_new, r_used = self._factorize(W, self.rank) else: W_new, r_used = self._factorize(W, self.rank) norm_orig = torch.norm(W, p="fro") rel_err = (torch.norm(W - W_new, p="fro") / (norm_orig + 1e-8)).item() self.layer_errors[name] = rel_err if rel_err > self.max_error: logger.warning( "LiLLaMA: err %.4f on %s > %.4f. %s", rel_err, name, self.max_error, ( "Keeping original." if self.fallback_to_original else "Proceeding with distilled weights." ), ) if self.fallback_to_original: self.fallback_layers.append(name) for p in module.parameters(recurse=False): p.requires_grad = False _count_params_of(module) continue # Shape integrity check. final_w = W_new.t().contiguous() if is_conv1d else W_new if final_w.shape != target_layer.weight.shape: logger.warning( "LiLLaMA: shape mismatch for %s: expected %s, got %s; skipping.", name, tuple(target_layer.weight.shape), tuple(final_w.shape), ) _count_params_of(module) continue target_layer.weight.data.copy_(final_w.to(dtype=dtype, device=device)) target_layer.weight.requires_grad = False if getattr(target_layer, "bias", None) is not None: target_layer.bias.requires_grad = False # Preserve LoRA adapter trainability. if hasattr(module, "lora_A") and hasattr(module, "lora_B"): for attr in ("lora_A", "lora_B"): p = getattr(module, attr, None) if p is None: continue if hasattr(p, "weight"): p.weight.requires_grad = True elif isinstance(p, nn.Parameter): p.requires_grad = True self.compressed_layers.append(name) _count_params_of(module) self.layer_details[name] = { "rank": int(r_used), "rel_error": float(rel_err), "used_cross_cov": bool(used_cross_cov), "shape": tuple(final_w.shape), } # LL-FIX-07: forward sanity check. forward_check_success = True try: with torch.no_grad(): dummy = torch.randint(0, 100, (1, 8), device=self.target_device, dtype=torch.long) try: self.model(input_ids=dummy, attention_mask=torch.ones_like(dummy)) except Exception: try: self.model(dummy) except Exception as e2: logger.warning("LiLLaMA: forward check failed: %s", e2) forward_check_success = False except Exception as e: logger.warning("LiLLaMA: forward check raised: %s", e) forward_check_success = False try: self._compressed_params = self._count_params(self.model) except Exception: self._compressed_params = sum(p.numel() for p in self.model.parameters()) # LL-FIX-04: avg error only over compressed layers. compressed_errs = [ self.layer_errors[n] for n in self.compressed_layers if n in self.layer_errors ] avg_err = sum(compressed_errs) / len(compressed_errs) if compressed_errs else 0.0 eff_ratio = total_comp_params / max(1, total_orig_params) try: stats = self.get_compression_stats() except Exception: stats = {} stats.update( { "distilled_layers": len(self.compressed_layers), "compressed_layers": self.compressed_layers, "fallback_layers": self.fallback_layers, "num_compressed": len(self.compressed_layers), "num_fallback": len(self.fallback_layers), "rank": self.rank, "max_error": self.max_error, "fallback_to_original": self.fallback_to_original, "avg_reconstruction_error": avg_err, "layer_errors": self.layer_errors, "layer_details": self.layer_details, "effective_parameter_ratio": eff_ratio, "forward_check_success": forward_check_success, "method": "lillama_cross_covariance_svd", # LL-FIX-12: honest note. "note": ( "LiLLaMA rewrites weights in-place with the distilled reconstruction. " "The dense weight keeps its full (out, in) shape; framework-level " "compression_rate reads 1.0. Use effective_parameter_ratio for the " "theoretical storage metric." ), } ) self.stats = stats # LL-FIX-13: write stats onto model. try: if not hasattr(self.model, "compression_stats") or self.model.compression_stats is None: self.model.compression_stats = {} self.model.compression_stats.update( { "num_compressed": len(self.compressed_layers), "num_fallback": len(self.fallback_layers), "compression_method": "lillama_cross_covariance_svd", "effective_parameter_ratio": eff_ratio, "avg_reconstruction_error": avg_err, } ) except Exception: pass self.model._lillama_compressed = True logger.info( "LiLLaMA: %d distilled, %d fallback. Avg err %.4f, eff_param_ratio %.2fx.", len(self.compressed_layers), len(self.fallback_layers), avg_err, eff_ratio, ) return self.model, statsBase class for all compressors. Not a nn.Module to avoid parameter storage.
Ancestors
Class variables
var category : str-
The type of the None singleton.
var is_custom_attention : bool-
The type of the None singleton.
Methods
def compress(self, **kwargs: Any) ‑> tuple[torch.nn.modules.module.Module, dict[str, typing.Any]]-
Expand source code
def compress(self, **kwargs: Any) -> tuple[nn.Module, dict[str, Any]]: # LL-FIX-08: idempotence. if getattr(self.model, "_lillama_compressed", False): logger.warning("LiLLaMA: model already compressed; skipping.") try: return self.model, self.get_compression_stats() except Exception: return self.model, {} self.compressed_layers = [] self.fallback_layers = [] self.layer_errors = {} self.layer_details = {} activations_map = self._collect_io_activations() logger.info("LiLLaMA: %d target activation sets collected.", len(activations_map)) # LL-FIX-06: diagnostic if no targets. target_list = [ (name, module) for name, module in self.model.named_modules() if self._is_target_module(name, module) ] if not target_list: logger.warning("LiLLaMA: no compressible layers found. Running diagnostic...") n_scanned = 0 for name, module in self.model.named_modules(): n_scanned += 1 self._is_target_module(name, module, _debug=True) logger.warning("LiLLaMA diagnostic: %d modules scanned, 0 targets.", n_scanned) try: return self.model, self.get_compression_stats() except Exception: return self.model, {} logger.info("LiLLaMA: %d target layers identified.", len(target_list)) total_orig_params = 0 total_comp_params = 0 # Deduplicated parameter accounting. seen_param_ids: set = set() count_param_ids: set = set() for p in self.model.parameters(): if id(p) not in seen_param_ids: seen_param_ids.add(id(p)) total_orig_params += p.numel() def _count_params_of(mod: nn.Module): nonlocal total_comp_params for p in mod.parameters(recurse=False): if id(p) not in count_param_ids: count_param_ids.add(id(p)) total_comp_params += p.numel() for name, module in target_list: target_layer = getattr(module, "base_layer", module) raw_w = target_layer.weight.data is_conv1d = _is_conv1d(target_layer) device = raw_w.device dtype = target_layer.weight.dtype W = raw_w.t().contiguous().clone().float() if is_conv1d else raw_w.clone().float() d_out, d_in = W.shape used_cross_cov = False r_used = self.rank # LL-FIX-04: initialize W_new to W (safe default). W_new = W if name in activations_map: inp, out = activations_map[name] try: inp_flat = inp.reshape(-1, inp.shape[-1]).float() out_flat = out.reshape(-1, out.shape[-1]).float() if inp_flat.shape[-1] == d_in and out_flat.shape[-1] == d_out: C = torch.matmul(out_flat.t().contiguous(), inp_flat) / max( len(inp_flat), 1 ) W_new, r_used = self._factorize(C, self.rank) used_cross_cov = True else: W_new, r_used = self._factorize(W, self.rank) except Exception as e: logger.debug("Cross-covariance factorization failed for %s: %s", name, e) W_new, r_used = self._factorize(W, self.rank) else: W_new, r_used = self._factorize(W, self.rank) norm_orig = torch.norm(W, p="fro") rel_err = (torch.norm(W - W_new, p="fro") / (norm_orig + 1e-8)).item() self.layer_errors[name] = rel_err if rel_err > self.max_error: logger.warning( "LiLLaMA: err %.4f on %s > %.4f. %s", rel_err, name, self.max_error, ( "Keeping original." if self.fallback_to_original else "Proceeding with distilled weights." ), ) if self.fallback_to_original: self.fallback_layers.append(name) for p in module.parameters(recurse=False): p.requires_grad = False _count_params_of(module) continue # Shape integrity check. final_w = W_new.t().contiguous() if is_conv1d else W_new if final_w.shape != target_layer.weight.shape: logger.warning( "LiLLaMA: shape mismatch for %s: expected %s, got %s; skipping.", name, tuple(target_layer.weight.shape), tuple(final_w.shape), ) _count_params_of(module) continue target_layer.weight.data.copy_(final_w.to(dtype=dtype, device=device)) target_layer.weight.requires_grad = False if getattr(target_layer, "bias", None) is not None: target_layer.bias.requires_grad = False # Preserve LoRA adapter trainability. if hasattr(module, "lora_A") and hasattr(module, "lora_B"): for attr in ("lora_A", "lora_B"): p = getattr(module, attr, None) if p is None: continue if hasattr(p, "weight"): p.weight.requires_grad = True elif isinstance(p, nn.Parameter): p.requires_grad = True self.compressed_layers.append(name) _count_params_of(module) self.layer_details[name] = { "rank": int(r_used), "rel_error": float(rel_err), "used_cross_cov": bool(used_cross_cov), "shape": tuple(final_w.shape), } # LL-FIX-07: forward sanity check. forward_check_success = True try: with torch.no_grad(): dummy = torch.randint(0, 100, (1, 8), device=self.target_device, dtype=torch.long) try: self.model(input_ids=dummy, attention_mask=torch.ones_like(dummy)) except Exception: try: self.model(dummy) except Exception as e2: logger.warning("LiLLaMA: forward check failed: %s", e2) forward_check_success = False except Exception as e: logger.warning("LiLLaMA: forward check raised: %s", e) forward_check_success = False try: self._compressed_params = self._count_params(self.model) except Exception: self._compressed_params = sum(p.numel() for p in self.model.parameters()) # LL-FIX-04: avg error only over compressed layers. compressed_errs = [ self.layer_errors[n] for n in self.compressed_layers if n in self.layer_errors ] avg_err = sum(compressed_errs) / len(compressed_errs) if compressed_errs else 0.0 eff_ratio = total_comp_params / max(1, total_orig_params) try: stats = self.get_compression_stats() except Exception: stats = {} stats.update( { "distilled_layers": len(self.compressed_layers), "compressed_layers": self.compressed_layers, "fallback_layers": self.fallback_layers, "num_compressed": len(self.compressed_layers), "num_fallback": len(self.fallback_layers), "rank": self.rank, "max_error": self.max_error, "fallback_to_original": self.fallback_to_original, "avg_reconstruction_error": avg_err, "layer_errors": self.layer_errors, "layer_details": self.layer_details, "effective_parameter_ratio": eff_ratio, "forward_check_success": forward_check_success, "method": "lillama_cross_covariance_svd", # LL-FIX-12: honest note. "note": ( "LiLLaMA rewrites weights in-place with the distilled reconstruction. " "The dense weight keeps its full (out, in) shape; framework-level " "compression_rate reads 1.0. Use effective_parameter_ratio for the " "theoretical storage metric." ), } ) self.stats = stats # LL-FIX-13: write stats onto model. try: if not hasattr(self.model, "compression_stats") or self.model.compression_stats is None: self.model.compression_stats = {} self.model.compression_stats.update( { "num_compressed": len(self.compressed_layers), "num_fallback": len(self.fallback_layers), "compression_method": "lillama_cross_covariance_svd", "effective_parameter_ratio": eff_ratio, "avg_reconstruction_error": avg_err, } ) except Exception: pass self.model._lillama_compressed = True logger.info( "LiLLaMA: %d distilled, %d fallback. Avg err %.4f, eff_param_ratio %.2fx.", len(self.compressed_layers), len(self.fallback_layers), avg_err, eff_ratio, ) return self.model, statsReturn (compressed_model, statistics).
class LiLLaMACompressor (model: nn.Module,
tokenizer: Any | None = None,
rank: int = 32,
calibration_texts: list[str] | None = None,
calibration_loader: Any | None = None,
device: str | torch.device | None = None,
max_error: float = 0.2,
fallback_to_original: bool = True,
skip_attention: bool = False,
target_attention_only: bool = False,
target_modules: list[str] | None = None,
min_layer_dim: int = 8,
skip_should_compress_filter: bool = False,
max_calibration_batches: int = 8,
**kwargs: Any)-
Expand source code
@COMPRESSOR_REGISTRY.register("lillama_class") @COMPRESSOR_REGISTRY.register("lillama_compressor_class") @COMPRESSOR_REGISTRY.register("lillama_distiller_class") class LiLLaMADistiller(BaseCompressor): # LL-FIX-01: this is a local distillation / weight-rewriter, not standard factorized attention. is_custom_attention: bool = False category: str = "weight_rewriting" def __init__( self, model: nn.Module, tokenizer: Any | None = None, rank: int = 32, calibration_texts: list[str] | None = None, calibration_loader: Any | None = None, device: str | torch.device | None = None, max_error: float = 0.20, fallback_to_original: bool = True, skip_attention: bool = False, target_attention_only: bool = False, target_modules: list[str] | None = None, min_layer_dim: int = 8, skip_should_compress_filter: bool = False, max_calibration_batches: int = 8, **kwargs: Any, ) -> None: super().__init__(model, name="lillama", **kwargs) cfg = kwargs.get("comp_config", kwargs.get("cfg")) if cfg is not None: rank = _cfg_get(cfg, "lillama_rank", _cfg_get(cfg, "rank", rank)) max_error = _cfg_get(cfg, "max_error", max_error) fallback_to_original = _cfg_get(cfg, "fallback_to_original", fallback_to_original) skip_attention = _cfg_get(cfg, "skip_attention", skip_attention) target_attention_only = _cfg_get(cfg, "target_attention_only", target_attention_only) min_layer_dim = _cfg_get(cfg, "min_layer_dim", min_layer_dim) skip_should_compress_filter = _cfg_get( cfg, "skip_should_compress_filter", skip_should_compress_filter, ) max_calibration_batches = _cfg_get( cfg, "max_calibration_batches", max_calibration_batches ) # LL-FIX-11: tokenizer resolution. self.tokenizer = ( tokenizer or getattr(model, "tokenizer", None) or getattr(getattr(model, "config", None), "tokenizer", None) ) self.rank = max(1, int(rank)) self.calibration_texts = calibration_texts self.calibration_loader = calibration_loader self.max_error = float(max_error) self.fallback_to_original = bool(fallback_to_original) self.skip_attention = bool(skip_attention) self.target_attention_only = bool(target_attention_only) self.target_modules = target_modules self.min_layer_dim = max(1, int(min_layer_dim)) self.skip_should_compress_filter = bool(skip_should_compress_filter) self.max_calibration_batches = max(1, int(max_calibration_batches)) # LL-FIX-03: safe device. if device is not None: self.target_device = torch.device(device) else: self.target_device = _get_device(model) # LL-FIX-15: initialize all lists in __init__. self.compressed_layers: list[str] = [] self.fallback_layers: list[str] = [] self.layer_errors: dict[str, float] = {} self.layer_details: dict[str, Any] = {} self.stats: dict[str, Any] = {} self._compressed_params: int = 0 # ------------------------------------------------------------------ # Target check (LL-FIX-06) # ------------------------------------------------------------------ def _is_target_module(self, name: str, module: nn.Module, _debug: bool = False) -> bool: name_lower = name.lower() if any(part in name.split(".") for part in ("lora_A", "lora_B", "base_layer")): if _debug: logger.info(f" [skip] {name}: lora/base_layer part") return False # Skip already-compressed. cls_name = module.__class__.__name__ if cls_name in ( "LSCPrunedLoRALinear", "DynamicSVDLinear", "SVDLinear", "FactorisedLinear", "LowRankPlusSparseLinear", ): if _debug: logger.info(f" [skip] {name}: already compressed ({cls_name})") return False if any( skip in name_lower for skip in [ "classifier", "score", "lm_head", "pooler", "embedding", "router", "gate", ] ): if _debug: logger.info(f" [skip] {name}: skip keyword") return False target_mod = getattr(module, "base_layer", module) cls_lower = target_mod.__class__.__name__.lower() is_linear_like = ( isinstance(target_mod, nn.Linear) or "conv1d" in cls_lower or hasattr(target_mod, "nf") ) if not is_linear_like: if _debug: logger.info(f" [skip] {name}: not linear-like") return False # LL-FIX-09: allow bypassing framework filter. if not self.skip_should_compress_filter: try: if not should_compress_linear(self.model, name): if _debug: logger.info(f" [skip] {name}: should_compress_linear=False") return False except Exception as e: if _debug: logger.info(f" [skip] {name}: should_compress_linear raised {e}") return False # LL-FIX-10: filter tiny layers. w = getattr(target_mod, "weight", None) if w is None: if _debug: logger.info(f" [skip] {name}: weight None") return False if min(w.shape[0], w.shape[1]) < self.min_layer_dim: if _debug: logger.info(f" [skip] {name}: dim too small {tuple(w.shape)}") return False if self.target_attention_only: is_attn = any(k in name_lower for k in _ATTN_NAMES) if not is_attn: if _debug: logger.info(f" [skip] {name}: not attention") return False if self.skip_attention: attn_keywords = { "query", "key", "value", "q_lin", "k_lin", "v_lin", "q_proj", "k_proj", "v_proj", "out_lin", "out_proj", "dense", "o_proj", "c_attn", } last = name.split(".")[-1].lower() if last in attn_keywords or any(k in name_lower for k in ("self_attn", "attn.c_attn")): if _debug: logger.info(f" [skip] {name}: skip_attention") return False if self.target_modules is not None: ok = any(t in name for t in self.target_modules) if not ok and _debug: logger.info(f" [skip] {name}: not in target_modules") return ok if _debug: logger.info(f" [TARGET] {name}: shape={tuple(w.shape)}") return True # ------------------------------------------------------------------ # IO activation collection # ------------------------------------------------------------------ def _collect_io_activations( self, ) -> dict[str, tuple[torch.Tensor, torch.Tensor]]: """Collect input/output activations across target layers.""" self.model.eval() activations: dict[str, dict[str, list[torch.Tensor]]] = {} hooks = [] def make_hook(layer_name: str): def hook(module: nn.Module, inputs: tuple[Any, ...], output: Any): inp = inputs[0].detach() if inputs and isinstance(inputs[0], torch.Tensor) else None if isinstance(output, torch.Tensor): out = output.detach() elif ( isinstance(output, (tuple, list)) and output and isinstance(output[0], torch.Tensor) ): out = output[0].detach() else: out = None if inp is not None and out is not None: if layer_name not in activations: activations[layer_name] = {"in": [], "out": []} activations[layer_name]["in"].append(inp.cpu()) activations[layer_name]["out"].append(out.cpu()) return hook target_list = [ (name, module) for name, module in self.model.named_modules() if self._is_target_module(name, module) ] if not target_list: return {} for name, module in target_list: hooks.append(module.register_forward_hook(make_hook(name))) try: calib_inputs = get_calibration_tensors( self.model, tokenizer=self.tokenizer, calibration_loader=self.calibration_loader, calibration_texts=self.calibration_texts, device=self.target_device, ) batches = self._normalize_batches(calib_inputs) n_batches = min(len(batches), self.max_calibration_batches) with torch.no_grad(): for i in range(n_batches): batch = batches[i] try: if isinstance(batch, dict): batch = { k: (v.to(self.target_device) if isinstance(v, torch.Tensor) else v) for k, v in batch.items() } self.model(**batch) elif isinstance(batch, torch.Tensor): self.model(batch.to(self.target_device)) elif isinstance(batch, (list, tuple)): batch = [ (v.to(self.target_device) if isinstance(v, torch.Tensor) else v) for v in batch ] self.model(*batch) except Exception as e: logger.debug("Calibration batch %d failed: %s", i, e) except Exception as e: logger.warning("LiLLaMA: calibration forward raised: %s", e) finally: for h in hooks: h.remove() aggregated: dict[str, tuple[torch.Tensor, torch.Tensor]] = {} MAX_ACTIVATION_SAMPLES = 512 for name, data in activations.items(): if data["in"] and data["out"]: try: cat_in = torch.cat(data["in"], dim=0) cat_out = torch.cat(data["out"], dim=0) if cat_in.size(0) > MAX_ACTIVATION_SAMPLES: cat_in = cat_in[:MAX_ACTIVATION_SAMPLES] cat_out = cat_out[:MAX_ACTIVATION_SAMPLES] aggregated[name] = (cat_in, cat_out) except Exception as e: logger.debug("Activation concat failed for %s: %s", name, e) return aggregated @staticmethod def _normalize_batches(calib_inputs: Any) -> list[Any]: if calib_inputs is None: return [] if isinstance(calib_inputs, torch.Tensor): return [calib_inputs] if isinstance(calib_inputs, dict): return [calib_inputs] if isinstance(calib_inputs, (list, tuple)): if len(calib_inputs) == 0: return [] first = calib_inputs[0] if isinstance(first, (dict, torch.Tensor)): return list(calib_inputs) return [calib_inputs] return [calib_inputs] # ------------------------------------------------------------------ # SVD factorization helper # ------------------------------------------------------------------ def _factorize( self, W: torch.Tensor, rank: int, ) -> tuple[torch.Tensor, int]: """Return (W_recon, rank_used).""" try: U, S, Vh = torch.linalg.svd(W, full_matrices=False) total_energy = (S**2).sum().clamp(min=1e-12) cum_ratio = torch.cumsum(S**2, 0) / total_energy r = min(rank, len(S)) cum_e = cum_ratio[r - 1].item() if r > 0 else 0.0 if cum_e < 0.95: idx = torch.where(cum_ratio >= 0.95)[0] if len(idx) > 0: r = min(int(idx[0].item()) + 1, len(S)) W_recon = (U[:, :r] * S[:r].unsqueeze(0)) @ Vh[:r, :] return W_recon, r except Exception as e: logger.debug("LiLLaMA SVD factorization failed: %s", e) return W, 0 # ------------------------------------------------------------------ # Main compress # ------------------------------------------------------------------ def compress(self, **kwargs: Any) -> tuple[nn.Module, dict[str, Any]]: # LL-FIX-08: idempotence. if getattr(self.model, "_lillama_compressed", False): logger.warning("LiLLaMA: model already compressed; skipping.") try: return self.model, self.get_compression_stats() except Exception: return self.model, {} self.compressed_layers = [] self.fallback_layers = [] self.layer_errors = {} self.layer_details = {} activations_map = self._collect_io_activations() logger.info("LiLLaMA: %d target activation sets collected.", len(activations_map)) # LL-FIX-06: diagnostic if no targets. target_list = [ (name, module) for name, module in self.model.named_modules() if self._is_target_module(name, module) ] if not target_list: logger.warning("LiLLaMA: no compressible layers found. Running diagnostic...") n_scanned = 0 for name, module in self.model.named_modules(): n_scanned += 1 self._is_target_module(name, module, _debug=True) logger.warning("LiLLaMA diagnostic: %d modules scanned, 0 targets.", n_scanned) try: return self.model, self.get_compression_stats() except Exception: return self.model, {} logger.info("LiLLaMA: %d target layers identified.", len(target_list)) total_orig_params = 0 total_comp_params = 0 # Deduplicated parameter accounting. seen_param_ids: set = set() count_param_ids: set = set() for p in self.model.parameters(): if id(p) not in seen_param_ids: seen_param_ids.add(id(p)) total_orig_params += p.numel() def _count_params_of(mod: nn.Module): nonlocal total_comp_params for p in mod.parameters(recurse=False): if id(p) not in count_param_ids: count_param_ids.add(id(p)) total_comp_params += p.numel() for name, module in target_list: target_layer = getattr(module, "base_layer", module) raw_w = target_layer.weight.data is_conv1d = _is_conv1d(target_layer) device = raw_w.device dtype = target_layer.weight.dtype W = raw_w.t().contiguous().clone().float() if is_conv1d else raw_w.clone().float() d_out, d_in = W.shape used_cross_cov = False r_used = self.rank # LL-FIX-04: initialize W_new to W (safe default). W_new = W if name in activations_map: inp, out = activations_map[name] try: inp_flat = inp.reshape(-1, inp.shape[-1]).float() out_flat = out.reshape(-1, out.shape[-1]).float() if inp_flat.shape[-1] == d_in and out_flat.shape[-1] == d_out: C = torch.matmul(out_flat.t().contiguous(), inp_flat) / max( len(inp_flat), 1 ) W_new, r_used = self._factorize(C, self.rank) used_cross_cov = True else: W_new, r_used = self._factorize(W, self.rank) except Exception as e: logger.debug("Cross-covariance factorization failed for %s: %s", name, e) W_new, r_used = self._factorize(W, self.rank) else: W_new, r_used = self._factorize(W, self.rank) norm_orig = torch.norm(W, p="fro") rel_err = (torch.norm(W - W_new, p="fro") / (norm_orig + 1e-8)).item() self.layer_errors[name] = rel_err if rel_err > self.max_error: logger.warning( "LiLLaMA: err %.4f on %s > %.4f. %s", rel_err, name, self.max_error, ( "Keeping original." if self.fallback_to_original else "Proceeding with distilled weights." ), ) if self.fallback_to_original: self.fallback_layers.append(name) for p in module.parameters(recurse=False): p.requires_grad = False _count_params_of(module) continue # Shape integrity check. final_w = W_new.t().contiguous() if is_conv1d else W_new if final_w.shape != target_layer.weight.shape: logger.warning( "LiLLaMA: shape mismatch for %s: expected %s, got %s; skipping.", name, tuple(target_layer.weight.shape), tuple(final_w.shape), ) _count_params_of(module) continue target_layer.weight.data.copy_(final_w.to(dtype=dtype, device=device)) target_layer.weight.requires_grad = False if getattr(target_layer, "bias", None) is not None: target_layer.bias.requires_grad = False # Preserve LoRA adapter trainability. if hasattr(module, "lora_A") and hasattr(module, "lora_B"): for attr in ("lora_A", "lora_B"): p = getattr(module, attr, None) if p is None: continue if hasattr(p, "weight"): p.weight.requires_grad = True elif isinstance(p, nn.Parameter): p.requires_grad = True self.compressed_layers.append(name) _count_params_of(module) self.layer_details[name] = { "rank": int(r_used), "rel_error": float(rel_err), "used_cross_cov": bool(used_cross_cov), "shape": tuple(final_w.shape), } # LL-FIX-07: forward sanity check. forward_check_success = True try: with torch.no_grad(): dummy = torch.randint(0, 100, (1, 8), device=self.target_device, dtype=torch.long) try: self.model(input_ids=dummy, attention_mask=torch.ones_like(dummy)) except Exception: try: self.model(dummy) except Exception as e2: logger.warning("LiLLaMA: forward check failed: %s", e2) forward_check_success = False except Exception as e: logger.warning("LiLLaMA: forward check raised: %s", e) forward_check_success = False try: self._compressed_params = self._count_params(self.model) except Exception: self._compressed_params = sum(p.numel() for p in self.model.parameters()) # LL-FIX-04: avg error only over compressed layers. compressed_errs = [ self.layer_errors[n] for n in self.compressed_layers if n in self.layer_errors ] avg_err = sum(compressed_errs) / len(compressed_errs) if compressed_errs else 0.0 eff_ratio = total_comp_params / max(1, total_orig_params) try: stats = self.get_compression_stats() except Exception: stats = {} stats.update( { "distilled_layers": len(self.compressed_layers), "compressed_layers": self.compressed_layers, "fallback_layers": self.fallback_layers, "num_compressed": len(self.compressed_layers), "num_fallback": len(self.fallback_layers), "rank": self.rank, "max_error": self.max_error, "fallback_to_original": self.fallback_to_original, "avg_reconstruction_error": avg_err, "layer_errors": self.layer_errors, "layer_details": self.layer_details, "effective_parameter_ratio": eff_ratio, "forward_check_success": forward_check_success, "method": "lillama_cross_covariance_svd", # LL-FIX-12: honest note. "note": ( "LiLLaMA rewrites weights in-place with the distilled reconstruction. " "The dense weight keeps its full (out, in) shape; framework-level " "compression_rate reads 1.0. Use effective_parameter_ratio for the " "theoretical storage metric." ), } ) self.stats = stats # LL-FIX-13: write stats onto model. try: if not hasattr(self.model, "compression_stats") or self.model.compression_stats is None: self.model.compression_stats = {} self.model.compression_stats.update( { "num_compressed": len(self.compressed_layers), "num_fallback": len(self.fallback_layers), "compression_method": "lillama_cross_covariance_svd", "effective_parameter_ratio": eff_ratio, "avg_reconstruction_error": avg_err, } ) except Exception: pass self.model._lillama_compressed = True logger.info( "LiLLaMA: %d distilled, %d fallback. Avg err %.4f, eff_param_ratio %.2fx.", len(self.compressed_layers), len(self.fallback_layers), avg_err, eff_ratio, ) return self.model, statsBase class for all compressors. Not a nn.Module to avoid parameter storage.
Ancestors
Class variables
var category : str-
The type of the None singleton.
var is_custom_attention : bool-
The type of the None singleton.
Inherited members