Download tests/test_correctness.py from Jakevin/mlx-ternary: direct link, hf CLI and curl.
- Browser
- Download file 4.65 kB
-
https://huggingface.co/Jakevin/mlx-ternary/resolve/main/tests/test_correctness.py
- Command line
-
hf download hf://Jakevin/mlx-ternary/tests/test_correctness.py
-
curl -L -o test_correctness.py https://huggingface.co/Jakevin/mlx-ternary/resolve/main/tests/test_correctness.py
4.65 kB
| # Copyright 2026 Jakevin | |
| # SPDX-License-Identifier: Apache-2.0 | |
| import mlx.core as mx | |
| import numpy as np | |
| import pytest | |
| from mlx_ternary import dequantize, pack_trits, tern_matmul | |
| # Shapes probed with the v3 kernel. The published maximum was 1.44e-6. | |
| CASES = [ | |
| (8, 64, 1), | |
| (16, 2048, 1), | |
| (16, 2048, 128), | |
| (40, 128, 3), | |
| (64, 256, 64), | |
| (64, 2048, 32), | |
| (128, 192, 32), | |
| (64, 6144, 32), | |
| (41, 512, 1), | |
| (41, 512, 7), | |
| (640, 2560, 1), | |
| (640, 2560, 128), | |
| (2560, 640, 1), | |
| (2560, 640, 128), | |
| (2048, 6144, 1), | |
| ] | |
| def _rel(y, ref) -> float: | |
| err = mx.abs(y - ref) | |
| mx.eval(err) | |
| denom = float(mx.max(mx.abs(ref)).item()) | |
| return float(mx.max(err).item()) / max(denom, 1e-8) | |
| def _layer(n, k, trits, scales): | |
| packed, _row = pack_trits(trits) | |
| return mx.array(packed), mx.array(np.asarray(scales, dtype=np.float32)), mx.array(dequantize(trits, scales)) | |
| def test_exact_ones_cover_qmv_and_gemm(): | |
| # Tail GEMV (K=64), 512-wide GEMV, 640 = 512+128, and the 32x32 GEMM. | |
| specs = [ | |
| (2, 64, 1, 0.5, 32.0), | |
| (1, 512, 1, 0.25, 128.0), | |
| (4, 640, 1, 1.0, 640.0), | |
| (32, 64, 32, 0.5, 32.0), | |
| (32, 128, 32, 0.25, 32.0), | |
| ] | |
| for n, k, batch, scale, expect in specs: | |
| trits = np.ones((n, k), dtype=np.int8) | |
| scales = np.full((n, k // 64), scale, dtype=np.float32) | |
| codes, scale_mx, _ref = _layer(n, k, trits, scales) | |
| x = mx.ones((batch, k), dtype=mx.float32) | |
| y = tern_matmul(x, codes, scale_mx, k) | |
| mx.eval(y) | |
| assert y.shape == (batch, n) | |
| assert float(mx.max(mx.abs(y - expect)).item()) == pytest.approx(0.0, abs=1e-4) | |
| def test_negative_and_zero_trits(): | |
| n, k = 2, 64 | |
| trits = np.zeros((n, k), dtype=np.int8) | |
| trits[0] = -1 | |
| scales = np.ones((n, 1), dtype=np.float32) | |
| codes, scale_mx, _ref = _layer(n, k, trits, scales) | |
| x = mx.ones((1, k), dtype=mx.float32) | |
| y = tern_matmul(x, codes, scale_mx, k) | |
| mx.eval(y) | |
| assert float(y[0, 0].item()) == pytest.approx(-64.0, abs=1e-4) | |
| assert float(y[0, 1].item()) == pytest.approx(0.0, abs=1e-4) | |
| def test_vector_input_is_one_row(): | |
| trits = np.ones((3, 64), dtype=np.int8) | |
| scales = np.ones((3, 1), dtype=np.float32) | |
| codes, scale_mx, _ref = _layer(3, 64, trits, scales) | |
| y = tern_matmul(mx.ones((64,), dtype=mx.float32), codes, scale_mx, 64) | |
| mx.eval(y) | |
| assert y.shape == (1, 3) | |
| def test_random_shapes_under_1e_3(): | |
| rng = np.random.default_rng(7) | |
| worst = 0.0 | |
| for n, k, batch in CASES: | |
| trits = rng.integers(-1, 2, size=(n, k), dtype=np.int8) | |
| scales = rng.uniform(0.02, 0.2, size=(n, k // 64)).astype(np.float32) | |
| codes, scale_mx, ref_w = _layer(n, k, trits, scales) | |
| x = mx.array(rng.standard_normal((batch, k)).astype(np.float32)) | |
| y = tern_matmul(x, codes, scale_mx, k) | |
| ref = x @ ref_w.T | |
| mx.eval(y, ref) | |
| rel = _rel(y, ref) | |
| worst = max(worst, rel) | |
| assert np.isfinite(rel) and rel < 1e-3, f"{n}x{k} B={batch} rel={rel:.3e}" | |
| del codes, scale_mx, ref_w, x, y, ref | |
| mx.clear_cache() | |
| assert worst < 1e-3 | |
| def test_rejects_bad_shapes(): | |
| trits = np.ones((2, 64), dtype=np.int8) | |
| codes, scale_mx, _ref = _layer(2, 64, trits, np.ones((2, 1), np.float32)) | |
| with pytest.raises(ValueError): | |
| tern_matmul(mx.ones((2, 3, 64)), codes, scale_mx, 64) | |
| with pytest.raises(ValueError): | |
| tern_matmul(mx.ones((1, 32)), codes, scale_mx, 32) | |
| def _case(n, k, b, seed=7): | |
| rng = np.random.default_rng(seed) | |
| trits = rng.integers(-1, 2, size=(n, k), dtype=np.int8) | |
| scales = rng.uniform(0.05, 0.3, size=(n, k // 64)).astype(np.float32) | |
| packed, _ = pack_trits(trits) | |
| x = rng.standard_normal((b, k)).astype(np.float32) | |
| return x, packed, scales, x @ dequantize(trits, scales).T | |
| def test_non_contiguous_x(b): | |
| x, packed, scales, ref = _case(64, 512, b) | |
| xt = mx.array(np.ascontiguousarray(x.T)).T # transposed view | |
| y = np.array(tern_matmul(xt, mx.array(packed), mx.array(scales), 512)) | |
| assert np.abs(y - ref).max() / np.abs(ref).max() < 1e-3 | |
| def test_shape_mismatch_raises(): | |
| with pytest.raises(ValueError): | |
| tern_matmul(mx.zeros((1, 128)), mx.zeros((4, 16), dtype=mx.uint8), mx.zeros((4, 2)), 128) | |
| with pytest.raises(ValueError): | |
| tern_matmul(mx.zeros((1, 128)), mx.zeros((4, 32), dtype=mx.uint8), mx.zeros((4, 3)), 128) | |
| def test_empty_batch(): | |
| y = tern_matmul(mx.zeros((0, 128)), mx.zeros((4, 32), dtype=mx.uint8), mx.ones((4, 2)), 128) | |
| assert tuple(y.shape) == (0, 4) | |