|
10 | 10 | from axlearn.audio import frontend_utils |
11 | 11 | from axlearn.audio.encoder_asr import ASREncoder, SpeechContextNetwork, SpeechFeatureLayer |
12 | 12 | from axlearn.audio.test_utils import fake_audio |
| 13 | +from axlearn.common.attention import RepeatedTransformerLayer |
| 14 | +from axlearn.common.kv_cache.sliding_window_kv_cache import enable_sliding_window_attention |
13 | 15 | from axlearn.common.module import functional as F |
14 | 16 | from axlearn.common.test_utils import TestCase |
15 | 17 | from axlearn.common.utils import Tensor, shapes |
@@ -173,6 +175,88 @@ def test_speech_context_network(self, is_training: bool): |
173 | 175 | output_collections.summaries["activations/speech_context_norm"].weight, weights |
174 | 176 | ) |
175 | 177 |
|
| 178 | + @parameterized.parameters([True, False]) |
| 179 | + def test_transformer(self, is_training: bool) -> None: |
| 180 | + """Test the code branch with RepeatedTransformerLayer as context layer. |
| 181 | +
|
| 182 | + Args: |
| 183 | + is_training: Whether the is_training code path is tested. |
| 184 | + """ |
| 185 | + input_dim, output_dim, dropout_rate, num_layers = 32, 16, 0.2, 2 |
| 186 | + num_heads = 8 |
| 187 | + hidden_dim = 4 * input_dim |
| 188 | + |
| 189 | + cfg = SpeechContextNetwork.default_config().set( |
| 190 | + input_dim=input_dim, output_dim=output_dim, dtype=jnp.float64 |
| 191 | + ) |
| 192 | + cfg.dropout.rate = dropout_rate |
| 193 | + cfg.context = RepeatedTransformerLayer.default_config().set(num_layers=num_layers) |
| 194 | + attention = cfg.context.layer.self_attention.attention |
| 195 | + attention.num_heads = num_heads |
| 196 | + attention = enable_sliding_window_attention(attention, sliding_window_size=3) |
| 197 | + cfg.context.layer.self_attention.attention = attention |
| 198 | + # Dropout in transformer |
| 199 | + cfg.context.layer.self_attention.dropout.rate = dropout_rate |
| 200 | + cfg.context.layer.feed_forward.set( |
| 201 | + hidden_dim=hidden_dim, |
| 202 | + ) |
| 203 | + |
| 204 | + # Initialize layer parameters. |
| 205 | + prng_key = jax.random.PRNGKey(123) |
| 206 | + prng_key, init_key, input_key, length_key = jax.random.split(prng_key, num=4) |
| 207 | + layer = cfg.set(name="test").instantiate(parent=None) |
| 208 | + layer_params = layer.initialize_parameters_recursively(init_key) |
| 209 | + |
| 210 | + # Generate inputs. |
| 211 | + batch_size, seq_len = 4, 10 |
| 212 | + inputs = jnp.tile( |
| 213 | + jax.random.normal(input_key, [batch_size // 2, seq_len, input_dim]), [2, 1, 1] |
| 214 | + ) |
| 215 | + lengths = jnp.tile( |
| 216 | + jax.random.randint(length_key, shape=[batch_size // 2, 1], minval=0, maxval=seq_len), |
| 217 | + [2, 1], |
| 218 | + ) |
| 219 | + paddings = jnp.arange(seq_len)[None, :] >= lengths |
| 220 | + padding_data = jax.random.normal(jax.random.PRNGKey(135), inputs.shape) |
| 221 | + inputs = jnp.where(paddings[..., None], padding_data, inputs) |
| 222 | + |
| 223 | + # Compute outputs. |
| 224 | + output_batch, output_collections = F( |
| 225 | + layer, |
| 226 | + inputs=dict(inputs=inputs, paddings=paddings), |
| 227 | + is_training=is_training, |
| 228 | + prng_key=prng_key, |
| 229 | + state=layer_params, |
| 230 | + ) |
| 231 | + outputs, output_paddings = output_batch["outputs"], output_batch["paddings"] |
| 232 | + self.assertSequenceEqual(outputs.shape, (batch_size, seq_len, output_dim)) |
| 233 | + self.assertTrue(jnp.all(output_paddings == paddings)) |
| 234 | + |
| 235 | + # If is_training, outputs should always be different due to augmentation. |
| 236 | + # Otherwise, outputs should be the same despite differences in padding. |
| 237 | + self.assertEqual(not is_training, bool(jnp.allclose(outputs[:2], outputs[2:]))) |
| 238 | + |
| 239 | + outputs = outputs * (1 - output_paddings[:, :, None]) |
| 240 | + weights = jnp.sum(1 - output_paddings) |
| 241 | + output_norms = jnp.sqrt(jnp.sum(outputs**2, axis=2)) / jnp.sqrt(output_dim) |
| 242 | + expected_outputs_mean = jnp.sum(outputs) / weights / output_dim |
| 243 | + expected_outputs_norm = jnp.sum(output_norms) / weights |
| 244 | + |
| 245 | + self.assertNestedAllClose( |
| 246 | + output_collections.summaries["activations/speech_context_mean"].mean, |
| 247 | + expected_outputs_mean, |
| 248 | + ) |
| 249 | + self.assertNestedAllClose( |
| 250 | + output_collections.summaries["activations/speech_context_norm"].mean, |
| 251 | + expected_outputs_norm, |
| 252 | + ) |
| 253 | + self.assertNestedAllClose( |
| 254 | + output_collections.summaries["activations/speech_context_mean"].weight, weights |
| 255 | + ) |
| 256 | + self.assertNestedAllClose( |
| 257 | + output_collections.summaries["activations/speech_context_norm"].weight, weights |
| 258 | + ) |
| 259 | + |
176 | 260 |
|
177 | 261 | class ASREncoderTest(TestCase): |
178 | 262 | """Tests ASREncoder.""" |
|
0 commit comments