Aleksei Ustimenko commited on
Commit
02f6649
·
1 Parent(s): e33f2ba

Restore production sharding and warm-start support

Browse files
README.md CHANGED
@@ -229,7 +229,11 @@ The example uses `datasets/train/foundation_5000.jsonl`, writes
229
  parameters through JSON. The command writes the trained model at the end of
230
  the run.
231
 
232
- To skip burn-in using compatible post-burn-in sampler states:
 
 
 
 
233
 
234
  ```bash
235
  hamiltonzero train examples/train.json --reuse-mcmc path/to/mcmc-states
@@ -237,7 +241,9 @@ hamiltonzero train examples/train.json --reuse-mcmc path/to/mcmc-states
237
 
238
  For multisystem training, the path is a directory containing
239
  `<system-index>.eqx` files. For a one-system training panel it may be a single
240
- file.
 
 
241
 
242
  ## Fine-tune
243
 
 
229
  parameters through JSON. The command writes the trained model at the end of
230
  the run.
231
 
232
+ Set the optional top-level `checkpoint` field to start from a full router-model
233
+ checkpoint. This loads model parameters only; KFAC state, sampler state, the
234
+ step counter, and the learning-rate schedule start fresh.
235
+
236
+ To load compatible sampler states:
237
 
238
  ```bash
239
  hamiltonzero train examples/train.json --reuse-mcmc path/to/mcmc-states
 
241
 
242
  For multisystem training, the path is a directory containing
243
  `<system-index>.eqx` files. For a one-system training panel it may be a single
244
+ file. Training runs `mcmc.burn_in` iterations after either fresh initialization
245
+ or loading reused states. Each iteration uses `mcmc.burn_in_replica_steps`
246
+ MCMC moves.
247
 
248
  ## Fine-tune
249
 
src/hamiltonzero/compiled/trunk.py CHANGED
@@ -127,3 +127,31 @@ def compile_shared_trunk(model, ctx) -> SharedTrunk:
127
  real_mask=ctx.mask,
128
  balanced_mask=ctx.bmask,
129
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  real_mask=ctx.mask,
128
  balanced_mask=ctx.bmask,
129
  )
130
+
131
+
132
+ def compile_shared_trunk_from_kernel(kernel: TrunkCompilerKernel, ctx) -> SharedTrunk:
133
+ edge_feat, local_feat, global_feat = kernel.featurizer(
134
+ ctx.J_double_prime,
135
+ ctx.mask,
136
+ ctx.h_prime,
137
+ )
138
+ g_seed = tree_sphere(global_feat.astype(local_feat.dtype))
139
+ node_raw, edge_raw, g_seed = kernel.trunk(
140
+ ctx,
141
+ edge_feat,
142
+ local_feat,
143
+ g_seed,
144
+ )
145
+ global_stream = kernel.shared_global(
146
+ g_seed.astype(edge_raw.dtype),
147
+ edge_raw,
148
+ ctx.mask,
149
+ )
150
+ return SharedTrunk(
151
+ node_raw=node_raw,
152
+ edge_raw=edge_raw,
153
+ global_raw=global_feat,
154
+ global_stream=global_stream,
155
+ real_mask=ctx.mask,
156
+ balanced_mask=ctx.bmask,
157
+ )
src/hamiltonzero/config.py CHANGED
@@ -151,6 +151,7 @@ class TrainConfig:
151
  steps: int
152
  seed: int = 777
153
  n_max: int = 64
 
154
  model: ModelConfig = field(default_factory=ModelConfig)
155
  router: RouterConfig = field(default_factory=RouterConfig)
156
  mcmc: MCMCConfig = field(default_factory=MCMCConfig)
 
151
  steps: int
152
  seed: int = 777
153
  n_max: int = 64
154
+ checkpoint: Path | None = None
155
  model: ModelConfig = field(default_factory=ModelConfig)
156
  router: RouterConfig = field(default_factory=RouterConfig)
157
  mcmc: MCMCConfig = field(default_factory=MCMCConfig)
src/hamiltonzero/evaluation/runtime.py CHANGED
@@ -40,6 +40,7 @@ from hamiltonzero.data.systems import (
40
  load_system as load_spin_hamiltonian,
41
  )
42
  from hamiltonzero.energy import vmc_energy_custom_lap_compiled
 
43
  from hamiltonzero.energy.frame import compile_energy_frame, route_energy_inputs
