fix(s31): close 1 review MAJOR — VECLIB_MAXIMUM_THREADS + honest determinism claim + the missing pin guard

This commit is contained in:
Kjell Tore Guttormsen 2026-07-25 12:52:52 +02:00
commit fb4c593924
2 changed files with 83 additions and 9 deletions

View file

@ -17,9 +17,20 @@ Registered in ``tests/test_okf.py``'s ``_MAF_FREE_MODULES`` (direct-import AST g
catches a lazy runtime import an import-time guard would miss by
``tests/test_semretrieval_loadbearing.py``.
Determinism: BLAS reduction order depends on the thread count, so the thread pins below are set
before numpy is imported (this module is the only numpy importer in the package). Vectors are
float64 and C-contiguous; ranking uses the total-order key ``(-round(score, 9), id)``.
Determinism, stated precisely because the two halves rest on different things:
* **Ranking order** is guaranteed by the total-order key ``(-round(score, 9), id)`` rounding
absorbs last-bit float noise and ``id`` settles exact ties. This is the guarantee that actually
holds, and it does NOT depend on the thread pins. It has to do real work: ``np.dot`` over two
separately allocated but bit-identical vectors can differ by one ulp, because the reduction path
varies with buffer alignment. Rounding is what makes that unobservable in the ranking.
* **Byte-identical vector artifacts** (the ``.npy``/``.jsonl`` store, across machines) are what the
thread pins below defend: BLAS reduction order depends on the thread count, and ``FakeEmbedder``
calls ``np.linalg.norm``, which can dispatch to BLAS. The pins are defence-in-depth for that
narrower claim never the basis of the ranking guarantee.
Vectors are float64 and C-contiguous. This module is the only numpy importer in the package, which
is what makes an import-time pin viable at all.
"""
from __future__ import annotations
@ -31,10 +42,21 @@ from collections.abc import Callable, Sequence
from pathlib import Path
from typing import TYPE_CHECKING, Protocol, runtime_checkable
# Pin BLAS threads BEFORE numpy is imported — OpenBLAS reads these at import time, and a
# thread-count-dependent reduction order is the one thing that makes float64 dot products drift
# between environments. Pinned to 1 => bit-exact run-to-run. ``setdefault`` so an operator can
# still override deliberately.
# Pin BLAS threads BEFORE numpy is imported — every one of these is latched by the time the numpy
# import completes (measured: setting them afterwards has no effect, even before the first BLAS
# call), so placement above the import is load-bearing, not stylistic. ``setdefault`` so an
# operator can still override deliberately.
#
# One variable per backend, because which one BITES depends on what numpy links:
# VECLIB_MAXIMUM_THREADS -> Accelerate (macOS; what numpy >= 2.0 links here by default)
# OPENBLAS_NUM_THREADS -> OpenBLAS (numpy's default wheel on Linux/Windows, and numpy 1.x)
# MKL_NUM_THREADS -> Intel MKL (conda-forge / Intel distributions)
# OMP_NUM_THREADS -> the OpenMP runtime underneath several of the above
# On THIS build the effective one is VECLIB_MAXIMUM_THREADS; the other three are measured no-ops
# against Accelerate. They stay because they are the correct knobs for an OpenBLAS/MKL/OpenMP
# deployment — that is a PORTABILITY argument, not a determinism one, and the distinction matters:
# before VECLIB was added, this block pinned nothing at all on the machine it ran on.
os.environ.setdefault("VECLIB_MAXIMUM_THREADS", "1")
os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")
os.environ.setdefault("OMP_NUM_THREADS", "1")
os.environ.setdefault("MKL_NUM_THREADS", "1")
@ -48,8 +70,11 @@ if TYPE_CHECKING: # verdicts imports agent_framework — keep it out of the run
# The structural score, passed in rather than imported — see module docstring.
SimilarityFn = Callable[[ProposalFeatures, ProposalFeatures], float]
# Dimension of the fake embedding. Small enough that a 500+ verdict base is a trivial matmul,
# large enough that distinct features do not collide.
# Dimension of the fake embedding. Large enough that distinct features do not collide, small
# enough that a 500+ verdict base stays cheap. Note there is no matmul on the ranking path:
# ``HybridRanker.rank`` calls ``cosine()`` once per candidate from inside the sort key — a Python
# loop over per-pair ``np.dot`` on 64-vectors — and re-invokes the embedder for every candidate on
# every call, caching nothing. Fine at this scale; the thing to change first if it ever is not.
EMBED_DIM = 64
# Blend weight for HybridRanker: score = weight * cosine + (1 - weight) * structural.