Metadata-Version: 2.4
Name: sigularty
Version: 1.0.6
Summary: Production-grade PyTorch model compression: pruning, LRF, clustering, quantization, GPTQ.
Author: sigularty
License: # PolyForm Noncommercial License 1.0.0
        
        <https://polyformproject.org/licenses/noncommercial/1.0.0>
        
        ## Acceptance
        
        In order to get any license under these terms, you must agree
        to them as both strict obligations and conditions to all
        your licenses.
        
        ## Copyright License
        
        The licensor grants you a copyright license for the
        software to do everything you might do with the software
        that would otherwise infringe the licensor's copyright
        in it for any permitted purpose.  However, you may
        only distribute the software according to [Distribution
        License](#distribution-license) and make changes or new works
        based on the software according to [Changes and New Works
        License](#changes-and-new-works-license).
        
        ## Distribution License
        
        The licensor grants you an additional copyright license
        to distribute copies of the software.  Your license
        to distribute covers distributing the software with
        changes and new works permitted by [Changes and New Works
        License](#changes-and-new-works-license).
        
        ## Notices
        
        You must ensure that anyone who gets a copy of any part of
        the software from you also gets a copy of these terms or the
        URL for them above, as well as copies of any plain-text lines
        beginning with `Required Notice:` that the licensor provided
        with the software.  For example:
        
        > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
        
        ## Changes and New Works License
        
        The licensor grants you an additional copyright license to
        make changes and new works based on the software for any
        permitted purpose.
        
        ## Patent License
        
        The licensor grants you a patent license for the software that
        covers patent claims the licensor can license, or becomes able
        to license, that you would infringe by using the software.
        
        ## Noncommercial Purposes
        
        Any noncommercial purpose is a permitted purpose.
        
        ## Personal Uses
        
        Personal use for research, experiment, and testing for
        the benefit of public knowledge, personal study, private
        entertainment, hobby projects, amateur pursuits, or religious
        observance, without any anticipated commercial application,
        is use for a permitted purpose.
        
        ## Noncommercial Organizations
        
        Use by any charitable organization, educational institution,
        public research organization, public safety or health
        organization, environmental protection organization,
        or government institution is use for a permitted purpose
        regardless of the source of funding or obligations resulting
        from the funding.
        
        ## Fair Use
        
        You may have "fair use" rights for the software under the
        law. These terms do not limit them.
        
        ## No Other Rights
        
        These terms do not allow you to sublicense or transfer any of
        your licenses to anyone else, or prevent the licensor from
        granting licenses to anyone else.  These terms do not imply
        any other licenses.
        
        ## Patent Defense
        
        If you make any written claim that the software infringes or
        contributes to infringement of any patent, your patent license
        for the software granted under these terms ends immediately. If
        your company makes such a claim, your patent license ends
        immediately for work on behalf of your company.
        
        ## Violations
        
        The first time you are notified in writing that you have
        violated any of these terms, or done anything with the software
        not covered by your licenses, your licenses can nonetheless
        continue if you come into full compliance with these terms,
        and take practical steps to correct past violations, within
        32 days of receiving notice.  Otherwise, all your licenses
        end immediately.
        
        ## No Liability
        
        ***As far as the law allows, the software comes as is, without
        any warranty or condition, and the licensor will not be liable
        to you for any damages arising out of these terms or the use
        or nature of the software, under any kind of legal claim.***
        
        ## Definitions
        
        The **licensor** is the individual or entity offering these
        terms, and the **software** is the software the licensor makes
        available under these terms.
        
        **You** refers to the individual or entity agreeing to these
        terms.
        
        **Your company** is any legal entity, sole proprietorship,
        or other kind of organization that you work for, plus all
        organizations that have control over, are under the control of,
        or are under common control with that organization.  **Control**
        means ownership of substantially all the assets of an entity,
        or the power to direct its management and policies by vote,
        contract, or otherwise.  Control can be direct or indirect.
        
        **Your licenses** are all the licenses granted to you for the
        software under these terms.
        
        **Use** means anything you do with the software requiring one
        of your licenses.
        
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: Other/Proprietary License
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: torch>=2.0
Requires-Dist: torchvision>=0.15
Requires-Dist: torchmetrics>=0.11
Requires-Dist: tqdm
Requires-Dist: numpy
Requires-Dist: matplotlib
Provides-Extra: pruning
Requires-Dist: torch-pruning>=1.3; extra == "pruning"
Provides-Extra: full
Requires-Dist: torch-pruning>=1.3; extra == "full"
Requires-Dist: onnx; extra == "full"
Requires-Dist: onnxruntime; extra == "full"
Dynamic: license-file

# Using sigularty

This is a quick reference for calling sigularty as a library: how to
import it, the two ways to get a model in, and what every `compress()`
argument does. For the theory behind each technique, why pruning runs
before LRF, what CQI means, why quantization runs before the final
recovery step, see [README.md].

---

## Install

```bash
pip install sigularty
```

## Quickstart

**Fastest path: a registry model + its paired dataset in one call:**

```python
from sigularty import load_from_registry, compress

r = load_from_registry('resnet18', device='cuda')

result = compress(
    r.model,
    r.train_loader,
    test_loader=r.test_loader,
    num_classes=r.num_classes,
    use_pruning=True,
    use_lrf=True,
)

print(result)
# CompressionResult(ratio=3.42x, accuracy_retention=96.1%, size=42.7->12.5 MB, speedup=1.8x, cqi=2.113)

compressed_model = result.model
```

**Bring your own model:**

```python
from sigularty import compress

result = compress(model, train_loader, test_loader=test_loader, num_classes=num_classes)
compressed_model = result.model
```

`model` can be any `nn.Module`. `dataloader` (positional, second argument)
is used for calibration and fine-tuning throughout the pipeline: it's
never optional. `test_loader` is a genuine held-out set used for every
accuracy measurement — baseline, both hyperparameter searches, every
technique's gate, the final report. If you omit it, `compress()`
auto-splits `dataloader`'s dataset 70/30 and prints a warning when it
does; passing your own is always preferable, since the auto-split both
reduces how much data your model actually gets fine-tuned on and can't
guarantee class balance in the held-out portion. The original model is
never modified; `compress()` always works on a deep copy.

---

## Other entry points

```python
from sigularty import (
    compress,               # run the full compression pipeline
    analyze,                # inspect a model, get technique recommendations
    load_from_registry,     # pull a registry model + its dataset together
    finetune,                # fine-tune a model before compressing it
    find_best_lr,            # LR range test: run before finetune()/pretrain_*
    plot_compression_report,
    plot_pruning_report,
    plot_epsilon_landscape,
)
```

| Function | Signature | Returns |
|---|---|---|
| `analyze(model, dataloader=None, *, device=None)` | Inspects a model without modifying it. Pass a held-out `dataloader` if you want the reported accuracy to reflect generalisation rather than whatever the model has already been trained on. | `AnalysisResult` |
| `load_from_registry(model_name, *, device=None, train_sample=None, test_sample=500, batch_size=32, model_path=None, force_retrain=False, pretrain_epochs=10, pretrain_lr=1e-4)` | See list of registry model names below. Sole supported registry entry point. | `RegistryResult` |
| `finetune(model, train_loader, *, test_loader=None, num_classes=10, epochs=10, lr=1e-3, max_batches=0, device=None, save_path=None)` | Fine-tunes in place and returns the same object. `test_loader` defaults to `train_loader` if omitted — pass a genuine held-out loader if you want the per-epoch `test_acc` it prints to mean anything. | `nn.Module` |
| `find_best_lr(model, dataloader, *, device=None, num_classes=10, start_lr=1e-7, end_lr=10.0, num_steps=100)` | LR range test; run before `finetune()` or `pretrain_epochs > 0`. | `float` |

Registry model names (pass as `model_name`): `custom_cnn`, `resnet18`,
`resnet50`, `resnext50_32x4d`, `wide_resnet50_2`, `vgg16`, `densenet121`,
`convnext_tiny`, `regnet_y_400mf`, `shufflenet_v2_x1_0`, `squeezenet1_1`,
`mobilenet_v3_large`, `efficientnet_b0`, `inception_v3`, `vit_b_16`,
`swin_t`, `bert_base`, `distilbert`, `roberta_base`, `albert_base`,
`distilgpt2`.

---

## What `compress()` returns

```python
result.model                   # nn.Module: the compressed model, ready to use
result.compression_ratio       # size_original / size_compressed, e.g. 3.8 = 3.8x smaller
result.accuracy_retention      # (compressed_acc / original_acc) * 100
result.size_original_mb
result.size_compressed_mb
result.original_accuracy
result.compressed_accuracy
result.original_latency_ms
result.compressed_latency_ms
result.latency_speedup         # original_lat / compressed_lat, >1.0 = faster
result.cqi                     # Compression Quality Index: see README.md
result.techniques_applied      # list[str], only techniques that actually ran/survived their accuracy gate — see "Accuracy-drop gating" below
result.report_path             # path to the saved PNG report, or None
result.pruning_report          # dict of per-layer pruning detail, or None (also None if pruning ran but was reverted by the gate)
```

Every accuracy value on this object — `original_accuracy`,
`compressed_accuracy`, and everything derived from them
(`accuracy_retention`, `cqi`'s accuracy factor, every gate's keep/revert
decision along the way) — is measured against `test_loader`, never
against `dataloader`.

`analyze()` returns an `AnalysisResult`: `size_mb`, `num_parameters`,
`architecture_type` (`'cnn'` / `'transformer'` / `'hybrid'` / `'unknown'`),
`recommended_techniques`, `per_layer_signals` (a `LayerSignal` per eligible
layer: `name`, `layer_type`, `lrf_epsilon`, `prunable`, `size_kb`), and
`accuracy` if you passed a dataloader.

`load_from_registry()` returns a `RegistryResult`: `model`, `train_loader`,
`test_loader`, `num_classes`, `model_name`, `dataset_name`.

---

## `compress()` hyperparameters

Every argument after `model` and `dataloader` is keyword-only. Grouped
the same way the function itself groups them.

### Evaluation data

| Arg | Default | What it does |
|---|---|---|
| `test_loader` | `None` | Genuine held-out set. Every accuracy measurement in `compress()` — baseline, both hyperparameter searches, every technique's gate, the final report — reads from `test_loader`, never from `dataloader`. If omitted, `compress()` auto-splits `dataloader`'s dataset 70/30 (fixed seed, plain random) and reuses that one split for the entire call, printing a warning each time it happens. An extra warning prints if the resulting held-out split comes out under 100 samples. Passing your own is strongly recommended over relying on the auto-split — see the Quickstart note above. |

### Device

| Arg | Default | What it does |
|---|---|---|
| `device` | `None` | `'cuda'` or `'cpu'`. Auto-detected when not set. |

### Model metadata

| Arg | Default | What it does |
|---|---|---|
| `model_type` | `'unknown'` | Architecture class for pruning's safety clamp: `'classifier'`, `'embedding'`, `'generative'`, or `'unknown'`. |
| `num_classes` | `10` | Output class count, used by every fine-tune step's accuracy metric. |

### Pre-training

Use these when the model hasn't been trained on your target dataset yet
(e.g. fresh ImageNet weights with an untrained head, straight out of
`load_from_registry()` with no existing checkpoint). This step always
runs before `test_loader` is resolved via auto-split — pretrain trains on
`dataloader`'s 70% share, never on the held-out portion.

| Arg | Default | What it does |
|---|---|---|
| `pretrain_epochs` | `0` | Epochs to fine-tune before compressing. `0` skips this entirely. |
| `pretrain_lr` | `1e-3` | Learning rate for that pre-training. |
| `pretrain_test_loader` | `None` | Validation loader for pre-training. Falls back to `test_loader`. |

### Hyperparameter search (optional, adds evaluations before the pipeline runs)

| Arg | Default | What it does |
|---|---|---|
| `find_optimal_epsilon` | `False` | Auto-search for the best LRF epsilon instead of using `lrf_epsilon` as-is. |
| `find_optimal_pruning` | `False` | Auto-search for the best pruning ratio instead of using `pruning_ratio` as-is. |
| `epsilon_search_trials` | `15` | Evaluation budget for the epsilon search. |
| `pruning_search_trials` | `16` | Evaluation budget for the pruning search. |
| `pruning_search_ft_epochs` | `1` | Fine-tune epochs per trial during the pruning search (kept low for speed: the real run uses `pruning_fine_tune_epochs`). |
| `pruning_search_ft_lr` | `1e-4` | Fine-tune learning rate per trial during the pruning search. |
| `accuracy_drop_threshold` | `5.0` | Max acceptable accuracy drop in percentage points: used by both searches' final selection AND the pipeline's per-technique revert gate. |
| `early_abort_threshold` | `None` | Direct pp value (not a multiplier on `accuracy_drop_threshold`). After epoch 1 of Pruning's or LRF's own recovery fine-tune — in both the real pipeline and their searches — abort the remaining epochs if the drop vs. the original baseline already exceeds this. `None` (default) = disabled; every fine-tune always runs to completion. |
| `epsilon_cache_path` | `None` | Override the auto-derived per-model cache file for the epsilon search. `None` = `.sigularty_cache/epsilon_<model>.json`. |
| `pruning_cache_path` | `None` | Override the auto-derived per-model cache file for the pruning search. `None` = `.sigularty_cache/pruning_<model>.json`. |

**Accuracy-drop gating is always active.** Structured Pruning, Low-Rank
Factorization, Weight Clustering, GPTQ, standard Quantization, and the
final KD recovery fine-tune are each measured before/after and reverted
to their pre-technique state if that ONE technique's own marginal drop
exceeds `accuracy_drop_threshold`. Each technique gets an independent
budget — an earlier costly technique does not eat into a later
technique's allowance. A reverted technique will not appear in
`result.techniques_applied` — check the console output for a `❌
[TechniqueName] SKIPPED — accuracy dropped ...` line if a technique you
enabled seems to be missing.

**GPTQ uses a QAT-aware (straight-through estimator) path whenever
`use_kd_finetune=True`**, so the final KD fine-tune can actually update
GPTQ-quantized weights instead of finding them frozen — collapsed back to
compact storage automatically once KD finishes. If the final KD step's
cumulative recovery still falls short of `accuracy_drop_threshold` after
its own gate passes, one bounded, LR-adjusted retry runs automatically
before the result is accepted as-is. KD always runs after GPTQ and
standard quantization, never before, so it recovers accuracy lost to
every prior step at once, including quantization/GPTQ's own damage. fp16
is not skipped when GPTQ is absent or reverted — fp16-only stays a fully
independent, always-available configuration.

**Search result caching.** Each search caches trial results to disk
purely by hyperparameter value, with no reference to which model produced
them. `epsilon_cache_path`/`pruning_cache_path` default to a filename
derived from the model's class name + parameter count + `num_classes`, so
different models get separate cache files automatically. Pass an
explicit path yourself for a stronger guarantee (e.g. distinct caches per
dataset too, not just per model/class-count).

If you have `.sigularty_cache/` files from before `test_loader` existed,
delete them. Those cached accuracy numbers were measured against
`dataloader` (effectively training data, since `compress()` had no other
option at the time) rather than a genuine held-out set — reusing them now
would silently mix pre-fix and post-fix numbers under the same cache
keys, and the cached values would understate real accuracy drop.

### Technique enable flags

| Arg | Default |
|---|---|
| `use_pruning` | `False` |
| `use_lrf` | `True` |
| `use_clustering` | `True` |
| `use_quantization` | `True` |
| `use_kd_finetune` | `True` |
| `use_gptq` | `False` |

### Pruning hyperparameters

| Arg | Default | What it does |
|---|---|---|
| `pruning_ratio` | `0.3` | Target fraction of channels removed globally. |
| `pruning_max_ratio` | `0.95` | Hard cap on how much any single layer/dependency-group can be pruned. |
| `pruning_residual_max_ratio` | `None` | Ceiling specifically for auto-detected residual/skip-connection-coupled groups — the layers whose channel count IS the residual stream for an entire network stage, so collapsing them damages every downstream block in that stage, not just one layer's worth of capacity. `None` (default) falls back to `pruning_max_ratio` above. See README.md's "Architecture-Agnostic Residual Group Detection" section for the detection mechanism. |
| `pruning_model_type` | `'classifier'` | Same role as `model_type`, specific to pruning's clamp. |
| `pruning_fine_tune_epochs` | `3` | KD recovery epochs after pruning. |
| `pruning_fine_tune_lr` | `1e-4` | Learning rate for that recovery fine-tune. |
| `pruning_cal_batches` | `50` | Calibration batches for activation-statistics importance scoring. |
| `pruning_iterative_steps` | `1` | Prune in one shot (`1`) or multiple incremental rounds (more stable, slower). |
| `pruning_isomorphic` | `False` | Force identical pruning structure across coupled dependency groups. |
| `pruning_round_to` | `None` | Round pruned channel counts to a multiple of this (e.g. `8`/`16` for Tensor Core alignment). |

**Note:** `find_optimal_pruning=True` forwards `pruning_max_ratio` (and
`pruning_residual_max_ratio`) into the search itself, and reads the
search's winning max-ratio back out afterward — the config the search
recommends is the config the real pipeline actually applies.

### Low-Rank Factorization (LRF) hyperparameters

| Arg | Default | What it does |
|---|---|---|
| `lrf_epsilon` | `0.5` | Rank ratio kept. Lower = more compression, more accuracy risk. |
| `lrf_adaptive` | `False` | Compute epsilon per layer from SVD energy decay instead of one global value. |
| `lrf_energy_threshold` | `0.99` | Fraction of SVD energy retained per layer, when adaptive. |
| `lrf_min_layer_size` | `64` | Skip layers with a dimension below this. |
| `lrf_min_rank` | `2` | Skip a layer if its computed rank would fall below this. |
| `lrf_skip_large_kernels` | `False` | Skip Conv2d layers with kernel > 1x1: prevents latency regression on 3x3-heavy CNNs (ResNet/VGG-style). |

### Weight clustering hyperparameters

| Arg | Default | What it does |
|---|---|---|
| `num_clusters` | `16` | k for k-means weight clustering. |
| `cluster_fine_tune_epochs` | `5` | Recovery fine-tune epochs after clustering. |
| `cluster_fine_tune_lr` | `1e-5` | Learning rate for that recovery fine-tune. |

### Knowledge distillation fine-tuning hyperparameters

The final recovery step. KD always runs after quantization/GPTQ, never
before, so it recovers accuracy lost from every prior step at once —
including quantization/GPTQ's own damage, since those are gated too (see
"Accuracy-drop gating" above). If GPTQ is enabled, it uses a QAT-aware
path specifically so this step can update GPTQ-quantized weights rather
than finding them frozen.

| Arg | Default | What it does |
|---|---|---|
| `kd_epochs` | `3` | Fine-tune epochs. |
| `kd_lr` | `1e-5` | Learning rate. |
| `kd_temperature` | `4.0` | Softmax temperature for the teacher's soft labels. |
| `kd_alpha` | `0.7` | Weight on hard-label loss; `1 - kd_alpha` goes to the distillation loss. |
| `kd_max_batches` | `50` | Max batches per KD epoch. Use `0` for the full dataloader each epoch. |

### Quantization hyperparameters

| Arg | Default | What it does |
|---|---|---|
| `quant_mode` | `'fp16'` | `'fp16'`, `'dynamic'` (INT8, CPU-friendly), or `'static'` (auto-switched to `'dynamic'`). |
| `quant_cal_batches` | `100` | Calibration batches: only used by `'static'`. |

### GPTQ hyperparameters

| Arg | Default | What it does |
|---|---|---|
| `gptq_bits` | `4` | `4` for INT4 (8x compression) or `8` for INT8 (4x). |
| `gptq_cal_batches` | `16` | Batches used to estimate the Hessian from activations. |
| `gptq_block_size` | `128` | Columns processed per Hessian update block. |

### CQI scoring weights

Each is an exponent applied to that factor's ratio in the Compression
Quality Index: raise one to make the search/report weight that factor
more heavily.

| Arg | Default |
|---|---|
| `cqi_w_accuracy` | `1.0` |
| `cqi_w_size` | `1.0` |
| `cqi_w_latency` | `1.0` |
| `cqi_w_kl` | `1.0` (only meaningful when pruning ran) |

### Report

| Arg | Default | What it does |
|---|---|---|
| `save_report` | `True` | Generate the PNG compression report. |
| `report_path` | `'compression_report.png'` | Where to save it. |
