Skip to main content

economize

Composable efficiency techniques for Hugging Face causal language models.

economize attaches techniques to an unmodified transformers model. The base model keeps its module tree and its forward. Drafters and other added components bring their own parameters and act only while decoding; weight transforms (pruning, low rank, quantization) rewrite the base weights in place, once, when they attach. Any set of registered techniques can be combined in one call, and every architecture adapter, technique, verifier, cache and checkpoint format is added by registration.

Version 0.3 ships cross-layer jump drafting, static depth pruning, Wanda / SparseGPT pruning, SVD and SVD-LLM low rank, GPTQ / AWQ / round-to-nearest quantization, TEAL activation sparsity and draft-only quantization, on 44 model types (dense, MoE, and hybrid attention plus Mamba / linear-attention models), with batched decoding of ragged prompts, draft trees and lossless speculative sampling.

Install

pip install economize                       # torch>=2.4, transformers>=5.0, safetensors, huggingface_hub
pip install "economize[all]"                # every optional integration below except vLLM and SGLang

From source:

git clone https://github.com/Thiggel/economize.git
pip install -e "./economize[dev]"
Extra Installs For
test pytest the test suite
peft peft PEFT-wrapped base models and LoRA training of the base
quant torchao, compressed-tensors quantized kernels, compressed-tensors export
kernels kernels Hugging Face kernels hub kernels (economize.integrations.kernels)
train accelerate, datasets, trl drafter training and online RL rollouts
eval lm-eval[hf] economize-lm-eval, lm-evaluation-harness with economize's decoding loop
bench datasets prompt suites of economize.bench (Spec-Bench, GSM8K, HumanEval)
vllm, sglang vllm, sglang serving backends (economize.backends; drafters are written by economize.backends.export_drafter)

Installing the package registers the vLLM plugin (vllm.general_plugins), the compressed-tensors quantization backend and the economize-lm-eval command through entry points.

Quick start

import torch
import economize
from transformers import AutoModelForCausalLM, AutoTokenizer

base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-0.5B", dtype=torch.bfloat16, device_map="cuda")
model = economize.load(base, "path/to/drafter")            # a trained drafter: a folder or a Hub repo id
# or in one step, loading the base model named in the checkpoint:
model = economize.from_pretrained("path/to/drafter", dtype=torch.bfloat16, device_map="cuda")

tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B")
ids = tokenizer("The water cycle describes", return_tensors="pt").input_ids.cuda()
out = model.generate(ids, max_new_tokens=128)             # lossless: the same tokens as base.generate

model.generate accepts the usual transformers arguments (logits processors such as repetition_penalty, eos_token_id, return_dict_in_generate, ...) and routes them through economize's decoding loop. model.decode returns the tokens together with a log of every model call and the acceptance of each cycle:

result = model.decode(ids[0], 128, decoding=economize.DecodeConfig(cycle_length=4))
result.cost_summary()      # exact passes, draft steps, layer applications relative to plain decoding
result.mean_accepted       # accepted drafts per cycle

Jump drafting

A draft step for the next token predicts its hidden state after layer m from the current token's state after layer u and the next token's embedding, runs only layers m+1..r and reads out logits. Layers 1..m (and r+1..L) are skipped. One cycle of N = cycle_length tokens runs the unmodified model once, in parallel, over every token not yet computed exactly, which also yields one exact token, followed by up to N-1 draft steps.

config = economize.JumpConfig(
    target_layer=12,            # m
    source_layer=18,            # u
    exit_layer=None,            # r (default: the last layer; below it a readout adapter is added)
    adaptation="frozen",        # or "lora" (LoRA active only on the jump path) or "full" (trainable layer copies)
)
model = economize.apply(base, config)
list(model.technique_parameters())   # the added parameters only; the base model is untouched

DecodeConfig chooses how drafts are used:

