Skip to main content

FP64-accurate matrix multiplication from BF16 hardware via Ozaki Extract in JAX

Project description

ozaki-jax

FP64-accurate linear algebra on BF16/FP32-oriented hardware (e.g. TPU) using Ozaki Extract in JAX.

ozaki-jax features accurate matmul with higher-level routines for Gram matrices, residuals, and iterative-refinement solves.

Install

# inside an active environment
uv pip install -e .

Optional TPU dependency:

uv pip install -e ".[tpu]"

Public API

from ozaki_jax import gram, inv, lstsq, matmul, matmul_numpy, norm, residual, solve
  • matmul(A, B, ...): accurate matrix multiply with host or on-device Ozaki pipelines.
  • matmul_numpy(A, B, ...): NumPy-only mirror for host path and testing.
  • gram(A, ...): computes symmetric A.T @ A.
  • residual(A, x, b, ...): computes accurate b - A @ x.
  • solve(A, b, ...): iterative refinement solve with FP32 factorization plus accurate residuals.
  • inv(A, ...): accurate matrix inverse via iterative-refinement solve.
  • lstsq(A, b, ...): least squares min ||A x - b|| via FP32 QR with augmented-system refinement.
  • norm(x, ord=...): vector/matrix norms; matrix spectral norm uses the accurate Gram matrix.

Input/output typing:

  • NumPy input -> NumPy output
  • JAX input -> JAX output
  • Mixed NumPy/JAX inputs are rejected

Quick Start

1) Accurate Matmul (Default Host Pipeline)

import numpy as np
from ozaki_jax import matmul

A = np.random.randn(256, 256)
B = np.random.randn(256, 256)
C = matmul(A, B)

2) On-device Pipeline

import jax
from ozaki_jax import matmul

jax.config.update("jax_enable_x64", True)

C = matmul(A, B, pipeline="ondevice", accumulation="fused")
# or:
C2 = matmul(A, B, pipeline="ondevice", accumulation="bf16_interleaved")

3) Gram / Residual / Solve

import numpy as np
import jax
from ozaki_jax import gram, residual, solve

jax.config.update("jax_enable_x64", True)

A = np.random.randn(256, 256)
b = np.random.randn(256)
x = solve(A, b)                # default: residual_mode="f64"
r = residual(A, x, b)          # default: mode="f64"
G = gram(A)                    # default: mode="f64", symmetric result

Matmul Options

matmul(A, B, n_slices=8, safe_mode="raise", pipeline="host", accumulation="fused", precision="high")

Pipeline:

  • pipeline="host" (default): FP64 host extraction + triangular pair scheduling.
  • pipeline="ondevice": FP64 input -> FP32 hi/lo split + on-device extract/GEMM/accumulation.

On-device accumulation (pipeline="ondevice" only):

  • fused (default): split + extraction + GEMMs + 2Sum in one JIT call.
  • bf16_interleaved: BF16-cast extracted slices, interleaved GEMM+2Sum.
  • ondevice: separate on-device 2Sum accumulation after GEMMs.
  • host: transfer products and accumulate on host in FP64.

On-device precision presets (precision=):

  • high -> (n_hi=4, n_lo=1) -> 24 GEMMs
  • medium -> (n_hi=3, n_lo=1) -> 15 GEMMs
  • max -> (n_hi=5, n_lo=4) -> 65 GEMMs
  • custom tuple (n_hi, n_lo) is supported

Safety behavior (safe_mode=):

  • raise (default): raises ValueError when preflight fails.
  • fallback: returns plain FP64 A @ B when preflight fails.

Gram / Residual / Solve Details

gram(A, precision="high", accumulation="bf16_interleaved", mode="f64")

  • Computes A.T @ A, then symmetrizes via (G + G.T) / 2.
  • mode="f64" (default): native FP64 matmul, highest accuracy.
  • mode="ozaki": uses Ozaki pipeline and precision/accumulation.

residual(A, x, b, precision="high", accumulation="bf16_interleaved", mode="f64")

  • Computes b - A @ x for vector or matrix x.
  • Supports rectangular A.
  • mode="f64" (default) or mode="ozaki".

solve(A, b, precision="high", accumulation="bf16_interleaved", max_iterations=3, residual_mode="f64")

  • Solves A x = b using iterative refinement.
  • Uses FP32 solve steps with accurate residual recomputation.
  • residual_mode="f64" (default): best accuracy and usually best convergence.
  • residual_mode="ozaki": available when FP64 throughput is constrained.

Inv / Lstsq / Norm Details

inv(A, precision="high", accumulation="bf16_interleaved", max_iterations=3, residual_mode="f64")

  • Computes A^-1 for square A by solving A X = I with the same iterative refinement as solve. Prefer solve(A, b) when you only need A^-1 @ b.

