# Copyright 2026 the HuggingFace Team. All rights reserved. # # 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. """Testing suite for the PyTorch DiffusionGemma model.""" import unittest from parameterized import parameterized from transformers import is_torch_available from transformers.testing_utils import ( CaptureStdout, Expectations, cleanup, require_deterministic_for_xpu, require_torch, slow, tooslow, torch_device, ) from ...test_configuration_common import ConfigTester from ...test_modeling_common import ModelTesterMixin, floats_tensor, ids_tensor if is_torch_available(): import torch from transformers import ( AutoConfig, AutoProcessor, AutoTokenizer, Cache, DiffusionGemmaConfig, DiffusionGemmaForBlockDiffusion, DiffusionGemmaGenerationConfig, DiffusionGemmaModel, DynamicCache, EntropyBoundSamplerConfig, StaticCache, TextDiffusionStreamer, set_seed, ) class DiffusionGemmaVisionText2TextModelTester: def __init__( self, parent, batch_size=3, text_config={ # default text config test values "vocab_size": 99, "hidden_size": 32, "num_attention_heads": 2, "num_key_value_heads": 2, "intermediate_size": 32, "hidden_act": "gelu", "max_position_embeddings": 512, "initializer_range": 0.02, "pad_token_id": 0, "bos_token_id": 1, "eos_token_id": 2, "moe_intermediate_size": 8, "moe_num_shared_experts": 1, "num_experts": 4, "num_hidden_layers": 2, # model-specific text config values "layer_types": ["sliding_attention", "full_attention"], # we want to test both types "num_global_key_value_heads": 2, # key introduced by the gemma4 family "global_head_dim": 32 // 2, # hidden_size // num_attention_heads "top_k_experts": 2, # key introduced by the gemma4 family "use_bidirectional_attention": "vision", # Test if bidirectional image mask path works }, vision_config={ "use_labels": True, "image_size": 20, "patch_size": 5, "num_channels": 3, "is_training": True, "hidden_size": 16, "num_key_value_heads": 1, "num_hidden_layers": 2, "num_attention_heads": 4, "intermediate_size": 37, "dropout": 0.1, "attention_dropout": 0.1, "initializer_range": 0.02, }, mm_tokens_per_image=2, image_token_id=4, boi_token_id=5, eoi_token_id=6, seq_length=25, # See: `test_generate_beyond_sliding_window` before changing this value! self_conditioning_size=16, canvas_length=16, is_training=False, ): self.parent = parent self.batch_size = batch_size # `image_token_id` is set to 0 to pass "resize_embeddings" test, do not modify self.mm_tokens_per_image = mm_tokens_per_image self.image_token_id = image_token_id self.boi_token_id = boi_token_id self.eoi_token_id = eoi_token_id self.text_config = text_config self.vision_config = vision_config self.seq_length = seq_length self.is_training = is_training self.self_conditioning_size = self_conditioning_size self.canvas_length = canvas_length self.pad_token_id = text_config["pad_token_id"] self.num_hidden_layers = text_config["num_hidden_layers"] self.vocab_size = text_config["vocab_size"] self.hidden_size = text_config["hidden_size"] self.num_attention_heads = text_config["num_attention_heads"] self.num_channels = vision_config["num_channels"] self.image_size = vision_config["image_size"] def get_config(self): return DiffusionGemmaConfig( text_config=self.text_config, vision_config=self.vision_config, image_token_id=self.image_token_id, boi_token_id=self.boi_token_id, eoi_token_id=self.eoi_token_id, mm_tokens_per_image=self.mm_tokens_per_image, self_conditioning_size=self.self_conditioning_size, canvas_length=self.canvas_length, ) def prepare_config_and_inputs_for_common(self): # 1. config preparation config = self.get_config() config.vision_config.pooling_kernel_size = 2 # 2. image inputs preparation # (num_images, max_num_patches, patch_size * patch_size * num_channels) patch_size = config.vision_config.patch_size pixel_values = floats_tensor( [ self.batch_size, self.vision_config["image_size"], patch_size * patch_size * self.vision_config["num_channels"], ] ) # (num_images, max_num_patches, 2) for height/width positions. Let it be all ones for testign pixel_position_ids = torch.ones(self.vision_config["image_size"], device=torch_device, dtype=torch.long) pixel_position_ids = pixel_position_ids[None, :, None].repeat(self.batch_size, 1, 2) # 3. text input preparation input_ids = ids_tensor([self.batch_size, self.seq_length], config.text_config.vocab_size - 1) + 1 decoder_input_ids = ids_tensor([self.batch_size, self.canvas_length], config.text_config.vocab_size - 1) + 1 attention_mask = input_ids.ne(self.pad_token_id).to(torch_device) # Ensure no tokens accidentally match special token IDs input_ids[input_ids == config.image_token_id] = self.pad_token_id input_ids[:, :1] = config.image_token_id mm_token_type_ids = torch.zeros_like(input_ids) mm_token_type_ids[input_ids == config.image_token_id] = 1 # 4. build the input dict inputs_dict = { "pixel_values": pixel_values, "image_position_ids": pixel_position_ids, "input_ids": input_ids, "decoder_input_ids": decoder_input_ids, "attention_mask": attention_mask, "mm_token_type_ids": mm_token_type_ids, } return config, inputs_dict @require_torch class DiffusionGemmaVisionText2TextModelTest(ModelTesterMixin, unittest.TestCase): all_model_classes = (DiffusionGemmaModel, DiffusionGemmaForBlockDiffusion) if is_torch_available() else () all_generative_model_classes = () # No class inherits `GenerationMixin` additional_model_inputs = ["mm_token_type_ids", "decoder_input_ids", "image_position_ids"] def setUp(self): self.model_tester = DiffusionGemmaVisionText2TextModelTester(self) self.config_tester = ConfigTester(self, config_class=DiffusionGemmaConfig, has_text_modality=False) # Tests adapted from `CausalLMModelTest` (when appropriate, as this is not an AR model). We can use the test # name to check the original equivalent that served as inspiration. def test_config(self): """Tests basic config properties""" self.config_tester.run_common_tests() def test_model(self): """Tests that we can run a forward pass with the base model""" config, inputs_dict = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaModel(config=config).to(torch_device).eval() expected_shape = (self.model_tester.batch_size, self.model_tester.canvas_length, self.model_tester.hidden_size) result = model(inputs_dict["input_ids"], attention_mask=inputs_dict["attention_mask"]) self.assertEqual(result.last_hidden_state.shape, expected_shape) result = model(inputs_dict["input_ids"], decoder_input_ids=inputs_dict["decoder_input_ids"]) self.assertEqual(result.last_hidden_state.shape, expected_shape) result = model(inputs_dict["input_ids"]) self.assertEqual(result.last_hidden_state.shape, expected_shape) # Tests overwritten from `ModelTesterMixin`. @unittest.skip(reason="TODO") def test_attention_outputs(self): # TODO(joaogante): Write a custom test for this model. The base test can't be used because # the decoder has query len = canvas len and key/value shape = kv cache + canvas len. # On top of this, we should also check the encoder attentions. pass @unittest.skip(reason="TODO") def test_hidden_states_output(self): # TODO(joaogante): Same as `test_attention_outputs` pass @unittest.skip(reason="TODO") def test_can_init_all_missing_weights(self): # TODO(joaogante): failing on tied weights. Explore later. pass @unittest.skip(reason="TODO") def test_init_weights_can_init_buffers(self): # TODO(joaogante): probably related to `test_can_init_all_missing_weights` pass @unittest.skip(reason="TODO") def test_tp_plan_matches_params(self): # TODO(joaogante): explore TP config after other issues are sorted pass @unittest.skip(reason="Hard to specify `self.model_split_percents` due to tied weights. Skip for now.") def test_cpu_offload(self): pass @unittest.skip(reason="Hard to specify `self.model_split_percents` due to tied weights. Skip for now.") def test_disk_offload_bin(self): pass @unittest.skip(reason="Hard to specify `self.model_split_percents` due to tied weights. Skip for now.") def test_disk_offload_safetensors(self): pass @unittest.skip(reason="Hard to specify `self.model_split_percents` due to tied weights. Skip for now.") def test_model_parallelism(self): pass # Tests designed specifically for `DiffusionGemma`, excluding generation tests. def test_tied_weights(self): """ Tests that randomly initialized models have their weights properly tied. There are two sets of ties in DiffusionGemma: 1. The text encoder trainable params are the same as the decoder trainable params, if we exclude the decoder self-conditioning weights 2. The LM Head weights are the same as the embedding weights -- in both the text encoder and in the decoder! """ config, _ = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() # 1. text encoder trainable params == decoder trainable params (excluding decoder self-conditioning) # In other words, all text encoder trainable params must be decoder trainable params. decoder_trainable_params = dict(model.model.decoder.named_parameters()) for name, encoder_text_param in model.model.encoder.language_model.named_parameters(): decoder_trainable_param = decoder_trainable_params[name] self.assertTrue(encoder_text_param is decoder_trainable_param) # 2. lm head weights == text encoder embedding weights == decoder embedding weights self.assertTrue(model.lm_head.weight is model.model.decoder.embed_tokens.weight) self.assertTrue(model.lm_head.weight is model.model.encoder.language_model.embed_tokens.weight) def test_use_cache_raises_exception(self): """ DiffusionGemma always use cache. Therefore, the common kwarg `use_cache` isn't used -- and we raise an exception """ config, model_inputs = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() with self.assertRaises(ValueError): model(**model_inputs, use_cache=False) with self.assertRaises(ValueError): model(**model_inputs, use_cache=True) def test_diffusion_decoder_mask_no_cache_raises_exception(self): """ DiffusionGemma has a custom function to create an attention mask for the decoder. Contrarily to other mask creation functions, it requires a KV cache """ config, _ = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() dummy_canvas = torch.ones((2, 4), dtype=torch.int32, device=torch_device) with self.assertRaises(ValueError): _ = model.model.decoder.create_diffusion_decoder_attention_mask( config=config.text_config, inputs_embeds=dummy_canvas.unsqueeze(-1), past_key_values=None, # this will trigger an exception ) def test_diffusion_decoder_mask_dynamic_cache(self): """ This is the simplest test: the resulting mask should be full of 1s. As a shortcut, in eager execution + dynamic cache, it simply returns `None` attention masks """ prefill_length = 8 canvas_length = 4 concat_kv_length = prefill_length + canvas_length batch_size = 2 config, _ = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() # Apply prefill (smaller than sliding window) past_key_values = DynamicCache(config=config) prefill_input_ids = torch.ones((batch_size, prefill_length), dtype=torch.int32, device=torch_device) * 50 past_key_values = model.model.encoder(prefill_input_ids, past_key_values=past_key_values).past_key_values # Get the mask decoder_attention_mask = torch.ones((batch_size, concat_kv_length), dtype=torch.bool, device=torch_device) dummy_canvas = torch.ones((batch_size, canvas_length), dtype=torch.int32, device=torch_device) mask_mapping = model.model.decoder.create_diffusion_decoder_attention_mask( config=config.text_config, inputs_embeds=dummy_canvas.unsqueeze(-1), past_key_values=past_key_values, decoder_attention_mask=decoder_attention_mask, ) self.assertIsNone(mask_mapping["full_attention"]) self.assertIsNone(mask_mapping["sliding_attention"]) # We get the same result if we pass `attention_mask = None` mask_mapping = model.model.decoder.create_diffusion_decoder_attention_mask( config=config.text_config, inputs_embeds=dummy_canvas.unsqueeze(-1), past_key_values=past_key_values, decoder_attention_mask=None, ) self.assertIsNone(mask_mapping["full_attention"]) self.assertIsNone(mask_mapping["sliding_attention"]) def test_diffusion_decoder_mask_dynamic_cache_left_padding(self): """ This test is similar to `test_diffusion_decoder_mask_dynamic_cache`, but with left-padding on one of the rows. There should be a few zeros on the final mask, corresponding to the left-padded items. """ prefill_length = 8 canvas_length = 4 concat_kv_length = prefill_length + canvas_length batch_size = 2 left_padding_length = 2 # only applied on batch item 0 expected_non_zero = ( ((concat_kv_length - left_padding_length) * canvas_length) # batch item 0 + (concat_kv_length * canvas_length) # batch item 1 ) expected_attention_mask_shape = (batch_size, 1, canvas_length, concat_kv_length) config, _ = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() # Apply prefill (smaller than sliding window) past_key_values = DynamicCache(config=config) prefill_input_ids = torch.ones((batch_size, prefill_length), dtype=torch.int32, device=torch_device) * 50 past_key_values = model.model.encoder(prefill_input_ids, past_key_values=past_key_values).past_key_values # Get the mask decoder_attention_mask = torch.ones((batch_size, concat_kv_length), dtype=torch.bool, device=torch_device) decoder_attention_mask[0, :left_padding_length] = 0 dummy_canvas = torch.ones((batch_size, canvas_length), dtype=torch.int32, device=torch_device) mask_mapping = model.model.decoder.create_diffusion_decoder_attention_mask( config=config.text_config, inputs_embeds=dummy_canvas.unsqueeze(-1), past_key_values=past_key_values, decoder_attention_mask=decoder_attention_mask, ) self.assertEqual(mask_mapping["full_attention"].shape, expected_attention_mask_shape) self.assertEqual(mask_mapping["full_attention"].sum(), expected_non_zero) self.assertEqual(mask_mapping["full_attention"][0, 0, :, :left_padding_length].sum(), 0) self.assertEqual(mask_mapping["sliding_attention"].shape, expected_attention_mask_shape) self.assertEqual(mask_mapping["sliding_attention"].sum(), expected_non_zero) self.assertEqual(mask_mapping["sliding_attention"][0, 0, :, :left_padding_length].sum(), 0) def test_diffusion_decoder_mask_dynamic_cache_beyond_sliding_window(self): """ This tests builds upon `test_diffusion_decoder_mask_dynamic_cache_left_padding`: it tests the case with left-padding AND going beyond the sliding window. The prefill length is longer than the sliding window length. The short left-padding should become invisible in the sliding window layers, after prefill. """ prefill_length = 16 sliding_window_length = 8 canvas_length = 4 concat_kv_length_full = prefill_length + canvas_length # -1 -> the DYNAMIC sliding window kv cache has len=window-1 concat_kv_length_sliding = sliding_window_length + canvas_length - 1 batch_size = 2 left_padding_length = 2 # only applied on batch item 0 expected_non_zero_full = ( ((concat_kv_length_full - left_padding_length) * canvas_length) # batch item 0 + (concat_kv_length_full * canvas_length) # batch item 1 ) expected_attention_mask_shape_full = (batch_size, 1, canvas_length, concat_kv_length_full) expected_non_zero_sliding = concat_kv_length_sliding * batch_size * canvas_length expected_attention_mask_shape_sliding = ( batch_size, 1, canvas_length, sliding_window_length + canvas_length - 1, ) # Double-check test assumption that left-padding should be past the sliding window self.assertTrue(prefill_length - left_padding_length > sliding_window_length) config, _ = self.model_tester.prepare_config_and_inputs_for_common() config.text_config.sliding_window = sliding_window_length model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() # Apply prefill (LARGER than sliding window) past_key_values = DynamicCache(config=config) prefill_input_ids = torch.ones((batch_size, prefill_length), dtype=torch.int32, device=torch_device) * 50 past_key_values = model.model.encoder(prefill_input_ids, past_key_values=past_key_values).past_key_values # Get the mask decoder_attention_mask = torch.ones((batch_size, concat_kv_length_full), dtype=torch.bool, device=torch_device) decoder_attention_mask[0, :left_padding_length] = 0 dummy_canvas = torch.ones((batch_size, canvas_length), dtype=torch.int32, device=torch_device) mask_mapping = model.model.decoder.create_diffusion_decoder_attention_mask( config=config.text_config, inputs_embeds=dummy_canvas.unsqueeze(-1), past_key_values=past_key_values, decoder_attention_mask=decoder_attention_mask, ) self.assertEqual(mask_mapping["full_attention"].shape, expected_attention_mask_shape_full) self.assertEqual(mask_mapping["full_attention"].sum(), expected_non_zero_full) self.assertEqual(mask_mapping["full_attention"][0, 0, :, :left_padding_length].sum(), 0) self.assertEqual(mask_mapping["sliding_attention"].shape, expected_attention_mask_shape_sliding) self.assertEqual(mask_mapping["sliding_attention"].sum(), expected_non_zero_sliding) # sliding window got beyond padding, so the first tokens in the sliding window will not be masked self.assertEqual( mask_mapping["sliding_attention"][0, 0, :, :left_padding_length].sum(), left_padding_length * canvas_length ) def test_diffusion_decoder_mask_static_cache(self): """ Same as `test_diffusion_decoder_mask_dynamic_cache`, but with a Static Cache. Contrarily to the original test, the mask is materialized (a materialized mask is needed at compile time) """ config, _ = self.model_tester.prepare_config_and_inputs_for_common() prefill_length = 8 canvas_length = config.canvas_length total_input_length = prefill_length + canvas_length # More than input so we don't hit sliding window being cropped yet static_cache_length = total_input_length + 20 concat_kv_length = static_cache_length + canvas_length batch_size = 2 expected_non_zero = total_input_length * canvas_length * batch_size expected_attention_mask_shape = (batch_size, 1, canvas_length, concat_kv_length) model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() # Apply prefill (smaller than sliding window) past_key_values = StaticCache(config=config, max_cache_len=static_cache_length) prefill_input_ids = torch.ones((batch_size, prefill_length), dtype=torch.int32, device=torch_device) * 50 past_key_values = model.model.encoder(prefill_input_ids, past_key_values=past_key_values).past_key_values # Get the mask (correctly designed for the static cache) decoder_attention_mask = torch.ones((batch_size, total_input_length), dtype=torch.bool, device=torch_device) dummy_canvas = torch.ones((batch_size, canvas_length), dtype=torch.int32, device=torch_device) mask_mapping = model.model.decoder.create_diffusion_decoder_attention_mask( config=config, inputs_embeds=dummy_canvas.unsqueeze(-1), past_key_values=past_key_values, decoder_attention_mask=decoder_attention_mask, ) self.assertEqual(mask_mapping["full_attention"].shape, expected_attention_mask_shape) self.assertEqual(mask_mapping["full_attention"].sum(), expected_non_zero) self.assertEqual(mask_mapping["sliding_attention"].shape, expected_attention_mask_shape) self.assertEqual(mask_mapping["sliding_attention"].sum(), expected_non_zero) def test_diffusion_decoder_mask_static_cache_beyond_sliding_window(self): """ Same as `test_diffusion_decoder_mask_dynamic_cache_beyond_sliding_window`, but with a Static Cache. This is the most complex mask preparation test, including static caches, left-padding, and mask preparation beyond the sliding window length. """ config, _ = self.model_tester.prepare_config_and_inputs_for_common() prefill_length = 16 sliding_window_length = 8 canvas_length = config.canvas_length total_input_length = prefill_length + canvas_length # Max-cache-len is less than `total_input_length` to trigger going beyond sliding window concat_kv_length_sliding = sliding_window_length + canvas_length - 1 static_cache_length = total_input_length - 4 concat_kv_length_full = static_cache_length + canvas_length batch_size = 2 left_padding_length = 2 # only applied on batch item 0 # Current query (`canvas`) attends to key (`prefill + itself`). Optionally, doesn't attend if pading present expected_non_zero_full = ( ((total_input_length - left_padding_length) * canvas_length) # batch item 0 + (total_input_length * canvas_length) # batch item 1 ) expected_attention_mask_shape_full = (batch_size, 1, canvas_length, concat_kv_length_full) expected_non_zero_sliding = concat_kv_length_sliding * batch_size * canvas_length expected_attention_mask_shape_sliding = ( batch_size, 1, canvas_length, concat_kv_length_sliding + 1, ) # Double-check test assumption that left-padding should be past the sliding window self.assertTrue(prefill_length - left_padding_length > sliding_window_length) config.text_config.sliding_window = sliding_window_length model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() # Apply prefill (LARGER than sliding window) past_key_values = StaticCache(config=config, max_cache_len=static_cache_length) prefill_input_ids = torch.ones((batch_size, prefill_length), dtype=torch.int32, device=torch_device) * 50 past_key_values = model.model.encoder(prefill_input_ids, past_key_values=past_key_values).past_key_values # Get the mask (correctly designed for the static cache) decoder_attention_mask = torch.ones((batch_size, total_input_length), dtype=torch.bool, device=torch_device) decoder_attention_mask[0, :left_padding_length] = 0 dummy_canvas = torch.ones((batch_size, canvas_length), dtype=torch.int32, device=torch_device) mask_mapping = model.model.decoder.create_diffusion_decoder_attention_mask( config=config, inputs_embeds=dummy_canvas.unsqueeze(-1), past_key_values=past_key_values, decoder_attention_mask=decoder_attention_mask, ) self.assertEqual(mask_mapping["full_attention"].shape, expected_attention_mask_shape_full) self.assertEqual(mask_mapping["full_attention"].sum(), expected_non_zero_full) self.assertEqual(mask_mapping["full_attention"][0, 0, :, :left_padding_length].sum(), 0) self.assertEqual(mask_mapping["sliding_attention"].shape, expected_attention_mask_shape_sliding) self.assertEqual(mask_mapping["sliding_attention"].sum(), expected_non_zero_sliding) # sliding window got beyond padding, so the first tokens in the sliding window will not be masked self.assertEqual( mask_mapping["sliding_attention"][0, 0, :, :left_padding_length].sum(), left_padding_length * canvas_length ) # Generation tests @parameterized.expand( [ # (sub_test_name, {generate_kwargs}), # Cache interface ("dynamic_cache", {}), # default cache ("static_cache", {"cache_implementation": "static"}), # static cache -> triggers compilation # Diffusion-specific common kwargs ("custom_sampler", {"sampler_config": EntropyBoundSamplerConfig(0.1)}), ( "custom_temperature", {"t_min": 0.1, "t_max": 0.2}, ), ( "custom_stopping", {"stability_threshold": 0, "confidence_threshold": 1e-1}, ), ] ) def test_generate(self, name, generate_kwargs): """Tests `generate` calls with common flags""" config, model_inputs = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() model.generation_config.eos_token_id = None # force generation up to `max_new_tokens` generation_outputs = model.generate( **model_inputs, max_new_tokens=16, max_denoising_steps=2, **generate_kwargs ) expected_shape = (model_inputs["input_ids"].shape[0], model_inputs["input_ids"].shape[1] + 16) self.assertEqual(generation_outputs.sequences.shape, expected_shape) def test_generate_text_only(self): """Same as `test_generate`, but only with text inputs (no images)""" config, model_inputs = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() model.generation_config.eos_token_id = None # force generation up to `max_new_tokens` generation_outputs = model.generate(model_inputs["input_ids"], max_new_tokens=16, max_denoising_steps=2) expected_shape = (model_inputs["input_ids"].shape[0], model_inputs["input_ids"].shape[1] + 16) self.assertEqual(generation_outputs.sequences.shape, expected_shape) def test_generate_without_return_dict(self): """Same as `test_generate_text_only`, but return_dict_in_generate=False""" config, model_inputs = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() model.generation_config.eos_token_id = None # force generation up to `max_new_tokens` model.generation_config.return_dict_in_generate = False generation_outputs = model.generate(model_inputs["input_ids"], max_new_tokens=16, max_denoising_steps=2) expected_shape = (model_inputs["input_ids"].shape[0], model_inputs["input_ids"].shape[1] + 16) self.assertEqual(generation_outputs.shape, expected_shape) def test_generate_from_generation_config(self): """ Same as the base case in `test_generate`, but we parameterize the generation call with a `DiffusionGemmaGenerationConfig` """ config, model_inputs = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() # force `eos_token_id = None` to not stop before reaching `max_new_tokens` generation_config = DiffusionGemmaGenerationConfig(eos_token_id=None, max_new_tokens=16, max_denoising_steps=2) generation_outputs = model.generate(**model_inputs, generation_config=generation_config) expected_shape = (model_inputs["input_ids"].shape[0], model_inputs["input_ids"].shape[1] + 16) self.assertEqual(generation_outputs.sequences.shape, expected_shape) def test_generate_kwarg_overrides(self): """ Same as `test_generate_from_generation_config`, but we override some parameters with kwargs in the `generate` call. Remember: kwargs > generation config """ config, model_inputs = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() # force `eos_token_id = None` to not stop before reaching `max_new_tokens` generation_config = DiffusionGemmaGenerationConfig(eos_token_id=None, max_new_tokens=16, max_denoising_steps=2) generation_outputs = model.generate(**model_inputs, generation_config=generation_config, max_new_tokens=32) expected_shape = (model_inputs["input_ids"].shape[0], model_inputs["input_ids"].shape[1] + 32) self.assertEqual(generation_outputs.sequences.shape, expected_shape) def test_generate_with_past_key_values(self): """ Tests that we can pass in past_key_values outputted from a previous `generate` into a subsequent call. This pattern is used in chat sessions. """ config, model_inputs = self.model_tester.prepare_config_and_inputs_for_common() model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() model.generation_config.eos_token_id = None # force generation up to `max_new_tokens` # 1st generate call, without KV cache generation_1_outputs = model.generate(**model_inputs, max_new_tokens=16, max_denoising_steps=2) self.assertIsInstance(generation_1_outputs.past_key_values, Cache) expected_shape = (model_inputs["input_ids"].shape[0], model_inputs["input_ids"].shape[1] + 16) self.assertEqual(generation_1_outputs.sequences.shape, expected_shape) # 2nd generate call, with KV cache from previous call # NOTE: `input_ids` in `generate` can only contain **unprocessed** tokens new_input_ids = torch.cat((generation_1_outputs.sequences, model_inputs["input_ids"]), dim=-1) generation_2_outputs = model.generate( input_ids=new_input_ids, past_key_values=generation_1_outputs.past_key_values, max_new_tokens=16, max_diffusion_steps=2, ) # in total, we feed in `model_inputs["input_ids"]` twice, and get 16 new tokens twice expected_shape = (model_inputs["input_ids"].shape[0], (model_inputs["input_ids"].shape[1] + 16) * 2) self.assertEqual(generation_2_outputs.sequences.shape, expected_shape) @parameterized.expand( [ ("dynamic_cache", None), # default cache ("static_cache", "static"), # static cache -> triggers compilation under the hood ] ) @slow def test_generate_beyond_sliding_window(self, name, cache_implementation): """Tests that generate can run beyond the sliding window length""" config, model_inputs = self.model_tester.prepare_config_and_inputs_for_common() # Canvas length has to be much smaller than sliding window to correctly check the mask # and input-length has to be smaller than sliding length! We will check three cases: # 1) input/mask is smaller than window # 2) input length approaching full cache length # 3) input length goes beyond window length # Input length is 25 in model tester, so we will set sliding window to 48 tokens # After generating `max_new_tokens=32`, the final length will go beyond sliding window config.text_config.sliding_window = 48 model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() model.generation_config.eos_token_id = None # force generation up to `max_new_tokens` generation_outputs = model.generate( **model_inputs, max_new_tokens=32, max_denoising_steps=2, disable_compile=True, cache_implementation=cache_implementation, ) self.assertTrue(generation_outputs.sequences.shape[1] > config.text_config.sliding_window) expected_shape = (model_inputs["input_ids"].shape[0], model_inputs["input_ids"].shape[1] + 32) self.assertEqual(generation_outputs.sequences.shape, expected_shape) @unittest.skip(reason="TODO(joaogante): red in CI, fix me") def test_diffusion_streaming(self): """Tests `TextDiffusionStreamer`""" # TODO(huggingface team): after a tiny diffusion model is created, this test can be moved into # its right place, i.e. tests/generation/test_streamers.py config, model_inputs = self.model_tester.prepare_config_and_inputs_for_common() input_ids = model_inputs["input_ids"][:1, :] # streaming requires bsz=1 model = DiffusionGemmaForBlockDiffusion(config=config).to(torch_device).eval() # Any tokenizer will do, as long as it has the same vocab size. tokenizer = AutoTokenizer.from_pretrained("hf-internal-testing/tiny-random-gpt2") with CaptureStdout() as cs: streamer = TextDiffusionStreamer(tokenizer) model_out = model.generate(input_ids, max_new_tokens=16, max_denoising_steps=2, streamer=streamer) streamer_text = cs.out generate_text = tokenizer.decode(model_out.sequences[0]) # the streamer ends with a `\n` self.assertTrue(streamer_text.endswith("\n")) # Important for this test: max_new_tokens==canvas_length. The last piece of text thrown by the streamer # is the finalized canvas. self.assertEqual(streamer_text[-(len(generate_text) + 1) : -1], generate_text) # `\x1b7` = save cursor position; `\x1b8` = restore cursor position; # Used to overwrite draft tokens while streaming. self.assertIn("\x1b7", streamer_text) self.assertIn("\x1b8", streamer_text) @require_torch class DiffusionGemmaIntegrationTest(unittest.TestCase): _model_path = "google/diffusiongemma-26B-A4B-it" def setup(self): cleanup(torch_device, gc_collect=True) def tearDown(self): cleanup(torch_device, gc_collect=True) @slow def test_diffusion_gemma_chat_template(self): # fmt: off expected_input_ids = [ [ 2, 105, 2364, 107, 6974, 496, 1440, 20494, 1003, 23613, 236761, 106, 107, 105, 4368, 107, 100, 45518, 107, 101 ] ] # fmt: on expected_decoded_tokens = [ "<|turn>user\nWrite a long essay about Portugal.\n<|turn>model\n<|channel>thought\n" ] processor = AutoProcessor.from_pretrained(self._model_path) chat = [ {"role": "user", "content": "Write a long essay about Portugal."}, ] input_ids = processor.apply_chat_template(chat, tokenize=True, return_tensors="pt", add_generation_prompt=True) self.assertEqual(input_ids.tolist(), expected_input_ids) decoded_tokens = processor.decode(input_ids) self.assertIn("<|turn>", decoded_tokens[0]) self.assertIn("", decoded_tokens[0]) self.assertIn("<|channel>", decoded_tokens[0]) self.assertIn("", decoded_tokens[0]) self.assertNotIn("<|think|>", decoded_tokens[0]) # shouldn't have the think token self.assertEqual(decoded_tokens, expected_decoded_tokens) @slow def test_diffusion_gemma_chat_template_image(self): image_tokens = [255999, 258880, 258882] # These tokens must be present in the `input_ids` # TODO(joao): this should be 280! Something is wrong with processing? image_token_count = 256 processor = AutoProcessor.from_pretrained(self._model_path) chat = [ { "role": "user", "content": [ { "type": "image", "url": "https://raw.githubusercontent.com/google-gemma/cookbook/refs/heads/main/apps/sample-data/GoldenGate.png", }, {"type": "text", "text": "What is shown in this image?"}, ], } ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True ) for token in image_tokens: self.assertIn(token, model_inputs["input_ids"]) self.assertTrue((model_inputs["input_ids"] == 258880).sum() == image_token_count) for expected_model_input in ("attention_mask", "pixel_values", "image_position_ids", "mm_token_type_ids"): self.assertIn(expected_model_input, model_inputs) @slow def test_diffusion_gemma_chat_template_with_thinking(self): # fmt: off expected_input_ids = [ [ 2, 105, 9731, 107, 98, 107, 106, 107, 105, 2364, 107, 6974, 496, 1440, 20494, 1003, 23613, 236761, 106, 107, 105, 4368, 107 ] ] # fmt: on expected_decoded_tokens = [ "<|turn>system\n<|think|>\n\n<|turn>user\nWrite a long essay about Portugal.\n<|turn>model\n" ] processor = AutoProcessor.from_pretrained(self._model_path) chat = [ {"role": "user", "content": "Write a long essay about Portugal."}, ] input_ids = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, enable_thinking=True ) self.assertEqual(input_ids.tolist(), expected_input_ids) decoded_tokens = processor.decode(input_ids) self.assertIn("<|turn>", decoded_tokens[0]) self.assertIn("", decoded_tokens[0]) self.assertIn("<|think|>", decoded_tokens[0]) # must have the think token self.assertEqual(decoded_tokens, expected_decoded_tokens) # Only 1 checkpoint got released: the 26B MoE. At release time, MoE quantization was not working properly, # and the HF CI didn't have 80GB GPUs. As such, tests against the original model are tagged with `@tooslow`. # Meaning: these tests won't run in the `py.test` command, and you have to uncomment the line. These tests # exist to keep track of the model's original numerics. def _load_model(self, minified=False): if minified: config = AutoConfig.from_pretrained(self._model_path) config.text_config.num_hidden_layers = 6 # loaded model requires ~12.7 GB config.text_config.layer_types = config.text_config.layer_types[:6] model = DiffusionGemmaForBlockDiffusion.from_pretrained( self._model_path, config=config, dtype=torch.bfloat16, device_map="auto", ) else: model = DiffusionGemmaForBlockDiffusion.from_pretrained( self._model_path, dtype=torch.bfloat16, device_map="auto", ) return model @tooslow def test_diffusion_gemma_forward_text_only(self): """bsz=1, no image""" # fmt: off # print(model_out.logits[:, :10, :12]) # Printed after running `torch.set_printoptions(precision=8, threshold=200, linewidth=100)` expected_logits = Expectations( { ("cuda", None): [ [ [ 4.12986183, 20.66659164, 6.75712347, 4.16051483, 13.11566544, 3.59206009, 3.46879172, 3.86895919, 3.14465618, 2.47871161, 4.43598938, 4.77164030], [ 3.20645595, 22.52679825, 7.78647614, 2.71133161, 14.73279667, 4.55818033, 4.16051483, 3.03644228, 3.91504526, 1.58446193, 4.52764702, 4.46655130], [ 4.77164030, 23.55075455, 5.80103159, 3.65364885, 15.66178513, 4.19115925, 5.01498747, 2.92814803, 4.55818033, 2.92814803, 5.28794241, 7.87383986], [ 4.64972258, 24.27903175, 6.01130199, 3.66904116, 15.57065392, 3.17555952, 5.25765753, 3.40711212, 4.95421267, 4.28303957, 5.71073294, 8.10610390], [ 5.16673803, 24.27903175, 6.42998266, 3.36083388, 16.89584923, 4.49710369, 5.77094412, 4.40541792, 5.22736216, 4.12986183, 7.05306482, 8.68214703], [ 5.28794241, 24.36471176, 7.58207989, 3.53044105, 17.31759644, 4.49710369, 5.31821585, 4.40541792, 4.98460531, 3.88432312, 7.46494198, 8.33732700], [ 5.10607004, 24.27903175, 7.17103910, 3.34540391, 16.55123901, 3.96111226, 4.09919930, 4.25242186, 4.34424734, 4.46655130, 5.71073294, 8.27962112], [ 6.81642532, 24.19219208, 8.56747913, 5.37872887, 17.15010071, 5.80103159, 5.86117029, 4.25242186, 6.34047985, 4.49710369, 7.61132526, 9.36425304], [ 6.99399042, 24.44923782, 8.96760273, 5.34847832, 16.81029892, 5.77094412, 4.95421267, 4.49710369, 5.68060923, 4.68021679, 6.93485880, 8.85363007], [ 7.93200207, 23.83295822, 7.81561375, 6.51936626, 17.72932053, 5.98130083, 5.83110666, 5.43919706, 6.60862780, 5.28794241, 7.66977119, 9.86893463] ] ] } ) # fmt: on model = self._load_model() processor = AutoProcessor.from_pretrained(self._model_path) chat = [ {"role": "user", "content": "Write a long essay about Portugal."}, ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True ).to(model.device) # hardcodes input canvas to avoid depending on random canvas sampling decoder_input_ids = torch.arange(model.config.canvas_length, dtype=torch.int32, device=model.device).unsqueeze( 0 ) model_inputs["decoder_input_ids"] = decoder_input_ids with torch.no_grad(): model_out = model(**model_inputs) self.assertEqual( list(model_out.logits.shape), [1, model.config.canvas_length, model.config.text_config.vocab_size] ) torch.testing.assert_close( model_out.logits[:, :10, :12].cpu(), torch.tensor(expected_logits.get_expectation()) ) @tooslow def test_diffusion_gemma_forward_with_image(self): """bsz=1, with image""" # fmt: off # print(model_out.logits[:, :10, :12]) # Printed after running `torch.set_printoptions(precision=8, threshold=200, linewidth=100)` expected_logits = Expectations( { ("cuda", None): [ [ [ 6.93485880, 20.60072327, 3.89968514, 7.49424887, 6.99399042, -0.03344725, 0.44332713, -0.53900456, 2.18363142, 0.35545215, 1.24927723, 3.53044105], [ 6.81642532, 19.92110252, 4.92381001, 5.74084425, 10.31235313, 4.83253860, 0.94890219, 1.05425322, 0.87475199, 0.16503741, 0.06933582, 2.74232340], [ 6.40016174, 22.63516045, 2.88171268, 6.75712347, 12.08642769, 5.25765753, 2.60281467, 2.60281467, 5.55999660, 2.66483307, 2.05925679, 5.98130083], [ 9.86893463, 21.78874207, 2.35452294, 7.78647614, 15.01560497, 7.11208105, 5.07571983, 5.80103159, 4.68021679, 4.86297274, 3.45337439, 7.78647614], [ 5.01498747, 21.18004990, 1.31166339, 4.95421267, 11.13002872, 2.54077387, 0.40427244, 2.40110350, 0.42770541, 1.23367894, -0.29100651, 1.06205606], [ 5.74084425, 21.42788887, 2.12145352, 4.77164030, 11.82355690, 3.12920213, 3.66904116, 3.43795562, 2.64933062, 2.88171268, 2.71133161, 4.89339685], [ 6.28074551, 22.30597687, 4.16051483, 4.52764702, 12.08642769, 4.64972258, 4.31364775, 5.89122152, 1.52212954, 3.74597287, 2.13699985, 6.31061935], [ 6.37032795, 23.92457008, 9.02448273, 3.28366685, 10.64161205, 3.22190189, 3.00550938, 4.83253860, 0.88255781, 1.63899159, 1.81808197, 5.49961996], [ 7.46494198, 25.78687286, 9.36425304, 3.63825440, 13.06506538, 2.02815175, 3.23734570, 5.37872887, 3.02097654, 2.27686334, 2.16808867, 6.63835430], [ 6.16112089, 26.16215897, 5.71073294, 3.34540391, 11.92896748, 1.55329728, 4.77164030, 4.12986183, 1.02304065, 3.49961996, 1.73244548, 6.45979071] ] ] } ) # fmt: on model = self._load_model() processor = AutoProcessor.from_pretrained(self._model_path) chat = [ { "role": "user", "content": [ { "type": "image", "url": "https://raw.githubusercontent.com/google-gemma/cookbook/refs/heads/main/apps/sample-data/GoldenGate.png", }, {"type": "text", "text": "What is shown in this image?"}, ], } ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True ).to(model.device) # hardcodes input canvas to avoid depending on random canvas sampling decoder_input_ids = torch.arange(model.config.canvas_length, dtype=torch.int32, device=model.device).unsqueeze( 0 ) model_inputs["decoder_input_ids"] = decoder_input_ids # sanity check: has image inputs self.assertIn("pixel_values", model_inputs) with torch.no_grad(): model_out = model(**model_inputs) self.assertEqual( list(model_out.logits.shape), [1, model.config.canvas_length, model.config.text_config.vocab_size] ) torch.testing.assert_close( model_out.logits[:, :10, :12].cpu(), torch.tensor(expected_logits.get_expectation()) ) @tooslow def test_diffusion_gemma_forward_batched(self): """bsz>1, no image""" # fmt: off # print(model_out.logits[:, :5, :12]) # Printed after running `torch.set_printoptions(precision=8, threshold=200, linewidth=100)` expected_logits = Expectations( { ("cuda", None): [ [ [-1.56108880e+00, 1.99211025e+01, 1.60004282e+00, 1.01073846e-01, 7.58207989e+00, -5.98130083e+00, -4.40541792e+00, -1.73244548e+00, -1.42860627e+00, -5.97577214e-01, -1.81808197e+00, 8.74021053e-02], [-1.02304065e+00, 2.26888294e+01, 9.53315258e+00, -3.37876350e-01, 8.91065121e+00, 1.29394531e-02, 5.35099506e-01, -2.52526045e+00, -7.91013837e-02, -2.02815175e+00, 2.27686334e+00, 8.86460662e-01], [-2.33393744e-01, 2.34054356e+01, 5.04535866e+00, -4.94095922e-01, 1.10760899e+01, 1.23367894e+00, 9.56706762e-01, -1.94259071e+00, 1.51433694e+00, 1.03864717e+00, 2.61832142e+00, 5.65047359e+00], [-1.21808004e+00, 2.25267982e+01, 4.43598938e+00, -1.23367894e+00, 8.62484741e+00, 6.79571211e-01, 7.10804522e-01, -2.02815175e+00, -4.82380331e-01, 7.57651389e-01, 2.01259756e+00, 4.92381001e+00], [ 4.56995904e-01, 2.34541931e+01, 4.09919930e+00, -5.39550222e-02, 1.30143728e+01, 7.14708507e-01, 1.21028030e+00, 5.11669159e-01, 1.81029797e+00, 1.63120234e+00, 4.68021679e+00, 7.72815514e+00] ], [ [ 1.15722090e-01, 2.06007233e+01, 3.12920213e+00, -2.12145352e+00, 5.37872887e+00, -3.46879172e+00, -5.80103159e+00, -4.12986183e+00, -2.47871161e+00, -5.52981424e+00, -5.01498747e+00, -8.16204786e-01], [ 2.88171268e+00, 2.27421646e+01, 8.62484741e+00, 5.11669159e-01, 8.51004219e+00, 2.41662765e+00, 5.89767814e-01, 2.88171268e+00, -5.19479334e-01, -2.66483307e+00, -2.52526045e+00, 2.33899355e+00], [ 5.37872887e+00, 2.53779068e+01, 8.96760273e+00, 1.32725811e+00, 1.13449488e+01, 1.28826988e+00, -6.79571211e-01, 3.45337439e+00, 3.66904116e+00, -2.52526045e+00, -1.18688023e+00, 2.61832142e+00], [ 5.04535866e+00, 2.42790318e+01, 8.68214703e+00, 4.43598938e+00, 9.47692776e+00, 3.78886133e-01, -4.61921787e+00, 6.28814161e-01, 4.99953747e-01, -3.69981956e+00, -6.49413019e-02, 3.63825440e+00], [ 6.13118267e+00, 2.50096397e+01, 8.68214703e+00, 4.52764702e+00, 1.17707224e+01, -9.52148531e-03, -3.34540391e+00, 2.16808867e+00, 3.08282948e+00, -3.32997227e+00, -6.32718682e-01, 3.26822829e+00] ] ] } ) # fmt: on model = self._load_model() processor = AutoProcessor.from_pretrained(self._model_path) chat = [ [ {"role": "user", "content": "Write a long essay about Portugal."}, ], [ {"role": "user", "content": "Why is 6 affraid of 7?"}, ], ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True ).to(model.device) # hardcodes input canvas to avoid depending on random canvas sampling decoder_input_ids = torch.arange(model.config.canvas_length, dtype=torch.int32, device=model.device) decoder_input_ids = ( torch.ones((2, model.config.canvas_length), dtype=torch.int32, device=model.device) * decoder_input_ids ) model_inputs["decoder_input_ids"] = decoder_input_ids # sanity check: the input has bsz=2 self.assertEqual(model_inputs["input_ids"].shape[0], 2) # sanity check: has padding, and it is left-padding self.assertIn(model.config.text_config.pad_token_id, model_inputs["input_ids"]) self.assertFalse((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, -1]).any()) self.assertTrue((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, 0]).any()) with torch.no_grad(): model_out = model(**model_inputs) self.assertEqual( list(model_out.logits.shape), [2, model.config.canvas_length, model.config.text_config.vocab_size] ) torch.testing.assert_close(model_out.logits[:, :5, :12].cpu(), torch.tensor(expected_logits.get_expectation())) @tooslow def test_diffusion_gemma_generate_with_image_batched(self): """ bsz>1, with image, calling `generate`. This test has a LOT of non-determinism (sampling canvases, sampling tokens), so it is normal if different platforms need to set different expected outcomes. """ # fmt: off # print(generated_tokens) # Printed after running `torch.set_printoptions(threshold=10000, linewidth=100)` expected_sequences = Expectations( { ("cuda", None): [ [ 1018, 818, 21065, 41025, 236787, 562, 61774, 77985, 529, 23613, 1018, 108, 5141, 3059, 580, 506, 16425, 3374, 7377, 529, 49566, 3879, 236764, 1298, 506, 211050, 2601, 6861, 15741, 531, 506, 12529, 21065, 18414, 236764, 23613, 563, 496, 7097, 5221, 684, 1061, 4191, 607, 506, 5442, 236761, 1701, 24744, 236764, 625, 815, 7779, 618, 496, 44701, 1534, 506, 10687, 4109, 532, 506, 1799, 236764, 496, 11825, 1534, 20117, 532, 70548, 236761, 2282, 3050, 23613, 563, 531, 3050, 496, 3996, 105362, 529, 49094, 27877, 236764, 44040, 11858, 24637, 236764, 532, 496, 4709, 9226, 78729, 3224, 618, 808, 4834, 689, 1007, 236829, 237028, 236746, 5268, 236764, 85539, 7833, 86508, 573, 2613, 5745, 653, 8229, 2752, 1765, 236761, 108, 10354, 562, 34641, 84181, 236787, 4934, 117838, 576, 4848, 531, 41254, 108, 818, 4083, 529, 23613, 563, 886, 529, 35691, 532, 10534, 236761, 42251, 618, 496, 21880, 528, 506, 236743, 236770, 236778, 594, 7691, 1913, 506, 117838, 576, 4848, 699, 78763, 1044, 6157, 236764, 23613, 5452, 886, 529, 506, 24625, 7097, 236772, 26582, 528, 3879, 236761, 5978, 1061, 23789, 964, 3187, 204045, 528, 6145, 77486, 42469, 236764, 23613, 6976, 44630, 236761, 236743, 108, 818, 236743, 236770, 236810, 594, 532, 236743, 236770, 236825, 594, 24744, 11373, 1061, 16522, 17884, 236764, 506, 17884, 529, 41254, 236761, 8382, 506, 91295, 529, 9732, 1133, 18934, 12297, 506, 60137, 236764, 41082, 2556, 41516, 19482, 6998, 506, 623, 46811, 529, 106819, 2098, 15468, 506, 9992, 9383, 532, 17154, 4673, 236761, 125620, 1776, 150225, 11788, 506, 5442, 9116, 531, 4673, 528, 236743, 236770, 236812, 236819, 236828, 236764 ], [ 2094, 2471, 3831, 506, 16522, 26010, 17936, 528, 5054, 14322, 236764, 7151, 236761, 669, 4429, 563, 3523, 699, 506, 15891, 2678, 236764, 18482, 9975, 10654, 528, 506, 44529, 532, 506, 69480, 10901, 6615, 528, 506, 1695, 236761, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ] } ) # fmt: on model = self._load_model() processor = AutoProcessor.from_pretrained(self._model_path) chat = [ [ {"role": "user", "content": "Write a long essay about Portugal."}, ], [ { "role": "user", "content": [ { "type": "image", "url": "https://raw.githubusercontent.com/google-gemma/cookbook/refs/heads/main/apps/sample-data/GoldenGate.png", }, {"type": "text", "text": "What is shown in this image?"}, ], } ], ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True ).to(model.device) # sanity check: the input has bsz=2 self.assertEqual(model_inputs["input_ids"].shape[0], 2) # sanity check: the input has an image component self.assertIn("pixel_values", model_inputs) # sanity check: has padding, and it is left-padding self.assertIn(model.config.text_config.pad_token_id, model_inputs["input_ids"]) self.assertFalse((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, -1]).any()) self.assertTrue((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, 0]).any()) set_seed(42) gen_out = model.generate(**model_inputs, max_new_tokens=256) generated_tokens = gen_out.sequences[:, -256:] self.assertEqual(generated_tokens.tolist(), expected_sequences.get_expectation()) @tooslow def test_diffusion_gemma_generate_with_image_batched_long(self): """ Ultimate test: bsz>1, with image, with thinking on, calling `generate` with max_new_tokens > sliding attention length. This test has a LOT of non-determinism (sampling canvases, sampling tokens), so it is normal if different platforms need to set different expected outcomes. """ # fmt: off # print(generated_tokens) # Printed after running `torch.set_printoptions(threshold=10000, linewidth=100)` expected_sequences = Expectations( { ("cuda", None): [ [ 21042, 2846, 236764, 23613, 691, 506, 3988, 529, 506, 1902, 236764, 614, 38613, 600, 39349, 699, 14600, 531, 126382, 236764, 532, 73420, 531, 72449, 236761, 1174, 6933, 6111, 36628, 12821, 532, 9226, 8770, 236764, 175676, 528, 506, 94079, 37704, 688, 31035, 3117, 236764, 17202, 684, 1061, 82647, 59491, 1133, 70186, 236764, 35809, 236764, 532, 83798, 47397, 236761, 16961, 236764, 506, 3825, 529, 38613, 10734, 5378, 531, 16670, 236764, 6641, 684, 1440, 13443, 529, 6658, 23049, 532, 506, 56845, 808, 66856, 107130, 236829, 102708, 1208, 187920, 569, 81237, 118882, 236761, 1030, 691, 711, 3097, 506, 33275, 567, 24817, 529, 236743, 236770, 236819, 236832, 236812, 237028, 236746, 73887, 236764, 569, 236772, 18377, 236748, 4806, 1933, 7820, 28622, 1298, 11838, 7006, 37067, 847, 528, 506, 520, 204532, 529, 18187, 236789, 79689, 237028, 7705, 23613, 130661, 1131, 506, 28239, 236764, 29810, 6693, 8927, 236772, 14531, 7097, 625, 563, 3124, 236761, 108, 8551, 69565, 236764, 23613, 563, 33887, 12801, 9785, 1061, 30997, 2425, 236761, 669, 4695, 563, 52803, 532, 3826, 236764, 2033, 531, 506, 176165, 15706, 530, 11849, 236764, 506, 1902, 236858, 236751, 24625, 115368, 774, 10135, 4128, 236764, 1298, 6944, 3776, 81026, 103534, 531, 25465, 26607, 1949, 236761, 669, 3988, 563, 27222, 684, 506, 1429, 3194, 5400, 236764, 496, 12529, 236764, 13935, 152990, 529, 26012, 154863, 532, 64192, 32049, 24599, 1298, 1972, 12489, 657, 496, 31878, 17723, 236761, 2282, 506, 8710, 236764, 506, 201172, 5469, 20997, 62260, 236764, 11497, 5442, 505, 21294, 236764, 532, 496, 6962, 32920, 10022, 236761, 43725, 506, 2891, 236764, 506, 21065, 18414, 7474, 506, 4512, 61102 ], [ 563, 88076, 580, 1903, 529, 672, 5441, 236761, 108, 902, 506, 1695, 236764, 3418, 506, 1813, 236764, 506, 19519, 236764, 127150, 236772, 30497, 26607, 529, 506, 69480, 10901, 6615, 659, 11325, 1208, 506, 6425, 1933, 7217, 236761, 669, 15408, 563, 7804, 532, 1982, 236764, 22169, 496, 17635, 1719, 236764, 30439, 3538, 37676, 580, 506, 31035, 4065, 529, 506, 11825, 532, 506, 7845, 236761, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ] } ) # fmt: on model = self._load_model() processor = AutoProcessor.from_pretrained(self._model_path) chat = [ [ {"role": "user", "content": "Write a long essay about Portugal."}, ], [ { "role": "user", "content": [ { "type": "image", "url": "https://raw.githubusercontent.com/google-gemma/cookbook/refs/heads/main/apps/sample-data/GoldenGate.png", }, {"type": "text", "text": "Describe the image in detail."}, ], } ], ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True, enable_thinking=True, ).to(model.device) # sanity check: the input has bsz=2 self.assertEqual(model_inputs["input_ids"].shape[0], 2) # sanity check: the input has an image component self.assertIn("pixel_values", model_inputs) # sanity check: has padding, and it is left-padding self.assertIn(model.config.text_config.pad_token_id, model_inputs["input_ids"]) self.assertFalse((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, -1]).any()) self.assertTrue((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, 0]).any()) set_seed(42) gen_out = model.generate(**model_inputs, max_new_tokens=1280) # sanity check: we went beyond the sliding window length self.assertTrue(gen_out.sequences.shape[1] > model.config.text_config.sliding_window) generated_tokens = gen_out.sequences[:, -256:] self.assertEqual(generated_tokens.tolist(), expected_sequences.get_expectation()) # The tests below use a botched model, using the first six layers, to respect memory constraints of HF's CI. # Six layers = minimum number of layers to have both sliding window attention and full attention. @slow def test_minified_diffusion_gemma_forward_text_only(self): """[Minified (6 first layers)] bsz=1, no image""" # fmt: off # print(model_out.logits[:, :10, :12]) # Printed after running `torch.set_printoptions(precision=8, threshold=200, linewidth=100)` expected_logits = Expectations( { ("cuda", None): [ [[ 20.99029922, -29.92623520, -3.96111226, 29.14220428, 29.95949554, 28.15386963, 27.72417641, 1.10106778, -27.28695297, -29.00568390, -1.60783303, -18.21017647], [ 29.49378586, 17.06575012, 25.72111511, 29.87012291, 29.99975395, -17.31759644, -29.92115784, -20.06009483, -14.73279667, -28.84785843, -29.78616714, -29.99817467], [ -1.54550576, -29.99688721, -29.98895645, 26.56055069, 13.76502132, -24.44923782, -4.55818033, -17.40074539, -29.42227936, -29.94157219, -25.58675003, -29.89002419], [ -1.14787686, -29.44104195, 16.55123901, 26.77043152, 29.86572838, 13.56690025, 21.36647987, -10.31235313, -29.22277641, -28.15386963, 2.49422908, -24.27903175], [ 28.09328270, -28.88492966, 27.24345016, 29.90688133, 29.90688133, 18.13103485, 27.99878311, 4.00716066, -24.53262520, -28.88492966, 24.44923782, -22.52679825], [ 0.10205039, -29.93098831, 7.55281830, 28.48003769, 29.93098831, -14.25369835, 21.11716843, -19.27641296, -29.42227936, -29.02185059, 8.16400814, -27.41356468], [ 22.19349098, -27.10892868, -24.10417366, 27.37199974, 26.82098389, -10.03579903, 4.71070147, -16.46408844, -29.68155670, -25.30630493, 24.77605438, 1.80251384], [ 20.73208427, -29.44104195, 17.97154999, 29.67082214, 29.82481384, -9.47692776, 26.77043152, -12.86174679, -29.40289116, -29.44104195, 4.77164030, -22.13672829], [ 7.81561375, -29.63641167, 15.66178513, 29.14220428, 29.40289116, -15.93279934, 28.37808609, -13.11566544, -28.35157776, -29.76376724, -7.49424887, -19.20282745], [ 17.72932053, -29.98895645, 29.31869316, 29.31869316, 29.69194221, -25.00963974, 7.58207989, -28.12381363, -29.51024628, -29.93098831, -25.58675003, -26.87078667]] ], ("xpu", 5): [ [[ 21.18004799, -29.92373848, -3.05190611, 29.14220428, 29.96082115, 28.18346024, 27.83115768, 0.69128382, -27.28695488, -28.98925400, -0.99962980, -17.81046677], [ 29.47677803, 17.40074348, 25.85169601, 29.87012291, 29.99976730, -16.98100090, -29.91848755, -18.97970009, -14.15672588, -28.78997993, -29.76376724, -29.99829102], [ -2.10590601, -29.99708939, -29.98819351, 26.50611115, 14.20525932, -24.77605629, -4.55818033, -17.81046677, -29.42227936, -29.94157028, -25.37790871, -29.89002419], [ -1.60004294, -29.42227936, 16.46408844, 26.77043152, 29.86119080, 14.59008121, 22.02214432, -10.96796513, -29.24793625, -28.24126434, 2.33899355, -24.10417557], [ 28.06227303, -28.97256088, 27.15444565, 29.90688133, 29.90992928, 18.52275848, 27.86572647, 4.64972305, -24.69602013, -28.92082787, 24.36471176, -22.36170006], [ 2.86623120, -29.89710236, 5.16673756, 28.57573891, 29.90688133, -13.76502132, 21.11716843, -18.82897186, -29.38286209, -29.12799644, 4.98460531, -27.57362747], [ 22.07961273, -24.93285179, -22.47210693, 27.19928551, 27.49481773, -9.58930302, 5.13640928, -16.81029701, -29.71171188, -25.30630493, 24.77605438, 1.87256217], [ 20.86193848, -29.44104195, 17.72932243, 29.67082405, 29.83608246, -9.13802814, 26.82098389, -13.41732407, -29.42227936, -29.44104195, 5.34847832, -22.13672447], [ 8.73937702, -29.52617455, 14.54231644, 29.24793625, 29.36216545, -16.72434616, 28.72925377, -11.98154259, -28.32464409, -29.70199013, -5.98130035, -17.72932053], [ 17.89120865, -29.98819351, 29.29587173, 29.31869316, 29.69194031, -25.00963974, 7.55281830, -28.15386772, -29.51024628, -29.92865372, -25.58675194, -26.71912193]] ] } ) # fmt: on model = self._load_model(minified=True) processor = AutoProcessor.from_pretrained(self._model_path) chat = [ {"role": "user", "content": "Write a long essay about Portugal."}, ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True ).to(model.device) # hardcodes input canvas to avoid depending on random canvas sampling decoder_input_ids = torch.arange(model.config.canvas_length, dtype=torch.int32, device=model.device).unsqueeze( 0 ) model_inputs["decoder_input_ids"] = decoder_input_ids with torch.no_grad(): model_out = model(**model_inputs) self.assertEqual( list(model_out.logits.shape), [1, model.config.canvas_length, model.config.text_config.vocab_size] ) torch.testing.assert_close( model_out.logits[:, :10, :12].cpu(), torch.tensor(expected_logits.get_expectation()) ) @slow @require_deterministic_for_xpu def test_minified_diffusion_gemma_forward_with_image(self): """[Minified (6 first layers)] bsz=1, with image""" # fmt: off # print(model_out.logits[:, :10, :12]) # Printed after running `torch.set_printoptions(precision=8, threshold=200, linewidth=100)` expected_logits = Expectations( { ("cuda", None): [[ [ 18.59990692, -29.96792030, -26.71912193, 27.65005684, 29.36216545, 24.36471176, 6.40016174, 6.81642532, -19.49482346, -22.19349098, -0.41794172, -24.77605438], [ 28.35157776, 26.91985321, 28.80958176, 29.63641167, 29.99991417, -9.47692776, -29.90373039, -25.30630493, -0.17089660, -29.27229309, -29.90688133, -29.99860191], [ -2.52526045, -29.38286209, -29.96454811, 25.08536720, 7.08258057, -27.01581001, -21.11716843, -26.45086479, -28.62135887, -29.92865181, -28.90302277, -29.99154091], [ -3.26822829, -27.10892868, -26.91985321, 25.91559219, 29.90373039, 1.14007568, 1.52212954, -5.98130083, -27.61213493, -8.79653835, -8.22184658, -25.91559219], [ 28.74981880, -29.79991150, 28.92082787, 29.91287804, 29.98117638, 9.47692776, 27.68740082, -8.51004219, -12.13874054, -26.56055069, 28.45515060, -22.13672829], [ 0.53900456, -29.77148056, -0.21874613, 26.22159958, 29.67082214, -20.92630577, 3.65364885, -21.18004990, -29.55650711, -28.18346024, -3.20645595, -28.64363098], [-18.52275848, -26.16215897, -28.62135887, 14.05937004, -12.29514503, -25.58675003, -15.38721085, -26.33790970, -29.47677803, -7.93200207, 27.37199974, -2.44767213], [ 2.43215036, -29.29586983, 12.39897156, 28.50453377, 26.66704369, -24.44923782, 3.40711212, -24.10417366, -29.74754906, -29.64826202, -6.42998266, -26.66704369], [-14.49445248, -29.22277641, 9.98025513, 27.01581001, 25.30630493, -23.40543556, 14.01054859, -17.48348808, -28.62135887, -29.36216545, -9.08129215, -27.61213493], [ 4.64972258, -29.98651123, 28.42986679, 28.88492966, 26.39480209, -28.40418053, -15.38721085, -28.42986679, -29.09888268, -29.77148056, -26.61419106, -28.35157776] ]], ("xpu", 5): [[ [ 18.97970009, -29.96791840, -26.61419106, 27.76038742, 29.54159164, 24.27903175, 8.22184658, 8.04813480, -18.97970009, -22.74216461, -1.06205606, -24.36471176], [ 28.35157585, 27.45450211, 28.68712234, 29.65972710, 29.99992943, -9.02448273, -29.89710236, -25.23368645, 0.80449456, -29.29587173, -29.90688324, -29.99850464], [ -3.60746002, -29.76376724, -29.95053482, 24.01497269, 3.29910398, -26.22159958, -19.49482346, -27.19928741, -28.90302277, -29.94886017, -27.72417450, -29.98458862], [ -7.81561375, -26.96819115, -25.85169601, 26.16215897, 29.90688133, -3.36083364, 3.63825440, -3.60746002, -27.83115768, -9.02448273, -11.02206802, -25.65441132], [ 28.70835495, -29.78616714, 29.05341339, 29.90992928, 29.98179245, 9.58930302, 27.57362556, -8.96760178, -13.01437187, -26.82098389, 28.55237770, -22.36170006], [ 0.66395408, -29.73022270, -1.74023187, 26.50611115, 29.76376534, -19.05447006, 0.96841347, -21.78874397, -29.47677803, -27.68740082, 1.40522099, -28.55237961], [-17.15010071, -26.28017807, -28.86654472, 17.48348999, -6.93485880, -26.50610924, -17.81046677, -24.85499382, -29.47677803, -6.81642532, 27.53452492, -3.60746002], [ 3.59206033, -29.29587173, 14.05937004, 28.59873009, 26.71912193, -24.10417557, 4.19115925, -23.59856415, -29.74754906, -29.62416267, -5.25765705, -26.71912193], [-13.76502132, -29.19678307, 10.58693695, 27.10892868, 26.16215897, -23.45419121, 14.01054859, -16.63799286, -28.57573891, -29.36216545, -8.68214703, -27.57362747], [ 6.87567043, -29.98605347, 28.42986679, 29.00568199, 26.82098389, -28.06227493, -15.57065392, -28.40418053, -29.15618324, -29.77894592, -26.71912193, -28.40418053] ]] } ) # fmt: on model = self._load_model(minified=True) processor = AutoProcessor.from_pretrained(self._model_path) chat = [ { "role": "user", "content": [ { "type": "image", "url": "https://raw.githubusercontent.com/google-gemma/cookbook/refs/heads/main/apps/sample-data/GoldenGate.png", }, {"type": "text", "text": "What is shown in this image?"}, ], } ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True ).to(model.device) # hardcodes input canvas to avoid depending on random canvas sampling decoder_input_ids = torch.arange(model.config.canvas_length, dtype=torch.int32, device=model.device).unsqueeze( 0 ) model_inputs["decoder_input_ids"] = decoder_input_ids # sanity check: has image inputs self.assertIn("pixel_values", model_inputs) with torch.no_grad(): model_out = model(**model_inputs) self.assertEqual( list(model_out.logits.shape), [1, model.config.canvas_length, model.config.text_config.vocab_size] ) torch.testing.assert_close( model_out.logits[:, :10, :12].cpu(), torch.tensor(expected_logits.get_expectation()) ) @slow @require_deterministic_for_xpu def test_minified_diffusion_gemma_forward_batched(self): """[Minified (6 first layers)] bsz>1, no image""" # print(model_out.logits[:, :5, :12]) # Printed after running `torch.set_printoptions(precision=8, threshold=200, linewidth=100)` expected_logits = Expectations( { ("cuda", None): [ [[ 21.60994148, -29.94348526, -9.81315899, 29.09888268, 29.96454811, 28.35157776, 27.37199974, 5.13640928, -26.56055069, -28.70835686, -0.13183509, -18.97969818], [ 29.54159164, 12.19096375, 24.93285370, 29.89002419, 29.99975395, -18.44520950, -29.92623520, -21.05392075, -17.23404694, -29.08397102, -29.74754906, -29.99850464], [ -0.04980464, -29.99727631, -29.99208641, 26.61419106, 17.81046486, -23.92457008, -6.10123110, -18.13103485, -29.27229309, -29.93543434, -25.30630493, -29.86572838], [ 0.15429553, -29.38286209, 14.54231548, 26.77043152, 29.90373039, 15.93279934, 21.30470467, -10.20196629, -29.19678497, -27.99878311, 4.03784895, -24.36471176], [ 27.99878311, -28.93834877, 27.01581001, 29.90992928, 29.92865181, 18.36726570, 27.53452492, 3.69981956, -24.27903175, -28.82887459, 25.23368835, -22.58115005]], [[ 20.73208427, -29.81889534, -14.01054859, 29.42227936, 29.85649490, 27.61213493, 23.05516243, 9.81315899, -28.26949692, -28.06227112, -1.21028030, -25.08536720], [ 29.09888268, 20.12901306, 19.99079323, 29.89002419, 29.99984360, -17.64777756, -29.86572838, -14.54231548, -17.64777756, -28.52864838, -29.72112274, -29.99791336], [ 12.08642769, -29.97701073, -29.97776413, 28.92082787, 18.90453339, -20.33346367, 4.09919930, -11.18388462, -29.52617455, -29.87437057, -22.74216461, -29.72112274], [-10.03579903, -28.59873009, 15.47912979, 27.06271935, 29.69194221, 15.01560497, 16.81029892, -15.66178513, -29.49378586, -27.86572838, -3.56125450, -24.19219208], [ 25.37790680, -29.47677803, 27.32979774, 29.89710426, 29.67082214, 13.41732597, 24.01497269, 4.95421267, -26.96818924, -28.42986679, 19.92110252, -24.44923782]] ], ("xpu", 5): [ [[ 21.96432686, -29.94348526, -9.53315163, 29.14220428, 29.96683502, 28.37808609, 27.45450211, 6.10123110, -26.50610924, -28.70835495, 0.21093403, -18.36726570], [ 29.54159164, 12.39897156, 25.08536530, 29.88630295, 29.99973488, -18.05149078, -29.91848755, -21.18004990, -16.63799286, -29.15618324, -29.74754906, -29.99850464], [ -0.82791477, -29.99761581, -29.99153900, 26.50611115, 17.72932243, -23.92457008, -6.54913378, -17.81046677, -29.24793625, -29.93543434, -25.08536720, -29.87012291], [ 1.48316491, -29.42227936, 14.10809517, 27.01580811, 29.90688133, 16.02234268, 21.30470467, -9.64538002, -29.16993713, -27.93329048, 4.34424686, -24.19219208], [ 28.06227303, -28.84785843, 27.06271935, 29.91287804, 29.92623711, 18.21017647, 27.65005684, 4.16051531, -24.19219208, -28.77005959, 25.30630302, -22.95215416]], [[ 20.86193848, -29.81277466, -14.30204105, 29.42228127, 29.85649490, 27.65005684, 23.05516243, 9.81316090, -28.26949692, -28.06227493, -0.92158508, -25.08536720], [ 29.11355782, 20.79719925, 20.73208427, 29.89710236, 29.99983406, -17.81046677, -29.87012291, -15.01560688, -17.72932053, -28.42986488, -29.73022270, -29.99791336], [ 11.61169815, -29.97701073, -29.97623253, 28.88492966, 18.28891945, -20.19754601, 4.12986183, -11.18388557, -29.52617455, -29.87437248, -22.79516220, -29.73022270], [-10.42242527, -28.57573891, 15.57065582, 26.91985321, 29.70199203, 15.38721275, 16.46408844, -14.82745552, -29.47677803, -27.76038551, -4.00716066, -24.19219208], [ 25.44850922, -29.49378586, 27.41356277, 29.89710236, 29.64826012, 13.46727657, 24.19219208, 4.40541744, -27.10892868, -28.50453377, 19.42241287, -24.36471176]] ], } ) # fmt: skip model = self._load_model(minified=True) processor = AutoProcessor.from_pretrained(self._model_path) chat = [ [ {"role": "user", "content": "Write a long essay about Portugal."}, ], [ {"role": "user", "content": "Why is 6 affraid of 7?"}, ], ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True ).to(model.device) # hardcodes input canvas to avoid depending on random canvas sampling decoder_input_ids = torch.arange(model.config.canvas_length, dtype=torch.int32, device=model.device) decoder_input_ids = ( torch.ones((2, model.config.canvas_length), dtype=torch.int32, device=model.device) * decoder_input_ids ) model_inputs["decoder_input_ids"] = decoder_input_ids # sanity check: the input has bsz=2 self.assertEqual(model_inputs["input_ids"].shape[0], 2) # sanity check: has padding, and it is left-padding self.assertIn(model.config.text_config.pad_token_id, model_inputs["input_ids"]) self.assertFalse((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, -1]).any()) self.assertTrue((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, 0]).any()) with torch.no_grad(): model_out = model(**model_inputs) self.assertEqual( list(model_out.logits.shape), [2, model.config.canvas_length, model.config.text_config.vocab_size] ) torch.testing.assert_close(model_out.logits[:, :5, :12].cpu(), torch.tensor(expected_logits.get_expectation())) @slow @require_deterministic_for_xpu def test_minified_diffusion_gemma_generate_with_image_batched(self): """ [Minified (6 first layers)] bsz>1, with image, calling `generate`. This test has a LOT of non-determinism (sampling canvases, sampling tokens), so it is normal if different platforms need to set different expected outcomes. """ # print(generated_tokens) # Printed after running `torch.set_printoptions(threshold=10000, linewidth=100)` expected_sequences = Expectations( { ("cuda", None): [ [48, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 728, 48, 48, 48, 48, 598, 371, 48, 48, 595, 48, 48, 983, 371, 48, 598, 371, 48, 48, 595, 48, 48, 371, 595, 371, 516, 371, 595, 516, 516, 516, 379, 48, 371, 48, 48, 371, 371, 173, 516, 4, 595, 516, 371, 595, 48, 516, 48, 371, 516, 495, 371, 516, 516, 983, 371, 516, 516, 48, 371, 516, 371, 48, 516, 48, 371, 516, 48, 983, 48, 841, 371, 48, 516, 48, 379, 516, 832, 48, 371, 48, 371, 371, 371, 48, 48, 48, 371, 371, 48, 48, 48, 48, 48, 371, 371, 48, 48, 173, 48, 48, 48, 516, 48, 48, 48, 371, 379, 48, 371, 48, 516, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 841, 48, 48, 48, 48, 516, 173, 371, 48, 48, 48, 516, 48, 48, 516, 48, 48, 48, 48, 371, 48, 371, 48, 48, 371, 48, 379, 48, 371, 48, 832, 595, 48, 688, 48, 48, 371, 48, 371, 48, 48, 48, 48, 48, 516, 371, 371, 48, 48, 48, 371, 48, 405, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 371, 48, 371, 48, 48, 371, 371, 516, 48, 371, 48, 516, 516, 48, 48, 48, 173, 48, 48, 48, 516, 371, 516, 48, 160, 516, 48, 371, 173, 48, 48, 48, 371, 48, 48, 48, 48, 516], [48, 371, 627, 371, 48, 651, 371, 379, 379, 371, 371, 371, 595, 48, 48, 595, 371, 48, 371, 379, 595, 595, 371, 977, 48, 48, 48, 517, 371, 841, 48, 48, 517, 832, 516, 371, 418, 48, 48, 48, 48, 371, 48, 48, 48, 48, 371, 516, 379, 371, 371, 48, 48, 371, 371, 656, 371, 48, 371, 371, 371, 48, 48, 48, 48, 371, 371, 841, 371, 371, 841, 516, 371, 371, 841, 371, 656, 48, 48, 371, 983, 48, 48, 379, 48, 48, 595, 48, 371, 983, 371, 983, 48, 48, 48, 371, 48, 48, 48, 48, 516, 48, 48, 48, 48, 48, 48, 48, 48, 48, 172, 48, 48, 371, 598, 48, 48, 48, 371, 656, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 371, 841, 48, 48, 48, 48, 832, 48, 48, 498, 832, 371, 48, 371, 371, 48, 48, 371, 48, 371, 371, 48, 48, 371, 48, 654, 516, 48, 48, 48, 172, 371, 634, 48, 48, 48, 48, 516, 48, 48, 371, 48, 48, 595, 371, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 371, 379, 595, 48, 48, 48, 371, 48, 48, 634, 48, 48, 48, 172, 48, 48, 48, 48, 379, 48, 48, 48, 371, 48, 371, 48, 48, 48, 371, 48, 48, 371, 48, 48, 48, 371, 48, 48, 371, 48, 371, 48, 48, 549, 48, 48, 48, 48, 48, 634, 499, 172, 48, 48, 48, 48, 48, 48, 48 ] ], ("xpu", 5): [ [48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 173, 4, 48, 48, 48, 48, 48, 48, 371, 48, 48, 371, 371, 48, 48, 48, 371, 371, 371, 48, 371, 516, 499, 516, 371, 371, 371, 173, 516, 48, 371, 371, 48, 371, 48, 595, 173, 371, 379, 371, 516, 379, 48, 48, 48, 371, 48, 371, 371, 48, 516, 516, 48, 595, 48, 48, 48, 841, 371, 516, 48, 516, 371, 48, 48, 371, 48, 500, 859, 48, 48, 48, 48, 48, 48, 48, 371, 48, 371, 48, 371, 371, 48, 48, 48, 48, 48, 371, 48, 48, 48, 48, 172, 48, 48, 371, 371, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 405, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 516, 48, 48, 549, 48, 48, 371, 48, 48, 48, 48, 371, 48, 48, 48, 48, 371, 371, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 48, 516, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 172, 48, 48, 173, 48, 48, 516, 371, 48, 516, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48], [5, 371, 173, 48, 371, 48, 48, 371, 48, 48, 371, 48, 371, 48, 371, 379, 841, 48, 48, 48, 371, 48, 48, 48, 371, 371, 371, 48, 48, 379, 656, 48, 379, 48, 371, 172, 48, 371, 48, 48, 371, 48, 48, 371, 48, 371, 371, 48, 48, 48, 48, 48, 48, 371, 371, 48, 48, 48, 48, 371, 48, 371, 48, 371, 48, 371, 48, 48, 48, 48, 48, 371, 173, 371, 371, 379, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 48, 841, 48, 371, 983, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 371, 48, 371, 48, 48, 371, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 48, 48, 48, 516, 48, 48, 48, 371, 371, 841, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 371, 371, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 48, 48, 371, 371, 48, 48, 48, 48, 371, 48, 48, 371, 172, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 4, 48, 371 ] ] } ) # fmt: skip model = self._load_model(minified=True) processor = AutoProcessor.from_pretrained(self._model_path) chat = [ [ {"role": "user", "content": "Write a long essay about Portugal."}, ], [ { "role": "user", "content": [ { "type": "image", "url": "https://raw.githubusercontent.com/google-gemma/cookbook/refs/heads/main/apps/sample-data/GoldenGate.png", }, {"type": "text", "text": "What is shown in this image?"}, ], } ], ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True ).to(model.device) # sanity check: the input has bsz=2 self.assertEqual(model_inputs["input_ids"].shape[0], 2) # sanity check: the input has an image component self.assertIn("pixel_values", model_inputs) # sanity check: has padding, and it is left-padding self.assertIn(model.config.text_config.pad_token_id, model_inputs["input_ids"]) self.assertFalse((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, -1]).any()) self.assertTrue((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, 0]).any()) set_seed(42) gen_out = model.generate(**model_inputs, max_new_tokens=256) generated_tokens = gen_out.sequences[:, -256:] self.assertEqual(generated_tokens.tolist(), expected_sequences.get_expectation()) @slow @require_deterministic_for_xpu def test_minified_diffusion_gemma_generate_with_image_batched_long(self): """ [Minified (6 first layers)] Ultimate test: bsz>1, with image, with thinking on, calling `generate` with max_new_tokens > sliding attention length. This test has a LOT of non-determinism (sampling canvases, sampling tokens), so it is normal if different platforms need to set different expected outcomes. """ # print(generated_tokens) # Printed after running `torch.set_printoptions(threshold=10000, linewidth=100)` expected_sequences = Expectations( { ("cuda", None): [ [48, 48, 48, 567, 48, 405, 48, 48, 379, 48, 379, 379, 379, 379, 379, 379, 48, 48, 48, 48, 48, 379, 379, 379, 379, 48, 48, 379, 48, 48, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 595, 48, 48, 379, 48, 379, 48, 379, 379, 48, 516, 379, 48, 48, 48, 379, 379, 48, 516, 379, 595, 379, 379, 379, 48, 516, 418, 48, 379, 48, 379, 48, 48, 379, 48, 48, 379, 48, 379, 379, 379, 48, 48, 48, 48, 379, 48, 48, 379, 379, 379, 48, 48, 48, 48, 48, 48, 379, 379, 48, 48, 379, 48, 48, 516, 48, 48, 379, 48, 379, 48, 48, 48, 48, 48, 48, 48, 379, 379, 48, 379, 48, 48, 379, 379, 48, 48, 48, 48, 48, 48, 48, 379, 379, 48, 48, 379, 48, 48, 379, 48, 379, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 977, 48, 48, 379, 48, 379, 379, 379, 379, 379, 48, 48, 48, 48, 48, 379, 379, 379, 379, 48, 48, 48, 379, 48, 48, 48, 48, 379, 379, 379, 48, 379, 48, 48, 48, 48, 48, 48, 379, 48, 379, 48, 48, 48, 48, 48, 516, 379, 48, 48, 379, 48, 48, 48, 516, 48, 379, 48, 48, 48, 48, 379, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 48, 48, 516, 48, 48, 48, 379, 379, 48, 379, 48, 371, 48, 379, 48, 48, 379, 48, 48, 379, 48, 48, 48, 48, 48 ], [48, 48, 48, 379, 48, 379, 379, 48, 48, 379, 379, 516, 516, 379, 48, 48, 379, 379, 379, 379, 379, 379, 379, 379, 48, 48, 379, 379, 379, 48, 48, 48, 379, 379, 379, 595, 379, 379, 379, 379, 379, 48, 48, 516, 48, 379, 379, 379, 379, 48, 48, 379, 595, 48, 379, 379, 379, 48, 379, 379, 379, 48, 379, 48, 516, 379, 379, 48, 379, 379, 379, 379, 379, 379, 379, 379, 48, 48, 48, 379, 48, 379, 48, 48, 379, 516, 48, 48, 379, 379, 379, 379, 379, 379, 48, 48, 48, 516, 379, 379, 48, 595, 48, 379, 48, 48, 516, 48, 499, 48, 48, 48, 379, 379, 48, 48, 379, 379, 48, 379, 516, 379, 379, 48, 379, 379, 379, 379, 48, 379, 379, 48, 379, 379, 379, 379, 371, 48, 379, 48, 48, 371, 48, 48, 48, 48, 379, 379, 379, 379, 48, 379, 379, 48, 48, 48, 48, 48, 379, 379, 48, 379, 379, 379, 48, 379, 48, 379, 379, 379, 48, 379, 48, 841, 379, 379, 379, 48, 48, 379, 379, 48, 48, 48, 379, 48, 48, 379, 499, 379, 48, 516, 48, 48, 379, 48, 48, 379, 48, 379, 379, 48, 379, 379, 379, 48, 379, 48, 379, 379, 48, 379, 516, 48, 48, 516, 379, 379, 48, 379, 48, 379, 48, 379, 379, 379, 48, 48, 48, 48, 379, 48, 379, 379, 48, 48, 48, 48, 379, 48, 48, 379, 371, 48, 48, 371, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48 ] ], ("xpu", 5): [ [48, 48, 48, 48, 48, 48, 48, 379, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 379, 48, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 379, 48, 48, 371, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 405, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 379, 48, 379, 48, 48, 379, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 379, 48, 379, 48, 48, 48, 379, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 48, 371, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48 ], [21, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 379, 48, 48, 379, 48, 48, 48, 379, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 379, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 418, 48, 48, 379, 48, 379, 48, 48, 48, 379, 48, 48, 379, 48, 48, 48, 48, 48, 379, 48, 48, 379, 48, 48, 379, 379, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 405, 379, 48, 48, 48, 48, 48, 379, 48, 48, 48, 379, 48, 48, 379, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 379, 48, 48, 379, 48, 48, 48, 48, 48, 48, 379, 48, 48, 379, 48, 48, 48, 48, 379, 499, 48, 48, 48, 379, 48, 48, 48, 48, 48, 499, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 379, 379, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 418, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 371, 48, 48, 48, 48, 379, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48 ] ] } ) # fmt: skip model = self._load_model(minified=True) processor = AutoProcessor.from_pretrained(self._model_path) chat = [ [ {"role": "user", "content": "Write a long essay about Portugal."}, ], [ { "role": "user", "content": [ { "type": "image", "url": "https://raw.githubusercontent.com/google-gemma/cookbook/refs/heads/main/apps/sample-data/GoldenGate.png", }, {"type": "text", "text": "Describe the image in detail."}, ], } ], ] model_inputs = processor.apply_chat_template( chat, tokenize=True, return_tensors="pt", add_generation_prompt=True, return_dict=True, enable_thinking=True, ).to(model.device) # sanity check: the input has bsz=2 self.assertEqual(model_inputs["input_ids"].shape[0], 2) # sanity check: the input has an image component self.assertIn("pixel_values", model_inputs) # sanity check: has padding, and it is left-padding self.assertIn(model.config.text_config.pad_token_id, model_inputs["input_ids"]) self.assertFalse((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, -1]).any()) self.assertTrue((model.config.text_config.pad_token_id == model_inputs["input_ids"][:, 0]).any()) set_seed(42) gen_out = model.generate(**model_inputs, max_new_tokens=1280) # sanity check: we went beyond the sliding window length self.assertTrue(gen_out.sequences.shape[1] > model.config.text_config.sliding_window) generated_tokens = gen_out.sequences[:, -256:] self.assertEqual(generated_tokens.tolist(), expected_sequences.get_expectation())