OneScience's picture
Upload folder using huggingface_hub
4516781 verified
Raw
History Blame Contribute Delete
17.3 kB
# Copyright 2026 Google LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from absl.testing import absltest
from absl.testing import parameterized
from flax_model.alphagenome._sdk.data import junction_data
from flax_model.alphagenome._sdk.data import track_data
from flax_model.alphagenome._sdk.models import dna_model
from flax_model.alphagenome.model import embeddings as embeddings_lib
from flax_model.alphagenome.model import model as model_lib
from flax_model.alphagenome.model import schemas
from flax_model.alphagenome.model.metadata import metadata as metadata_lib
import chex
import haiku as hk
import jax
import jax.numpy as jnp
from jaxtyping import Array, Float, Int # pylint: disable=g-importing-member, g-multiple-import
import numpy as np
import pandas as pd
def _mock_track_metadata(
num_tracks: int, num_padding: int = 0
) -> track_data.TrackMetadata:
return pd.DataFrame({
'name': (
[f'track_{i}' for i in range(num_tracks)] + ['padding'] * num_padding
),
'nonzero_mean': [1.0] * num_tracks + [0.0] * num_padding,
})
def _mock_junction_metadata(
num_tissues: int, num_padding: int = 0
) -> junction_data.JunctionMetadata:
return pd.DataFrame({
'tissue': (
[f'tissue_{i}' for i in range(num_tissues)]
+ ['padding'] * num_padding
),
'name': (
[f'tissue_{i}' for i in range(num_tissues)]
+ ['padding'] * num_padding
),
})
class ModelTest(parameterized.TestCase):
def test_create_model_metadata_consistency(self):
"""Tests that the model raises an error if the metadata is inconsistent."""
output_metadata_missing = {
dna_model.Organism.HOMO_SAPIENS: metadata_lib.AlphaGenomeOutputMetadata(
atac=_mock_track_metadata(num_tracks=13)
),
dna_model.Organism.MUS_MUSCULUS: (
metadata_lib.AlphaGenomeOutputMetadata()
),
}
@hk.transform_with_state
def forward(dna_sequence, organism_index):
return model_lib.AlphaGenome(output_metadata_missing)(
dna_sequence, organism_index
)
with self.assertRaisesRegex(
ValueError,
r'No metadata found for output type "ATAC" for the following organisms:'
r'.*MUS_MUSCULUS.*',
):
jax.eval_shape(
forward.init,
jax.random.key(0),
jax.ShapeDtypeStruct((1, 2048, 4), jnp.float32),
jax.ShapeDtypeStruct((1,), jnp.int32),
)
@parameterized.named_parameters(
dict(
testcase_name='no_organisms',
organisms=(),
),
dict(
testcase_name='one_organism',
organisms=(dna_model.Organism.HOMO_SAPIENS,),
),
dict(
testcase_name='two_organisms',
organisms=(
dna_model.Organism.HOMO_SAPIENS,
dna_model.Organism.MUS_MUSCULUS,
),
),
)
def test_create_headless_model(
self, organisms: tuple[dna_model.Organism, ...]
):
"""Tests that the model can be created without any heads."""
output_metadata = {
organism: metadata_lib.AlphaGenomeOutputMetadata()
for organism in organisms
}
@hk.transform_with_state
def forward(dna_sequence, organism_index):
return model_lib.AlphaGenome(output_metadata)(
dna_sequence, organism_index
)
params_shape, state_shape = jax.eval_shape(
forward.init,
jax.random.key(0),
seq_input := jax.ShapeDtypeStruct((1, 2048, 4), jnp.float32),
organism_index := jax.ShapeDtypeStruct((1,), jnp.int32),
)
(predictions_shape, embeddings_shape), _ = jax.eval_shape(
forward.apply,
params_shape,
state_shape,
jax.random.key(0),
seq_input,
organism_index,
)
# Predictions are only the embeddings in this case.
chex.assert_trees_all_equal_shapes(
predictions_shape,
{'embeddings_1bp': jnp.zeros((1, 2048, 1536))},
)
chex.assert_trees_all_equal_shapes(
embeddings_shape,
embeddings_lib.Embeddings(
embeddings_1bp=jnp.zeros((1, 2048, 1536)),
embeddings_128bp=jnp.zeros((1, 16, 3072), jnp.float32),
embeddings_pair=jnp.zeros((1, 1, 1, 128), jnp.float32),
),
)
def test_create_model(self):
num_tracks = 13
output_metadata = {
dna_model.Organism.HOMO_SAPIENS: metadata_lib.AlphaGenomeOutputMetadata(
atac=_mock_track_metadata(num_tracks=num_tracks)
),
dna_model.Organism.MUS_MUSCULUS: metadata_lib.AlphaGenomeOutputMetadata(
atac=_mock_track_metadata(num_tracks=7, num_padding=num_tracks - 7)
),
}
@hk.transform_with_state
def forward(
dna_sequence: Float[Array, 'B S 4'], organism_index: Int[Array, 'B']
):
return model_lib.AlphaGenome(output_metadata)(
dna_sequence, organism_index
)
seq_length = int(2**14)
key = jax.random.PRNGKey(42)
dna_sequence = jnp.zeros((1, seq_length, 4))
organism_index = jnp.zeros((1,), dtype=jnp.int32)
params, state = forward.init(key, dna_sequence, organism_index)
(predictions, embeddings), _ = forward.apply(
params, state, key, dna_sequence, organism_index
)
self.assertEqual(embeddings.embeddings_1bp.shape, (1, seq_length, 1536))
self.assertEqual(
embeddings.embeddings_128bp.shape, (1, seq_length // 128, 3072)
)
self.assertEqual(
embeddings.embeddings_pair.shape,
(1, seq_length // 128 // 16, seq_length // 128 // 16, 128),
)
self.assertEqual(
predictions['atac']['scaled_predictions_1bp'].shape,
(1, seq_length, num_tracks),
)
self.assertEqual(
predictions['atac']['scaled_predictions_128bp'].shape,
(1, seq_length // 128, num_tracks),
)
def test_predict_junctions_with_same_params(self):
seq_length = 2048
num_tissues = 15
num_splice_sites = 17
output_metadata = {
dna_model.Organism.HOMO_SAPIENS: metadata_lib.AlphaGenomeOutputMetadata(
splice_sites=_mock_track_metadata(5),
splice_junctions=_mock_junction_metadata(num_tissues),
),
}
@hk.transform_with_state
def forward(
dna_sequence: Float[Array, 'B S 4'], organism_index: Int[Array, 'B']
):
return model_lib.AlphaGenome(
output_metadata, num_splice_sites=num_splice_sites
)(dna_sequence, organism_index)
key = jax.random.PRNGKey(42)
dna_sequence = jax.ShapeDtypeStruct((1, seq_length, 4), jnp.float32)
organism_index = jax.ShapeDtypeStruct((1,), jnp.int32)
params, state = jax.eval_shape(
forward.init, key, dna_sequence, organism_index
)
(predictions, embeddings), _ = jax.eval_shape(
forward.apply, params, state, key, dna_sequence, organism_index
)
@hk.transform_with_state
def predict_junctions_fn(embeddings, splice_site_positions, organism_index):
return model_lib.AlphaGenome(
output_metadata, num_splice_sites=num_splice_sites
).predict_junctions(embeddings, splice_site_positions, organism_index)
splice_site_positions = predictions['splice_sites_junction'][
'splice_site_positions'
]
self.assertEqual(splice_site_positions.shape, (1, 4, num_splice_sites))
junction_predictions, _ = jax.eval_shape(
predict_junctions_fn.apply,
params,
state,
key,
embeddings.embeddings_1bp,
splice_site_positions,
organism_index,
)
self.assertIsNotNone(junction_predictions)
self.assertEqual(
junction_predictions['predictions'].shape,
(1, num_splice_sites, num_splice_sites, num_tissues * 2),
)
self.assertEqual(
junction_predictions['splice_junction_mask'].shape,
(1, num_splice_sites, num_splice_sites, num_tissues * 2),
)
def test_loss_and_grads(self):
seq_length = 131072 # Must be larger or equal to multinomial_resolution.
batch_size = 1
num_splice_sites = 17
num_tissues_junctions = 1
output_metadata = {
dna_model.Organism.HOMO_SAPIENS: metadata_lib.AlphaGenomeOutputMetadata(
atac=_mock_track_metadata(num_tracks=1),
contact_maps=_mock_track_metadata(num_tracks=1),
splice_sites=_mock_track_metadata(num_tracks=5),
splice_site_usage=_mock_track_metadata(num_tracks=4),
splice_junctions=_mock_junction_metadata(
num_tissues=num_tissues_junctions
),
)
}
batch = schemas.DataBatch(
dna_sequence=jnp.zeros((batch_size, seq_length, 4), dtype=jnp.float32),
organism_index=jnp.zeros((batch_size,), dtype=jnp.int32),
atac=jnp.zeros((batch_size, seq_length, 1), dtype=jnp.float32),
atac_mask=jnp.ones((batch_size, 1, 1), dtype=bool),
contact_maps=jnp.zeros(
(batch_size, seq_length // 2048, seq_length // 2048, 1),
dtype=jnp.float32,
),
splice_sites=jnp.zeros((batch_size, seq_length, 5), dtype=bool),
splice_site_usage=jnp.zeros(
(batch_size, seq_length, 4), dtype=jnp.float32
),
splice_junctions=jnp.zeros(
(
batch_size,
num_splice_sites,
num_splice_sites,
num_tissues_junctions * 2,
),
dtype=jnp.float32,
),
)
@hk.transform_with_state
def forward(batch):
return model_lib.AlphaGenome(
output_metadata, num_splice_sites=num_splice_sites
).loss(batch)
key = jax.random.PRNGKey(42)
params, state = jax.eval_shape(forward.init, key, batch)
(loss, scalars, predictions), _ = jax.eval_shape(
forward.apply, params, state, key, batch
)
chex.assert_shape(loss, ())
chex.assert_tree_shape(scalars, ())
chex.assert_tree_shape_prefix(predictions, (batch_size,))
self.assertContainsSubset(
[
'atac_loss',
'contact_maps_loss',
'splice_sites_classification_loss',
'splice_sites_usage_loss',
'splice_sites_junction_loss',
],
scalars.keys(),
)
def grads_fn(params, state, batch):
def _loss_fn(params):
(loss, scalars, predictions), new_state = forward.apply(
params, state, None, batch
)
return loss, (scalars, predictions, new_state)
(loss, (scalars, predictions, new_state)), grads = jax.value_and_grad(
_loss_fn, has_aux=True
)(params)
return loss, scalars, new_state, predictions, grads
loss2, scalars2, state2, predictions2, grads = jax.eval_shape(
grads_fn, params, state, batch
)
chex.assert_trees_all_equal_shapes(loss, loss2)
chex.assert_trees_all_equal_shapes(scalars, scalars2)
chex.assert_trees_all_equal_shapes(state, state2)
chex.assert_trees_all_equal_shapes(predictions, predictions2)
chex.assert_trees_all_equal_shapes(params, grads)
def test_finetuning_weights_merging(self):
"""Tests that the weights can be merged for fine-tuning.
We test this by creating a base model and a fine-tuning model, where the
fine-tuning model has fewer heads and tracks than the base model. We then
merge the weights of the base model and the fine-tuning model, and check
that the loss can be computed with the merged weights.
"""
seq_length = 131072
batch_size = 1
key = jax.random.key(0)
# Setup base model.
base_metadata = {
dna_model.Organism.HOMO_SAPIENS: metadata_lib.AlphaGenomeOutputMetadata(
atac=_mock_track_metadata(num_tracks=(atac_tracks_base := 5)),
rna_seq=_mock_track_metadata(num_tracks=(rna_seq_tracks_base := 3)),
),
}
@hk.transform_with_state
def base_forward(batch: schemas.DataBatch):
return model_lib.AlphaGenome(base_metadata).loss(batch)
base_batch = schemas.DataBatch(
dna_sequence=np.zeros((batch_size, seq_length, 4), dtype=np.float32),
organism_index=np.zeros((batch_size,), dtype=np.int32),
atac=jnp.zeros(
(batch_size, seq_length, atac_tracks_base), dtype=np.float32
),
atac_mask=np.ones((batch_size, 1, atac_tracks_base), dtype=bool),
rna_seq=np.zeros(
(batch_size, seq_length, rna_seq_tracks_base), dtype=np.float32
),
rna_seq_mask=np.ones((batch_size, 1, rna_seq_tracks_base), dtype=bool),
)
base_params, base_state = jax.eval_shape(base_forward.init, key, base_batch)
# 2. Setup fine-tuning model.
ft_metadata = {
dna_model.Organism.HOMO_SAPIENS: metadata_lib.AlphaGenomeOutputMetadata(
atac=_mock_track_metadata(num_tracks=(atac_tracks_ft := 7)),
),
}
@hk.transform_with_state
def ft_forward(batch: schemas.DataBatch):
return model_lib.AlphaGenome(ft_metadata).loss(batch)
ft_batch = schemas.DataBatch(
dna_sequence=np.zeros((batch_size, seq_length, 4), dtype=np.float32),
organism_index=np.zeros((batch_size,), dtype=np.int32),
atac=jnp.zeros(
(batch_size, seq_length, atac_tracks_ft), dtype=np.float32
),
atac_mask=np.ones((batch_size, 1, atac_tracks_ft), dtype=bool),
)
ft_params, ft_state = jax.eval_shape(ft_forward.init, key, ft_batch)
# 3. Merge weights: base model weights + fine-tuning model head weights.
def merge(base_weights, ft_weights):
ft_head_weights = hk.data_structures.filter(
lambda module_name, name, v: 'head' in module_name, ft_weights
)
return hk.data_structures.merge(base_weights, ft_head_weights)
merged_params = merge(base_params, ft_params)
merged_state = merge(base_state, ft_state)
# 4. Check that we can compute loss with merged weights
(loss, scalars, predictions), _ = jax.eval_shape(
ft_forward.apply, merged_params, merged_state, key, ft_batch
)
chex.assert_shape(loss, ())
self.assertContainsSubset(['atac_loss'], scalars.keys())
self.assertNotIn('rna_seq_loss', scalars.keys())
chex.assert_tree_shape_prefix(predictions, (batch_size,))
self.assertEqual(
predictions['atac']['scaled_predictions_1bp'].shape,
(batch_size, seq_length, atac_tracks_ft),
)
def test_freeze_trunk_embeddings(self):
if jax.local_devices()[0].platform.lower() not in {'gpu', 'tpu'}:
self.skipTest('This test requires accelerator devices.')
seq_length, batch_size, key = 131072, 1, jax.random.key(0)
metadata = {
dna_model.Organism.HOMO_SAPIENS: metadata_lib.AlphaGenomeOutputMetadata(
atac=_mock_track_metadata(num_tracks=(atac_tracks_base := 5)),
rna_seq=_mock_track_metadata(num_tracks=(rna_seq_tracks_base := 3)),
),
}
@hk.transform_with_state
def forward(batch: schemas.DataBatch):
return model_lib.AlphaGenome(metadata, freeze_trunk_embeddings=True).loss(
batch
)
@jax.jit
def grads_fn(params, state, batch):
def loss_fn(params, state, batch):
(loss, _, _), _ = forward.apply(params, state, None, batch)
return loss
grads_fn = jax.grad(loss_fn)
return grads_fn(params, state, batch)
batch = schemas.DataBatch(
dna_sequence=np.zeros((batch_size, seq_length, 4), dtype=np.float32),
organism_index=np.zeros((batch_size,), dtype=np.int32),
atac=jnp.zeros(
(batch_size, seq_length, atac_tracks_base), dtype=np.float32
),
atac_mask=np.ones((batch_size, 1, atac_tracks_base), dtype=bool),
rna_seq=np.zeros(
(batch_size, seq_length, rna_seq_tracks_base), dtype=np.float32
),
rna_seq_mask=np.ones((batch_size, 1, rna_seq_tracks_base), dtype=bool),
)
batch = jax.device_put(batch, jax.local_devices()[0])
params, state = jax.jit(forward.init)(key, batch)
grads = jax.jit(grads_fn)(params, state, batch)
chex.assert_trees_all_equal_shapes(params, grads)
grads_trunk = hk.data_structures.filter(
lambda module_name, name, v: 'head' not in module_name, grads
)
grads_head = hk.data_structures.filter(
lambda module_name, name, v: 'head' in module_name, grads
)
# Gradients in the trunk should be zero.
chex.assert_trees_all_equal(
grads_trunk,
jax.tree.map(np.zeros_like, grads_trunk),
)
# Gradients in the head should be non-zero.
head_grad_sq_norm = jax.tree_util.tree_reduce(
lambda acc, x: acc + jnp.sum(jnp.square(x)),
grads_head,
initializer=0.0,
)
self.assertGreater(head_grad_sq_norm, 0.0)
if __name__ == '__main__':
absltest.main()