| |
| |
|
|
|
|
| from __future__ import annotations |
|
|
| import gc |
| from typing import NamedTuple |
|
|
| import jax |
| import jax.numpy as jnp |
| import numpy as np |
| from jax.sharding import Mesh, NamedSharding, PartitionSpec as P |
|
|
| from hamiltonzero.compiled.tree import bind_physical_compiler_kernel |
| from hamiltonzero.compiled.trunk import ( |
| bind_shared_kernel, |
| bind_trunk_compiler_kernel, |
| ) |
| from hamiltonzero.compiled.types import CompiledWaveFunction |
| from hamiltonzero.model.route_pointer import TreePrefixPointerMHSEA |
| from .sequence_trunk import ( |
| build_sequence_pair_permute, |
| build_sequence_parallel_contextualizer, |
| build_sequence_parallel_edge_global_update, |
| build_sequence_parallel_physical_leaf, |
| build_sequence_parallel_physical_reducer, |
| build_sequence_parallel_shared_trunk, |
| ) |
| from .greedy_router import build_compact_greedy_router |
|
|
|
|
| class LargeNCompiledEvalResult(NamedTuple): |
| wavefunction: CompiledWaveFunction |
| perm: jax.Array |
| logp: jax.Array |
|
|
|
|
| def _sequence_mesh(n: int, requested_shards: int) -> Mesh: |
| devices = tuple(jax.devices()) |
| shards = len(devices) if requested_shards == 0 else requested_shards |
| if shards < 1 or shards > len(devices): |
| raise ValueError( |
| f"compiled eval requested {shards} seq shards, but JAX exposes " |
| f"{len(devices)} devices" |
| ) |
| if n % shards: |
| raise ValueError(f"N={n} must be divisible by seq_shards={shards}") |
| local_rows = n // shards |
| if n & (n - 1) or local_rows & (local_rows - 1): |
| raise ValueError( |
| "large-N physical compilation requires a power-of-two padded " |
| f"width and power-of-two rows per lane; got N={n}, " |
| f"local_rows={local_rows}" |
| ) |
| return Mesh(np.asarray(devices[:shards], dtype=object), ("seq",)) |
|
|
|
|
| def _replicate(tree, sharding: NamedSharding): |
| return jax.device_put(tree, jax.tree_util.tree_map(lambda _leaf: sharding, tree)) |
|
|
|
|
| class _LargeNXlaTreePrefixPointer(TreePrefixPointerMHSEA): |
| def _resolve_heavy_attn_impl(self, n: int): |
| del n |
| return None |
|
|
| def _resolve_tree_attn_impl(self): |
| return None |
|
|
| def _route_attention( |
| self, |
| q, |
| k, |
| v, |
| edge_bias, |
| key_mask, |
| *, |
| impl, |
| key_mask_only=False, |
| attention_mask=None, |
| sequence_axis_name=None, |
| sequence_mesh=None, |
| ): |
| del impl, key_mask_only |
| dtype = q.dtype |
| valid = key_mask.astype(bool)[:, None, :] |
| if attention_mask is not None: |
| valid = valid & attention_mask.astype(bool) |
| has_key = jnp.any(valid, axis=-1) |
| q_c = q.astype(jnp.float32) |
| k_c = k.astype(jnp.float32) |
| v_c = v.astype(jnp.float32) |
| bias_c = edge_bias.astype(jnp.float32) |
| if sequence_axis_name is not None: |
|
|
| def sharding(*axes): |
| spec = P(*axes) |
| return ( |
| NamedSharding(sequence_mesh, spec) |
| if sequence_mesh is not None |
| else spec |
| ) |
|
|
| q_c = jax.lax.with_sharding_constraint( |
| q_c, sharding(None, sequence_axis_name, None, None) |
| ) |
| k_c = jax.lax.with_sharding_constraint( |
| k_c, sharding(None, None, None, None) |
| ) |
| v_c = jax.lax.with_sharding_constraint( |
| v_c, sharding(None, None, None, None) |
| ) |
| bias_c = jax.lax.with_sharding_constraint( |
| bias_c, sharding(None, sequence_axis_name, None, None) |
| ) |
| logits = jnp.einsum("bihd,bjhd->bhij", q_c, k_c) |
| logits = logits / jnp.sqrt(jnp.asarray(self.d_head, dtype=jnp.float32)) |
| logits = logits + jnp.transpose(bias_c, (0, 3, 1, 2)) |
| if sequence_axis_name is not None: |
| logits = jax.lax.with_sharding_constraint( |
| logits, sharding(None, None, sequence_axis_name, None) |
| ) |
| logits = jnp.where( |
| valid[:, None, :, :], |
| logits, |
| jnp.asarray(-1.0e30, dtype=jnp.float32), |
| ) |
| if sequence_axis_name is not None: |
| logits = jax.lax.with_sharding_constraint( |
| logits, sharding(None, None, sequence_axis_name, None) |
| ) |
| alpha = jax.nn.softmax(logits, axis=-1) |
| if sequence_axis_name is not None: |
| alpha = jax.lax.with_sharding_constraint( |
| alpha, sharding(None, None, sequence_axis_name, None) |
| ) |
| out = jnp.einsum("bhij,bjhd->bihd", alpha, v_c) |
| if sequence_axis_name is not None: |
| out = jax.lax.with_sharding_constraint( |
| out, sharding(None, sequence_axis_name, None, None) |
| ) |
| out = out.astype(dtype) |
| return jnp.where(has_key[..., None, None], out, jnp.zeros_like(out)) |
|
|
|
|
| def _large_n_xla_decoder_view(decoder: TreePrefixPointerMHSEA): |
| compiled = object.__new__(_LargeNXlaTreePrefixPointer) |
| compiled.__dict__.update(decoder.__dict__) |
| return compiled |
|
|
|
|
| def _validate_large_n_model(model) -> None: |
| decoder = getattr(model, "route_decoder", None) |
| failures: list[str] = [] |
| if not isinstance(decoder, TreePrefixPointerMHSEA): |
| failures.append("route decoder must be TreePrefixPointerMHSEA") |
| if getattr(model, "route_contextualizer", None) is None: |
| failures.append("route contextualizer must be enabled") |
| if getattr(model, "gladder_fork_route", None) is None: |
| failures.append("route global fork must be enabled") |
| if getattr(model, "readout_leaf_context", None) is None: |
| failures.append("physical contextualizer must be enabled") |
| if failures: |
| raise ValueError( |
| "unsupported large-N eval compiler model: " + "; ".join(failures) |
| ) |
|
|
|
|
| def _validate_context(ctx) -> int: |
| jdp = jnp.asarray(ctx.J_double_prime) |
| mask = jnp.asarray(ctx.mask) |
| bmask = jnp.asarray(ctx.bmask) |
| h_prime = jnp.asarray(ctx.h_prime) |
| if jdp.ndim != 3 or jdp.shape[-1] != 10: |
| raise ValueError("ctx.J_double_prime must have shape [N,N,10]") |
| n = int(jdp.shape[0]) |
| if jdp.shape[1] != n: |
| raise ValueError("ctx.J_double_prime pair axes must be square") |
| if mask.shape != (n,) or bmask.shape != (n,): |
| raise ValueError("ctx.mask and ctx.bmask must both have shape [N]") |
| if h_prime.shape != (n, 3): |
| raise ValueError("ctx.h_prime must have shape [N,3]") |
| if jdp.dtype != jnp.float32 or h_prime.dtype != jnp.float32: |
| raise TypeError( |
| "large-N eval keeps streamed pair/frontier arithmetic in fp32; " |
| f"got J={jdp.dtype}, h={h_prime.dtype}" |
| ) |
| return n |
|
|
|
|
| def compile_large_n_eval_wavefunction( |
| model, |
| ctx, |
| *, |
| seq_shards: int = 0, |
| pair_tile_size: int = 128, |
| tau: float = 1.0, |
| ) -> LargeNCompiledEvalResult: |
|
|
| _validate_large_n_model(model) |
| n = _validate_context(ctx) |
| if pair_tile_size < 1: |
| raise ValueError("pair_tile_size must be positive") |
| mesh = _sequence_mesh(n, int(seq_shards)) |
| rep = NamedSharding(mesh, P()) |
| seq_edge = NamedSharding(mesh, P("seq", None, None)) |
|
|
| trunk_kernel = _replicate(bind_trunk_compiler_kernel(model), rep) |
| route_contextualizer = _replicate(model.route_contextualizer, rep) |
| route_global_fork = _replicate(model.gladder_fork_route, rep) |
| decoder = _replicate(_large_n_xla_decoder_view(model.route_decoder), rep) |
| physical_kernel = _replicate(bind_physical_compiler_kernel(model), rep) |
|
|
| jdp = jax.device_put(jnp.asarray(ctx.J_double_prime), seq_edge) |
| h_prime, real_mask, structural_mask = jax.device_put( |
| ( |
| jnp.asarray(ctx.h_prime), |
| jnp.asarray(ctx.mask), |
| jnp.asarray(ctx.bmask), |
| ), |
| rep, |
| ) |
|
|
| trunk_entry = build_sequence_parallel_shared_trunk( |
| mesh=mesh, |
| kernel_template=trunk_kernel, |
| featurizer_tile_size=int(pair_tile_size), |
| ) |
| trunk = trunk_entry(trunk_kernel, jdp, h_prime, real_mask, structural_mask) |
|
|
| route_context_entry = build_sequence_parallel_contextualizer( |
| mesh=mesh, |
| contextualizer_template=route_contextualizer, |
| g_template=trunk.global_stream, |
| tile_size=int(pair_tile_size), |
| ) |
| route_node, route_edge, route_g = route_context_entry( |
| route_contextualizer, |
| trunk.node_raw, |
| trunk.edge_raw, |
| real_mask, |
| structural_mask, |
| trunk.global_stream, |
| ) |
| route_global_entry = build_sequence_parallel_edge_global_update( |
| mesh=mesh, |
| module_template=route_global_fork, |
| tile_size=int(pair_tile_size), |
| ) |
| route_global = route_global_entry( |
| route_global_fork, route_g, route_edge, structural_mask |
| ) |
|
|
| route_entry = build_compact_greedy_router( |
| mesh=mesh, |
| decoder_template=decoder, |
| pair_tile_size=int(pair_tile_size), |
| ) |
| perm, logp = route_entry( |
| decoder, |
| route_node, |
| route_edge, |
| structural_mask, |
| route_global, |
| jnp.asarray(tau, dtype=jnp.float32), |
| real_mask, |
| ) |
|
|
| jax.block_until_ready((perm, logp)) |
| del route_node, route_edge, route_g, route_global |
|
|
| pair_permute = build_sequence_pair_permute(mesh=mesh) |
| routed_node, routed_edge = pair_permute(trunk.node_raw, trunk.edge_raw, perm) |
| leaf_real = real_mask[perm] |
| global_stream = trunk.global_stream |
|
|
| jax.block_until_ready((routed_node, routed_edge, leaf_real, global_stream)) |
| del ( |
| trunk, |
| trunk_kernel, |
| route_contextualizer, |
| route_global_fork, |
| decoder, |
| jdp, |
| h_prime, |
| real_mask, |
| trunk_entry, |
| route_context_entry, |
| route_global_entry, |
| route_entry, |
| pair_permute, |
| ) |
| gc.collect() |
|
|
| physical_context_entry = build_sequence_parallel_contextualizer( |
| mesh=mesh, |
| contextualizer_template=physical_kernel.contextualizer, |
| g_template=global_stream, |
| tile_size=int(pair_tile_size), |
| ) |
| physical_node, physical_edge, physical_context_g = physical_context_entry( |
| physical_kernel.contextualizer, |
| routed_node, |
| routed_edge, |
| leaf_real, |
| structural_mask, |
| global_stream, |
| ) |
| jax.block_until_ready((physical_node, physical_edge, physical_context_g)) |
| del routed_node, routed_edge, global_stream, physical_context_entry |
| gc.collect() |
|
|
| physical_global_entry = build_sequence_parallel_edge_global_update( |
| mesh=mesh, |
| module_template=physical_kernel.global_fork, |
| tile_size=int(pair_tile_size), |
| ) |
| physical_global = physical_global_entry( |
| physical_kernel.global_fork, |
| physical_context_g, |
| physical_edge, |
| structural_mask, |
| ) |
| jax.block_until_ready(physical_global) |
| del physical_context_g, physical_global_entry |
| gc.collect() |
|
|
| physical_leaf_entry = build_sequence_parallel_physical_leaf( |
| mesh=mesh, |
| kernel_template=physical_kernel, |
| ) |
| leaf_h, c_rows = physical_leaf_entry( |
| physical_kernel, |
| physical_node, |
| physical_global, |
| ) |
| jax.block_until_ready((leaf_h, c_rows)) |
| del physical_node, physical_leaf_entry |
| gc.collect() |
|
|
| physical_reducer_entry = build_sequence_parallel_physical_reducer( |
| mesh=mesh, |
| kernel_template=physical_kernel, |
| edge_template=physical_edge, |
| replicate_threshold=min(512, n), |
| contextualizer_tile_size=int(pair_tile_size), |
| ) |
| tree = physical_reducer_entry( |
| physical_kernel, |
| physical_edge, |
| leaf_h, |
| c_rows, |
| leaf_real, |
| structural_mask, |
| physical_global, |
| perm, |
| ) |
| jax.block_until_ready(tree) |
|
|
| return LargeNCompiledEvalResult( |
| wavefunction=CompiledWaveFunction(bind_shared_kernel(model), tree), |
| perm=perm, |
| logp=logp, |
| ) |
|
|
|
|
| __all__ = [ |
| "LargeNCompiledEvalResult", |
| "compile_large_n_eval_wavefunction", |
| ] |
|
|