|
37 | 37 | Qwen3OmniMoeVisionRotaryEmbedding as JaxQwen3OmniMoeVisionRotaryEmbedding, |
38 | 38 | ) |
39 | 39 | from maxtext.layers.decoders import deepstack_process |
40 | | -from maxtext.layers.encoders import AudioEncoder |
| 40 | +from maxtext.layers.encoders import AudioEncoder, VisionEncoder |
41 | 41 | from maxtext.multimodal.processor_qwen3_omni import ( |
42 | 42 | maybe_pad_video_values_to_max_grid, |
43 | 43 | preprocess_video, |
@@ -868,6 +868,100 @@ def test_padded_video_valid_outputs_match_unpadded_batched(self): |
868 | 868 | atol=1e-4, |
869 | 869 | ) |
870 | 870 |
|
| 871 | + def test_padded_video_projected_outputs_match_unpadded_batched(self): |
| 872 | + """Padded tokens do not change valid projected outputs for batched inputs.""" |
| 873 | + raw_grid_1 = (2, 2, 2) |
| 874 | + raw_grid_2 = (1, 4, 4) |
| 875 | + max_grid = (3, 4, 4) |
| 876 | + config = pyconfig.initialize( |
| 877 | + ["", base_config_path], |
| 878 | + model_name="qwen3-omni-30b-a3b", |
| 879 | + attention="dot_product", |
| 880 | + attention_type="full", |
| 881 | + dtype="float32", |
| 882 | + dtype_mm="float32", |
| 883 | + weight_dtype="float32", |
| 884 | + override_model_config=True, |
| 885 | + attention_for_vit="dot_product", |
| 886 | + hidden_size_for_vit=16, |
| 887 | + num_attention_heads_for_vit=2, |
| 888 | + intermediate_size_for_vit=32, |
| 889 | + num_hidden_layers_for_vit=1, |
| 890 | + deepstack_visual_indexes_for_vit=[], |
| 891 | + video_max_grid_t=max_grid[0], |
| 892 | + video_max_grid_h=max_grid[1], |
| 893 | + video_max_grid_w=max_grid[2], |
| 894 | + out_hidden_size_for_vit=24, |
| 895 | + vision_encoder_block=common_types.VisionEncoderBlockType.QWEN3_OMNI, |
| 896 | + ) |
| 897 | + patch_size = config.patch_size_for_vit |
| 898 | + temporal_patch_size = config.temporal_patch_size_for_vit |
| 899 | + |
| 900 | + # Generate raw videos |
| 901 | + raw_shape_1 = ( |
| 902 | + 1, |
| 903 | + config.num_channels_for_vit, |
| 904 | + raw_grid_1[0] * temporal_patch_size, |
| 905 | + raw_grid_1[1] * patch_size, |
| 906 | + raw_grid_1[2] * patch_size, |
| 907 | + ) |
| 908 | + raw_shape_2 = ( |
| 909 | + 1, |
| 910 | + config.num_channels_for_vit, |
| 911 | + raw_grid_2[0] * temporal_patch_size, |
| 912 | + raw_grid_2[1] * patch_size, |
| 913 | + raw_grid_2[2] * patch_size, |
| 914 | + ) |
| 915 | + raw_video_1, _ = create_random_jax_torch(*raw_shape_1) |
| 916 | + raw_video_2, _ = create_random_jax_torch(*raw_shape_2) |
| 917 | + |
| 918 | + # Pad them individually |
| 919 | + padded_video_1, _, video_mask_1 = maybe_pad_video_values_to_max_grid( |
| 920 | + np.asarray(raw_video_1), np.asarray([raw_grid_1]), config |
| 921 | + ) |
| 922 | + padded_video_2, _, video_mask_2 = maybe_pad_video_values_to_max_grid( |
| 923 | + np.asarray(raw_video_2), np.asarray([raw_grid_2]), config |
| 924 | + ) |
| 925 | + |
| 926 | + # Batch them |
| 927 | + batched_padded_video = jnp.concatenate([padded_video_1, padded_video_2], axis=0) |
| 928 | + batched_video_mask = jnp.concatenate([video_mask_1, video_mask_2], axis=0) |
| 929 | + batched_grid_thw = jnp.array([raw_grid_1, raw_grid_2], dtype=jnp.int32) |
| 930 | + |
| 931 | + # Instantiate VisionEncoder (wraps encoder + projector) |
| 932 | + vision_encoder = VisionEncoder(config=config, mesh=self.mesh, rngs=nnx.Rngs(42)) |
| 933 | + |
| 934 | + # Run individual unpadded |
| 935 | + raw_output_1, _ = vision_encoder(raw_video_1) |
| 936 | + raw_output_2, _ = vision_encoder(raw_video_2) |
| 937 | + |
| 938 | + # Run batched padded |
| 939 | + batched_padded_output, _ = vision_encoder( |
| 940 | + batched_padded_video, |
| 941 | + input_masks=batched_video_mask, |
| 942 | + video_grid_thw=batched_grid_thw, |
| 943 | + ) |
| 944 | + |
| 945 | + # Downsample masks to projected tokens |
| 946 | + projected_mask_1 = mm_processor.downsample_video_mask_to_tokens(video_mask_1, config) |
| 947 | + projected_mask_2 = mm_processor.downsample_video_mask_to_tokens(video_mask_2, config) |
| 948 | + |
| 949 | + # Verify equivalence for batch item 0 |
| 950 | + np.testing.assert_allclose( |
| 951 | + np.asarray(raw_output_1).reshape(-1, config.out_hidden_size_for_vit), |
| 952 | + np.asarray(batched_padded_output[0])[np.asarray(projected_mask_1[0]).astype(bool)], |
| 953 | + rtol=1e-4, |
| 954 | + atol=1e-4, |
| 955 | + ) |
| 956 | + |
| 957 | + # Verify equivalence for batch item 1 |
| 958 | + np.testing.assert_allclose( |
| 959 | + np.asarray(raw_output_2).reshape(-1, config.out_hidden_size_for_vit), |
| 960 | + np.asarray(batched_padded_output[1])[np.asarray(projected_mask_2[0]).astype(bool)], |
| 961 | + rtol=1e-4, |
| 962 | + atol=1e-4, |
| 963 | + ) |
| 964 | + |
871 | 965 |
|
872 | 966 | class TestDeepstackProcess(unittest.TestCase): |
873 | 967 | """Tests for deepstack_process. |
|
0 commit comments