Count-based word embeddings (PPMI + SVD), a reimplementation of Levy, Goldberg &
Dagan (2015). CONTRIBUTING.md covers setup, test layout and releases. Everything
runs under uv (uv run pytest, uv run ruff check .).
uv.lock records the project's own version. After bumping version in
pyproject.toml, run uv lock and commit it. Locally invisible (uv run
re-locks silently), but CI uses --locked and every job fails — this has
broken a release tag once.
Same arguments must produce the same bits on any machine at any core count — users key cached artifacts and published results on this.
pair_counts.merge_order()pins the float32 summation order; it must never depend on completion order, core count or chunk count.- Accumulate in float64, cast to float32 once (
to_count_matrix). Accumulating in float32 rounds at every addition and changes the low bits. - Emit pairs centre-major, context position ascending —
np.add.atapplies additions in index order, so this is observable. - Never swap
random.Randomfor a numpy generator in the counting path; it would change every randomized result at the same seed. The vectorized counter reproduces the exact draw stream (seeCountPairsClosure._subsample_draws). - Per-chunk RNG is seeded from
Path(...).stem, not the full filename — the full name once made.pklvs.npzchunks draw different numbers.
The gate: tests/test_pair_counts_equivalence.py compares the live counter
bit-for-bit (assert_array_equal, deliberately not allclose) against the
frozen snapshot bench/reference.py. Run it before and after touching
hyperhyper/pair_counts.py:
uv run pytest tests/test_pair_counts_equivalence.py -m slow
bench/reference.py may only change for non-counting concerns (record why in
its header). If a change cannot hit bit-identity, that is a finding to raise,
not a tolerance to widen.
Several documented performance beliefs here were wrong when measured (e.g. the
tokenization pool was 5.6x slower than serial). Use bench/bench_pair_counts.py
and bench/bench_svd.py. This machine is ±35% noisy: alternate A/B ordering
and compare medians — back-to-back same-order runs manufacture regressions.
Quote best and report the noise level.
- Superseded claims are marked, not deleted — the wrong reasoning is kept on purpose. Comments say why, not what; tests document how their tolerances were derived.
tools/andbench/ship in the sdist but not the wheel; maintainer scripts belong there, never inhyperhyper/.- Bundled evaluation data needs per-artifact licence evidence (
docs/adr/0001). Never infer a dataset's licence from the article describing it. allow_pickle=Falseon everynp.load; the one pickle path (read_pickle, legacy.pklchunks) is trusted-input-only. Format is chosen by extension, never sniffed.- Fast suite by default;
-m slowadds full grids. spaCy tests skip withouten_core_web_sm— expected locally.