Option Meaning
cycle_length Tokens per cycle N (1 is ordinary decoding).
lossless=True Speculative decoding: drafts are verified by the exact pass, the output equals greedy decoding.
lossless=False Every draft is kept and the exact pass only refreshes the cache (lossy, cheaper).
min_confidence Stop drafting in a cycle once the drafter's top probability falls below this value.
verifier A registered verifier by name: "greedy", "rejection" (lossless speculative sampling at T > 0, chains and trees), "typical" (Medusa's typical acceptance, lossy) or "accept_all".
verifier_options Keyword options of the verifier, e.g. {"threshold": 0.09, "alpha": 0.3} for "typical".
cuda_graphs Replay the exact passes and the draft steps from CUDA graphs (see below).
sync_interval Ordinary decoding reads tokens back and checks stop conditions every this many steps (default 8; the output is the same as reading every step).

Other conditioning variants are part of JumpConfig: several source layers (extra_source_layers) combined by a registered mixer ("concat", "fused_linear", "layer_attention"), the predictor ("mlp", "linear", its width and depth, or "flow" / "flow_linear": that mean followed by a few Euler steps of a rectified flow, trained by flow matching, with flow_steps, flow_sigma, flow_width), and ablations (use_state, use_token, token_residual).

Several prompts of different lengths decode as one batch, each row accepting its own number of drafts per cycle. With verifier="rejection" and a sampling selector the output follows the model's own sampling distribution (speculative sampling):

results = model.decode_batch([ids_a, ids_b, ids_c], 128)          # one DecodeResult per prompt
sampled = model.decode_batch(
    [ids_a, ids_b], 128,
    decoding=economize.DecodeConfig(cycle_length=4, verifier="rejection"),
    selector=economize.TopPSampler(temperature=0.7, top_p=0.9, seed=0, device="cuda"),
)

Each row may also have its own selector, stop condition and token limit (selectors=, stops=, and a list for max_new_tokens). Rows that share a selector object are drafted and verified together.

At batch size 1 a draft step and a short exact pass are bound by kernel launches. With DecodeConfig(cuda_graphs=True) the decoder records its exact chain passes and the jump drafter's steps as CUDA graphs on a static KV cache and replays them. The model keeps one decoder per configuration, so the graphs recorded in the first generation serve the following ones; model.refresh() drops them after the weights or the techniques change. Draft trees, dynamic-depth policies, KV-cache techniques and models with recurrent layers decode eagerly whatever the flag says.

Combining techniques

economize.apply takes any number of configurations and attaches them in a fixed order, whatever the order of the arguments: weight transforms (by their order: depth pruning, pruning, low rank, quantization), activation hooks, KV-cache wrappers, dynamic depth, the drafter, then the rest. Techniques that need data calibrate on the model as it is at that point, so quantization sees the pruned weights.

from economize.techniques.prune import PruneConfig
from economize.techniques.quant import QuantConfig
from economize.techniques.sparsity import TealConfig

model = economize.apply(
    base,
    PruneConfig(method="wanda", sparsity="2:4"),
    QuantConfig(format="int4", method="gptq", group_size=128),
    TealConfig(sparsity=0.4),
    economize.JumpConfig(target_layer=12, source_layer=18),
    calibration=calibration_ids,          # LongTensor [N, S], an iterable of batches, or a callable
)

See docs/weights.md for every weight and activation technique and its options.

Training

economize.train trains what the techniques add (drafters, readout adapters, LoRA on a drafter's path, early-exit heads) and, where a technique needs it, the base model:

from economize.train import fit

model = economize.apply(base, economize.JumpConfig(target_layer=16, source_layer=17, exit_layer=17))
fit(model, "data/qwen/train.bin", steps=2000, output_dir="runs/jump_m16_u17")   # uint32 token file
model = economize.load(base, "runs/jump_m16_u17/final")

fit picks the objective registered for the attached drafter (jump_rollout for jump, the objective of tskip/train.py). An existing transformers.Trainer pipeline uses EffTrainer with the same objectives:

from economize.train import EffTrainer, JumpRollout, economize_training_args

objective = JumpRollout(horizon=16)
args = economize_training_args(objective, "runs/jump_trainer", max_steps=2000, per_device_train_batch_size=8)
EffTrainer(model, objective, args, train_dataset=dataset).train()

pip install "economize[train]" adds accelerate, datasets and trl (fit itself needs only torch). docs/training.md lists the objectives, the data formats, data-parallel runs under accelerate launch and how to register an objective.

Ecosystem

Library Entry point Details
transformers model.generate(...), custom_generate Hub recipe logits processors, stopping criteria, return_dict_in_generate
Hugging Face Hub model.save_pretrained, push_to_hub, economize.from_pretrained only the technique weights are uploaded
PEFT economize.apply(peft_model, ...) the adapter finds the base model inside a PeftModel
kernels economize.integrations.kernels.kernelize(model), op(name) hub kernels for the base model and every added module
lm-evaluation-harness economize-lm-eval run --model economize --model_args pretrained=...,economize=... generation runs economize's decoding loop
TRL economize.integrations.trl.attach_rollouts in-process GRPO / RLOO rollouts with the drafter refreshed after each step
vLLM economize.backends.vllm drafter export (export_drafter(model, dir, backend="vllm")), plugin entry point, jump drafting in the V2 model runner (tested with vLLM 0.30)
SGLang economize.backends.sglang interface only (JumpDraftBackend); its registered exporter raises UnsupportedBackendError, and nothing is patched in SGLang yet

docs/ecosystem.md describes each integration and its tests.

Saving and loading

model.save_pretrained("my_drafter")       # economize_config.json + technique weights in safetensors
model = economize.load(base, "my_drafter")

Only the technique weights are saved. Checkpoints of the research code (tskip, a jump.pt file or its run directory) are detected and converted on load. Further formats are added through the FORMATS registry.

Extending economize

Everything that varies is a registry, and new entries are added by registration, never by editing economize:

Registry Module Entry-point group
TECHNIQUES economize.core economize.techniques
ARCHITECTURES, LAYER_KINDS economize.arch economize.architectures, economize.layer_kinds
FORMATS economize.core economize.formats
VERIFIERS, POLICIES economize.decoding economize.verifiers, economize.draft_policies
CACHES, MASKS, MASK_FORMATS economize.runtime economize.caches, economize.masks, economize.mask_formats
PREDICTORS, MIXERS economize.techniques.jump economize.jump_predictors, economize.jump_mixers
SKIP_SEARCHES, SKIP_METRICS economize.techniques.layer_skip economize.skip_searches, economize.skip_metrics
MEDUSA_TREES economize.techniques.medusa economize.medusa_trees
MTP_FAMILIES economize.techniques.vendor_mtp economize.mtp_families
CONFIDENCE_MEASURES, FLEXI_ROUTERS economize.techniques.dynamic_depth economize.calm_confidence, economize.flexi_routers
PRUNE_METHODS, DEPTH_CRITERIA economize.techniques.prune economize.prune_methods, economize.depth_criteria
QUANT_METHODS, QUANT_BACKENDS, QUANT_FORMATS, AWQ_SCALE_GROUPS, TORCHAO_TENSORS economize.techniques.quant economize.quant_methods, economize.quant_backends, economize.quant_formats, economize.awq_scale_groups, economize.torchao_tensors
FACTORIZERS economize.techniques.lowrank economize.factorizers
TEAL_BACKENDS economize.techniques.sparsity economize.teal_backends
EVICTION_METHODS economize.techniques.kv_evict economize.kv_eviction
KERNEL_OPS economize.integrations.kernels economize.kernel_ops
OBJECTIVES, DIVERGENCES, SCHEDULES, STATE_LOSSES economize.train economize.objectives, economize.divergences, economize.schedules, economize.state_losses
PROMPT_SOURCES, HARDWARE, SHAPE_READERS economize.bench economize.prompt_sources, economize.hardware, economize.shape_readers
EXPORTERS economize.backends economize.exporters
VLLM_DRAFTERS economize.backends.vllm economize.vllm_drafters
CT_SCHEMES economize.integrations.compressed_tensors economize.ct_schemes

A new predictor, for example:

from economize.techniques.jump import PREDICTORS, Predictor

@PREDICTORS.register("residual_linear")
class ResidualLinear(Predictor):
    def __init__(self, hidden_size, config):
        super().__init__(hidden_size, config)
        self.proj = torch.nn.Linear(2 * hidden_size, hidden_size)

    def forward(self, source, token_embedding):
        return token_embedding.float() + self.proj(torch.cat([source, token_embedding], -1).float())

model = economize.apply(base, economize.JumpConfig(target_layer=12, source_layer=18, predictor="residual_linear"))

An installed package can contribute the same entry without an import, through the entry-point group in its pyproject.toml:

[project.entry-points."economize.jump_predictors"]
residual_linear = "my_pkg.predictors:ResidualLinear"

A new technique subclasses economize.Technique with a frozen TechniqueConfig dataclass and is registered with @economize.register_technique. A drafting technique declares Capability.DRAFTER and returns an object that follows the economize.decoding.Drafter protocol. A new model family registers an ArchAdapter under its model_type; most decoder-only families share the built-in StandardDecoderAdapter. Family facts that techniques and the cost model need are set on the adapter: layer_layout = "llama" gives AWQ the Llama scale groups, shape_fields corrects the cost model's config reading, and module_path(module) names modules for exporters. A drafter is written for a serving backend by a DrafterExporter registered with @economize.backends.register_exporter("<backend>", "<technique>"); a new backend is one such class. docs/ARCHITECTURE.md describes every contract.

Scope of version 0.2

  • Techniques: jump drafting (frozen, LoRA, full; one or several source layers mixed by concatenation, a fused linear map or layer attention; MLP or linear predictors), depth_prune, prune, lowrank, quant, teal, draft_quant.
  • Decoding: batched ragged prompts with per-row selectors, stops and limits, chains and draft trees, greedy verification, lossless speculative sampling on chains and trees, typical acceptance, lossy drafting, fixed or confidence-based draft length, CUDA graphs for chain decoding, a log of every model call for cost accounting.
  • Training: economize.train (fit, EffTrainer, registered objectives).
  • Checkpoints record a fingerprint of the base weights; loading onto different weights warns (economize.TeacherMismatchWarning, off with check_teacher=False).
  • Not yet: flex-attention masks for draft trees (trees use sdpa or eager masks), draft trees on models with recurrent layers, and the vLLM / SGLang draft step.

Tests

cd TJEPA-Eff && source env.sh
PYTHONPATH=economize/src:economize/tests:. $PY -m pytest economize/tests            # unit, technique, integration, oracle (CPU)
PYTHONPATH=economize/src:economize/tests:. $PY -m pytest economize/tests -m slow    # trained checkpoints on Qwen2.5-0.5B
PYTHONPATH=economize/src:economize/tests:. $PY -m pytest economize/tests -m gpu     # batched decoding on a CUDA device

The lint (uvx ruff check src tests hub) and type (uvx mypy --python-executable <python> src/economize) gates and the design rules a change must follow are in docs/CONTRIBUTING.md.

The unit tests check that the exact path equals the model's own logits on every supported family, that batched lossless decoding equals greedy decoding of each prompt alone (chains, trees and jump drafters, with partial acceptance and hybrid recurrent layers), that rejection sampling outputs the target distribution (chi-square tests with a negative control), the recipe's ordering and rollback, and the checkpoint round trips. The oracle tests check that economize produces the same tokens, the same log of model calls and the same acceptance as the research decoder tskip.decode.Decoder, on random drafters of a tiny model and on three trained drafters of Qwen2.5-0.5B.

License

Apache License 2.0, see LICENSE.

Metadata

Release files for economize 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for economize 0.3.0
File Size Uploaded Provenance Actions
economize-0.3.0.tar.gz 876.0 kB Without provenance Details for economize-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for economize 0.3.0
File Interpreter ABI Platform Actions
economize-0.3.0-py3-none-any.whl Python 3 none any Details for economize-0.3.0-py3-none-any.whl

Total release size: 1.5 MB

Release files / economize-0.3.0.tar.gz

Download URL economize-0.3.0.tar.gz
Size 876.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a55e9d6ed2abe68eadc4721d168cdc06c05293fa9ea12004e5b8d55bab632749
BLAKE2b-256 checksum
How to use checksums
d97b818e72fd8db341c479932b684a1e279aefafbc0a2a69451f185b8e08d3ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.12

Release files / economize-0.3.0-py3-none-any.whl

Download URL economize-0.3.0-py3-none-any.whl
Size 668.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d728f87e5762a4501d9c674b654df392eee5f3883587728585c7d374d6092ad6
BLAKE2b-256 checksum
How to use checksums
698ac268994bb1ed2276fad679a884869987e5ffcdb0c5f974c6fb58f3867def
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.12

Security for economize 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page