Update README.md
Browse files
README.md
CHANGED
|
@@ -1,25 +1,18 @@
|
|
| 1 |
---
|
| 2 |
-
|
|
|
|
| 3 |
tags:
|
| 4 |
-
- experimental
|
| 5 |
-
- inference
|
| 6 |
-
- feature-selection
|
| 7 |
-
- bayesian
|
| 8 |
-
-
|
| 9 |
-
-
|
| 10 |
-
- open-source
|
| 11 |
-
- language-model
|
| 12 |
-
- compression
|
| 13 |
-
- quantisation
|
| 14 |
-
|
| 15 |
-
|
| 16 |
---
|
| 17 |
|
| 18 |
-
#
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
**Status: experimental / work in progress.** This is a research test, not a production release, not a compression method, and not a claim of a new architecture. Numbers below are placeholders β real plots and stats will be added once benchmark runs are complete.Currently, according to tests, it doesn't use any RAM at all, but there may be errors. With this compression, we compressed by about 30%, but the main matrices are not multiplied.
|
| 23 |
|
| 24 |
## What this actually is
|
| 25 |
|
|
@@ -27,87 +20,69 @@ During autoregressive generation, this repo tracks a small set of statistical fe
|
|
| 27 |
|
| 28 |
The output metric is simple: **what fraction of tracked features get flagged as informative per step**, averaged over a generation. That's it. It's an exploration of whether activation statistics carry sparse, structured signal during inference β not a finished result and not a benchmark win yet.
|
| 29 |
|
|
|
|
| 30 |
|
|
|
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
| 35 |
|
|
|
|
| 36 |
|
|
|
|
| 37 |
|
| 38 |
-
|
| 39 |
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
|
| 43 |
-
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
|
| 44 |
-
<span style="font-size: 14px; color: #333; font-weight: 500;">Qwen2-0.5B</span>
|
| 45 |
-
<span style="font-size: 14px; color: #555; font-weight: 600;">900 MB</span>
|
| 46 |
-
</div>
|
| 47 |
-
<div style="width: 100%; height: 24px; background: #f0f0f0; border-radius: 6px; overflow: hidden;">
|
| 48 |
-
<div style="width: 100%; height: 100%; background: #e74c3c; border-radius: 6px;"></div>
|
| 49 |
-
</div>
|
| 50 |
-
</div>
|
| 51 |
|
| 52 |
-
|
| 53 |
-
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
|
| 54 |
-
<span style="font-size: 14px; color: #333; font-weight: 500;">Adaptive Sparse (this)</span>
|
| 55 |
-
<span style="font-size: 14px; color: #555; font-weight: 600;">10 MB</span>
|
| 56 |
-
</div>
|
| 57 |
-
<div style="width: 100%; height: 24px; background: #f0f0f0; border-radius: 6px; overflow: hidden;">
|
| 58 |
-
<div style="width: 1.1%; height: 100%; background: #3498db; border-radius: 6px;"></div>
|
| 59 |
-
</div>
|
| 60 |
-
</div>
|
| 61 |
|
| 62 |
-
|
| 63 |
-
<span style="font-size: 13px; color: #666;"><span style="display: inline-block; width: 12px; height: 12px; background: #e74c3c; border-radius: 3px; vertical-align: middle; margin-right: 4px;"></span> Original</span>
|
| 64 |
-
<span style="font-size: 13px; color: #666;"><span style="display: inline-block; width: 12px; height: 12px; background: #3498db; border-radius: 3px; vertical-align: middle; margin-right: 4px;"></span> This model</span>
|
| 65 |
-
<span style="font-size: 13px; font-weight: 600; color: #2c3e50;">90Γ smaller</span>
|
| 66 |
-
</div>
|
| 67 |
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
--
|
| 71 |
|
| 72 |
-
|
| 73 |
|
| 74 |
-
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
<span style="font-size: 14px; color: #333; font-weight: 500;">Qwen2-0.5B (original)</span>
|
| 79 |
-
<span style="font-size: 14px; color: #555; font-weight: 600;">4 GB</span>
|
| 80 |
-
</div>
|
| 81 |
-
<div style="width: 100%; height: 24px; background: #f0f0f0; border-radius: 6px; overflow: hidden;">
|
| 82 |
-
<div style="width: 100%; height: 100%; background: #e74c3c; border-radius: 6px;"></div>
|
| 83 |
-
</div>
|
| 84 |
-
</div>
|
| 85 |
|
| 86 |
-
|
| 87 |
-
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
|
| 88 |
-
<span style="font-size: 14px; color: #333; font-weight: 500;">Adaptive Sparse (this)</span>
|
| 89 |
-
<span style="font-size: 14px; color: #555; font-weight: 600;">~0 GB</span>
|
| 90 |
-
</div>
|
| 91 |
-
<div style="width: 100%; height: 24px; background: #f0f0f0; border-radius: 6px; overflow: hidden;">
|
| 92 |
-
<div style="width: 0%; height: 100%; background: #3498db; border-radius: 6px;"></div>
|
| 93 |
-
</div>
|
| 94 |
-
</div>
|
| 95 |
|
| 96 |
-
|
| 97 |
-
<span style="font-size: 13px; color: #666;"><span style="display: inline-block; width: 12px; height: 12px; background: #e74c3c; border-radius: 3px; vertical-align: middle; margin-right: 4px;"></span> Original</span>
|
| 98 |
-
<span style="font-size: 13px; color: #666;"><span style="display: inline-block; width: 12px; height: 12px; background: #3498db; border-radius: 3px; vertical-align: middle; margin-right: 4px;"></span> This model</span>
|
| 99 |
-
<span style="font-size: 13px; font-weight: 600; color: #2c3e50;">4 GB β 0 GB</span>
|
| 100 |
-
</div>
|
| 101 |
|
| 102 |
-
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
## Files
|
| 106 |
|
| 107 |
| File | What it does |
|
| 108 |
-
|---
|
| 109 |
| `terminal_chat_bayesian.py` | Main experiment. Loads Qwen2-0.5B, hooks every attention/FFN weight's input activations, runs the Bayesian feature selector during generation, prints the fraction of flagged features per response. Requires `bayes_analysis.safetensors` (see below). |
|
| 110 |
| `storage_reconstruction_test.py` | Secondary test. Splits weight tensors into `(mean_scalar, residual_tensor)` across a JSON + safetensors file, reconstructs on load. Included for transparency β this is a loading mechanics test, not a result. |
|
|
|
|
| 111 |
|
| 112 |
## Requirements
|
| 113 |
|
|
@@ -144,16 +119,38 @@ python storage_reconstruction_test.py
|
|
| 144 |
|
| 145 |
This will strip attention/FFN weights from the loaded model and reconstruct them from the two files, then start a basic chat loop. Reconstruction is exact by construction β see the "What this is not" section above for why.
|
| 146 |
|
| 147 |
-
### 3.
|
| 148 |
-
|
| 149 |
-
Show on R2 test 100% score with compression.Compressed approximately 30%
|
| 150 |
|
| 151 |
```bash
|
| 152 |
-
python
|
| 153 |
```
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
|
|
|
|
| 156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
## Code
|
| 159 |
|
|
@@ -661,9 +658,7 @@ if __name__ == "__main__":
|
|
| 661 |
run_fast_terminal_chat()
|
| 662 |
```
|
| 663 |
|
| 664 |
-
##
|
| 665 |
-
|
| 666 |
-
### `bayes_compression.py`
|
| 667 |
|
| 668 |
```python
|
| 669 |
from __future__ import annotations
|
|
@@ -734,6 +729,10 @@ def _packetize(raw: bytes, packet_size: int) -> List[bytes]:
|
|
| 734 |
|
| 735 |
|
| 736 |
def _bayes_features(raw: bytes) -> Dict[str, float]:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 737 |
if not raw:
|
| 738 |
return {
|
| 739 |
"n": 0,
|
|
@@ -751,7 +750,7 @@ def _bayes_features(raw: bytes) -> Dict[str, float]:
|
|
| 751 |
n = int(u8.numel())
|
| 752 |
|
| 753 |
counts = torch.bincount(u8.to(torch.int64), minlength=256).to(torch.float32)
|
| 754 |
-
posterior = counts + 1.0
|
| 755 |
total = float(posterior.sum().item())
|
| 756 |
probs = posterior / total
|
| 757 |
|
|
@@ -1096,7 +1095,9 @@ if __name__ == "__main__":
|
|
| 1096 |
|
| 1097 |
## Results
|
| 1098 |
|
| 1099 |
-
*
|
|
|
|
|
|
|
| 1100 |
|
| 1101 |
- [ ] Fraction of features flagged per layer, averaged across a test set of prompts
|
| 1102 |
- [ ] How the flagged fraction changes over the course of a generation (early tokens vs. late tokens)
|
|
@@ -1107,4 +1108,8 @@ if __name__ == "__main__":
|
|
| 1107 |
|
| 1108 |
- Does the flagged feature subset stay stable across different prompts, or does it change drastically session to session?
|
| 1109 |
- Is there a relationship between which features get flagged and attention patterns in the same layer?
|
| 1110 |
-
- Right now `BAYES_EVERY_N = 8` and the deviation threshold (`> 1.0` std) are picked without tuning β sweeping these would show whether the flagged fraction is a real signal or just a threshold artifact.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model: Qwen/Qwen2-0.5B
|
| 4 |
tags:
|
| 5 |
+
- experimental
|
| 6 |
+
- inference
|
| 7 |
+
- feature-selection
|
| 8 |
+
- bayesian
|
| 9 |
+
- compression
|
| 10 |
+
- lossless
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# Adaptive Sparse Feature Selection at Inference Time (Qwen2-0.5B)
|
| 14 |
|
| 15 |
+
**Status: experimental / work in progress.** This is a research test, not a production release, not a compression method, and not a claim of a new architecture. Numbers below are placeholders β real plots and stats will be added once benchmark runs are complete.
|
|
|
|
|
|
|
| 16 |
|
| 17 |
## What this actually is
|
| 18 |
|
|
|
|
| 20 |
|
| 21 |
The output metric is simple: **what fraction of tracked features get flagged as informative per step**, averaged over a generation. That's it. It's an exploration of whether activation statistics carry sparse, structured signal during inference β not a finished result and not a benchmark win yet.
|
| 22 |
|
| 23 |
+
## What this is *not*
|
| 24 |
|
| 25 |
+
To be upfront about scope, since it's easy to over-read either script:
|
| 26 |
|
| 27 |
+
- **Not a compression method.** There is a second script in this repo (`storage_reconstruction_test.py`) that splits a weight tensor into a scalar mean and a residual tensor stored in two separate files, then reconstructs the original by adding them back together. This is a **storage/loading split test** β reconstruction is mathematically exact by construction (`mean + (original - mean) = original`), so the RΒ²=1.0 you'll see is expected and is not a compression result. No compression ratio is claimed anywhere in this repo.
|
| 28 |
+
- **Not a new computation method.** The Bayesian selector changes *what gets measured and tracked* during inference, not *how the forward pass computes logits*. The underlying Qwen2-0.5B forward pass is untouched.
|
| 29 |
+
- **Not validated against a baseline yet.** There's no side-by-side comparison here (yet) showing that the selected feature subset actually predicts anything useful about output quality, speed, or attention patterns. Right now this is instrumentation, not a proven technique.
|
| 30 |
|
| 31 |
+
If any of that changes as testing continues, this README will be updated to reflect it β the goal is to keep the claims here matched to what's actually been measured.
|
| 32 |
|
| 33 |
+
## A third script: entropy-gated lossless compression
|
| 34 |
|
| 35 |
+
Separate from the two experiments above, this repo also includes `entropy_gated_compression.py`, which applies **standard lossless compression to raw model weight bytes**, using a per-packet entropy check to decide whether compression is worth attempting.
|
| 36 |
|
| 37 |
+
### Measured result (single run, self-reported)
|
| 38 |
|
| 39 |
+
```
|
| 40 |
+
Model: Qwen2-0.5B, model.safetensors
|
| 41 |
+
Original: 988,807,168 bytes (943 MB)
|
| 42 |
+
Compressed: 793,772,032 bytes (757 MB)
|
| 43 |
+
Space saved: 19.7%
|
| 44 |
+
Compression ratio: 1.246x
|
| 45 |
+
```
|
| 46 |
|
| 47 |
+
This is **lossless** β decompression was verified to reproduce every tensor bit-for-bit (SHA256 match on 100% of checked tensors, see "Integrity verification" below). There is no approximation, no quantization, and no information discarded. It's exact round-trip storage in less space, not a compressed *representation* of the model in the sense pruning or quantization would give you.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
### How the entropy gate works
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
+
For every 8 MB packet of raw tensor bytes, the packet is treated as a stream of 256 possible byte values (0β255) and a frequency histogram is built. From that histogram:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
+
```
|
| 54 |
+
p(v) = (count(v) + 1) / (total_bytes + 256) # Laplace-smoothed frequency
|
| 55 |
+
H = - Sum p(v) * log2 p(v) # Shannon entropy, bits/byte
|
| 56 |
+
```
|
| 57 |
|
| 58 |
+
The `+1` in the numerator (and `+256` in the denominator to keep it a valid distribution) is **Laplace smoothing** β a standard fix so that byte values with zero observed occurrences don't produce `log2(0)`. This keeps every probability strictly positive. To be precise about terminology: this smoothing is a Bayesian-flavored technique (it's equivalent to placing a uniform Dirichlet prior over the byte distribution and taking a posterior mean), but the compression *decision* itself is a simple **entropy threshold**, not a Bayesian inference procedure over model parameters. If `H >= 7.90` bits/byte, the packet is treated as high-entropy (close to indistinguishable from random noise) and stored raw β attempting `zlib` on it would cost CPU time for little or no space savings. If `H < 7.90`, the packet goes through `zlib` at compression level 1, and the result is kept only if it's actually smaller than the raw packet.
|
| 59 |
|
| 60 |
+
### Why weight tensors compress at all
|
| 61 |
|
| 62 |
+
Raw `float16`/`bfloat16` weights are not uniformly random at the byte level, even though they look noise-like in aggregate. Mantissa bits tend to be close to high-entropy, but:
|
| 63 |
|
| 64 |
+
- Normalization layer weights (`input_layernorm`, `post_attention_layernorm`) and biases occupy a narrower value range than general weight matrices, which tends to concentrate their byte-value histograms and lower measured entropy.
|
| 65 |
+
- Repeated or near-repeated byte patterns across a large tensor give `zlib`'s LZ77-style matching something to exploit, independent of the entropy gate.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
+
A 19.7% reduction is consistent with the range generally reported for applying generic lossless compressors (gzip/zlib-class) directly to raw transformer weight bytes β typically single-digit to ~25%, varying by layer type and model. This is **not a novel compression ratio** by research standards; it's roughly what's expected from entropy-aware lossless compression on this kind of data. The contribution here, if there is one, is the packet-level entropy gate deciding *where* to spend compression effort rather than applying `zlib` uniformly β this mainly saves CPU time on incompressible packets rather than improving the ratio itself.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
+
### Integrity verification
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
+
Every packet is SHA256-hashed before compression and checked again after decompression. The proof-of-correctness run showed **100.0000% match across all checked tensors** β every layer's weight, bias, and normalization tensor was recovered bit-identical to the source file. This is the expected outcome for a correctly implemented lossless codec with a working verification step, not a measured quality metric β it confirms the round-trip is correct, the same way a working checksum is expected to match rather than being a research result in itself.
|
| 72 |
|
| 73 |
+
## Why this might be interesting anyway
|
| 74 |
+
|
| 75 |
+
Most work on transformer internals looks at weights (pruning, quantization, low-rank decomposition). This script instead asks: at inference time, does the *activation* stream flowing through each layer have a small, identifiable subset of statistics that matter more than the rest at any given step? If that subset is small and stable, it's a hint (not proof) that there's structure worth digging into β for interpretability, for adaptive compute, or just as a diagnostic tool for understanding what a layer is "paying attention to" numerically.
|
| 76 |
+
|
| 77 |
+
That's the honest pitch. No claims beyond it yet.
|
| 78 |
|
| 79 |
## Files
|
| 80 |
|
| 81 |
| File | What it does |
|
| 82 |
+
|---|---|
|
| 83 |
| `terminal_chat_bayesian.py` | Main experiment. Loads Qwen2-0.5B, hooks every attention/FFN weight's input activations, runs the Bayesian feature selector during generation, prints the fraction of flagged features per response. Requires `bayes_analysis.safetensors` (see below). |
|
| 84 |
| `storage_reconstruction_test.py` | Secondary test. Splits weight tensors into `(mean_scalar, residual_tensor)` across a JSON + safetensors file, reconstructs on load. Included for transparency β this is a loading mechanics test, not a result. |
|
| 85 |
+
| `entropy_gated_compression.py` | Lossless compression of raw safetensors weight bytes using an entropy-gated packet scheme. Measured 19.7% size reduction on Qwen2-0.5B with verified bit-exact decompression. See "A third script" section above for details. |
|
| 86 |
|
| 87 |
## Requirements
|
| 88 |
|
|
|
|
| 119 |
|
| 120 |
This will strip attention/FFN weights from the loaded model and reconstruct them from the two files, then start a basic chat loop. Reconstruction is exact by construction β see the "What this is not" section above for why.
|
| 121 |
|
| 122 |
+
### 3. Entropy-gated lossless compression
|
|
|
|
|
|
|
| 123 |
|
| 124 |
```bash
|
| 125 |
+
python entropy_gated_compression.py
|
| 126 |
```
|
| 127 |
|
| 128 |
+
By default this compresses `/content/Qwen2-0.5B` (edit `source_model_dir` at the bottom of the script for a different path) into a bundle directory, then immediately decompresses it back to verify correctness. Output:
|
| 129 |
+
|
| 130 |
+
```
|
| 131 |
+
Compression finished.
|
| 132 |
+
Payload: qwen2_0_5b_bayes_zlib_bundle/payload.bin
|
| 133 |
+
Manifest: qwen2_0_5b_bayes_zlib_bundle/manifest.json
|
| 134 |
+
Packet: 8.0 MB
|
| 135 |
+
Restored to: qwen2_0_5b_restored
|
| 136 |
+
```
|
| 137 |
|
| 138 |
+
To use the compression/decompression functions directly instead of running the full round-trip:
|
| 139 |
|
| 140 |
+
```python
|
| 141 |
+
from entropy_gated_compression import compress_qwen2_safetensors_fast, decompress_qwen2_safetensors_fast
|
| 142 |
+
|
| 143 |
+
compress_qwen2_safetensors_fast(
|
| 144 |
+
model_dir="/path/to/Qwen2-0.5B",
|
| 145 |
+
output_bundle_dir="my_bundle",
|
| 146 |
+
packet_mb=8,
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
decompress_qwen2_safetensors_fast(
|
| 150 |
+
bundle_dir="my_bundle",
|
| 151 |
+
restored_model_dir="my_restored_model",
|
| 152 |
+
)
|
| 153 |
+
```
|
| 154 |
|
| 155 |
## Code
|
| 156 |
|
|
|
|
| 658 |
run_fast_terminal_chat()
|
| 659 |
```
|
| 660 |
|
| 661 |
+
### `entropy_gated_compression.py`
|
|
|
|
|
|
|
| 662 |
|
| 663 |
```python
|
| 664 |
from __future__ import annotations
|
|
|
|
| 729 |
|
| 730 |
|
| 731 |
def _bayes_features(raw: bytes) -> Dict[str, float]:
|
| 732 |
+
# NOTE: named for the Laplace-smoothed histogram below, which is a
|
| 733 |
+
# Bayesian-flavored smoothing technique (equivalent to a uniform
|
| 734 |
+
# Dirichlet prior). The compression decision itself is a plain
|
| 735 |
+
# entropy threshold, not Bayesian inference over model parameters.
|
| 736 |
if not raw:
|
| 737 |
return {
|
| 738 |
"n": 0,
|
|
|
|
| 750 |
n = int(u8.numel())
|
| 751 |
|
| 752 |
counts = torch.bincount(u8.to(torch.int64), minlength=256).to(torch.float32)
|
| 753 |
+
posterior = counts + 1.0 # Laplace smoothing (uniform Dirichlet prior, alpha=1)
|
| 754 |
total = float(posterior.sum().item())
|
| 755 |
probs = posterior / total
|
| 756 |
|
|
|
|
| 1095 |
|
| 1096 |
## Results
|
| 1097 |
|
| 1098 |
+
**Entropy-gated compression β measured, single run:** 19.7% size reduction (943 MB β 757 MB) on Qwen2-0.5B, with 100% bit-exact decompression verified via SHA256. See "A third script" section above for the full breakdown. This has been run once β repeating across other model sizes and dtypes (fp16 vs bf16 vs fp32) would show whether 19.7% generalizes or is specific to this checkpoint.
|
| 1099 |
+
|
| 1100 |
+
**Bayesian feature selection during inference β still placeholder.** The activation-tracking experiment (main script) has not yet been benchmarked systematically:
|
| 1101 |
|
| 1102 |
- [ ] Fraction of features flagged per layer, averaged across a test set of prompts
|
| 1103 |
- [ ] How the flagged fraction changes over the course of a generation (early tokens vs. late tokens)
|
|
|
|
| 1108 |
|
| 1109 |
- Does the flagged feature subset stay stable across different prompts, or does it change drastically session to session?
|
| 1110 |
- Is there a relationship between which features get flagged and attention patterns in the same layer?
|
| 1111 |
+
- Right now `BAYES_EVERY_N = 8` and the deviation threshold (`> 1.0` std) are picked without tuning β sweeping these would show whether the flagged fraction is a real signal or just a threshold artifact.
|
| 1112 |
+
|
| 1113 |
+
## License
|
| 1114 |
+
|
| 1115 |
+
Apache 2.0, matching the base model license. This repo builds on [Qwen/Qwen2-0.5B](https://huggingface.co/Qwen/Qwen2-0.5B).
|