Input and Output
This page is the reference for the two ends of a Compresso workflow: the dense
input the models expect, and the SRPTensor output
format — how it is laid out, how to convert it, and how to save it so another
run or project can reload it.
Input data
Every model and the trainer expect a single 2D dense matrix of shape
(n_samples, dim):
NumPy array or Torch tensor are both accepted; arrays are wrapped with
torch.as_tensor.Floating dtype. Integer inputs are promoted to float automatically. By default the models run in
float32; non-float32floating inputs are cast to the model dtype before training and encoding.2D only. A clear error is raised for other ranks — flatten higher-rank data yourself (as the A First Example: Seeing What an SAE Learns flattens 28×28 images to length 784).
A common pattern is to fit on a training subset and transform everything, including rows the model never saw during training:
trainer.fit(embeddings[train_idx])
codes = trainer.transform(embeddings) # SRPTensor for all rows
The SRPTensor format
SRPTensor (“Sparse Representation”) stores a row-packed
fixed-k matrix. Instead of a dense (rows, cols_total) array, it keeps two
compact (rows, k) tensors:
cols—int64column indices of the active entries in each row.vals— the signed values at those indices.
plus the logical dense shape and an optional prefix_shape (so a batched
tensor (*prefix, cols_total) can be restored). For a matrix with H
columns and k active entries, storage is rows × k × 2 instead of
rows × H — a large saving when H is in the thousands.
srp = trainer.transform(embeddings)
srp.shape # (rows, cols_total) logical dense shape
srp.k # active entries per row
srp.nnz # total stored values = rows * k
srp.rows # number of rows
srp.cols_total # logical number of columns
srp.device, srp.dtype
srp.cols # (rows, k) int64 indices
srp.vals # (rows, k) values
Like a tensor, it supports .to(...), .cpu(), .cuda(), .detach(),
.clone(), and .contiguous(), each returning a new SRPTensor.
Building one directly
You do not need the trainer to make an SRPTensor — any dense tensor can be
projected to its top-k entries with from_dense():
import torch
from compresso import SRPTensor
dense = torch.randn(1000, 4096)
srp = SRPTensor.from_dense(dense, k=32, score_mode="abs")
# score_mode: "abs" (largest magnitude), "raw" (largest signed), "relu"
Converting to other formats
When a downstream tool needs a standard layout, convert on demand:
srp.to_dense() # torch.Tensor (rows, cols_total)
srp.to_coo() # torch.sparse_coo_tensor
srp.to_csr() # torch.sparse_csr_tensor
srp.to_csc() # torch.sparse_csc_tensor
srp.to_bsr((br, bc)) # block-sparse rows
srp.to_scipy_coo() # scipy.sparse.coo_matrix
srp.to_scipy_csr() # scipy.sparse.csr_matrix
srp.to_numpy_dict() # {"cols", "vals", "shape", "prefix_shape"} as NumPy
The SciPy conversions are convenient for handing codes to scikit-learn or other sparse-matrix tooling.
Saving and loading
The custom on-disk format round-trips an SRPTensor losslessly. The
convention is the .srp.pt extension:
from compresso import save_srp_tensor, load_srp_tensor
save_srp_tensor("codes.srp.pt", srp) # note: path first, then tensor
restored = load_srp_tensor("codes.srp.pt")
assert restored.shape == srp.shape
assert restored.k == srp.k
load_srp_tensor accepts map_location (forwarded to torch.load) so you
can move tensors between devices on load, and validate=False to skip bounds
checks for trusted files.
Using codes in another run or project
Because the format is self-contained, a typical division of labour is to encode once and analyze later — possibly in a different codebase that only needs the sparse codes, not the model:
# --- producer: train once, persist the codes ---
srp = TopKSAETrainer(cfg).fit_transform(embeddings)
save_srp_tensor("artifacts/items.srp.pt", srp)
# --- consumer: a separate script/project ---
import compresso.clustering as cc
from compresso import load_srp_tensor
srp = load_srp_tensor("artifacts/items.srp.pt")
clusters = cc.ClusteringPipeline([...]).fit(srp)
For storage you control yourself (a database blob, an archive, a custom
serializer), to_dict() returns a flat, versioned
payload — shape/prefix_shape plus the cols/vals tensors — and
from_dict() rebuilds the tensor from it. For a pure
NumPy view (e.g. to feed a non-Torch store), use
to_numpy_dict():
payload = srp.to_dict() # {"version", "layout", "shape", "cols", "vals", ...}
srp2 = SRPTensor.from_dict(payload)
The Clustering Sparse Codes page picks up exactly here: it loads sparse codes and turns them into interpretable clusters.