Skip to content

Commit 568946d

Browse files
authored
Fix parallelization of input data in generate() (#2749)
* Fix Distribution * test * Address automatic comments * Addressing comments * Test fix * formatting * Fix test * Format and polishing * Format
1 parent 6bd0627 commit 568946d

2 files changed

Lines changed: 105 additions & 0 deletions

File tree

keras_hub/src/models/causal_lm.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import keras
55
from keras import ops
66
from keras import tree
7+
from keras.src.distribution import distribution_lib
78

89
from keras_hub.src.api_export import keras_hub_export
910
from keras_hub.src.models.task import Task
@@ -354,6 +355,26 @@ def preprocess(x):
354355
x, sequence_length=max_length
355356
)
356357

358+
def distribute(x):
359+
"""Distribute tensors according to the distribution library."""
360+
distribution = distribution_lib.distribution()
361+
if distribution is None:
362+
return x
363+
364+
def _distribute_tensor(value):
365+
if value is None:
366+
return None
367+
if not ops.is_tensor(value):
368+
value = ops.convert_to_tensor(value)
369+
layout = distribution.get_data_layout(value.shape)
370+
return (
371+
distribution_lib.distribute_tensor(value, layout)
372+
if layout
373+
else value
374+
)
375+
376+
return tree.map_structure(_distribute_tensor, x)
377+
357378
def generate(x):
358379
return generate_function(x, stop_token_ids=stop_token_ids)
359380

@@ -394,6 +415,8 @@ def postprocess(x):
394415
if self.preprocessor is not None:
395416
inputs = [preprocess(x) for x in inputs]
396417

418+
inputs = [distribute(x) for x in inputs]
419+
397420
if strip_prompt:
398421
outputs = [strip_prompt_function(generate(x), x) for x in inputs]
399422
else:

keras_hub/src/models/gpt2/gpt2_causal_lm_test.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)