44
  from hamiltonzero.mcmc.runtime import (
45
  adapt_batched,
@@ -212,7 +213,6 @@ def _step_single(
212
  )
213
 
214
 
215
- @partial(jax.jit, static_argnames=("replica_steps", "walker_chunk_size"))
216
  def _step_compiled_rows(
217
  state,
218
  model,
@@ -249,7 +249,6 @@ def _adapt_single(
249
  )
250
 
251
 
252
- @jax.jit
253
  def _adapt_rows(
254
  state,
255
  beta_history_weight,
@@ -269,27 +268,110 @@ def _adapt_rows(
269
 
270
 
271
  def _compiled_energy_single(kernel, tree, frame, q, *, chunk_size: int):
272
- values = vmc_energy_custom_lap_compiled(
273
  kernel,
274
  tree,
275
  frame,
276
  q,
277
  chunk_size=chunk_size,
278
  )
279
- return tuple(jnp.expand_dims(value, axis=0) for value in values)
280
 
281
 
282
- @partial(jax.jit, static_argnames=("chunk_size",))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  def _compiled_energy_rows(kernel, trees, frames, q, *, chunk_size: int):
284
- return jax.vmap(
285
- lambda tree, frame, q_row: vmc_energy_custom_lap_compiled(
 
 
 
 
 
 
286
  kernel,
287
  tree,
288
  frame,
289
  q_row,
290
  chunk_size=chunk_size,
291
  )
292
- )(trees, frames, q)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
 
295
  _compile_single = jax.jit(compile_wavefunction)
@@ -335,6 +417,131 @@ def _single_state_sharding(mesh: Mesh, state):
335
  )
336
 
337
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  def _system_sharding(mesh: Mesh, value):
339
  return jax.tree_util.tree_map(
340
  lambda array: NamedSharding(
@@ -440,6 +647,8 @@ class DefaultEvalBackend:
440
  self._energy_frames: dict[int, Any] = {}
441
  self._context_meshes: dict[int, Mesh] = {}
442
  self._contest_mesh: Mesh | None = None
 
 
443
  self._singular_mesh: Mesh | None = None
444
  self._singular_state_sharding: Any | None = None
445
  self._singular_model_sharding: Any | None = None
@@ -447,6 +656,7 @@ class DefaultEvalBackend:
447
  self._singular_step_entries: dict[tuple[int, int], Any] = {}
448
  self._singular_adapt_entry: Any | None = None
449
  self._singular_energy_entries: dict[int, Any] = {}
 
450
 
451
  def build_system(self, system, energy: EnergyConfig):
452
  context, energy_inputs = build_context_and_energy(
@@ -598,6 +808,9 @@ class DefaultEvalBackend:
598
  self._energy_frames[id(routed)] = frames
599
  self._context_meshes[id(routed)] = mesh
600
  self._contest_mesh = mesh
 
 
 
601
  return routed
602
 
603
  def release_context(self, context) -> None:
@@ -605,6 +818,9 @@ class DefaultEvalBackend:
605
  self._energy_frames.pop(id(context), None)
606
  self._context_meshes.pop(id(context), None)
607
  self._contest_mesh = None
 
 
 
608
 
609
  def beam_candidates(
610
  self,
@@ -850,31 +1066,35 @@ class DefaultEvalBackend:
850
  key = (int(replica_steps), int(walker_chunk_size))
851
  step = self._singular_step_entries.get(key)
852
  if step is None:
853
- step = jax.jit(
854
- partial(
855
- _step_single,
856
- replica_steps=key[0],
857
- walker_chunk_size=key[1],
858
- ),
859
- in_shardings=(
860
- self._singular_state_sharding,
861
- self._singular_model_sharding,
862
- self._singular_context_sharding,
863
- ),
864
- out_shardings=self._singular_state_sharding,
865
- donate_argnums=(0,),
866
  )
867
  self._singular_step_entries[key] = step
868
  return step(state, model, context)
869
  if not isinstance(model, CompiledWaveFunctions):
870
  raise TypeError("multirow eval MCMC requires compiled wavefunctions")
871
- return _step_compiled_rows(
872
- state,
873
- model,
874
- context,
875
- replica_steps=int(replica_steps),
876
- walker_chunk_size=int(walker_chunk_size),
877
- )
 
 
 
 
 
 
 
 
 
878
 
879
  def adapt_mcmc(self, state, config: EvalMCMCConfig):
880
  arguments = (
@@ -885,7 +1105,23 @@ class DefaultEvalBackend:
885
  jnp.asarray(config.haar_target_acceptance, dtype=jnp.float32),
886
  )
887
  if state.q.ndim == 5:
888
- return _adapt_rows(*arguments)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
889
  if self._singular_mesh is None or self._singular_state_sharding is None:
890
  raise RuntimeError("singular eval placement has not been prepared")
891
  if self._singular_adapt_entry is None:
@@ -969,12 +1205,18 @@ class DefaultEvalBackend:
969
  chunk_size = int(config.chunk_size)
970
  if isinstance(model, CompiledWaveFunctions):
971
  frames = self._frames(context)
972
- return _compiled_energy_rows(
 
 
 
 
 
 
 
973
  model.kernel,
974
  model.trees,
975
  frames,
976
  q,
977
- chunk_size=chunk_size,
978
  )
979
  if not isinstance(model, CompiledWaveFunction):
980
  raise TypeError("singular eval energy requires a compiled wavefunction")
@@ -988,11 +1230,6 @@ class DefaultEvalBackend:
988
  self._singular_mesh,
989
  P("batch", None, None),
990
  )
991
- energy_sharding = NamedSharding(
992
- self._singular_mesh,
993
- P(None, "batch"),
994
- )
995
- output_shardings = (energy_sharding,) * 4
996
  q = jax.device_put(q, q_sharding)
997
  frames = self._frames(context)
998
  frame_sharding = _replicated_sharding(
@@ -1002,26 +1239,18 @@ class DefaultEvalBackend:
1002
  frames = jax.device_put(frames, frame_sharding)
1003
  energy = self._singular_energy_entries.get(chunk_size)
1004
  if energy is None:
1005
- energy = jax.jit(
1006
- partial(
1007
- _compiled_energy_single,
1008
- chunk_size=chunk_size,
1009
- ),
1010
- in_shardings=(
1011
- self._singular_model_sharding.kernel,
1012
- self._singular_model_sharding.tree,
1013
- frame_sharding,
1014
- q_sharding,
1015
- ),
1016
- out_shardings=output_shardings,
1017
  )
1018
  self._singular_energy_entries[chunk_size] = energy
1019
- return energy(
1020
  model.kernel,
1021
  model.tree,
1022
  frames,
1023
  q,
1024
  )
 
1025
 
1026
  def block_until_ready(self, value) -> None:
1027
  jax.block_until_ready(value)
 
40
  load_system as load_spin_hamiltonian,
41
  )
42
  from hamiltonzero.energy import vmc_energy_custom_lap_compiled
43
+ from hamiltonzero.energy.custom_lap import build_W_levels
44
  from hamiltonzero.energy.frame import compile_energy_frame, route_energy_inputs
45
  from hamiltonzero.mcmc.runtime import (
46
  adapt_batched,
 
213
  )
214
 
215
 
 
216
  def _step_compiled_rows(
217
  state,
218
  model,
 
249
  )
250
 
251
 
 
252
  def _adapt_rows(
253
  state,
254
  beta_history_weight,
 
268
 
269
 
270
  def _compiled_energy_single(kernel, tree, frame, q, *, chunk_size: int):
271
+ return vmc_energy_custom_lap_compiled(
272
  kernel,
273
  tree,
274
  frame,
275
  q,
276
  chunk_size=chunk_size,
277
  )
 
278
 
279
 
280
+ def _build_compiled_energy_single(mesh: Mesh, chunk_size: int):
281
+ batch = P("batch", None, None)
282
+ batch_output = P("batch")
283
+
284
+ def local_energy(kernel, tree, frame, q):
285
+ outputs = _compiled_energy_single(
286
+ kernel,
287
+ tree,
288
+ frame,
289
+ q,
290
+ chunk_size=int(chunk_size),
291
+ )
292
+ local_count = jnp.asarray(q.shape[0], dtype=jnp.int32)
293
+ global_count = jax.lax.psum(local_count, "batch")
294
+ guard = global_count.astype(jnp.float32) * jnp.asarray(0.0, jnp.float32)
295
+ return tuple(value + guard.astype(value.dtype) for value in outputs)
296
+
297
+ mapped = jax.shard_map(
298
+ local_energy,
299
+ mesh=mesh,
300
+ in_specs=(P(), P(), P(), batch),
301
+ out_specs=(batch_output,) * 4,
302
+ check_vma=False,
303
+ )
304
+ replicated_sharding = NamedSharding(mesh, P())
305
+ batch_sharding = NamedSharding(mesh, batch)
306
+ batch_output_sharding = NamedSharding(mesh, batch_output)
307
+ return jax.jit(
308
+ mapped,
309
+ in_shardings=(
310
+ replicated_sharding,
311
+ replicated_sharding,
312
+ replicated_sharding,
313
+ batch_sharding,
314
+ ),
315
+ out_shardings=(batch_output_sharding,) * 4,
316
+ )
317
+
318
+
319
  def _compiled_energy_rows(kernel, trees, frames, q, *, chunk_size: int):
320
+ def energy_row(tree, frame, q_row):
321
+ n_sites = int(q_row.shape[-2])
322
+ frame = eqx.tree_at(
323
+ lambda value: value.w_levels,
324
+ frame,
325
+ tuple(build_W_levels(frame.custom_lap_J_eff, n_sites)),
326
+ )
327
+ return vmc_energy_custom_lap_compiled(
328
  kernel,
329
  tree,
330
  frame,
331
  q_row,
332
  chunk_size=chunk_size,
333
  )
334
+
335
+ return jax.vmap(energy_row)(trees, frames, q)
336
+
337
+
338
+ def _build_compiled_energy_rows(mesh: Mesh, chunk_size: int):
339
+ systems = P("systems")
340
+ system_batch = P("systems", None)
341
+
342
+ def local_energy(kernel, trees, frames, q):
343
+ outputs = _compiled_energy_rows(
344
+ kernel,
345
+ trees,
346
+ frames,
347
+ q,
348
+ chunk_size=int(chunk_size),
349
+ )
350
+ local_count = jnp.asarray(q.shape[0] * q.shape[1], dtype=jnp.int32)
351
+ global_count = jax.lax.psum(local_count, "systems")
352
+ guard = global_count.astype(jnp.float32) * jnp.asarray(0.0, jnp.float32)
353
+ return tuple(value + guard.astype(value.dtype) for value in outputs)
354
+
355
+ mapped = jax.shard_map(
356
+ local_energy,
357
+ mesh=mesh,
358
+ in_specs=(P(), systems, systems, system_batch),
359
+ out_specs=(system_batch,) * 4,
360
+ check_vma=False,
361
+ )
362
+ replicated_sharding = NamedSharding(mesh, P())
363
+ systems_sharding = NamedSharding(mesh, systems)
364
+ system_batch_sharding = NamedSharding(mesh, system_batch)
365
+ return jax.jit(
366
+ mapped,
367
+ in_shardings=(
368
+ replicated_sharding,
369
+ systems_sharding,
370
+ systems_sharding,
371
+ system_batch_sharding,
372
+ ),
373
+ out_shardings=(system_batch_sharding,) * 4,
374
+ )
375
 
376
 
377
  _compile_single = jax.jit(compile_wavefunction)
 
417
  )
418
 
419
 
420
+ def _single_state_specs(state):
421
+ walkers = P("batch")
422
+ replicated = P()
423
+ return type(state)(
424
+ q=walkers,
425
+ log_p=walkers,
426
+ grad_log_p=walkers,
427
+ beta=replicated,
428
+ sigma=replicated,
429
+ step=replicated,
430
+ key=walkers,
431
+ n_local_accept=walkers,
432
+ n_local=walkers,
433
+ n_swap_accept=walkers,
434
+ n_swap=walkers,
435
+ mask=replicated,
436
+ m=replicated,
437
+ n_haar_accept=walkers,
438
+ n_haar=walkers,
439
+ )
440
+
441
+
442
+ def _row_state_specs(state):
443
+ systems = P("systems")
444
+ return jax.tree_util.tree_map(lambda _value: systems, state)
445
+
446
+
447
+ def _row_model_specs(model):
448
+ return CompiledWaveFunctions(
449
+ kernel=jax.tree_util.tree_map(lambda _value: P(), model.kernel),
450
+ trees=jax.tree_util.tree_map(lambda _value: P("systems"), model.trees),
451
+ )
452
+
453
+
454
+ def _row_model_sharding(mesh: Mesh, model):
455
+ return CompiledWaveFunctions(
456
+ kernel=_replicated_sharding(mesh, model.kernel),
457
+ trees=_system_sharding(mesh, model.trees),
458
+ )
459
+
460
+
461
+ def _build_rows_mcmc_step(
462
+ mesh: Mesh,
463
+ state,
464
+ model,
465
+ context,
466
+ *,
467
+ replica_steps: int,
468
+ walker_chunk_size: int,
469
+ ):
470
+ state_specs = _row_state_specs(state)
471
+ model_specs = _row_model_specs(model)
472
+ context_specs = jax.tree_util.tree_map(lambda _value: P("systems"), context)
473
+ state_sharding = _system_sharding(mesh, state)
474
+ model_sharding = _row_model_sharding(mesh, model)
475
+ context_sharding = _system_sharding(mesh, context)
476
+
477
+ def local_step(state_local, model_local, context_local):
478
+ out = _step_compiled_rows(
479
+ state_local,
480
+ model_local,
481
+ context_local,
482
+ replica_steps=int(replica_steps),
483
+ walker_chunk_size=int(walker_chunk_size),
484
+ )
485
+ local_count = jnp.asarray(out.q.shape[0] * out.q.shape[1], dtype=jnp.int32)
486
+ global_count = jax.lax.psum(local_count, "systems")
487
+ guard = global_count.astype(out.q.dtype) * jnp.asarray(0.0, out.q.dtype)
488
+ return eqx.tree_at(lambda value: value.q, out, out.q + guard)
489
+
490
+ mapped = jax.shard_map(
491
+ local_step,
492
+ mesh=mesh,
493
+ in_specs=(state_specs, model_specs, context_specs),
494
+ out_specs=state_specs,
495
+ check_vma=False,
496
+ )
497
+ return jax.jit(
498
+ mapped,
499
+ in_shardings=(state_sharding, model_sharding, context_sharding),
500
+ out_shardings=state_sharding,
501
+ donate_argnums=(0,),
502
+ )
503
+
504
+
505
+ def _build_singular_mcmc_step(
506
+ mesh: Mesh,
507
+ state,
508
+ state_sharding,
509
+ model_sharding,
510
+ context_sharding,
511
+ *,
512
+ replica_steps: int,
513
+ walker_chunk_size: int,
514
+ ):
515
+ state_specs = _single_state_specs(state)
516
+
517
+ def local_step(state_local, model_local, context_local):
518
+ out = _step_single(
519
+ state_local,
520
+ model_local,
521
+ context_local,
522
+ replica_steps=int(replica_steps),
523
+ walker_chunk_size=int(walker_chunk_size),
524
+ )
525
+ local_count = jnp.asarray(out.q.shape[0], dtype=jnp.int32)
526
+ global_count = jax.lax.psum(local_count, "batch")
527
+ guard = global_count.astype(out.q.dtype) * jnp.asarray(0.0, out.q.dtype)
528
+ return eqx.tree_at(lambda value: value.q, out, out.q + guard)
529
+
530
+ mapped = jax.shard_map(
531
+ local_step,
532
+ mesh=mesh,
533
+ in_specs=(state_specs, P(), P()),
534
+ out_specs=state_specs,
535
+ check_vma=False,
536
+ )
537
+ return jax.jit(
538
+ mapped,
539
+ in_shardings=(state_sharding, model_sharding, context_sharding),
540
+ out_shardings=state_sharding,
541
+ donate_argnums=(0,),
542
+ )
543
+
544
+
545
  def _system_sharding(mesh: Mesh, value):
546
  return jax.tree_util.tree_map(
547
  lambda array: NamedSharding(
 
647
  self._energy_frames: dict[int, Any] = {}
648
  self._context_meshes: dict[int, Mesh] = {}
649
  self._contest_mesh: Mesh | None = None
650
+ self._contest_step_entries: dict[tuple[int, int], Any] = {}
651
+ self._contest_adapt_entry: Any | None = None
652
  self._singular_mesh: Mesh | None = None
653
  self._singular_state_sharding: Any | None = None
654
  self._singular_model_sharding: Any | None = None
 
656
  self._singular_step_entries: dict[tuple[int, int], Any] = {}
657
  self._singular_adapt_entry: Any | None = None
658
  self._singular_energy_entries: dict[int, Any] = {}
659
+ self._contest_energy_entries: dict[int, Any] = {}
660
 
661
  def build_system(self, system, energy: EnergyConfig):
662
  context, energy_inputs = build_context_and_energy(
 
808
  self._energy_frames[id(routed)] = frames
809
  self._context_meshes[id(routed)] = mesh
810
  self._contest_mesh = mesh
811
+ self._contest_step_entries.clear()
812
+ self._contest_adapt_entry = None
813
+ self._contest_energy_entries.clear()
814
  return routed
815
 
816
  def release_context(self, context) -> None:
 
818
  self._energy_frames.pop(id(context), None)
819
  self._context_meshes.pop(id(context), None)
820
  self._contest_mesh = None
821
+ self._contest_step_entries.clear()
822
+ self._contest_adapt_entry = None
823
+ self._contest_energy_entries.clear()
824
 
825
  def beam_candidates(
826
  self,
 
1066
  key = (int(replica_steps), int(walker_chunk_size))
1067
  step = self._singular_step_entries.get(key)
1068
  if step is None:
1069
+ step = _build_singular_mcmc_step(
1070
+ self._singular_mesh,
1071
+ state,
1072
+ self._singular_state_sharding,
1073
+ self._singular_model_sharding,
1074
+ self._singular_context_sharding,
1075
+ replica_steps=key[0],
1076
+ walker_chunk_size=key[1],
 
 
 
 
 
1077
  )
1078
  self._singular_step_entries[key] = step
1079
  return step(state, model, context)
1080
  if not isinstance(model, CompiledWaveFunctions):
1081
  raise TypeError("multirow eval MCMC requires compiled wavefunctions")
1082
+ mesh = self._context_meshes.get(id(context))
1083
+ if mesh is None:
1084
+ raise RuntimeError("contest MCMC mesh is unavailable")
1085
+ key = (int(replica_steps), int(walker_chunk_size))
1086
+ step = self._contest_step_entries.get(key)
1087
+ if step is None:
1088
+ step = _build_rows_mcmc_step(
1089
+ mesh,
1090
+ state,
1091
+ model,
1092
+ context,
1093
+ replica_steps=key[0],
1094
+ walker_chunk_size=key[1],
1095
+ )
1096
+ self._contest_step_entries[key] = step
1097
+ return step(state, model, context)
1098
 
1099
  def adapt_mcmc(self, state, config: EvalMCMCConfig):
1100
  arguments = (
 
1105
  jnp.asarray(config.haar_target_acceptance, dtype=jnp.float32),
1106
  )
1107
  if state.q.ndim == 5:
1108
+ if self._contest_mesh is None:
1109
+ raise RuntimeError("contest adaptation mesh is unavailable")
1110
+ if self._contest_adapt_entry is None:
1111
+ replicated = NamedSharding(self._contest_mesh, P())
1112
+ state_sharding = _system_sharding(self._contest_mesh, state)
1113
+ self._contest_adapt_entry = jax.jit(
1114
+ _adapt_rows,
1115
+ in_shardings=(
1116
+ state_sharding,
1117
+ replicated,
1118
+ replicated,
1119
+ replicated,
1120
+ replicated,
1121
+ ),
1122
+ out_shardings=state_sharding,
1123
+ )
1124
+ return self._contest_adapt_entry(*arguments)
1125
  if self._singular_mesh is None or self._singular_state_sharding is None:
1126
  raise RuntimeError("singular eval placement has not been prepared")
1127
  if self._singular_adapt_entry is None:
 
1205
  chunk_size = int(config.chunk_size)
1206
  if isinstance(model, CompiledWaveFunctions):
1207
  frames = self._frames(context)
1208
+ mesh = self._context_meshes.get(id(context))
1209
+ if mesh is None:
1210
+ raise RuntimeError("contest energy mesh is unavailable")
1211
+ energy = self._contest_energy_entries.get(chunk_size)
1212
+ if energy is None:
1213
+ energy = _build_compiled_energy_rows(mesh, chunk_size)
1214
+ self._contest_energy_entries[chunk_size] = energy
1215
+ return energy(
1216
  model.kernel,
1217
  model.trees,
1218
  frames,
1219
  q,
 
1220
  )
1221
  if not isinstance(model, CompiledWaveFunction):
1222
  raise TypeError("singular eval energy requires a compiled wavefunction")
 
1230
  self._singular_mesh,
1231
  P("batch", None, None),
1232
  )
 
 
 
 
 
1233
  q = jax.device_put(q, q_sharding)
1234
  frames = self._frames(context)
1235
  frame_sharding = _replicated_sharding(
 
1239
  frames = jax.device_put(frames, frame_sharding)
1240
  energy = self._singular_energy_entries.get(chunk_size)
1241
  if energy is None:
1242
+ energy = _build_compiled_energy_single(
1243
+ self._singular_mesh,
1244
+ chunk_size,
 
 
 
 
 
 
 
 
 
1245
  )
1246
  self._singular_energy_entries[chunk_size] = energy
1247
+ outputs = energy(
1248
  model.kernel,
1249
  model.tree,
1250
  frames,
1251
  q,
1252
  )
1253
+ return tuple(jnp.expand_dims(value, axis=0) for value in outputs)
1254
 
1255
  def block_until_ready(self, value) -> None:
1256
  jax.block_until_ready(value)
src/hamiltonzero/modes/finetune.py CHANGED
@@ -80,21 +80,14 @@ def _burn_in(
80
  context,
81
  state: REState,
82
  config: FineTuneConfig,
 
 
83
  ) -> REState:
84
- replica_steps = config.mcmc.burn_in_replica_steps
85
- step_mcmc = eqx.filter_jit(
86
- functools.partial(
87
- run_batched,
88
- n_steps=replica_steps,
89
- walker_chunk_size=config.mcmc.walker_chunk_size,
90
- )
91
- )
92
- adapt = eqx.filter_jit(functools.partial(_adapt, config=config))
93
  for iteration in range(config.mcmc.burn_in):
94
  state = step_mcmc(
 
95
  model,
96
  context,
97
- state,
98
  )
99
  if iteration > 0 and iteration % config.mcmc.adapt_every == 0:
100
  state = adapt(state)
@@ -109,9 +102,35 @@ def _replicate(value, sharding: NamedSharding):
109
 
110
 
111
  def _place_state(state: REState, mesh: Mesh) -> REState:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  replicated = NamedSharding(mesh, P())
113
  batched = NamedSharding(mesh, P("batch"))
114
- shardings = REState(
115
  q=batched,
116
  log_p=batched,
117
  grad_log_p=batched,
@@ -128,7 +147,83 @@ def _place_state(state: REState, mesh: Mesh) -> REState:
128
  n_haar_accept=batched,
129
  n_haar=batched,
130
  )
131
- return jax.device_put(state, shardings)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
 
134
  def _metric(
@@ -175,10 +270,8 @@ def run_finetune(
175
  initial_m=config.mcmc.initial_haar_sites,
176
  initial_sigma=config.mcmc.initial_sigma,
177
  )
178
- reused = False
179
  if config.mcmc.reuse_mcmc is not None:
180
  state = load_mcmc(config.mcmc.reuse_mcmc, state)
181
- reused = True
182
  key, _route_key = jax.random.split(key)
183
  freeze_route = eqx.filter_jit(
184
  functools.partial(
@@ -222,6 +315,10 @@ def run_finetune(
222
  )
223
  mesh = Mesh(np.asarray(devices, dtype=object), ("batch",))
224
  replicated = NamedSharding(mesh, P())
 
 
 
 
225
  model = _replicate(model, replicated)
226
  context = _replicate(context, replicated)
227
  energy_frame = _replicate(energy_frame, replicated)
@@ -234,6 +331,42 @@ def run_finetune(
234
  jnp.zeros((1, config.mcmc.batch_size), dtype=jnp.complex64),
235
  kfac_data,
236
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  kfac = init_finetune_kfac_state(
238
  config.kfac,
239
  model,
@@ -244,30 +377,23 @@ def run_finetune(
244
  key=jax.random.fold_in(key, 0xCAFE),
245
  multi_device=mesh.size > 1,
246
  )
247
- if not reused:
248
- state = _burn_in(model, context, state, config)
249
- step_mcmc = eqx.filter_jit(
250
- functools.partial(
251
- run_batched,
252
- n_steps=config.mcmc.steps,
253
- walker_chunk_size=config.mcmc.walker_chunk_size,
254
- )
255
- )
256
- adapt = eqx.filter_jit(functools.partial(_adapt, config=config))
257
- local_energy = eqx.filter_jit(
258
- functools.partial(
259
- vmc_energy_custom_lap_finetune,
260
- chunk_size=config.energy.chunk_size,
261
- )
262
  )
 
263
  run_started = time.perf_counter()
264
  last_metric = None
265
  for step in range(config.steps):
266
  step_started = time.perf_counter()
267
  state = step_mcmc(
 
268
  model,
269
  context,
270
- state,
271
  )
272
  if step > 0 and step % config.mcmc.adapt_every == 0:
273
  state = adapt(state)
@@ -277,10 +403,9 @@ def run_finetune(
277
  energy_frame,
278
  q_cold,
279
  )
280
- target = process_finetune_targets(
281
  total[None],
282
  context_batch.s_norm,
283
- mad_width=config.kfac.mad_clip_width,
284
  )
285
  key, key_kfac = jax.random.split(key)
286
  model, kfac = apply_finetune_kfac_step(
 
80
  context,
81
  state: REState,
82
  config: FineTuneConfig,
83
+ step_mcmc,
84
+ adapt,
85
  ) -> REState:
 
 
 
 
 
 
 
 
 
86
  for iteration in range(config.mcmc.burn_in):
87
  state = step_mcmc(
88
+ state,
89
  model,
90
  context,
 
91
  )
92
  if iteration > 0 and iteration % config.mcmc.adapt_every == 0:
93
  state = adapt(state)
 
102
 
103
 
104
  def _place_state(state: REState, mesh: Mesh) -> REState:
105
+ return jax.device_put(state, _state_sharding(mesh))
106
+
107
+
108
+ def _state_specs() -> REState:
109
+ walkers = P("batch")
110
+ replicated = P()
111
+ return REState(
112
+ q=walkers,
113
+ log_p=walkers,
114
+ grad_log_p=walkers,
115
+ beta=replicated,
116
+ sigma=replicated,
117
+ step=replicated,
118
+ key=walkers,
119
+ n_local_accept=walkers,
120
+ n_local=walkers,
121
+ n_swap_accept=walkers,
122
+ n_swap=walkers,
123
+ mask=replicated,
124
+ m=replicated,
125
+ n_haar_accept=walkers,
126
+ n_haar=walkers,
127
+ )
128
+
129
+
130
+ def _state_sharding(mesh: Mesh) -> REState:
131
  replicated = NamedSharding(mesh, P())
132
  batched = NamedSharding(mesh, P("batch"))
133
+ return REState(
134
  q=batched,
135
  log_p=batched,
136
  grad_log_p=batched,
 
147
  n_haar_accept=batched,
148
  n_haar=batched,
149
  )
150
+
151
+
152
+ def _build_mcmc_entry(
153
+ mesh: Mesh,
154
+ state_sharding,
155
+ model_sharding,
156
+ context_sharding,
157
+ *,
158
+ replica_steps: int,
159
+ walker_chunk_size: int | None,
160
+ ):
161
+ specs = _state_specs()
162
+
163
+ def local_step(state, model, context):
164
+ out = run_batched(
165
+ model,
166
+ context,
167
+ state,
168
+ n_steps=int(replica_steps),
169
+ walker_chunk_size=walker_chunk_size,
170
+ )
171
+ local_count = jnp.asarray(out.q.shape[0], dtype=jnp.int32)
172
+ global_count = jax.lax.psum(local_count, "batch")
173
+ guard = global_count.astype(out.q.dtype) * jnp.asarray(0.0, out.q.dtype)
174
+ return eqx.tree_at(lambda value: value.q, out, out.q + guard)
175
+
176
+ mapped = jax.shard_map(
177
+ local_step,
178
+ mesh=mesh,
179
+ in_specs=(specs, P(), P()),
180
+ out_specs=specs,
181
+ check_vma=False,
182
+ )
183
+ return jax.jit(
184
+ mapped,
185
+ in_shardings=(state_sharding, model_sharding, context_sharding),
186
+ out_shardings=state_sharding,
187
+ donate_argnums=(0,),
188
+ )
189
+
190
+
191
+ def _build_energy_entry(
192
+ mesh: Mesh,
193
+ model_sharding,
194
+ frame_sharding,
195
+ *,
196
+ chunk_size: int,
197
+ ):
198
+ q_spec = P("batch", None, None)
199
+ output_spec = P("batch")
200
+
201
+ def local_energy(model, frame, q):
202
+ outputs = vmc_energy_custom_lap_finetune(
203
+ model,
204
+ frame,
205
+ q,
206
+ chunk_size=int(chunk_size),
207
+ )
208
+ local_count = jnp.asarray(q.shape[0], dtype=jnp.int32)
209
+ global_count = jax.lax.psum(local_count, "batch")
210
+ guard = global_count.astype(jnp.float32) * jnp.asarray(0.0, jnp.float32)
211
+ return tuple(value + guard.astype(value.dtype) for value in outputs)
212
+
213
+ mapped = jax.shard_map(
214
+ local_energy,
215
+ mesh=mesh,
216
+ in_specs=(P(), P(), q_spec),
217
+ out_specs=(output_spec,) * 4,
218
+ check_vma=False,
219
+ )
220
+ q_sharding = NamedSharding(mesh, q_spec)
221
+ output_sharding = NamedSharding(mesh, output_spec)
222
+ return jax.jit(
223
+ mapped,
224
+ in_shardings=(model_sharding, frame_sharding, q_sharding),
225
+ out_shardings=(output_sharding,) * 4,
226
+ )
227
 
228
 
229
  def _metric(
 
270
  initial_m=config.mcmc.initial_haar_sites,
271
  initial_sigma=config.mcmc.initial_sigma,
272
  )
 
273
  if config.mcmc.reuse_mcmc is not None:
274
  state = load_mcmc(config.mcmc.reuse_mcmc, state)
 
275
  key, _route_key = jax.random.split(key)
276
  freeze_route = eqx.filter_jit(
277
  functools.partial(
 
315
  )
316
  mesh = Mesh(np.asarray(devices, dtype=object), ("batch",))
317
  replicated = NamedSharding(mesh, P())
318
+ state_sharding = _state_sharding(mesh)
319
+ model_sharding = jax.tree_util.tree_map(lambda _value: replicated, model)
320
+ context_sharding = jax.tree_util.tree_map(lambda _value: replicated, context)
321
+ frame_sharding = jax.tree_util.tree_map(lambda _value: replicated, energy_frame)
322
  model = _replicate(model, replicated)
323
  context = _replicate(context, replicated)
324
  energy_frame = _replicate(energy_frame, replicated)
 
331
  jnp.zeros((1, config.mcmc.batch_size), dtype=jnp.complex64),
332
  kfac_data,
333
  )
334
+ mcmc_entries = {}
335
+
336
+ def mcmc_entry(replica_steps: int):
337
+ entry_key = (int(replica_steps), config.mcmc.walker_chunk_size)
338
+ entry = mcmc_entries.get(entry_key)
339
+ if entry is None:
340
+ entry = _build_mcmc_entry(
341
+ mesh,
342
+ state_sharding,
343
+ model_sharding,
344
+ context_sharding,
345
+ replica_steps=entry_key[0],
346
+ walker_chunk_size=entry_key[1],
347
+ )
348
+ mcmc_entries[entry_key] = entry
349
+ return entry
350
+
351
+ adapt = jax.jit(
352
+ functools.partial(_adapt, config=config),
353
+ in_shardings=(state_sharding,),
354
+ out_shardings=state_sharding,
355
+ )
356
+ local_energy = _build_energy_entry(
357
+ mesh,
358
+ model_sharding,
359
+ frame_sharding,
360
+ chunk_size=config.energy.chunk_size,
361
+ )
362
+ target_entry = jax.jit(
363
+ functools.partial(
364
+ process_finetune_targets,
365
+ mad_width=config.kfac.mad_clip_width,
366
+ ),
367
+ in_shardings=(kfac_data, replicated),
368
+ out_shardings=kfac_data,
369
+ )
370
  kfac = init_finetune_kfac_state(
371
  config.kfac,
372
  model,
 
377
  key=jax.random.fold_in(key, 0xCAFE),
378
  multi_device=mesh.size > 1,
379
  )
380
+ state = _burn_in(
381
+ model,
382
+ context,
383
+ state,
384
+ config,
385
+ mcmc_entry(config.mcmc.burn_in_replica_steps),
386
+ adapt,
 
 
 
 
 
 
 
 
387
  )
388
+ step_mcmc = mcmc_entry(config.mcmc.steps)
389
  run_started = time.perf_counter()
390
  last_metric = None
391
  for step in range(config.steps):
392
  step_started = time.perf_counter()
393
  state = step_mcmc(
394
+ state,
395
  model,
396
  context,
 
397
  )
398
  if step > 0 and step % config.mcmc.adapt_every == 0:
399
  state = adapt(state)
 
403
  energy_frame,
404
  q_cold,
405
  )
406
+ target = target_entry(
407
  total[None],
408
  context_batch.s_norm,
 
409
  )
410
  key, key_kfac = jax.random.split(key)
411
  model, kfac = apply_finetune_kfac_step(
src/hamiltonzero/modes/train.py CHANGED
@@ -13,15 +13,23 @@ import jax.numpy as jnp
13
  import numpy as np
14
  from jax.sharding import Mesh, NamedSharding, PartitionSpec as P
15
 
16
- from hamiltonzero.checkpoint import load_mcmc, save_model
17
- from hamiltonzero.compiled.tree import compile_physical_tree_reference
18
- from hamiltonzero.compiled.trunk import bind_shared_kernel, compile_shared_trunk
 
 
 
 
 
 
 
19
  from hamiltonzero.compiled.types import (
20
  CompiledWaveFunction,
21
  )
22
  from hamiltonzero.config import TrainConfig
23
  from hamiltonzero.data import build_context_and_energy, load_systems
24
  from hamiltonzero.energy import vmc_energy_custom_lap_compiled
 
25
  from hamiltonzero.energy.frame import compile_energy_frame
26
  from hamiltonzero.mcmc import (
27
  REState,
@@ -83,25 +91,22 @@ def _identity_perms(n_max: int):
83
  )
84
 
85
 
86
- def _route_sharding(mesh: Mesh, value):
87
- return jax.tree_util.tree_map(
88
- lambda x: NamedSharding(
89
- mesh,
90
- P("systems", *([None] * (x.ndim - 1)))
91
- if x.ndim and x.shape[0] == ROUTE_SAMPLES
92
- else P(),
93
- ),
94
- value,
95
- )
96
 
97
 
98
  def _replicate(mesh: Mesh, value):
99
- replicated = NamedSharding(mesh, P())
100
- return jax.device_put(value, jax.tree_util.tree_map(lambda _: replicated, value))
101
 
102
 
103
  def _place_routes(mesh: Mesh, value):
104
- return jax.device_put(value, _route_sharding(mesh, value))
105
 
106
 
107
  def _host_pool(value):
@@ -132,34 +137,195 @@ def _activate_system(mesh: Mesh, state: _SystemState) -> _SystemState:
132
  )
133
 
134
 
135
- def _compile_trees(model, trunk, perms):
136
- return jax.vmap(lambda perm: compile_physical_tree_reference(model, trunk, perm))(
137
- perms
 
138
  )
139
 
140
 
141
- def _compile_frames(inputs, mask, bmask, perms):
142
- return jax.vmap(
143
- lambda perm: compile_energy_frame(
144
- inputs,
145
- mask,
146
- bmask,
147
- perm,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  )
149
- )(perms)
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
 
152
- def _run_routes(kernel, trees, contexts, state, n_steps, chunk_size):
153
- def one(tree, context, sampler):
154
- return run_batched(
155
- CompiledWaveFunction(kernel=kernel, tree=tree),
156
- context,
157
- sampler,
158
- n_steps,
159
- walker_chunk_size=chunk_size,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  )
161
 
162
- return jax.vmap(one)(trees, contexts, state)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
 
165
  def _adapt_routes(state, config):
@@ -176,6 +342,12 @@ def _adapt_routes(state, config):
176
 
177
  def _sampled_energy(kernel, trees, frames, q, chunk_size):
178
  def one(tree, frame, q_row):
 
 
 
 
 
 
179
  return vmc_energy_custom_lap_compiled(
180
  kernel,
181
  tree,
@@ -187,6 +359,102 @@ def _sampled_energy(kernel, trees, frames, q, chunk_size):
187
  return jax.vmap(one)(trees, frames, q)
188
 
189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  def _mode_energy(kernel, tree, frame, q_canonical, mode_perm, chunk_size):
191
  q_mode = jnp.take(q_canonical, mode_perm, axis=-2)
192
 
@@ -207,6 +475,92 @@ def _mode_energy(kernel, tree, frame, q_canonical, mode_perm, chunk_size):
207
  return jax.vmap(one)(q_mode)
208
 
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  def _initial_system_state(
211
  model,
212
  context,
@@ -218,7 +572,7 @@ def _initial_system_state(
218
  mesh,
219
  compile_plan,
220
  compile_trees,
221
- run_routes,
222
  ):
223
  walkers = config.mcmc.batch_size // ROUTE_SAMPLES
224
  cpu = jax.devices("cpu")[0]
@@ -253,21 +607,28 @@ def _initial_system_state(
253
  sampler = _place_routes(mesh, sampler)
254
  contexts = _place_routes(mesh, contexts)
255
  perms = _place_routes(mesh, perms)
256
- trunk = compile_plan(model, context)[0]
257
- kernel = bind_shared_kernel(model)
258
- trees = compile_trees(model, trunk, perms)
259
- if config.mcmc.reuse_mcmc is None:
260
- for iteration in range(config.mcmc.burn_in):
261
- sampler = run_routes(
262
- kernel,
263
- trees,
264
- contexts,
265
- sampler,
266
- config.mcmc.burn_in_replica_steps,
267
- config.mcmc.walker_chunk_size,
268
- )
269
- if iteration and iteration % config.mcmc.adapt_every == 0:
270
- sampler = _adapt_routes(sampler, config)
 
 
 
 
 
 
 
271
  return _SystemState(sampler=sampler, context=contexts, perms=perms)
272
 
273
 
@@ -290,6 +651,8 @@ def run_train(
290
  ) -> TrainResult:
291
  if config.mcmc.batch_size % ROUTE_SAMPLES:
292
  raise ValueError("mcmc.batch_size must be divisible by K=8")
 
 
293
  systems = load_systems(config.systems)
294
  if not systems:
295
  raise ValueError("training requires at least one system")
@@ -308,29 +671,53 @@ def run_train(
308
  energy_inputs = [energy for _context, energy in systems_data]
309
  key = jax.random.PRNGKey(config.seed)
310
  key_model, key_mcmc = jax.random.split(key)
311
- model = build_model(config.model, key_model, n_max=config.n_max)
312
  devices = tuple(jax.devices())
313
- if len(devices) < ROUTE_SAMPLES:
314
- raise ValueError(
315
- "learned-router train requires eight devices for the K=8 systems mesh"
316
- )
317
- mesh = Mesh(np.asarray(devices[:ROUTE_SAMPLES], dtype=object), ("systems",))
318
  model = _replicate(mesh, model)
319
- compile_plan = jax.jit(
320
- lambda model_value, context_value: (
321
- compile_shared_trunk(model_value, context_value),
322
- )
 
323
  )
324
- compile_trees = jax.jit(_compile_trees)
325
- compile_frames = jax.jit(_compile_frames)
326
- run_routes = jax.jit(
327
- _run_routes,
328
- static_argnums=(4, 5),
329
- donate_argnums=(3,),
 
 
330
  )
331
- sampled_energy = jax.jit(_sampled_energy, static_argnums=(4,))
332
- mode_energy = jax.jit(_mode_energy, static_argnums=(5,))
333
- reframe = jax.jit(reframe_state_context, donate_argnums=(0, 1))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  system_states: list[_SystemState | None] = [None] * len(systems)
335
 
336
  def get_system(index: int):
@@ -346,13 +733,19 @@ def run_train(
346
  mesh=mesh,
347
  compile_plan=compile_plan,
348
  compile_trees=compile_trees,
349
- run_routes=run_routes,
350
  )
351
  return _activate_system(mesh, cached)
352
 
353
  first = get_system(0)
354
  q_seed = jax.vmap(cold_samples)(first.sampler)
355
- energy_seed = _place_routes(mesh, np.zeros(q_seed.shape[:2], dtype=np.complex64))
 
 
 
 
 
 
356
  kfac = init_router_kfac_state(
357
  config.kfac,
358
  model,
@@ -365,6 +758,81 @@ def run_train(
365
  route_tau=config.router.temperature,
366
  route_loss_weight=config.router.loss_weight,
367
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  system_states[0] = _host_system_state(first)
369
  del first, q_seed, energy_seed
370
  order_rng = np.random.default_rng(config.seed)
@@ -378,9 +846,12 @@ def run_train(
378
  order_rng.shuffle(order)
379
  system_index = int(order[step % len(order)])
380
  state = get_system(system_index)
381
- trunk = compile_plan(model, contexts[system_index])[0]
 
 
 
382
  router_kernel = bind_router_kernel(model)
383
- router_static = compile_router_static(
384
  router_kernel,
385
  trunk,
386
  contexts[system_index].route_quotient_node_key,
@@ -388,13 +859,11 @@ def run_train(
388
  contexts[system_index].needs_fwl2,
389
  )
390
  tau = jnp.asarray(config.router.temperature, dtype=jnp.float32)
391
- router_kernel = _replicate(mesh, router_kernel)
392
- router_static = _replicate(mesh, router_static)
393
  key, key_route = jax.random.split(key)
394
- new_perms = build_route_sampler(mesh, router_kernel.decoder, router_static)(
395
  router_kernel.decoder, router_static, key_route, tau
396
  )
397
- mode_perm = build_beam16(mesh, router_kernel.decoder, router_static)(
398
  router_kernel.decoder, router_static, tau
399
  )
400
  state.sampler, state.context = reframe(
@@ -402,75 +871,81 @@ def run_train(
402
  )
403
  state.perms = new_perms
404
  kernel = bind_shared_kernel(model)
405
- trees = compile_trees(model, trunk, new_perms)
 
 
 
 
 
406
  frames = compile_frames(
407
  energy_inputs[system_index],
408
  contexts[system_index].mask,
409
  contexts[system_index].bmask,
410
  new_perms,
411
  )
412
- state.sampler = run_routes(
 
413
  kernel,
414
  trees,
415
- state.context,
416
- state.sampler,
417
  config.mcmc.steps,
418
- config.mcmc.walker_chunk_size,
419
- )
420
  if step and step % config.mcmc.adapt_every == 0:
421
- state.sampler = _adapt_routes(state.sampler, config)
422
  q_cold = jax.vmap(cold_samples)(state.sampler)
 
423
  total, _exchange, _casimir, _field = sampled_energy(
424
  kernel,
425
  trees,
426
  frames,
427
  q_cold,
428
- config.energy.chunk_size,
429
- )
430
- baseline_is_sampled = bool(
431
- np.asarray(
432
- jax.device_get(
433
- jnp.all(
434
- new_perms.astype(jnp.int32)
435
- == mode_perm[None, :].astype(jnp.int32)
436
- )
437
- )
438
- )
439
  )
 
440
  if baseline_is_sampled:
441
  baseline_total = total
442
- baseline_weights = jnp.full(
443
- total.shape,
444
- 1.0 / total.shape[-1],
445
- dtype=total.real.dtype,
446
- )
447
  else:
448
- mode_tree = compile_physical_tree_reference(model, trunk, mode_perm)
449
- mode_frame = compile_energy_frame(
 
450
  energy_inputs[system_index],
451
  contexts[system_index].mask,
452
  contexts[system_index].bmask,
453
  mode_perm,
454
  )
455
- q_canonical = rebase_cold_samples(q_cold, new_perms)
 
 
 
 
 
 
 
 
 
 
456
  baseline_total, _bx, _bc, _bf, candidate_log_p = mode_energy(
457
  kernel,
458
  mode_tree,
459
  mode_frame,
460
  q_canonical,
461
  mode_perm,
462
- config.energy.chunk_size,
463
  )
464
  sampled_log_p = state.sampler.log_p[..., -1]
465
- baseline_weights = snis_mode_baseline(
 
466
  baseline_total, candidate_log_p, sampled_log_p
467
  )
468
- target, advantage = process_route_targets(
469
  total,
470
  baseline_total,
471
  state.context.s_norm,
472
  baseline_weights,
473
- mad_width=config.kfac.mad_clip_width,
 
 
 
 
 
474
  )
475
  key, key_kfac = jax.random.split(key)
476
  model, kfac = apply_router_kfac_step(
 
13
  import numpy as np
14
  from jax.sharding import Mesh, NamedSharding, PartitionSpec as P
15
 
16
+ from hamiltonzero.checkpoint import load_mcmc, load_model, save_model
17
+ from hamiltonzero.compiled.tree import (
18
+ bind_physical_compiler_kernel,
19
+ compile_physical_tree_from_shared_trunk,
20
+ )
21
+ from hamiltonzero.compiled.trunk import (
22
+ bind_shared_kernel,
23
+ bind_trunk_compiler_kernel,
24
+ compile_shared_trunk_from_kernel,
25
+ )
26
  from hamiltonzero.compiled.types import (
27
  CompiledWaveFunction,
28
  )
29
  from hamiltonzero.config import TrainConfig
30
  from hamiltonzero.data import build_context_and_energy, load_systems
31
  from hamiltonzero.energy import vmc_energy_custom_lap_compiled
32
+ from hamiltonzero.energy.custom_lap import build_W_levels
33
  from hamiltonzero.energy.frame import compile_energy_frame
34
  from hamiltonzero.mcmc import (
35
  REState,
 
91
  )
92
 
93
 
94
+ def _systems_sharding(mesh: Mesh, value):
95
+ sharding = NamedSharding(mesh, P("systems"))
96
+ return jax.tree_util.tree_map(lambda _value: sharding, value)
97
+
98
+
99
+ def _replicated_sharding(mesh: Mesh, value):
100
+ sharding = NamedSharding(mesh, P())
101
+ return jax.tree_util.tree_map(lambda _value: sharding, value)
 
 
102
 
103
 
104
  def _replicate(mesh: Mesh, value):
105
+ return jax.device_put(value, _replicated_sharding(mesh, value))
 
106
 
107
 
108
  def _place_routes(mesh: Mesh, value):
109
+ return jax.device_put(value, _systems_sharding(mesh, value))
110
 
111
 
112
  def _host_pool(value):
 
137
  )
138
 
139
 
140
+ def _abstract(value):
141
+ return jax.tree_util.tree_map(
142
+ lambda leaf: jax.ShapeDtypeStruct(leaf.shape, leaf.dtype),
143
+ value,
144
  )
145
 
146
 
147
+ def _owner_reduce(value):
148
+ owner = jax.lax.axis_index("systems") == 0
149
+ if jnp.issubdtype(value.dtype, jnp.bool_):
150
+ return jax.lax.pmax(jnp.where(owner, value, jnp.zeros_like(value)), "systems")
151
+ return jax.lax.psum(jnp.where(owner, value, jnp.zeros_like(value)), "systems")
152
+
153
+
154
+ def _build_owner_entry(mesh: Mesh, function, templates):
155
+ output_template = jax.eval_shape(function, *_abstract(templates))
156
+ input_specs = jax.tree_util.tree_map(lambda _value: P(), templates)
157
+ output_specs = jax.tree_util.tree_map(lambda _value: P(), output_template)
158
+
159
+ def local(*values):
160
+ owner = jax.lax.axis_index("systems") == 0
161
+ output = jax.lax.cond(
162
+ owner,
163
+ lambda args: function(*args),
164
+ lambda _args: jax.tree_util.tree_map(
165
+ lambda value: jnp.zeros(value.shape, value.dtype),
166
+ output_template,
167
+ ),
168
+ values,
169
  )
170
+ return jax.tree_util.tree_map(_owner_reduce, output)
171
+
172
+ mapped = jax.shard_map(
173
+ local,
174
+ mesh=mesh,
175
+ in_specs=input_specs,
176
+ out_specs=output_specs,
177
+ check_vma=False,
178
+ )
179
+ return jax.jit(
180
+ mapped,
181
+ in_shardings=_replicated_sharding(mesh, templates),
182
+ out_shardings=_replicated_sharding(mesh, output_template),
183
+ )
184
 
185
 
186
+ def _compile_tree_local(physical_kernel, trunk, perms):
187
+ tree = compile_physical_tree_from_shared_trunk(
188
+ physical_kernel,
189
+ trunk,
190
+ perms[0],
191
+ )
192
+ return jax.tree_util.tree_map(lambda value: value[None], tree)
193
+
194
+
195
+ def _build_compile_trees(mesh: Mesh, physical_kernel, trunk, perms):
196
+ local_perms = jax.ShapeDtypeStruct((1, perms.shape[1]), perms.dtype)
197
+ local_output = jax.eval_shape(
198
+ _compile_tree_local,
199
+ _abstract(physical_kernel),
200
+ _abstract(trunk),
201
+ local_perms,
202
+ )
203
+ mapped = jax.shard_map(
204
+ _compile_tree_local,
205
+ mesh=mesh,
206
+ in_specs=(P(), P(), P("systems")),
207
+ out_specs=jax.tree_util.tree_map(lambda _value: P("systems"), local_output),
208
+ check_vma=False,
209
+ )
210
+ output_template = jax.eval_shape(
211
+ mapped,
212
+ _abstract(physical_kernel),
213
+ _abstract(trunk),
214
+ _abstract(perms),
215
+ )
216
+ return jax.jit(
217
+ mapped,
218
+ in_shardings=(
219
+ _replicated_sharding(mesh, physical_kernel),
220
+ _replicated_sharding(mesh, trunk),
221
+ NamedSharding(mesh, P("systems")),
222
+ ),
223
+ out_shardings=_systems_sharding(mesh, output_template),
224
+ )
225
+
226
+
227
+ def _compile_frame_local(inputs, mask, bmask, perms):
228
+ frame = compile_energy_frame(inputs, mask, bmask, perms[0])
229
+ return jax.tree_util.tree_map(lambda value: value[None], frame)
230
+
231
+
232
+ def _build_compile_frames(mesh: Mesh, inputs, mask, bmask, perms):
233
+ local_perms = jax.ShapeDtypeStruct((1, perms.shape[1]), perms.dtype)
234
+ local_output = jax.eval_shape(
235
+ _compile_frame_local,
236
+ _abstract(inputs),
237
+ _abstract(mask),
238
+ _abstract(bmask),
239
+ local_perms,
240
+ )
241
+ mapped = jax.shard_map(
242
+ _compile_frame_local,
243
+ mesh=mesh,
244
+ in_specs=(P(), P(), P(), P("systems")),
245
+ out_specs=jax.tree_util.tree_map(lambda _value: P("systems"), local_output),
246
+ check_vma=False,
247
+ )
248
+ output_template = jax.eval_shape(
249
+ mapped,
250
+ _abstract(inputs),
251
+ _abstract(mask),
252
+ _abstract(bmask),
253
+ _abstract(perms),
254
+ )
255
+ return jax.jit(
256
+ mapped,
257
+ in_shardings=(
258
+ _replicated_sharding(mesh, inputs),
259
+ NamedSharding(mesh, P()),
260
+ NamedSharding(mesh, P()),
261
+ NamedSharding(mesh, P("systems")),
262
+ ),
263
+ out_shardings=_systems_sharding(mesh, output_template),
264
+ )
265
+
266
+
267
+ def _run_routes_local(state, kernel, trees, n_steps, chunk_size):
268
+ sampler = jax.tree_util.tree_map(lambda value: value[0], state)
269
+ tree = jax.tree_util.tree_map(lambda value: value[0], trees)
270
+ sampler = run_batched(
271
+ CompiledWaveFunction(kernel=kernel, tree=tree),
272
+ None,
273
+ sampler,
274
+ int(n_steps),
275
+ walker_chunk_size=chunk_size,
276
+ )
277
+ return jax.tree_util.tree_map(lambda value: value[None], sampler)
278
+
279
+
280
+ def _build_run_routes(
281
+ mesh: Mesh,
282
+ state,
283
+ kernel,
284
+ trees,
285
+ *,
286
+ n_steps: int,
287
+ chunk_size: int | None,
288
+ ):
289
+ state_specs = jax.tree_util.tree_map(lambda _value: P("systems"), state)
290
+ tree_specs = jax.tree_util.tree_map(lambda _value: P("systems"), trees)
291
+
292
+ def local(state_value, kernel_value, trees_value):
293
+ output = _run_routes_local(
294
+ state_value,
295
+ kernel_value,
296
+ trees_value,
297
+ int(n_steps),
298
+ chunk_size,
299
+ )
300
+ local_count = jnp.asarray(
301
+ output.q.shape[0] * output.q.shape[1],
302
+ dtype=jnp.int32,
303
+ )
304
+ global_count = jax.lax.psum(local_count, "systems")
305
+ guard = global_count.astype(output.q.dtype) * jnp.asarray(0.0, output.q.dtype)
306
+ return eqx.tree_at(
307
+ lambda value: value.q,
308
+ output,
309
+ output.q + guard,
310
  )
311
 
312
+ mapped = jax.shard_map(
313
+ local,
314
+ mesh=mesh,
315
+ in_specs=(state_specs, P(), tree_specs),
316
+ out_specs=state_specs,
317
+ check_vma=False,
318
+ )
319
+ return jax.jit(
320
+ mapped,
321
+ in_shardings=(
322
+ _systems_sharding(mesh, state),
323
+ _replicated_sharding(mesh, kernel),
324
+ _systems_sharding(mesh, trees),
325
+ ),
326
+ out_shardings=_systems_sharding(mesh, state),
327
+ donate_argnums=(0,),
328
+ )
329
 
330
 
331
  def _adapt_routes(state, config):
 
342
 
343
  def _sampled_energy(kernel, trees, frames, q, chunk_size):
344
  def one(tree, frame, q_row):
345
+ n_sites = int(q_row.shape[-2])
346
+ frame = eqx.tree_at(
347
+ lambda value: value.w_levels,
348
+ frame,
349
+ tuple(build_W_levels(frame.custom_lap_J_eff, n_sites)),
350
+ )
351
  return vmc_energy_custom_lap_compiled(
352
  kernel,
353
  tree,
 
359
  return jax.vmap(one)(trees, frames, q)
360
 
361
 
362
+ def _build_sampled_energy(mesh: Mesh, chunk_size: int):
363
+ systems = P("systems")
364
+ system_batch = P("systems", None)
365
+
366
+ def local_energy(kernel, trees, frames, q):
367
+ outputs = _sampled_energy(
368
+ kernel,
369
+ trees,
370
+ frames,
371
+ q,
372
+ int(chunk_size),
373
+ )
374
+ local_count = jnp.asarray(q.shape[0] * q.shape[1], dtype=jnp.int32)
375
+ global_count = jax.lax.psum(local_count, "systems")
376
+ guard = global_count.astype(jnp.float32) * jnp.asarray(0.0, jnp.float32)
377
+ return tuple(value + guard.astype(value.dtype) for value in outputs)
378
+
379
+ mapped = jax.shard_map(
380
+ local_energy,
381
+ mesh=mesh,
382
+ in_specs=(P(), systems, systems, system_batch),
383
+ out_specs=(system_batch,) * 4,
384
+ check_vma=False,
385
+ )
386
+ replicated_sharding = NamedSharding(mesh, P())
387
+ systems_sharding = NamedSharding(mesh, systems)
388
+ system_batch_sharding = NamedSharding(mesh, system_batch)
389
+ return jax.jit(
390
+ mapped,
391
+ in_shardings=(
392
+ replicated_sharding,
393
+ systems_sharding,
394
+ systems_sharding,
395
+ system_batch_sharding,
396
+ ),
397
+ out_shardings=(system_batch_sharding,) * 4,
398
+ )
399
+
400
+
401
+ def _build_reframe(mesh: Mesh, state, context, perms):
402
+ state_specs = jax.tree_util.tree_map(lambda _value: P("systems"), state)
403
+ context_specs = jax.tree_util.tree_map(lambda _value: P("systems"), context)
404
+
405
+ def local(state_value, context_value, old_perms, new_perms):
406
+ return reframe_state_context(
407
+ state_value,
408
+ context_value,
409
+ old_perms,
410
+ new_perms,
411
+ )
412
+
413
+ mapped = jax.shard_map(
414
+ local,
415
+ mesh=mesh,
416
+ in_specs=(
417
+ state_specs,
418
+ context_specs,
419
+ P("systems"),
420
+ P("systems"),
421
+ ),
422
+ out_specs=(state_specs, context_specs),
423
+ check_vma=False,
424
+ )
425
+ return jax.jit(
426
+ mapped,
427
+ in_shardings=(
428
+ _systems_sharding(mesh, state),
429
+ _systems_sharding(mesh, context),
430
+ NamedSharding(mesh, P("systems")),
431
+ NamedSharding(mesh, P("systems")),
432
+ ),
433
+ out_shardings=(
434
+ _systems_sharding(mesh, state),
435
+ _systems_sharding(mesh, context),
436
+ ),
437
+ )
438
+
439
+
440
+ def _build_rebase(mesh: Mesh, q, perms):
441
+ mapped = jax.shard_map(
442
+ rebase_cold_samples,
443
+ mesh=mesh,
444
+ in_specs=(P("systems"), P("systems")),
445
+ out_specs=P("systems"),
446
+ check_vma=False,
447
+ )
448
+ return jax.jit(
449
+ mapped,
450
+ in_shardings=(
451
+ NamedSharding(mesh, P("systems")),
452
+ NamedSharding(mesh, P("systems")),
453
+ ),
454
+ out_shardings=NamedSharding(mesh, P("systems")),
455
+ )
456
+
457
+
458
  def _mode_energy(kernel, tree, frame, q_canonical, mode_perm, chunk_size):
459
  q_mode = jnp.take(q_canonical, mode_perm, axis=-2)
460
 
 
475
  return jax.vmap(one)(q_mode)
476
 
477
 
478
+ def _build_mode_energy(mesh: Mesh, kernel, tree, frame, q, mode_perm, chunk_size):
479
+ system_batch = P("systems", None)
480
+
481
+ def local(kernel_value, tree_value, frame_value, q_value, perm_value):
482
+ return _mode_energy(
483
+ kernel_value,
484
+ tree_value,
485
+ frame_value,
486
+ q_value,
487
+ perm_value,
488
+ int(chunk_size),
489
+ )
490
+
491
+ mapped = jax.shard_map(
492
+ local,
493
+ mesh=mesh,
494
+ in_specs=(P(), P(), P(), system_batch, P()),
495
+ out_specs=(system_batch,) * 5,
496
+ check_vma=False,
497
+ )
498
+ output_sharding = NamedSharding(mesh, system_batch)
499
+ return jax.jit(
500
+ mapped,
501
+ in_shardings=(
502
+ _replicated_sharding(mesh, kernel),
503
+ _replicated_sharding(mesh, tree),
504
+ _replicated_sharding(mesh, frame),
505
+ NamedSharding(mesh, system_batch),
506
+ NamedSharding(mesh, P()),
507
+ ),
508
+ out_shardings=(output_sharding,) * 5,
509
+ )
510
+
511
+
512
+ def _compile_mode(physical_kernel, trunk, inputs, mask, bmask, perm):
513
+ tree = compile_physical_tree_from_shared_trunk(physical_kernel, trunk, perm)
514
+ frame = compile_energy_frame(inputs, mask, bmask, perm)
515
+ return tree, frame
516
+
517
+
518
+ def _build_exact_skip(mesh: Mesh):
519
+ def local(sampled, mode):
520
+ equal = jnp.all(sampled.astype(jnp.int32) == mode[None].astype(jnp.int32))
521
+ return jax.lax.pmin(equal.astype(jnp.int32), "systems").astype(jnp.bool_)
522
+
523
+ mapped = jax.shard_map(
524
+ local,
525
+ mesh=mesh,
526
+ in_specs=(P("systems"), P()),
527
+ out_specs=P(),
528
+ check_vma=False,
529
+ )
530
+ replicated = NamedSharding(mesh, P())
531
+ return jax.jit(
532
+ mapped,
533
+ in_shardings=(NamedSharding(mesh, P("systems")), replicated),
534
+ out_shardings=replicated,
535
+ )
536
+
537
+
538
+ def _initial_model(config: TrainConfig, key):
539
+ model = build_model(config.model, key, n_max=config.n_max)
540
+ if config.checkpoint is not None:
541
+ model = load_model(config.checkpoint, model)
542
+ return model
543
+
544
+
545
+ def _run_route_burn_in(
546
+ sampler,
547
+ kernel,
548
+ trees,
549
+ config,
550
+ run_routes,
551
+ adapt,
552
+ ):
553
+ for iteration in range(config.mcmc.burn_in):
554
+ sampler = run_routes(
555
+ sampler,
556
+ kernel,
557
+ trees,
558
+ )
559
+ if iteration and iteration % config.mcmc.adapt_every == 0:
560
+ sampler = adapt(sampler)
561
+ return sampler
562
+
563
+
564
  def _initial_system_state(
565
  model,
566
  context,
 
572
  mesh,
573
  compile_plan,
574
  compile_trees,
575
+ get_run_routes,
576
  ):
577
  walkers = config.mcmc.batch_size // ROUTE_SAMPLES
578
  cpu = jax.devices("cpu")[0]
 
607
  sampler = _place_routes(mesh, sampler)
608
  contexts = _place_routes(mesh, contexts)
609
  perms = _place_routes(mesh, perms)
610
+ trunk = compile_plan(bind_trunk_compiler_kernel(model), context)
611
+ physical_kernel = bind_physical_compiler_kernel(model)
612
+ trees = compile_trees(physical_kernel, trunk, perms)
613
+ run_routes = get_run_routes(
614
+ sampler,
615
+ bind_shared_kernel(model),
616
+ trees,
617
+ config.mcmc.burn_in_replica_steps,
618
+ )
619
+ adapt = jax.jit(
620
+ lambda state: _adapt_routes(state, config),
621
+ in_shardings=(_systems_sharding(mesh, sampler),),
622
+ out_shardings=_systems_sharding(mesh, sampler),
623
+ )
624
+ sampler = _run_route_burn_in(
625
+ sampler,
626
+ bind_shared_kernel(model),
627
+ trees,
628
+ config,
629
+ run_routes,
630
+ adapt,
631
+ )
632
  return _SystemState(sampler=sampler, context=contexts, perms=perms)
633
 
634
 
 
651
  ) -> TrainResult:
652
  if config.mcmc.batch_size % ROUTE_SAMPLES:
653
  raise ValueError("mcmc.batch_size must be divisible by K=8")
654
+ if config.mcmc.burn_in < 0:
655
+ raise ValueError("mcmc.burn_in must be non-negative")
656
  systems = load_systems(config.systems)
657
  if not systems:
658
  raise ValueError("training requires at least one system")
 
671
  energy_inputs = [energy for _context, energy in systems_data]
672
  key = jax.random.PRNGKey(config.seed)
673
  key_model, key_mcmc = jax.random.split(key)
674
+ model = _initial_model(config, key_model)
675
  devices = tuple(jax.devices())
676
+ if len(devices) != ROUTE_SAMPLES:
677
+ raise ValueError("learned-router train requires exactly eight visible devices")
678
+ mesh = Mesh(np.asarray(devices, dtype=object), ("systems",))
 
 
679
  model = _replicate(mesh, model)
680
+ trunk_kernel = bind_trunk_compiler_kernel(model)
681
+ compile_plan = _build_owner_entry(
682
+ mesh,
683
+ compile_shared_trunk_from_kernel,
684
+ (trunk_kernel, contexts[0]),
685
  )
686
+ trunk_template = compile_plan(trunk_kernel, contexts[0])
687
+ physical_kernel = bind_physical_compiler_kernel(model)
688
+ perms_template = _place_routes(mesh, _identity_perms(config.n_max))
689
+ compile_trees = _build_compile_trees(
690
+ mesh,
691
+ physical_kernel,
692
+ trunk_template,
693
+ perms_template,
694
  )
695
+ compile_frames = _build_compile_frames(
696
+ mesh,
697
+ energy_inputs[0],
698
+ contexts[0].mask,
699
+ contexts[0].bmask,
700
+ perms_template,
701
+ )
702
+ sampled_energy = _build_sampled_energy(mesh, config.energy.chunk_size)
703
+ exact_skip = _build_exact_skip(mesh)
704
+ mcmc_entries = {}
705
+
706
+ def get_run_routes(state, kernel, trees, n_steps):
707
+ entry_key = (int(n_steps), config.mcmc.walker_chunk_size)
708
+ entry = mcmc_entries.get(entry_key)
709
+ if entry is None:
710
+ entry = _build_run_routes(
711
+ mesh,
712
+ state,
713
+ kernel,
714
+ trees,
715
+ n_steps=entry_key[0],
716
+ chunk_size=entry_key[1],
717
+ )
718
+ mcmc_entries[entry_key] = entry
719
+ return entry
720
+
721
  system_states: list[_SystemState | None] = [None] * len(systems)
722
 
723
  def get_system(index: int):
 
733
  mesh=mesh,
734
  compile_plan=compile_plan,
735
  compile_trees=compile_trees,
736
+ get_run_routes=get_run_routes,
737
  )
738
  return _activate_system(mesh, cached)
739
 
740
  first = get_system(0)
741
  q_seed = jax.vmap(cold_samples)(first.sampler)
742
+ system_batch_sharding = NamedSharding(mesh, P("systems", None))
743
+ systems_sharding = NamedSharding(mesh, P("systems"))
744
+ q_seed = jax.device_put(q_seed, system_batch_sharding)
745
+ energy_seed = jax.device_put(
746
+ np.zeros(q_seed.shape[:2], dtype=np.complex64),
747
+ system_batch_sharding,
748
+ )
749
  kfac = init_router_kfac_state(
750
  config.kfac,
751
  model,
 
758
  route_tau=config.router.temperature,
759
  route_loss_weight=config.router.loss_weight,
760
  )
761
+ reframe = _build_reframe(mesh, first.sampler, first.context, first.perms)
762
+ rebase = _build_rebase(mesh, q_seed, first.perms)
763
+ adapt_routes = jax.jit(
764
+ lambda state: _adapt_routes(state, config),
765
+ in_shardings=(_systems_sharding(mesh, first.sampler),),
766
+ out_shardings=_systems_sharding(mesh, first.sampler),
767
+ )
768
+ target_entry = jax.jit(
769
+ lambda sampled, baseline, sigma, weights: process_route_targets(
770
+ sampled,
771
+ baseline,
772
+ sigma,
773
+ weights,
774
+ mad_width=config.kfac.mad_clip_width,
775
+ ),
776
+ in_shardings=(
777
+ system_batch_sharding,
778
+ system_batch_sharding,
779
+ systems_sharding,
780
+ system_batch_sharding,
781
+ ),
782
+ out_shardings=(system_batch_sharding, systems_sharding),
783
+ )
784
+ snis_entry = jax.jit(
785
+ snis_mode_baseline,
786
+ in_shardings=(
787
+ system_batch_sharding,
788
+ system_batch_sharding,
789
+ system_batch_sharding,
790
+ ),
791
+ out_shardings=system_batch_sharding,
792
+ )
793
+ router_kernel_template = bind_router_kernel(model)
794
+ compile_router = _build_owner_entry(
795
+ mesh,
796
+ compile_router_static,
797
+ (
798
+ router_kernel_template,
799
+ trunk_template,
800
+ contexts[0].route_quotient_node_key,
801
+ contexts[0].route_quotient_edge_key,
802
+ contexts[0].needs_fwl2,
803
+ ),
804
+ )
805
+ router_static_template = compile_router(
806
+ router_kernel_template,
807
+ trunk_template,
808
+ contexts[0].route_quotient_node_key,
809
+ contexts[0].route_quotient_edge_key,
810
+ contexts[0].needs_fwl2,
811
+ )
812
+ route_sampler = build_route_sampler(
813
+ mesh,
814
+ router_kernel_template.decoder,
815
+ router_static_template,
816
+ )
817
+ mode_sampler = build_beam16(
818
+ mesh,
819
+ router_kernel_template.decoder,
820
+ router_static_template,
821
+ )
822
+ mode_perm_template = jnp.arange(config.n_max, dtype=jnp.int32)
823
+ compile_mode = _build_owner_entry(
824
+ mesh,
825
+ _compile_mode,
826
+ (
827
+ physical_kernel,
828
+ trunk_template,
829
+ energy_inputs[0],
830
+ contexts[0].mask,
831
+ contexts[0].bmask,
832
+ mode_perm_template,
833
+ ),
834
+ )
835
+ mode_energy = None
836
  system_states[0] = _host_system_state(first)
837
  del first, q_seed, energy_seed
838
  order_rng = np.random.default_rng(config.seed)
 
846
  order_rng.shuffle(order)
847
  system_index = int(order[step % len(order)])
848
  state = get_system(system_index)
849
+ trunk = compile_plan(
850
+ bind_trunk_compiler_kernel(model),
851
+ contexts[system_index],
852
+ )
853
  router_kernel = bind_router_kernel(model)
854
+ router_static = compile_router(
855
  router_kernel,
856
  trunk,
857
  contexts[system_index].route_quotient_node_key,
 
859
  contexts[system_index].needs_fwl2,
860
  )
861
  tau = jnp.asarray(config.router.temperature, dtype=jnp.float32)
 
 
862
  key, key_route = jax.random.split(key)
863
+ new_perms = route_sampler(
864
  router_kernel.decoder, router_static, key_route, tau
865
  )
866
+ mode_perm = mode_sampler(
867
  router_kernel.decoder, router_static, tau
868
  )
869
  state.sampler, state.context = reframe(
 
871
  )
872
  state.perms = new_perms
873
  kernel = bind_shared_kernel(model)
874
+ physical_kernel = bind_physical_compiler_kernel(model)
875
+ trees = compile_trees(
876
+ physical_kernel,
877
+ trunk,
878
+ new_perms,
879
+ )
880
  frames = compile_frames(
881
  energy_inputs[system_index],
882
  contexts[system_index].mask,
883
  contexts[system_index].bmask,
884
  new_perms,
885
  )
886
+ state.sampler = get_run_routes(
887
+ state.sampler,
888
  kernel,
889
  trees,
 
 
890
  config.mcmc.steps,
891
+ )(state.sampler, kernel, trees)
 
892
  if step and step % config.mcmc.adapt_every == 0:
893
+ state.sampler = adapt_routes(state.sampler)
894
  q_cold = jax.vmap(cold_samples)(state.sampler)
895
+ q_cold = jax.device_put(q_cold, system_batch_sharding)
896
  total, _exchange, _casimir, _field = sampled_energy(
897
  kernel,
898
  trees,
899
  frames,
900
  q_cold,
 
 
 
 
 
 
 
 
 
 
 
901
  )
902
+ baseline_is_sampled = bool(np.asarray(jax.device_get(exact_skip(new_perms, mode_perm))))
903
  if baseline_is_sampled:
904
  baseline_total = total
905
+ baseline_weights = jnp.ones_like(total.real) / total.shape[-1]
 
 
 
 
906
  else:
907
+ mode_tree, mode_frame = compile_mode(
908
+ physical_kernel,
909
+ trunk,
910
  energy_inputs[system_index],
911
  contexts[system_index].mask,
912
  contexts[system_index].bmask,
913
  mode_perm,
914
  )
915
+ q_canonical = rebase(q_cold, new_perms)
916
+ if mode_energy is None:
917
+ mode_energy = _build_mode_energy(
918
+ mesh,
919
+ kernel,
920
+ mode_tree,
921
+ mode_frame,
922
+ q_canonical,
923
+ mode_perm,
924
+ config.energy.chunk_size,
925
+ )
926
  baseline_total, _bx, _bc, _bf, candidate_log_p = mode_energy(
927
  kernel,
928
  mode_tree,
929
  mode_frame,
930
  q_canonical,
931
  mode_perm,
 
932
  )
933
  sampled_log_p = state.sampler.log_p[..., -1]
934
+ sampled_log_p = jax.device_put(sampled_log_p, system_batch_sharding)
935
+ baseline_weights = snis_entry(
936
  baseline_total, candidate_log_p, sampled_log_p
937
  )
938
+ target, advantage = target_entry(
939
  total,
940
  baseline_total,
941
  state.context.s_norm,
942
  baseline_weights,
943
+ )
944
+ target = jax.device_put(target, system_batch_sharding)
945
+ advantage = jax.device_put(advantage, systems_sharding)
946
+ state.context = jax.device_put(
947
+ state.context,
948
+ _systems_sharding(mesh, state.context),
949
  )
950
  key, key_kfac = jax.random.split(key)
951
  model, kfac = apply_router_kfac_step(
src/kfac_jax/_src/utils/staging.py CHANGED
@@ -338,6 +338,7 @@ def staged(
338
  jax.tree_util.tree_map(_spec_for, a) for a in dynamic_args
339
  )
340
  cache_key = (
 
341
  instance.pmap_axis_name,
342
  tuple(mesh.axis_names),
343
  tuple(mesh.shape.items()),
 
338
  jax.tree_util.tree_map(_spec_for, a) for a in dynamic_args
339
  )
340
  cache_key = (
341
+ id(instance),
342
  instance.pmap_axis_name,
343
  tuple(mesh.axis_names),
344
  tuple(mesh.shape.items()),