lstsq(A, b, precision="high", accumulation="bf16_interleaved", max_iterations=3, residual_mode="f64")

  • Minimizes ||A x - b||_2 for full-rank A with rows >= cols (overdetermined or square). Underdetermined systems are rejected.
  • FP32 QR factored once, then refined on the augmented (Björck) system [I A; A^T 0][r; x] = [b; 0]. Refining the residual r alongside x is what reaches ~FP64 accuracy even when the least-squares residual is large; plain residual refinement of x alone stalls near FP32 there.
  • residual_mode="f64" (default) or "ozaki", as in solve.

norm(x, ord=None, precision="high", accumulation="bf16_interleaved", mode="f64")

  • Vector (1D) orders: None/2 (Euclidean), 1, inf, -inf, 0, or any p.
  • Matrix (2D) orders: None/'fro', 1, -1, inf, -inf, 2 (spectral).
  • Only the matrix spectral norm (ord=2) uses the Ozaki pipeline, computing sqrt(lambda_max(A^T A)) from the accurate Gram matrix; precision, accumulation, and mode are ignored for all other orders (exact FP64 reductions).
  • A general p-norm (p not in {1, 2, inf}) uses x**p/**(1/p), whose transcendentals run at ~fp32 precision on TPU (~1e-8); standard orders stay at ~fp64 on all backends. Validated on TPU v6e (benchmarks/ci_tier1_validate.py).

x64 Requirement

Enable x64 before using:

  • matmul(..., pipeline="ondevice", accumulation in {"fused", "bf16_interleaved"})
  • gram()
  • residual()
  • solve()
  • inv()
  • lstsq()
  • norm()
import jax
jax.config.update("jax_enable_x64", True)

Safety and Exactness

Preflight checks (for matmul/matmul_numpy) include:

  • Rank/shape compatibility
  • Finite inputs (no NaN/Inf)
  • Mantissa/extract budget constraints
  • BF16->FP32 exact inner-product bound

Core exactness condition for BF16-bounded slice values:

K * (2^p - 1)^2 < 2^24

In the default BF16 setting (p = 7), this gives K <= 1040.

Validation and Benchmark Scripts

Run from repo root with uv run:

# Core accuracy/validation
uv run python benchmarks/ci_cpu_validate.py
uv run python benchmarks/ci_gram_validate.py
uv run python benchmarks/ci_solve_validate.py
uv run python benchmarks/ci_tier1_validate.py
uv run python benchmarks/tpu_validate.py
uv run python benchmarks/tpu_full_validate.py

# Performance / profiling sweeps
uv run python benchmarks/bench_bf16_interleaved.py
uv run python benchmarks/bench_bf16_broadcast.py
uv run python benchmarks/bench_interleaved.py
uv run python benchmarks/tpu_phase_profile.py
uv run python benchmarks/tpu_scaling_sweep.py

Limitations

  • Unblocked exactness model still limits supported K for strict guarantees.
  • CPU execution does not represent TPU BF16/MXU behavior.
  • TPU speedups depend on matrix size, backend, and accumulation mode; profile on target hardware.

References

  • Mukunoki, D., Ogita, T., & Imamura, T. (2020). "DGEMM using Tensor Cores, and Its Accurate and Reproducible Versions." ISC High Performance 2020.
  • Mukunoki, D. (2025). "Ozaki Scheme-Based Accurate Matrix Multiplication on FP8 Tensor Cores." arXiv:2508.00441
  • Ozaki, K., Ogita, T., Oishi, S., & Rump, S.M. (2012). "Error-Free Transformations of Matrix Multiplication by Using Fast Routines of Matrix Multiplication and Its Applications." Numerical Algorithms, 59(1).

License

Apache 2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ozaki_jax-0.7.0.tar.gz (82.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ozaki_jax-0.7.0-py3-none-any.whl (34.2 kB view details)

Uploaded Python 3

File details

Details for the file ozaki_jax-0.7.0.tar.gz.

File metadata

  • Download URL: ozaki_jax-0.7.0.tar.gz
  • Upload date:
  • Size: 82.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for ozaki_jax-0.7.0.tar.gz
Algorithm Hash digest
SHA256 18b8108afd5ab37cc42ba56bbd4565f43218da5ff7e989da6c4348de5954b62b
MD5 3975a4dfea9f26cb9c158489465523ec
BLAKE2b-256 bf3ad07efb755bbd12af476a7949198c713c759745f5080956521a1e04de2f98

See more details on using hashes here.

File details

Details for the file ozaki_jax-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: ozaki_jax-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 34.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for ozaki_jax-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e92e44a813555e27272ed116b1f87f6aed7160cdfdb609a13b64f00e028b5287
MD5 15445a265c2e768f4b6b77f20d273679
BLAKE2b-256 dbc0565ae71ecf77e30ad4730748e80afd7041496e0aaf6e20180f281bee77b9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page