SOL - Sliced Optimal Transport for Language
SOL measures the gap between two text corpora with a double-sliced Wasserstein distance over contextual token embeddings. Lower is closer; identical corpora score zero.
Paper · Website · API · Models · Changelog · Contributing
Install
pip install 'sol-metric[text]' # compare texts (PyTorch + Transformers)
pip install 'sol-metric[torch]' # score existing embeddings on a GPU
pip install sol-metric # score existing embeddings with NumPy only
Python 3.10 or newer. For a specific CUDA version, install
PyTorch first; add accelerate to
load large checkpoints straight onto the GPU.
Use
from sol_metric import SOL
reference = ["The train arrived at noon.", "It rained throughout the night."]
generated = ["The afternoon train was late.", "Heavy rain fell overnight."]
print(SOL().from_texts(reference, generated))
The default encoder is Dream 7B, as in the paper: a 15 GB download that needs a GPU with about 16 GB. Any Hugging Face model can be used instead:
from sol_metric import SOL, TextEncoder
encoder = TextEncoder('gpt2-large')
metric = SOL()
print(metric.from_texts(reference, generated, encoder=encoder))
Features can be saved and reused, for example one reference against several generated corpora:
from tempfile import TemporaryDirectory
from sol_metric import Features
with TemporaryDirectory() as tmp:
metric.featurize_texts(reference, encoder=encoder, path=f'{tmp}/reference').close()
with Features.load(f'{tmp}/reference') as ref:
with metric.featurize_texts(generated, encoder=encoder) as gen:
print(metric.from_features(ref, gen))
Existing token embeddings need only NumPy. Stack the token states of all documents in one array and mark where each document starts and ends:
import numpy as np
from sol_metric import SOL
rng = np.random.default_rng(0)
x = rng.normal(size=(12, 16)) # corpus 1: 12 token states of dimension 16
x_offsets = [0, 4, 7, 12] # 3 documents: rows 0-3, 4-6 and 7-11
y = rng.normal(size=(10, 16)) # corpus 2: 10 token states
y_offsets = [0, 5, 10] # 2 documents: rows 0-4 and 5-9
print(SOL().from_tokens(x, x_offsets, y, y_offsets))
Good to know:
- Results are reproducible: the random directions and GP draws are seeded
(
seed,gp_seed, both 0 by default). Change them to measure the spread over seeds, as the paper does with nine seed pairs. - The paper's encoders (Dream 7B, GPT-2 Large, OLMo 2 1B, Qwen3 0.6B) are pinned
to the paper's checkpoint commits; pass
revision='main'for the latest. - Encoders run in FP16 on a GPU and FP32 on a CPU. Each document is encoded whole up to the model's limit (2,048 tokens for Dream, 1,024 for GPT-2 Large); longer documents are truncated with a warning.
- A GPU is used when PyTorch sees one; pass
device='cpu'to force the CPU. metric.last_runrecords the score, encoder, revision and all settings.
Reproduce the paper
The repository bundles LM1B and OpenWebText comparisons, encoded with GPT-2 Large. The 5k OpenWebText pair is the paper's seed-0 subset for MDLM.
python examples/compare_corpora.py owt # 32 texts per corpus
python examples/compare_corpora.py owt --validate --device cuda:0 # 5,000 per corpus
| 5k pair | Generated by | Expected score (FP16) |
|---|---|---|
| LM1B | vMF flow model | 0.961872093 |
| OpenWebText | MDLM, exact sampling | 2.167415507 |
--validate re-encodes both corpora and checks the score against the paper's
evaluation code on the same texts; this package matches it to 1e-10. On an RTX
5090 the OpenWebText check takes about 2.5 minutes. The paper's table reports
2.167356 because it used GPT-2 token IDs instead of the decoded text. The 5k
texts (20 MB) are in the Git repository, not the PyPI package; see the
validation manifest.
Settings
SOL(...) |
Default | Meaning |
|---|---|---|
n_directions, n_projections |
8,192 for Dream, else 1,024 | token directions and GP slices |
n_quantiles |
64 | quantile bins per direction |
lengthscale |
0.1 | RBF length scale of the GP slices |
seed, gp_seed |
0, 0 | random directions and GP draws |
scale |
hidden dimension | factor on the squared distance |
device |
'auto' |
CUDA when available, else NumPy |
Memory is handled automatically: features stay in RAM while they fit and go to temporary files otherwise, and batch sizes never change scores; if memory runs out, the error names the batch size to lower. All settings are in the API reference.
What SOL computes
Each document is the uniform measure μᵢ on its token states in ℝᵈ, and a corpus is the uniform distribution over its documents. A unit direction ξ and a Gaussian-process draw g map every document to one number, M(μᵢ) = ⟨g, Q((e_ξ)#μᵢ)⟩, the GP-weighted integral of its projected quantile function. SOL averages one-dimensional Wasserstein distances over both:
\mathrm{SOL}(\boldsymbol\mu,\boldsymbol\nu)^2
= d\;\mathbb E_{\xi\sim\mathcal U(\mathbb S^{d-1}),\,g\sim\gamma}
\Big[\mathrm W_2^2\big((M_{\xi,g})_\#\boldsymbol\mu,\ (M_{\xi,g})_\#\boldsymbol\nu\big)\Big]
with d the hidden dimension and γ a zero-mean GP with RBF covariance. The
estimator uses n_directions × n_projections slices, exact quantile-bin
integrals and exact transport for unequal corpus sizes.
Cite
@misc{kornhardt2026solmeasuringgapstext,
title={SOL: Measuring Gaps between Text Distributions by Double Sliced Wasserstein Metrics},
author={Gregor Kornhardt and Moritz Piening and Jannis Chemseddine and Gabriele Steidl},
year={2026},
eprint={2610.06513},
archivePrefix={arXiv},
primaryClass={cs.CL},
url={https://arxiv.org/abs/2610.06513},
}
License
Apache-2.0. Model checkpoints and corpora keep their own licenses.
Metadata
Release files for sol-metric 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| sol_metric-0.1.1.tar.gz | 243.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sol_metric-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 279.0 kB
Release files / sol_metric-0.1.1.tar.gz
| Download URL | sol_metric-0.1.1.tar.gz |
|---|---|
| Size | 243.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5ef4c21d42d7f2710bebe17c47352be339f637d4b83541888e5baba383c97698
|
|
BLAKE2b-256 checksum How to use checksums |
14ac56d63f8f7418763ad7b9ab2b7c3c85531b2cdb124171a88b9f78f41932c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / sol_metric-0.1.1-py3-none-any.whl
| Download URL | sol_metric-0.1.1-py3-none-any.whl |
|---|---|
| Size | 35.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0b2c7373088932f23e908f0009f70d8db64cd7ccb95a1f6f381f80aae4199989
|
|
BLAKE2b-256 checksum How to use checksums |
f2691535ed88271c6baf90b8c8a3646187cc10af528bc9cc4aed783537dfa55c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logSecurity for sol-metric 0.1.1
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for sol_metric-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logA 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.