Skip to content

Commit fb82175

Browse files
committed
Speeding up serialisation of eigenvalues
1 parent a85385c commit fb82175

1 file changed

Lines changed: 39 additions & 13 deletions

File tree

colibri/ntk/ntkutils.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,31 @@ def get_replica_idx_list(replicas_path):
524524
return rep_list
525525

526526

527+
def _ntk_from_jacobian(jacobian_func, params):
528+
"""The NTK from an already-built jacobian function, as a host NumPy array.
529+
530+
Split out of :func:`compute_ntk` so that callers with an *epoch loop* can build
531+
the jacobian **once** and reuse it (see ``compute_eigenvalues_for_replica``).
532+
533+
Returns
534+
-------
535+
ntk : np.ndarray
536+
The NTK matrix, flattened to (nflavours * n_xgrid) x (nflavours * n_xgrid)
537+
ntk_shape : tuple
538+
Shape of the NTK matrix before flattening
539+
"""
540+
jacobian = jacobian_func(params)
541+
542+
# Compute NTK (nf,ng,nf,ng) -> assumes shape from jacobian
543+
ntk = jnp.einsum("ijk,lmk->ijlm", jacobian, jacobian)
544+
545+
# Flatten to (nflavors * n_xgrid) × (nflavors * n_xgrid)
546+
d1, d2, d3, d4 = ntk.shape # d1=nf, d2=ng, d3=nf, d4=ng
547+
ntk = ntk.reshape(d1 * d2, d3 * d4, order=NTK_ORDERING)
548+
549+
return np.asarray(ntk), (d1, d2, d3, d4)
550+
551+
527552
def compute_ntk(pdf_model, params, **kwargs):
528553
"""
529554
Compute the NTK matrix given model parameters.
@@ -546,26 +571,17 @@ def compute_ntk(pdf_model, params, **kwargs):
546571
Shape of the NTK matrix
547572
"""
548573
pdf_func = pdf_model.grid_values_func(XGRID, **kwargs)
549-
jacobian_func = jax.jacfwd(pdf_func)
550-
jacobian = jacobian_func(params)
574+
ntk, shape = _ntk_from_jacobian(jax.jacfwd(pdf_func), params)
551575

552-
# Compute NTK (nf,ng,nf,ng) -> assumes shape from jacobian
553-
ntk = jnp.einsum("ijk,lmk->ijlm", jacobian, jacobian)
554-
555-
# Flatten to (nflavors * n_xgrid) × (nflavors * n_xgrid)
556-
d1, d2, d3, d4 = ntk.shape # d1=nf, d2=ng, d3=nf, d4=ng
557-
ntk = ntk.reshape(d1 * d2, d3 * d4, order=NTK_ORDERING)
558-
559-
# Materialise the NTK to host (NumPy), then drop JAX's compilation cache. Each call
576+
# The NTK is on the host by now, so drop JAX's compilation cache. Each call
560577
# rebuilds the model -> a fresh jacfwd -> a new XLA program with this epoch's weights
561578
# baked in; without clearing, those compiled programs accumulate (~0.3 GB/call). This
562579
# matters because the report recomputes the *same* snapshot epochs many times (the
563580
# eigenvector_grid is resolved per presentation leaf, and the bounded functools cache
564581
# thrashes when those epochs interleave), so the per-call leak compounds -> OOM.
565-
ntk = np.asarray(ntk)
566582
jax.clear_caches()
567583

568-
return ntk, (d1, d2, d3, d4)
584+
return ntk, shape
569585

570586

571587
def compute_eigendecomposition(ntk_matrix, hermitian=True):
@@ -650,12 +666,22 @@ def compute_eigenvalues_for_replica(
650666
if epoch in pending_epochs
651667
}
652668

669+
# Build the jacobian ONCE for this replica, not once per epoch. It depends only
670+
# on the model and ``kwargs`` -- never on the weights, which enter as an argument
671+
# -- so a single ``jit`` cache key serves every epoch: one trace, one compiled
672+
# program, reused.
673+
# Do not clear caches inside this loop: it is process-global and replicas run in a
674+
# ThreadPoolExecutor, so one thread clearing would force its siblings to recompile.
675+
# ``jac`` is released with this frame when the replica is done.
676+
pdf_func = pdf_model.grid_values_func(XGRID, **kwargs)
677+
jac = jax.jit(jax.jacfwd(pdf_func))
678+
653679
for epoch, param_file in param_files.items():
654680
if max_epoch is not None and epoch > max_epoch:
655681
continue
656682
params = jnp.load(param_file)["params"]
657683

658-
ntk, shape = compute_ntk(pdf_model, params, **kwargs)
684+
ntk, shape = _ntk_from_jacobian(jac, params)
659685
if ntk_shape is None:
660686
ntk_shape = shape
661687

0 commit comments

Comments
 (0)