@@ -244,3 +244,85 @@ def test_get_quantization_layer_structure(self):
244244 )
245245
246246 self .assertIsNone (causal_lm .get_quantization_layer_structure ("int8" ))
247+
248+
249+ @pytest .mark .skipif (keras .src .backend .backend () != "jax" , reason = "JAX only" )
250+ @pytest .mark .multi_device
251+ class GPT2CausalLMDistributionTest (TestCase ):
252+ def setUp (self ):
253+ super ().setUp ()
254+
255+ self .device_count = len (keras .distribution .list_devices ())
256+
257+ # Initialize kwargs for model creation
258+ self .merges = ["Ġ a" , "Ġ t" , "Ġ i" , "Ġ b" , "a i" , "p l" , "n e" ]
259+ self .merges += ["Ġa t" , "p o" , "r t" , "Ġt h" , "ai r" , "pl a" , "po rt" ]
260+ self .merges += ["Ġai r" , "Ġa i" , "pla ne" ]
261+ self .vocab = []
262+ for merge in self .merges :
263+ a , b = merge .split (" " )
264+ self .vocab .extend ([a , b , a + b ])
265+ self .vocab += ["!" , "<|endoftext|>" ]
266+ self .vocab = sorted (set (self .vocab )) # Remove duplicates
267+ self .vocab = dict ([(token , i ) for i , token in enumerate (self .vocab )])
268+ self .vocabulary_size = len (self .vocab )
269+
270+ self .tokenizer = GPT2Tokenizer (
271+ vocabulary = self .vocab ,
272+ merges = self .merges ,
273+ )
274+ self .preprocessor = GPT2CausalLMPreprocessor (
275+ tokenizer = self .tokenizer ,
276+ sequence_length = 8 ,
277+ )
278+ self .backbone = GPT2Backbone (
279+ vocabulary_size = self .vocabulary_size ,
280+ num_layers = 2 ,
281+ num_heads = 2 ,
282+ hidden_dim = 4 ,
283+ intermediate_dim = 4 ,
284+ max_sequence_length = 8 ,
285+ )
286+ self .init_kwargs = {
287+ "backbone" : self .backbone ,
288+ "preprocessor" : self .preprocessor ,
289+ }
290+
291+ self .device_mesh = keras .distribution .DeviceMesh (
292+ shape = (self .device_count ,),
293+ axis_names = ["batch" ],
294+ devices = keras .distribution .list_devices (),
295+ )
296+
297+ self .layout_map = keras .distribution .LayoutMap (self .device_mesh )
298+
299+ self .distribution = keras .distribution .DataParallel (
300+ self .device_mesh ,
301+ self .layout_map ,
302+ )
303+
304+ def test_e2e_data_parallel_generate (self ):
305+ with self .distribution .scope ():
306+ causal_lm = GPT2CausalLM (** self .init_kwargs )
307+
308+ # Pass prompts to match the number of devices.
309+ prompts = [" airplane at airport" ] * self .device_count
310+
311+ # This should run without errors and use the distribution.
312+ output = causal_lm .generate (prompts )
313+ self .assertIsInstance (output , (list , tuple ))
314+ self .assertLen (output , self .device_count )
315+
316+ def test_e2e_data_parallel_generate_indivisible_error (self ):
317+ with self .distribution .scope ():
318+ if self .device_count < 2 :
319+ pytest .skip ("This test requires at least 2 devices" )
320+ keras .distribution .set_distribution (self .distribution )
321+ causal_lm = GPT2CausalLM (** self .init_kwargs )
322+
323+ # Pass only 1 prompt, which is not divisible by the number of
324+ # devices.
325+ prompt = " airplane at airport"
326+
327+ with self .assertRaises (Exception ):
328+ causal_lm .generate (prompt )
0 commit comments