Tessero Unreleased
Unreleased, upcoming changes on main.
Added
-
Opt-in GPU-matching floating-point profile (
csrc/src/fpenv.c, declared intessero.h; featurefp_flush_profile). NVIDIA GPUs flush subnormal values to zero and expose no preserve mode, and NaN results can carry different bit patterns on different processors.tsr_fpenv_flush_enter/tsr_fpenv_flush_leaveswitch the calling thread to flush-to-zero for a scope and restore its previous state exactly;tsr_flush_subnormal_f32andtsr_canon_nan_f32normalise arrays in place with integer bit operations;tsr_nan_first_f32finds the first NaN, with f64 twins of the three array helpers. The flush scope is for f32 work only: NVIDIA GPUs keep f64 subnormals, so f64 inside a scope would diverge. Additive only: no existing function, default, golden or self-check changes, and nothing runs unless called. -
Reproducible-f32 transcendental primitives (
tsr_repro_rsqrt_f32,tsr_repro_exp2_f32,tsr_repro_exp_f32,tsr_repro_erf_f32;csrc/src/repro_f32.c, declared incsrc/include/tessero.h). A new, original contribution, NOT part of the ported NumPy/SciPy architecture. NumPy/SciPy transcendentals are accuracy-first and defer to the systemlibm/vendor math, which is not bit-reproducible across hardware, drivers, or SIMD width. These are reproducible by construction - built only from correctly-rounded f32 operations, giving one defined f32 bit-value on every SIMD width, thread count, and CPU ISA (and, via a mirrored GPU shader, the GPU). Proven bit-identical across scalar/AVX2/AVX-512/NEON/SVE, 1 vs 4 threads, and x86 == ARM (csrc/repro/ARCH_MATRIX.md); four frozen goldens + canonical spec incsrc/repro/GOLDENS.md. Minimax coefficients are from Cephes (public domain) and Abramowitz & Stegun 7.1.26; the reproducible construction and the resulting bit-identity are the contribution. The ~1,845 parity-gated NumPy/SciPy functions are unaffected. -
Reproducible
recipandtanhprimitives (tsr_repro_recip_f32,tsr_repro_tanh_f32), with goldensf7f9ec19and5b43f3d6incsrc/repro/GOLDENS.md. -
Reproducible composites (
tsr_repro_{sigmoid,gelu_tanh,softmax,rmsnorm,layernorm}_f32): one definition of each composition, built on the primitives. Goldens confirmed on two independent hosts, each scalar == AVX2 == 4-thread. -
Reproducible backward composites (gradients of
gelu_tanh,erf,sigmoid,tanh,rmsnorm,layernorm, and the normgammagradient) in the single headercsrc/include/tessero_repro_f32.h. Goldens recorded incsrc/repro/GOLDENS.md, approved on two hosts. -
Talario's GPU kernels mirror these definitions and match them bit for bit on twelve NVIDIA GPU models across four generations, Ampere to Blackwell (attested at 1024 x 1024 by Talario); other GPUs are not yet attested.
-
NumPy/SciPy parity program (
prompts/numpy-scipy-parity-program.md, ADR 0011, ADR 0012). Coverage is measured, not asserted.tools/parity/census.pylists every public symbol of NumPy 2.4.4 and SciPy 1.17.1, andtools/parity/report.pyclassifies each one from the code and the recorded fixture runs (docs/project/numpy-scipy-coverage.md).- Kernel function registry (ADR 0011). One C table of functions (ufunc, gufunc, distribution, routine,
random) interpreted at run time by both bindings. The extension registers
Tessero\Ext\Special,Stats,NpandRandom\Generatorfrom it at MINIT. The FFI façadesTessero\Special,Stats,Npand theGeneratormethods are generated from it (tools/parity/gen-facades.php). - scipy.special: every real-valued ufunc, from the code SciPy itself runs (xsf, Boost.Math and SciPy's
glue, vendored with their licences; see
THIRD_PARTY_NOTICES.md). - scipy.stats: the continuous and discrete distributions, on a port of
rv_continuous/rv_discrete(csrc/cxx/stats_dist.*: the public-method semantics and the generic QUADPACK/brentq/bisection fallbacks, with SciPy's QUADPACK and brentq vendored). Also descriptive statistics, correlation and hypothesis tests. A new classTessero\Stats\Distribution(andTessero\Ext\Distribution) providespdf...isf,stats,moment,entropy,intervalandrvs. - NumPy statistics, sets and searching (
Tessero\Np,Tessero\Ext\Np): median, quantile/percentile (all methods), nan-reductions, histogram/bincount/digitize, cov/corrcoef, unique and set operations, partition, lexsort, searchsorted, correlate/convolve. - NumPy array construction and manipulation (
Tessero\Np,Tessero\Ext\Np, ADR 0013): the split and stack families,concatenate,tile/repeat/resize,flip/roll/rot90, axis moves, broadcasting,append/insert/delete,diag/tri*/*_indices,*_like,take/compress/nonzero, index conversions,meshgrid,select/choose,pad(all modes). Also the numeric routines:outer/inner/kron/cross/tensordot,diff/gradient/trapezoid/unwrap,interp,logspace/geomspace/vander, thepoly*functions, the window functions,i0/sinc,nan_to_num, and the in-place writersput,putmask,place,copyto,fill_diagonalandput_along_axis. That is 118 functions, verified by NumPy fixtures on both backends. Integer results are exact above 2^53, includingunique, the set operations andquantile's discrete methods, which now keep the input's integer or bool dtype as NumPy does. - numpy.random.Generator distributions from NumPy's own
distributions.con the kernel PCG64. Streams are bit-identical to NumPy on both backends. The extension gains a statefulTessero\Ext\Random\Generator(defaultRng,integers,permutation,shuffle,getState/setState). - Fixtures generated by NumPy/SciPy for every registry function, replayed on both backends by
tools/parity/run-tests.php. They are generated in a pinned numerical environment (tools/parity/gen-fixtures.sh, ADR 0012), and every file records that environment. - Deviation rules (
docs/project/reference-deviations.md, with counts generated from the fixtures). These cover inputs where SciPy crashes or never returns, rows where SciPy's own value is undefined (found with AddressSanitizer or by history dependence), SciPy exceptions on invalid parameters where its contract says NaN, and a vectorised result that disagrees with SciPy's element-by-element one. tools/parity/claims-check.phpfails the build on coverage language or percentages that the generated pages do not back. CI runs the fixtures on both backends.- Coverage of real-world usage (
docs/project/usage-weighted-coverage.md).tools/parity/usage.pycounts how often the library code of 18 widely used scientific Python packages calls each NumPy and SciPy function, andreport.pyweights the verified coverage by those counts and lists the most-called functions that are not yet verified on both backends.
- Kernel function registry (ADR 0011). One C table of functions (ufunc, gufunc, distribution, routine,
random) interpreted at run time by both bindings. The extension registers
-
Beta tracking.
- The NumPy/SciPy coverage inventory
(
docs/project/numpy-scipy-coverage.md, 169 rows) is generated from the public API bytools/api-dump.phpandtools/gap_inventory.py, so it cannot claim a function that does not exist. - A gap analysis interprets the inventory.
- An open-issue list: 50 issues with acceptance criteria, kept in
.github/beta-issues.json.tools/beta-issues.phprenders it and imports it into GitHub withgh. - A live-testing checklist for Windows, macOS, arm64/musl, FPM, Laravel and long soaks.
- The NumPy/SciPy coverage inventory
(
-
Backend parity (ADR 0010,
docs/architecture/adr/0010-shared-kernels-and-vector-math.md):- The loop ufuncs moved from the extension into libtessero (
csrc/src/ufunc.c,tsr_ufunc_*). The FFI package gainsTessero\Math, with the same 74 functions, signatures,out:semantics and results asTessero\Ext\Math. - Memory mapping and
.npyheaders moved into libtessero (csrc/src/mmap.c:tsr_mmap_plan/open/flush/close,tsr_npy_header,tsr_npy_write_header). - The FFI package gains
NDArray::memmap(),openMemmap(),load($path, $mmapMode),save(),flush(),isMemmap(),isReadonly(),isWritable(),filename(), andTessero::info()['memory_mapped']. It checks open_basedir and refuses stream wrappers, like the extension. - The extension gains
.npyI/O:NDArray::load($path, ?$mmapMode),$a->save($path)(byte-identical tonumpy.save) andNDArray::openMemmap(), with Fortran-order files mapped with Fortran strides. - The extension reduces over a list of axes with
keepdims:sum prod min max any all mean var std, andargmin/argmaxwithkeepdims. Results are bit-identical to the FFI package. - Laravel:
Tessero::memmap(),load()andopenMemmap()on either backend.
- The loop ufuncs moved from the extension into libtessero (
-
Hardening:
- libFuzzer targets for the slice parser, the
.npyheader parser, memory-map planning and the JSON writer (csrc/fuzz/,make -C csrc fuzz), with seed corpora and oracles. - A standalone mutation driver for machines without libFuzzer
(
make fuzz-standalone). - Property-based tests (
tests/Unit/PropertyTest.php). - A cross-backend byte-parity test (
tests/Ext/BackendParityTest.php). - A soak harness (
tools/soak.php). - A nightly workflow: 30 min of fuzzing per target, 5.5 h soaks per backend, property tests ×50.
docs/operations/soak-testing.md, the memory-safety review, a Safeguards table in the memory model, and crash-debugging steps (core dumps, gdb, ASan builds of both bindings) in troubleshooting.tests/Unit/MemorySafetyTest.phpandext/tests/014-memory-safety.phpt.
- libFuzzer targets for the slice parser, the
-
Performance:
- Vectorised
exp/logfor float64 and float32 (csrc/src/vmath.h: branch-free, fdlibm algorithms, < 1 ulp).expon 1M values went from 9.3 ms to 1.2 ms. - Real-input FFT (
tsr_rfft,tsr_irfft: half-length transform);Fft::rfftandirfftuse it. sortuses an MSD + LSD hybrid radix sort with parallel buckets: 1M float64 went from 53 ms to 30 ms.
- Vectorised
-
Bench cases
log 1Mandrfft 2^20 real..npyfixtures written by NumPy (tests/fixtures/generate_npy.py). -
Error code -9
TSR_EIOfor operating-system I/O errors.
Earlier in this cycle
- Universal functions
Tessero\Ext\Math: 74 element-wise functions with NumPy broadcasting, type resolution andout:arrays.- Kernel family: bit-identical to the
NDArraymethods. - Loop family:
cbrt exp2 trunc arcsinh arccosh arctanh degrees radians erf erfc gamma lgamma signbit fmax fmin copysign nextafter logaddexp logaddexp2 fmod. - Checked against NumPy/SciPy (64 cases).
- Kernel family: bit-identical to the
- Memory-mapped arrays
NDArray::memmap($file, $mode, $shape, $dtype, $offset): the modes of numpy.memmap, shape inference,flush()/flush(sync: true), and the mapping released after the last view. - ADR-0009, guide pages Universal functions and Memory-mapped arrays.
Security
From the memory-safety review (docs/project/memory-safety-review.md):
- Element-count overflow (both backends, and C reshape). A shape whose
element count wraps around 64 bits could describe a small allocation as a
huge array. Such shapes could come from
fromBytes, fromunserialize()of a cached or queued payload, fromzeros/empty, or fromreshape. Negative dimensions with a positive product were also accepted when unserializing. All sizes are now overflow-checked before allocating. - FFI
Generator::shuffleon a read-only memory map wrote throughPROT_READpages and crashed the process. It now throws. - Preload header in the shared temp directory was written under a predictable name and reused if present. It is now created exclusively (0600) and deleted after loading.
- Library version check. The FFI package refuses a libtessero binary from a different version instead of risking mismatched layouts.
- Released memory is never passed to C. This covers
flush()after a mapping is closed and LAPACK pointers during PHP's shutdown destructor pass.
Fixed
-
Registry routine errors now use Tessero's exception classes. On both backends, an index error from a registry routine is
IndexError(IndexExceptionin the extension), and a dtype or type error isDTypeError(DTypeException); these used to be PHP'sValueError. FFI messages name the method in camelCase, as the extension's do (Np::putAlongAxis). A float given where an integer is required is a type error, as in NumPy. -
Np::unique, the set operations,quantileand 0-d integer results lost precision above 2^53. They are exact now. -
Np::searchsortedaccepted a sorted array that is not 1-D. It now raises, as NumPy does. -
Np::bincountrejected a PHP list of floats. It now accepts one and converts it withint(), as NumPy does for a Python list. -
Reference values depended on the generating machine. On AVX-512 CPUs, NumPy's float64
exp/log/power/expm1/log1p/sinh/arcsin/cbrtdiffer from the C library by an ulp on some inputs. All fixtures, including the olderparity.jsonandufunc.json, are now generated with those dispatch targets disabled (ADR 0012). Hand-set tolerances that only absorbed the difference were removed: random draws are compared exactly, andcov/corrcoef/correlate/convolveuse the default tolerance. -
Math::cbrtreturns the C library's value, as NumPy does outside AVX-512 (cbrt(27) = 3.0000000000000004). The Newton correction that matched AVX-512 NumPy was removed. -
Memory safety of the new numerical code (an independent verification and a sanitizer fuzz pass over all 539 registry functions, about 370 000 calls under AddressSanitizer and UBSan):
- Negative trimming proportions read out of bounds, and could crash PHP (
trim_mean,trimboth,trim1, andlevene/flignerwithcenter='trimmed'). lmomentwith a huge order read out of bounds.cumulative_sum/cumulative_prodwithinclude_initialother than 0/1 returned uninitialised memory.bincountwith ±inf indexed memory wildly.- C++ exceptions (allocation failures, oversized requests) terminated the PHP process instead of raising. Every C++ entry point is now guarded, including callbacks from the vendored QUADPACK and brentq.
- Parameters that size an array now raise
MemoryErrorbeyond the memory budget, or the machine's RAM when none is set. - NaN integer parameters, oversized axis and bin values, and integer overflow in
fisher_exactandcramervonmises_2sampare now handled; every float→int conversion is defined. tests/Unit/AdversarialInputTest.phppins every case on both backends.
- Negative trimming proportions read out of bounds, and could crash PHP (
-
Where SciPy itself crashes, Tessero returns NaN; where SciPy raises, Tessero raises. Mathieu functions for |q| > 2·10⁷ or m > 3·10⁸;
hyp2f1for |c| ≥ 2⁵³ on its recurrence;hyp1f1with |b| ≥ 2⁵² on its integer-b path (these three crash SciPy).levy_stablewith α = 1 far in the tail raises (SciPy:RecursionError). A Boost overflow raises (SciPy:OverflowError), for example inncf.pdfandncf.isf. -
Integer inputs follow NumPy's int64 arithmetic.
ptp,nansum,nanprod,nanmin,nanmax, the cumulative functions,partition,correlateandconvolveare exact above 2⁵³ and wrap on overflow as NumPy does.correlate/convolvereturn NumPy's integer dtype. -
NumPy input validation:
bincountrejects float arrays and a non-integerminlength,ptprejects booleans, andaveragechecks weight shapes (_weights_are_valid). -
Vendored SciPy code on inputs where SciPy itself fails (
THIRD_PARTY_NOTICES.md). The spheroidal wave functions' work arrays are sized for their loops (SciPy overruns them).qstarno longer reads before its array for m = 0. cdflib's non-central t sum (nctdtridf,nctdtrinc) stops only when it provably cannot terminate, instead of looping forever. -
sort()/argsort()ignored kernel errors (both bindings). When the memory budget ran out while the kernel allocated sort scratch space, the result came back unsorted (or with zeroed indices) instead of throwing. Both bindings now check every return code and throwMemoryErrororMemoryException. The FFI sparse constructors and products (tsr_dense_to_csr,tsr_csr_matvec,tsr_csr_matmat) are now checked too. Regression tests are inMemorySafetyTestand014-memory-safety.phpt(finding F-9 in the memory-safety review). -
Slice parser, found by fuzzing:
- A blank item (
"0, ,1") read past its comma, an out-of-bounds read in C. Blanks are now bounded, and"1 , 2"(blanks around commas) parses as it should; it was wrongly rejected before. - Steps near 2^62 overflowed the element-count and stride arithmetic, in C and in the FFI package's PHP slicer.
- A blank item (
-
FFI
NDArraybinary and unary operations without:wrote straight intoouteven whenoutpartially overlapped an input (a shifted view of the same buffer), so they read already-overwritten values. They now go through a temporary, as the extension does. -
cloneof an FFI array now copies the data (it shared the buffer), matching the extension. Writing a clone no longer changes the original or a memory map. -
Multi-axis
mean/var/stdin the FFI package now merge the axes in ascending order whatever order they are listed in, so results do not depend on the listing order. -
mean,varandstdcompute exactly as NumPy does (two-pass pairwise) on both backends;cbrtcorrects glibc's 1-ulp error on exact cubes.
Verification record (reference machine: Linux x86-64, 2 cores, AVX-512, PHP 8.4.21, GCC 13, NumPy 2.4.4)
- C: 270 checks, optimised, ASan + UBSan, Clang without OpenMP.
- Fuzzing: about 13 million mutated inputs across the four targets under ASan + UBSan (standalone driver), clean after the two slice-parser fixes. libFuzzer itself could not be installed here (package downloads blocked); CI runs it.
- FFI suites: 706 tests in all three JIT modes (JIT off, function, tracing).
- Extension: 14 phpt files and 505 parity/ufunc cases, also under ASan + UBSan. Unit, extension and backend-parity suites together: 1 214 pass, 26 FFI-only skipped. Preload end-to-end passes.
- Laravel bridge: 5 tests on both backends. Documentation examples: 59 checks with both backends.
- Soak (
tools/soak.php): 30 minutes per backend, running concurrently.- Extension, JIT off: 785 608 iterations.
- FFI package, tracing JIT: 475 522 iterations.
- Result: native and mapped bytes back at 0 after every iteration; heap slope 0; RSS slope +13.7 / -5.2 KiB/min (thresholds 16 / 64); no descriptor growth. PASS.
- An earlier 11-minute run of both backends was also clean.
- Not run: the Windows memory-map path (
csrc/src/mmap.c, Win32 API, never compiled), the CI matrix, 24-72 h soaks.