"""Haiku modules for the Diffuser model.""" from collections.abc import Sequence from typing import Literal from flax_model.alphafold3.common import base_config from flax_model.alphafold3.jax.attention import attention from flax_model.alphafold3.jax.gated_linear_unit import gated_linear_unit from flax_model.alphafold3.model import model_config from flax_model.alphafold3.model.components import haiku_modules as hm from flax_model.alphafold3.model.components import mapping from flax_model.alphafold3.model.network import diffusion_transformer import haiku as hk import jax import jax.numpy as jnp def get_shard_size( num_residues: int, shard_spec: Sequence[tuple[int | None, int | None]] ) -> int | None: shard_size = shard_spec[0][-1] for num_residues_upper_bound, num_residues_shard_size in shard_spec: shard_size = num_residues_shard_size if ( num_residues_upper_bound is None or num_residues <= num_residues_upper_bound ): break return shard_size class TransitionBlock(hk.Module): """Transition block for transformer.""" class Config(base_config.BaseConfig): num_intermediate_factor: int = 4 use_glu_kernel: bool = True def __init__( self, config: Config, global_config: model_config.GlobalConfig, *, name ): super().__init__(name=name) self.config = config self.global_config = global_config def __call__(self, act, broadcast_dim=0): num_channels = act.shape[-1] num_intermediate = int(num_channels * self.config.num_intermediate_factor) act = hm.LayerNorm(name='input_layer_norm')(act) if self.config.use_glu_kernel: weights, _ = hm.haiku_linear_get_params( act, num_output=num_intermediate * 2, initializer='relu', name='transition1', ) weights = jnp.reshape(weights, (len(weights), 2, num_intermediate)) c = gated_linear_unit.gated_linear_unit( x=act, weight=weights, implementation=None, activation=jax.nn.swish ) else: act = hm.Linear( num_intermediate * 2, initializer='relu', name='transition1' )(act) a, b = jnp.split(act, 2, axis=-1) c = jax.nn.swish(a) * b return hm.Linear( num_channels, initializer=self.global_config.final_init, name='transition2', )(c) class MSAAttention(hk.Module): """MSA Attention.""" class Config(base_config.BaseConfig): num_head: int = 8 def __init__( self, config: Config, global_config: model_config.GlobalConfig, *, name ): super().__init__(name=name) self.config = config self.global_config = global_config def __call__(self, act, mask, pair_act): act = hm.LayerNorm(name='act_norm')(act) pair_act = hm.LayerNorm(name='pair_norm')(pair_act) logits = hm.Linear( self.config.num_head, use_bias=False, name='pair_logits' )(pair_act) logits = jnp.transpose(logits, [2, 0, 1]) logits += 1e9 * (jnp.max(mask, axis=0) - 1.0) weights = jax.nn.softmax(logits, axis=-1) num_channels = act.shape[-1] value_dim = num_channels // self.config.num_head v = hm.Linear( [self.config.num_head, value_dim], use_bias=False, name='v_projection' )(act) v_avg = jnp.einsum('hqk, bkhc -> bqhc', weights, v) v_avg = jnp.reshape(v_avg, v_avg.shape[:-2] + (-1,)) gate_values = hm.Linear( self.config.num_head * value_dim, bias_init=1.0, initializer='zeros', name='gating_query', )(act) v_avg *= jax.nn.sigmoid(gate_values) return hm.Linear( num_channels, initializer=self.global_config.final_init, name='output_projection', )(v_avg) class GridSelfAttention(hk.Module): """Self attention that is either per-sequence or per-residue.""" class Config(base_config.BaseConfig): num_head: int = 4 def __init__( self, config: Config, global_config: model_config.GlobalConfig, transpose: bool, *, name: str, ): super().__init__(name=name) self.config = config self.global_config = global_config self.transpose = transpose @hk.transparent def _attention( self, act, mask, bias, ): num_channels = act.shape[-1] assert num_channels % self.config.num_head == 0 # Triton requires a minimum dimension of 16 for doing matmul. qkv_dim = max(num_channels // self.config.num_head, 16) qkv_shape = (self.config.num_head, qkv_dim) q = hm.Linear( qkv_shape, use_bias=False, name='q_projection', transpose_weights=True )(act) k = hm.Linear( qkv_shape, use_bias=False, name='k_projection', transpose_weights=True )(act) v = hm.Linear(qkv_shape, use_bias=False, name='v_projection')(act) # Dot product attention requires the bias term to have a batch dimension. bias = jnp.expand_dims(bias, 0) weighted_avg = attention.dot_product_attention( q, k, v, mask=mask, bias=bias, implementation=self.global_config.flash_attention_implementation, ) weighted_avg = jnp.reshape(weighted_avg, weighted_avg.shape[:-2] + (-1,)) gate_values = hm.Linear( self.config.num_head * qkv_dim, bias_init=1.0, initializer='zeros', transpose_weights=True, name='gating_query', )(act) weighted_avg *= jax.nn.sigmoid(gate_values) return hm.Linear( num_channels, initializer=self.global_config.final_init, name='output_projection', )(weighted_avg) def __call__(self, act, pair_mask): """Builds a module. Arguments: act: [num_seq, num_res, channels] activations tensor pair_mask: [num_seq, num_res] mask of non-padded regions in the tensor. Only used in inducing points attention currently. Returns: Result of the self-attention operation. """ assert len(act.shape) == 3 assert len(pair_mask.shape) == 2 pair_mask = jnp.swapaxes(pair_mask, -1, -2) act = hm.LayerNorm(name='act_norm')(act) nonbatched_bias = hm.Linear( self.config.num_head, use_bias=False, name='pair_bias_projection' )(act) nonbatched_bias = jnp.transpose(nonbatched_bias, [2, 0, 1]) num_residues = act.shape[0] chunk_size = get_shard_size( num_residues, self.global_config.pair_attention_chunk_size ) if self.transpose: act = jnp.swapaxes(act, -2, -3) pair_mask = pair_mask[:, None, None, :].astype(jnp.bool_) act = mapping.inference_subbatch( self._attention, chunk_size, batched_args=[act, pair_mask], nonbatched_args=[nonbatched_bias], ) if self.transpose: act = jnp.swapaxes(act, -2, -3) return act class TriangleMultiplication(hk.Module): """Triangle Multiplication.""" class Config(base_config.BaseConfig): equation: Literal['ikc,jkc->ijc', 'kjc,kic->ijc'] use_glu_kernel: bool = True def __init__( self, config: Config, global_config: model_config.GlobalConfig, *, name ): super().__init__(name=name) self.config = config self.global_config = global_config def __call__(self, act, mask): """Applies Module. Args: act: The activation. mask: The mask. Returns: Outputs, should have same shape/type as output_act """ mask = mask[None, ...] num_channels = act.shape[-1] equation = { 'ikc,jkc->ijc': 'cik,cjk->cij', 'kjc,kic->ijc': 'ckj,cki->cij', }[self.config.equation] act = hm.LayerNorm(name='left_norm_input')(act) input_act = act if self.config.use_glu_kernel: weights_projection, _ = hm.haiku_linear_get_params( act, num_output=num_channels * 2, name='projection' ) weights_gate, _ = hm.haiku_linear_get_params( act, num_output=num_channels * 2, initializer=self.global_config.final_init, name='gate', ) weights_glu = jnp.stack([weights_gate, weights_projection], axis=1) projection = gated_linear_unit.gated_linear_unit( x=act, weight=weights_glu, activation=jax.nn.sigmoid, implementation=None, ) projection = jnp.transpose(projection, (2, 0, 1)) projection *= mask else: projection = hm.Linear(num_channels * 2, name='projection')(act) projection = jnp.transpose(projection, (2, 0, 1)) projection *= mask gate = hm.Linear( num_channels * 2, name='gate', bias_init=1.0, initializer=self.global_config.final_init, )(act) gate = jnp.transpose(gate, (2, 0, 1)) projection *= jax.nn.sigmoid(gate) projection = projection.reshape(num_channels, 2, *projection.shape[1:]) a, b = jnp.split(projection, 2, axis=1) a, b = jnp.squeeze(a, axis=1), jnp.squeeze(b, axis=1) act = jnp.einsum(equation, a, b) act = hm.LayerNorm(name='center_norm', axis=0, param_axis=0)(act) act = jnp.transpose(act, (1, 2, 0)) act = hm.Linear( num_channels, initializer=self.global_config.final_init, name='output_projection', )(act) gate_out = hm.Linear( num_channels, name='gating_linear', bias_init=1.0, initializer=self.global_config.final_init, )(input_act) act *= jax.nn.sigmoid(gate_out) return act class OuterProductMean(hk.Module): """Computed mean outer product.""" class Config(base_config.BaseConfig): chunk_size: int = 128 num_outer_channel: int = 32 def __init__( self, config: Config, global_config: model_config.GlobalConfig, num_output_channel, *, name, ): super().__init__(name=name) self.global_config = global_config self.config = config self.num_output_channel = num_output_channel def __call__(self, act, mask): mask = mask[..., None] act = hm.LayerNorm(name='layer_norm_input')(act) left_act = mask * hm.Linear( self.config.num_outer_channel, initializer='linear', name='left_projection', )(act) right_act = mask * hm.Linear( self.config.num_outer_channel, initializer='linear', name='right_projection', )(act) if self.global_config.final_init == 'zeros': w_init = hk.initializers.Constant(0.0) else: w_init = hk.initializers.VarianceScaling(scale=2.0, mode='fan_in') output_w = hk.get_parameter( 'output_w', shape=( self.config.num_outer_channel, self.config.num_outer_channel, self.num_output_channel, ), dtype=act.dtype, init=w_init, ) output_b = hk.get_parameter( 'output_b', shape=(self.num_output_channel,), dtype=act.dtype, init=hk.initializers.Constant(0.0), ) def compute_chunk(left_act): # Make sure that the 'b' dimension is the most minor batch like dimension # so it will be treated as the real batch by XLA (both during the forward # and the backward pass) left_act = jnp.transpose(left_act, [0, 2, 1]) act = jnp.einsum('acb,ade->dceb', left_act, right_act) act = jnp.einsum('dceb,cef->dbf', act, output_w) + output_b return jnp.transpose(act, [1, 0, 2]) act = mapping.inference_subbatch( compute_chunk, self.config.chunk_size, batched_args=[left_act], nonbatched_args=[], input_subbatch_dim=1, output_subbatch_dim=0, ) epsilon = 1e-3 norm = jnp.einsum('abc,adc->bdc', mask, mask) return act / (epsilon + norm) class PairFormerIteration(hk.Module): """Single Iteration of Pair Former.""" class Config(base_config.BaseConfig): """Config for PairFormerIteration.""" num_layer: int pair_attention: GridSelfAttention.Config = base_config.autocreate() pair_transition: TransitionBlock.Config = base_config.autocreate() single_attention: diffusion_transformer.SelfAttentionConfig | None = None single_transition: TransitionBlock.Config | None = None triangle_multiplication_incoming: TriangleMultiplication.Config = ( base_config.autocreate(equation='kjc,kic->ijc') ) triangle_multiplication_outgoing: TriangleMultiplication.Config = ( base_config.autocreate(equation='ikc,jkc->ijc') ) shard_transition_blocks: bool = True def __init__( self, config: Config, global_config: model_config.GlobalConfig, with_single=False, *, name, ): super().__init__(name=name) self.config = config self.global_config = global_config self.with_single = with_single def __call__( self, act, pair_mask, single_act=None, seq_mask=None, ): """Build a single iteration of the pair former. Args: act: [num_res, num_res, num_channel] Input pairwise activations. pair_mask: [num_res, num_res] padding mask. single_act: [num_res, single_channel] Single Input activations, optional seq_mask: [num_res] Sequence Mask, optional. Returns: [num_res, num_res, num_channel] tensor of activations. """ num_residues = act.shape[0] act += TriangleMultiplication( self.config.triangle_multiplication_outgoing, self.global_config, name='triangle_multiplication_outgoing', )(act, pair_mask) act += TriangleMultiplication( self.config.triangle_multiplication_incoming, self.global_config, name='triangle_multiplication_incoming', )(act, pair_mask) act += GridSelfAttention( self.config.pair_attention, self.global_config, name='pair_attention1', transpose=False, )(act, pair_mask) act += GridSelfAttention( self.config.pair_attention, self.global_config, name='pair_attention2', transpose=True, )(act, pair_mask) transition_block = TransitionBlock( self.config.pair_transition, self.global_config, name='pair_transition' ) if self.config.shard_transition_blocks: transition_block = mapping.sharded_apply( transition_block, get_shard_size( num_residues, self.global_config.pair_transition_shard_spec ), ) act += transition_block(act) if self.with_single: assert self.config.single_attention is not None pair_logits = hm.Linear( self.config.single_attention.num_head, name='single_pair_logits_projection', )(hm.LayerNorm(name='single_pair_logits_norm')(act)) pair_logits = jnp.transpose(pair_logits, [2, 0, 1]) single_act += diffusion_transformer.self_attention( single_act, seq_mask, pair_logits=pair_logits, config=self.config.single_attention, global_config=self.global_config, name='single_attention_', ) single_act += TransitionBlock( self.config.single_transition, self.global_config, name='single_transition', )(single_act, broadcast_dim=None) return act, single_act else: return act class EvoformerIteration(hk.Module): """Single Iteration of Evoformer Main Stack.""" class Config(base_config.BaseConfig): """Configuration for EvoformerIteration.""" num_layer: int = 4 msa_attention: MSAAttention.Config = base_config.autocreate() outer_product_mean: OuterProductMean.Config = base_config.autocreate() msa_transition: TransitionBlock.Config = base_config.autocreate() pair_attention: GridSelfAttention.Config = base_config.autocreate() pair_transition: TransitionBlock.Config = base_config.autocreate() triangle_multiplication_incoming: TriangleMultiplication.Config = ( base_config.autocreate(equation='kjc,kic->ijc') ) triangle_multiplication_outgoing: TriangleMultiplication.Config = ( base_config.autocreate(equation='ikc,jkc->ijc') ) shard_transition_blocks: bool = True def __init__( self, config: Config, global_config: model_config.GlobalConfig, name='evoformer_iteration', ): super().__init__(name=name) self.config = config self.global_config = global_config def __call__(self, activations, masks): msa_act, pair_act = activations['msa'], activations['pair'] num_residues = pair_act.shape[0] msa_mask, pair_mask = masks['msa'], masks['pair'] pair_act += OuterProductMean( config=self.config.outer_product_mean, global_config=self.global_config, num_output_channel=int(pair_act.shape[-1]), name='outer_product_mean', )(msa_act, msa_mask) msa_act += MSAAttention( self.config.msa_attention, self.global_config, name='msa_attention1' )(msa_act, msa_mask, pair_act=pair_act) msa_act += TransitionBlock( self.config.msa_transition, self.global_config, name='msa_transition' )(msa_act) pair_act += TriangleMultiplication( self.config.triangle_multiplication_outgoing, self.global_config, name='triangle_multiplication_outgoing', )(pair_act, pair_mask) pair_act += TriangleMultiplication( self.config.triangle_multiplication_incoming, self.global_config, name='triangle_multiplication_incoming', )(pair_act, pair_mask) pair_act += GridSelfAttention( self.config.pair_attention, self.global_config, name='pair_attention1', transpose=False, )(pair_act, pair_mask) pair_act += GridSelfAttention( self.config.pair_attention, self.global_config, name='pair_attention2', transpose=True, )(pair_act, pair_mask) transition_block = TransitionBlock( self.config.pair_transition, self.global_config, name='pair_transition' ) if self.config.shard_transition_blocks: transition_block = mapping.sharded_apply( transition_block, get_shard_size( num_residues, self.global_config.pair_transition_shard_spec ), ) pair_act += transition_block(pair_act) return {'msa': msa_act, 'pair': pair_act}