|
|
|
|
| """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 |
| |
| 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) |
|
|
| |
| 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): |
| |
| |
| |
| 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} |
|
|