Skip to content

LU factorization¤

jaxmg.getrf computes \(PA=LU\) for a square nonsingular matrix, returning the packed factors and a replicated vector of zero-based pivot indices.

Common setup¤

This example uses four Python processes and a \(2 \times 2\) process grid. See distributed execution for initialization and launch options.

import jax

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

import jax.numpy as jnp
from jax.sharding import NamedSharding, PartitionSpec as P
from jaxmg import getrf


mesh = jax.make_mesh((2, 2), ("pr", "pc"))
jax.set_mesh(mesh)
matrix_sharding = NamedSharding(mesh, P("pr", "pc"))

T_A = 128
N = 512


@jax.jit
def make_matrix():
    lower = jnp.eye(N, dtype=jnp.float64) + jnp.tril(jnp.full((N, N), 0.5), -1)
    upper = jnp.diag(jnp.linspace(1.0, 2.0, N)) + jnp.triu(jnp.full((N, N), 0.25), 1)
    a = (lower @ upper)[::-1]
    expected_lu = jnp.tril(lower, -1) + upper
    return jax.reshard(a, matrix_sharding), jax.reshard(expected_lu, matrix_sharding)

Compute the factors and pivots¤

The constructed matrix requires a reversal of its rows. The packed result stores \(U\) on and above the diagonal and the off-diagonal entries of \(L\) below it; the diagonal of \(L\) is implicitly one.

a, expected_lu = make_matrix()
lu, piv = getrf(a, T_A=T_A)

# Validate the factors and permutation against the known solution.
permutation = jax.lax.linalg.lu_pivots_to_permutation(piv, N)
correct = jnp.allclose(lu, expected_lu) & jnp.array_equal(
    permutation, jnp.arange(N - 1, -1, -1, dtype=permutation.dtype)
)
correct.block_until_ready()
if jax.process_index() == 0:
    print(correct)

Warning

The public wrapper donates a to the compiled factorization. Do not use the input array after the call. Use donate=False to preserve it. This is less memory efficient because the original input and working buffer must coexist.

Advanced: control the outer jax.jit¤

getrf_jit_ctx leaves the outer compilation and donation boundary to the caller. The packed factor aliases the input work buffer; the pivot vector is a separate replicated result.

from functools import partial

from jaxmg import getrf_jit_ctx


@partial(jax.jit, donate_argnums=(0,))
def compiled_getrf(a):
    return getrf_jit_ctx(a, T_A=T_A)


a, _ = make_matrix()
lu, piv, status = compiled_getrf(a)
lu.block_until_ready()

When a is created inside the compiled function, XLA owns its lifetime and donate_argnums is unnecessary.

Advanced: call from inside jax.shard_map¤

getrf_shardmap_ctx accepts the local matrix block. Its factor remains local, while the pivot vector is identical on every rank and uses P() in the output specification.

from jaxmg import getrf_shardmap_ctx


@partial(jax.jit, donate_argnums=(0,))
@partial(jax.shard_map, mesh=mesh, in_specs=P("pr", "pc"),
         out_specs=(P("pr", "pc"), P(), P(("pr", "pc"))))
def local_factorize(a_block):
    return getrf_shardmap_ctx(a_block, T_A=T_A)


a, _ = make_matrix()
lu, piv, status = local_factorize(a)
lu.block_until_ready()

Ready-to-run scripts for the direct and caller-jitted interfaces are provided in the examples directory. See the getrf API reference for the complete argument and return-value documentation.