ASHQ1 / README.md
wepiqx's picture
note: v6 dropped empirical heuristics
fe9d986
|
Raw
History Blame Contribute Delete
9.58 kB
---
license: mit
language:
- en
pipeline_tag: text-generation
library_name: gguf
tags:
- quantization
- gguf
- llama-cpp
- imatrix
- hybrid-quantization
- selective-quantization
- priority-queue
- mse
- theoretical-optimization
- qwen3.5
- gemma4
- moe
- mtp
---
# ASHQ1 — Autonomous Selective Hybrid Quantization
> ⚠️ **Experimental.** ASHQ1 is a personal research project that I will be refining over time. Use at your own risk. Results may vary between architectures and fine-tunes. Feedback and contributions welcome.
**Latest update (v6):** The classifier has been overhauled — the empirical depth-weighting heuristic was removed after A/B testing confirmed it added zero value. Quality improved as a result. The same budget now goes further.
ASHQ1 is a post-training quantization method for GGUF models that uses an **imatrix-driven priority queue** to maximise theoretical quality per megabyte. Instead of uniform bit-depth or heuristic layer-blocking, it treats tied tensor groups as monolithic entities and greedily upgrades them by strict mathematical utility — the product of summed importance and theoretical MSE reduction, divided by size cost.
## Results
| Method | Model | Size | PPL (ctx 1024) | Δ vs Uniform |
|:-------|:------|:----:|:--------------:|:------------:|
| **ASHQ1** (v6) | Ornith-1.0-9B-MTP | 6012 MiB | **7.4697 ± 0.04862** | **−0.1551** |
| Uniform Q6_K | Ornith-1.0-9B-MTP | 7198 MiB | 7.6248 ± 0.05039 | baseline |
ASHQ1 beats uniform Q6_K by **0.155 PPL** while being **16.5% smaller** (−1186 MiB). The current classifier (v6) dropped empirical depth-weighting heuristics — the theoretical priority queue now works even better.
ASHQ1 is often on par with hand-tuned SHQ quants in quality, and sometimes surpasses them. At the same time, it saves significant time and effort — just set your target size and go.
## Real-World Validation
ASHQ1's theoretical quality advantage transfers to real agentic coding. We tested Ornith-1.0-9B ASHQ1 6500 (6.4 GB, 33% smaller than Q8_0) as the backend for [Pi](https://pi.dev/), an autonomous coding agent that uses `llama.cpp` as its LLM backend.
At `temperature 0.6`, the model was tasked with building a complete personal finance dashboard as a single HTML file — Canvas charts, budget tracker, dark mode, transaction filtering, upcoming bills, responsive layout. The agent worked autonomously: planned the architecture, wrote the entire ~1100-line file, caught its own bugs (`date.now` → `date.getTime`), fixed dark mode logic, ran Node.js validation, and iterated until all checks passed. The final `finance-dashboard.html` was a polished, production-quality single-page app — no external dependencies, no hallucinations, no broken features.
This is not cherry-picked. It's the first test we ran. The benchmarks didn't lie — ASHQ1 preserves enough quality that a 6.4 GB quant can drive an autonomous coding agent to build complete, working applications from scratch.
## How It Works
### 1. Floor Assignment
Every tensor starts at a minimum tier by class. SSM params and norms lock at F16. Embeddings start at Q5_K. Weight matrices start at Q4_K (or IQ4_XS for QAT models). MTP heads deploy at Q8_0.
With `--allow-q3-or-lower`, low-importance tensors (`ffn_down`, `attn_output`, `ssm_out`) start as low as IQ2_XXS, giving the priority queue more room to upgrade important tensors to Q8_0. Tensors missing imatrix data are kept at Q4_K to avoid garbage at low bitrates.
### 2. Importance
Imatrix `in_sum2` measures how much each weight contributes to the output variance. Layer position weighting was tested but showed no PPL benefit and has been removed.
### 3. Tied Group Detection
Tensors with numerically identical `in_sum2` arrays are tied (shared weights). They form a single upgrade group — all members upgrade together as one unit. Group importance is the **sum** of its members' importance, preventing large groups from being starved of budget.
### 4. Priority Queue Drain
All possible single-tier upgrades are pushed into a max-heap:
```
utility/MiB = sum(timp[group]) × (MSE(cur) − MSE(next)) / (size(next) − size(cur))
```
MSE per tier is theoretical: `MSE = 2^(-2 × bpw)`. K-quants get +0.1 effective bpw vs IQ-quants at the same real bpw, so IQ4_NL→Q4_K is a free quality gain.
The queue pops the highest-utility upgrade, applies it, pushes the next upgrade for that group, and drains until the budget is exhausted. A final pass catches any remaining zero-cost upgrades.
## Why It Works
| Problem | ASHQ1 Solution |
|---------|---------------|
| Uniform quant wastes bits on low-importance tensors | Priority queue allocates budget where it matters |
| Heuristic hand-tuning doesn't scale | Single knob: `--size` in MiB |
| Hand-tuned SHQ hybrids need days of PPL sweeps | Queue converges in ~1 sec for any budget |
| Large tied groups starved by per-tensor logic | `sum(timp)` prevents 32× group penalty |
| IQ4_NL→Q4_K at same bpw is a no-op | Free-upgrade pass catches zero-cost quality gains |
| No PPL-per-budget curve needed | Queue optimises for MSE directly |
| Tensors without imatrix crash at low bitrates | `has_imatrix` check falls back to Q4_K floor |
## Supported Architectures
| Arch | Detection | Features |
|------|-----------|----------|
| `qwen35` | SSM + QKV | Hybrid attention, SSM layers, GQA, **MTP support** |
| `mellum2` | MoE (`exps` tensors) | Mixture of Experts, GQA, router F16 |
| `gemma4` | Layer-scale norms | QAT support, Q4_K attention floor |
MTP (Multi-Token Prediction) heads are handled explicitly: MTP tensors deploy at Q8_0 and are excluded from the classifier's budget (their cost is subtracted from the target upfront). Tensor names with `nextn.*` or layers beyond `n_layers` are detected as MTP at runtime.
### Looking for: Qwen3.6 support
Qwen3.6 is one of the most capable local LLMs right now, but I can't handle it on my hardware. The BF16 source is ~55 GB — I don't have enough RAM to even load it, let alone quantize. If you have access to a Qwen3.6 GGUF (any quantization) and can run `llama-imatrix` on it — or if you'd like to collaborate on adding architecture detection — please reach out. I can handle the integration, I just need the raw tensor names and imatrix data to map out the class system.
New architectures can be added via `ARCH_FEATURES` in `constants.py`.
## Code Structure
| File | Role |
|------|------|
| `main.py` | CLI entry point, orchestration, `--show-floors`, multiple `--imatrix` support |
| `model_reader.py` | Reads GGUF, detects architecture/prefix/n_layers/MTP at runtime |
| `imatrix_reader.py` | Parses imatrix GGUF, detects tied groups via `np.allclose(in_sum2)`, combines multiple imatrix |
| `classifier.py` | Floor assignment → tied group building → priority queue drain → free upgrade pass |
| `config_generator.py` | Generates `--tensor-type` regex rules from classified tensors (valid ECMAScript regex with pipe-alternated ranges) |
| `quantizer.py` | Subprocess wrapper around `llama-quantize` |
| `constants.py` | TENSOR_CLASS mapping, CLASS_HARD_FLOORS, CLASS_MAX_TIER, MSE_BPW, TIER_BPW, ARCH_FEATURES |
## Usage
### Quantization
```bash
pip install -r requirements.txt
# Dry run (∼1 sec)
python main.py --model model.gguf --imatrix imatrix.gguf --size 6800
# Actual quant (∼10 min)
python main.py --model model.gguf --imatrix imatrix.gguf --size 6800 --run
# Show hard floors
python main.py --show-floors
# Multiple imatrix (combined with max/mean)
python main.py --model model.gguf --imatrix i1.gguf --imatrix i2.gguf \
--imatrix-method max --size 6800 --run
# Allow low-bit tensors (IQ2_XXS through Q8_0 spread)
python main.py --model model.gguf --imatrix imatrix.gguf --size 6000 \
--allow-q3-or-lower --run
```
The `llama-quantize` binary path is set in `quantizer.py:6`.
### Inference (llama-server)
Recommended server flags for serving ASHQ1 quants:
```bash
./build/bin/llama-server \
-m model-ASHQ1.gguf \
-c 50000 \
--jinja \
-fit off \
-ngl 99 \
--flash-attn on \
--cache-type-k q8_0 \
--cache-type-v q8_0 \
--port 8080 \
--mmap \
--temp 1.0 \
--top-p 0.95 \
--min-p 0 \
--top-k 20 \
--seed -1 \
--parallel 1
```
## Tier Reference
| Tier | BPW | MSE_BPW |
|------|:---:|:-------:|
| F16 | 16.0 | 16.0 |
| Q8_0 | 8.50 | 8.50 |
| Q6_K | 6.5625 | 6.5625 |
| Q5_K | 5.50 | 5.50 |
| Q4_K | 4.50 | 4.50 |
| IQ4_NL | 4.50 | (2) |
| IQ4_XS | 4.25 | 4.25 |
| Q3_K | 3.4375 | 3.4375 |
| IQ3_M | 3.66 | — |
| IQ3_S | 3.44 | 3.44 |
| IQ3_XXS | 3.0625 | 3.0625 |
| IQ2_S | 2.50 | 2.50 |
| IQ2_XS | 2.3125 | 2.3125 |
| IQ2_XXS | 2.0625 | 2.0625 |
| IQ1_S | 1.5625 | 1.5625 |
> (2) IQ4_NL uses IQ4_XS MSE_BPW for the free-upgrade pass (same real bpw as Q4_K).
## Quantization Configs
Generated configs are valid `llama-quantize` arguments with ECMAScript-compatible regex patterns. Each `--tensor-type` rule matches a group of tensors that share the same target tier, with layers grouped into contiguous ranges:
- `(blk|BLK)\.(3|7|11|15|19|23|27|31)\.attn_k=Q8_0` — specific attention layers at Q8_0
- `(blk|BLK)\.((?:22|23|24|25|26))\.ffn_gate=Q6_K` — range of FFN layers at Q6_K
- `.*output_norm.*=F16` — global catch-all
Rules are sorted by specificity (specific layers, high tiers first) because `llama-quantize` uses first-match-wins.
## References
- [ASHQ1 repo](https://huggingface.co/wepiqx/ASHQ1)
- [GGUF specification](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md)
- [llama.cpp](https://github.com/ggerganov/llama.cpp)