Skip to content

Commit 1e9a204

Browse files
lc5211The tunix Authors
authored andcommitted
[Tunix] Replace legacy TrainExample with RLTrainerPayload in experimental orchestrator and workers.
PiperOrigin-RevId: 974615656
1 parent 5cb3847 commit 1e9a204

8 files changed

Lines changed: 43 additions & 307 deletions

File tree

tests/experimental/orchestrator/batch_assembly_test.py

Lines changed: 16 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import numpy as np
2020
from tunix.experimental.common import datatypes
2121
from tunix.experimental.orchestrator import batch_assembly
22-
from tunix.rl import common as rl_common
2322

2423

2524
class HelperFunctionsTest(absltest.TestCase):
@@ -104,8 +103,8 @@ def test_completion_aligned_slices_full_sequence(self):
104103

105104
class WithRefPerTokenLogpsTest(absltest.TestCase):
106105

107-
def _make_train_example(self, b=2, p=3, c=4):
108-
return rl_common.TrainExample(
106+
def _make_payload(self, b=2, p=3, c=4):
107+
return datatypes.RLTrainerPayload(
109108
prompt_ids=np.ones((b, p), dtype=np.int32),
110109
prompt_mask=np.ones((b, p), dtype=np.float32),
111110
completion_ids=np.ones((b, c), dtype=np.int32),
@@ -116,31 +115,33 @@ def _make_train_example(self, b=2, p=3, c=4):
116115
)
117116

118117
def test_success_with_ndarray(self):
119-
batch = self._make_train_example(b=2, p=3, c=4)
118+
batch = self._make_payload(b=2, p=3, c=4)
120119
ref_logps = np.full((2, 4), -0.5, dtype=np.float32)
121120
updated = batch_assembly.with_ref_per_token_logps(batch, ref_logps)
122121

122+
self.assertIsInstance(updated, datatypes.RLTrainerPayload)
123123
self.assertIsNotNone(updated.ref_per_token_logps)
124124
self.assertEqual(updated.ref_per_token_logps.shape, (2, 4))
125125
np.testing.assert_allclose(updated.ref_per_token_logps, ref_logps)
126126
np.testing.assert_array_equal(updated.prompt_ids, batch.prompt_ids)
127127
np.testing.assert_array_equal(updated.completion_ids, batch.completion_ids)
128128

129129
def test_success_with_logprobs_response(self):
130-
batch = self._make_train_example(b=2, p=3, c=4)
130+
batch = self._make_payload(b=2, p=3, c=4)
131131
resp = datatypes.LogprobsResponse(
132132
per_token_logps=np.full((2, 4), -0.8, dtype=np.float32)
133133
)
134134
updated = batch_assembly.with_ref_per_token_logps(batch, resp)
135135

136+
self.assertIsInstance(updated, datatypes.RLTrainerPayload)
136137
self.assertIsNotNone(updated.ref_per_token_logps)
137138
self.assertEqual(updated.ref_per_token_logps.shape, (2, 4))
138139
np.testing.assert_allclose(
139140
updated.ref_per_token_logps, resp.per_token_logps
140141
)
141142

142143
def test_error_in_logprobs_response_raises_runtime_error(self):
143-
batch = self._make_train_example(b=2, p=3, c=4)
144+
batch = self._make_payload(b=2, p=3, c=4)
144145
resp = datatypes.LogprobsResponse(
145146
per_token_logps=None,
146147
error=datatypes.ErrorInfo(
@@ -150,18 +151,14 @@ def test_error_in_logprobs_response_raises_runtime_error(self):
150151
with self.assertRaisesRegex(RuntimeError, "inference worker failed"):
151152
batch_assembly.with_ref_per_token_logps(batch, resp)
152153

153-
def test_rejects_non_train_example(self):
154-
payload = datatypes.RLTrainerPayload(
155-
token_ids=np.array([1, 2], dtype=np.int32),
156-
token_mask=np.array([1, 1], dtype=np.float32),
157-
loss_mask=np.array([0, 1], dtype=np.float32),
158-
advantages=np.array([1.0, 1.0], dtype=np.float32),
159-
)
160-
with self.assertRaisesRegex(TypeError, "expects a padded TrainExample"):
161-
batch_assembly.with_ref_per_token_logps(payload, np.zeros((2, 2)))
154+
def test_rejects_unsupported_type(self):
155+
with self.assertRaisesRegex(TypeError, "expects a padded RLTrainerPayload"):
156+
batch_assembly.with_ref_per_token_logps(
157+
{"raw": "batch"}, np.zeros((2, 2))
158+
)
162159

163160
def test_mismatched_shape_raises_value_error(self):
164-
batch = self._make_train_example(b=2, p=3, c=4)
161+
batch = self._make_payload(b=2, p=3, c=4)
165162
bad_shape_logps = np.zeros((2, 3), dtype=np.float32)
166163
with self.assertRaisesRegex(
167164
ValueError,
@@ -277,121 +274,6 @@ def test_sequence_packed_assembler_multiple_bins(self):
277274
self.assertEqual(payloads[1].token_ids.shape, (1, 12))
278275

279276

280-
class GRPOTrainExampleAssemblerTest(absltest.TestCase):
281-
282-
def test_rejects_non_positive_batch_size(self):
283-
with self.assertRaisesRegex(ValueError, "batch size must be positive"):
284-
batch_assembly.GRPOTrainExampleAssembler(
285-
batch_size=0,
286-
max_prompt_length=4,
287-
max_response_length=5,
288-
pad_id=0,
289-
)
290-
291-
def test_empty_input_returns_empty_list(self):
292-
assembler = batch_assembly.GRPOTrainExampleAssembler(
293-
batch_size=2,
294-
max_prompt_length=4,
295-
max_response_length=5,
296-
pad_id=0,
297-
)
298-
self.assertEmpty(assembler.pack([]))
299-
300-
def test_grpo_train_example_assembler_basic(self):
301-
payload = datatypes.RLTrainerPayload(
302-
token_ids=np.array([10, 11, 20, 21, 22], dtype=np.int32),
303-
token_mask=np.ones(5, dtype=np.float32),
304-
loss_mask=np.array([0, 0, 1, 1, 0], dtype=np.float32),
305-
action_mask=np.array([0, 0, 1, 1, 0], dtype=np.float32),
306-
advantages=np.array([0, 0, 2, 2, 2], dtype=np.float32),
307-
prompt_ids=np.array([10, 11], dtype=np.int32),
308-
prompt_mask=np.ones(2, dtype=np.float32),
309-
completion_ids=np.array([20, 21, 22], dtype=np.int32),
310-
completion_mask=np.array([1, 1, 0], dtype=np.float32),
311-
)
312-
313-
assembler = batch_assembly.GRPOTrainExampleAssembler(
314-
batch_size=2,
315-
max_prompt_length=4,
316-
max_response_length=5,
317-
pad_id=0,
318-
)
319-
train_example = assembler.pack([payload])[0]
320-
321-
self.assertEqual(train_example.prompt_ids.shape, (2, 4))
322-
self.assertEqual(train_example.completion_ids.shape, (2, 5))
323-
np.testing.assert_array_equal(
324-
train_example.prompt_ids[0], np.array([0, 0, 10, 11])
325-
)
326-
np.testing.assert_array_equal(
327-
train_example.completion_ids[0], np.array([20, 21, 22, 0, 0])
328-
)
329-
np.testing.assert_array_equal(
330-
train_example.completion_mask[0], np.array([1, 1, 0, 0, 0])
331-
)
332-
np.testing.assert_array_equal(
333-
train_example.advantages[0], np.array([2, 2, 2, 0, 0])
334-
)
335-
336-
def test_grpo_assembler_optional_fields_propagation(self):
337-
payload = datatypes.RLTrainerPayload(
338-
token_ids=np.array([10, 11, 20, 21, 22], dtype=np.int32),
339-
token_mask=np.ones(5, dtype=np.float32),
340-
loss_mask=np.array([0, 0, 1, 1, 1], dtype=np.float32),
341-
action_mask=np.array([0, 0, 1, 1, 1], dtype=np.float32),
342-
advantages=np.array([2, 2, 2], dtype=np.float32),
343-
prompt_ids=np.array([10, 11], dtype=np.int32),
344-
prompt_mask=np.ones(2, dtype=np.float32),
345-
completion_ids=np.array([20, 21, 22], dtype=np.int32),
346-
completion_mask=np.ones(3, dtype=np.float32),
347-
ref_per_token_logps=np.array([-0.3, -0.4, -0.5], dtype=np.float32),
348-
old_per_token_logps=np.array([-0.1, -0.2, -0.3], dtype=np.float32),
349-
)
350-
351-
assembler = batch_assembly.GRPOTrainExampleAssembler(
352-
batch_size=2,
353-
max_prompt_length=4,
354-
max_response_length=5,
355-
pad_id=0,
356-
)
357-
train_example = assembler.pack([payload])[0]
358-
359-
self.assertIsNotNone(train_example.ref_per_token_logps)
360-
self.assertIsNotNone(train_example.old_per_token_logps)
361-
362-
self.assertEqual(train_example.ref_per_token_logps.shape, (2, 5))
363-
self.assertEqual(train_example.old_per_token_logps.shape, (2, 5))
364-
365-
np.testing.assert_allclose(
366-
train_example.ref_per_token_logps[0], [-0.3, -0.4, -0.5, 0.0, 0.0]
367-
)
368-
np.testing.assert_allclose(
369-
train_example.old_per_token_logps[0], [-0.1, -0.2, -0.3, 0.0, 0.0]
370-
)
371-
372-
def test_grpo_assembler_chunks_multiple_microbatches(self):
373-
payload = datatypes.RLTrainerPayload(
374-
token_ids=np.array([1, 2, 3], dtype=np.int32),
375-
token_mask=np.ones(3, dtype=np.float32),
376-
loss_mask=np.array([0, 1, 1], dtype=np.float32),
377-
advantages=np.array([1.0, 1.0], dtype=np.float32),
378-
prompt_ids=np.array([1], dtype=np.int32),
379-
completion_ids=np.array([2, 3], dtype=np.int32),
380-
)
381-
382-
assembler = batch_assembly.GRPOTrainExampleAssembler(
383-
batch_size=2,
384-
max_prompt_length=3,
385-
max_response_length=4,
386-
pad_id=0,
387-
)
388-
train_examples = assembler.pack([payload, payload, payload])
389-
390-
self.assertLen(train_examples, 2)
391-
self.assertEqual(train_examples[0].prompt_ids.shape, (2, 3))
392-
self.assertEqual(train_examples[1].prompt_ids.shape, (2, 3))
393-
394-
395277
def _make_payload(
396278
prompt_len: int,
397279
completion_len: int,
@@ -585,8 +467,9 @@ def test_scalar_advantage_broadcasts_over_completion(self):
585467
np.testing.assert_allclose(payload.advantages[0], [2.5, 2.5, 2.5, 0, 0])
586468

587469
def test_sequence_aligned_advantage_is_sliced_to_completion(self):
588-
item = _make_payload(2, 3)
589-
item.advantages = np.array([0, 0, 2, 2, 2], dtype=np.float32)
470+
item = _make_payload(
471+
2, 3, advantage=np.array([0, 0, 2, 2, 2], dtype=np.float32)
472+
)
590473
payload = self._assembler().pack([item])[0]
591474

592475
np.testing.assert_allclose(payload.advantages[0], [2, 2, 2, 0, 0])

tests/experimental/orchestrator/rl_program_test.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from tunix.experimental.orchestrator import distributed_rl_engine
2929
from tunix.experimental.orchestrator import rl_program
3030
from tunix.experimental.worker import remote_execution
31-
from tunix.rl import common as rl_common
3231
from tunix.sft import metrics_logger as metrics_logger_lib
3332
from tunix.sft import utils as sft_utils
3433

@@ -726,7 +725,7 @@ async def _run():
726725
def test_reference_kl_logprobs_scoring_in_train_stage(self):
727726
async def _run():
728727
self.mock_algo.requires_reference_kl = True
729-
mock_train_example = rl_common.TrainExample(
728+
mock_payload = datatypes.RLTrainerPayload(
730729
prompt_ids=np.array([[1, 2]], dtype=np.int32),
731730
prompt_mask=np.ones((1, 2), dtype=np.float32),
732731
completion_ids=np.array([[3, 4]], dtype=np.int32),
@@ -735,7 +734,7 @@ async def _run():
735734
ref_per_token_logps=None,
736735
old_per_token_logps=None,
737736
)
738-
self.assembler.pack = mock.MagicMock(return_value=[mock_train_example])
737+
self.assembler.pack = mock.MagicMock(return_value=[mock_payload])
739738
self.mock_engine.per_token_logps = mock.AsyncMock(
740739
return_value=np.array([[-0.1, -0.2]], dtype=np.float32)
741740
)
@@ -746,7 +745,7 @@ async def _run():
746745
await program.run_async(self.mock_engine)
747746

748747
self.mock_engine.per_token_logps.assert_called_once_with(
749-
datatypes.Role.REFERENCE, items=mock_train_example
748+
datatypes.Role.REFERENCE, items=mock_payload
750749
)
751750
self.assertEqual(program.step, 1)
752751

@@ -755,7 +754,7 @@ async def _run():
755754
def test_reference_kl_raises_type_error_for_invalid_microbatch(self):
756755
async def _run():
757756
self.mock_algo.requires_reference_kl = True
758-
# Returning a raw dict instead of TrainExample
757+
# Returning a raw dict instead of RLTrainerPayload
759758
self.assembler.pack = mock.MagicMock(return_value=[{"raw": "batch"}])
760759
_set_mock_poll_batches(self.mock_engine, _make_trajectory_group())
761760
program = self._create_program(dataset=["prompt_0"])

tests/experimental/worker/inference_worker_test.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from tunix.experimental.common import datatypes
2222
from tunix.experimental.common import rpc_utils
2323
from tunix.experimental.worker import inference_worker as inference_lib
24-
from tunix.rl import common as rl_common
2524

2625
WorkerState = datatypes.WorkerState
2726

@@ -107,7 +106,7 @@ def test_chunking_matches_single_pass(self):
107106

108107
def test_per_token_logps_uses_padded_batch_without_repadding(self):
109108
core = _StubCore()
110-
batch = rl_common.TrainExample(
109+
batch = datatypes.RLTrainerPayload(
111110
prompt_ids=np.array([[0, 0, 5, 6], [0, 7, 8, 9]], dtype=np.int32),
112111
prompt_mask=np.array([[0, 0, 1, 1], [0, 1, 1, 1]], dtype=np.float32),
113112
completion_ids=np.array([[10, 11, 0], [12, 0, 0]], dtype=np.int32),

tunix/experimental/common/datatypes.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import enum
2424
import time
2525
from typing import Any, Dict
26+
import flax
2627
import uuid
2728
from jax.typing import ArrayLike # pylint: disable=g-importing-member
2829
import numpy as np
@@ -519,7 +520,7 @@ def __post_init__(self):
519520
##### Training DTOs #####
520521

521522

522-
@dataclasses.dataclass(kw_only=True)
523+
@flax.struct.dataclass(frozen=True, kw_only=True)
523524
class TrainerPayload:
524525
"""Base class for generic trainer payloads.
525526
@@ -540,7 +541,7 @@ class TrainerPayload:
540541
segment_positions: ArrayLike | None = None
541542

542543

543-
@dataclasses.dataclass(kw_only=True)
544+
@flax.struct.dataclass(frozen=True, kw_only=True)
544545
class SFTTrainerPayload(TrainerPayload):
545546
"""Supervised Fine-Tuning (SFT) trainer payload.
546547
@@ -560,7 +561,7 @@ class SFTTrainerPayload(TrainerPayload):
560561

561562
# TODO(tunix-dev): Introduce PPOTrainerPayload to replace generic
562563
# RLTrainerPayload when PPO specific fields are needed.
563-
@dataclasses.dataclass(kw_only=True)
564+
@flax.struct.dataclass(frozen=True, kw_only=True)
564565
class RLTrainerPayload(TrainerPayload):
565566
"""RL training payload.
566567
@@ -582,8 +583,8 @@ class RLTrainerPayload(TrainerPayload):
582583
metadata: Extra payload metadata dictionary.
583584
"""
584585

585-
advantages: ArrayLike
586-
loss_mask: ArrayLike
586+
advantages: ArrayLike | None = None
587+
loss_mask: ArrayLike | None = None
587588
action_mask: ArrayLike | None = None
588589
# TODO(tunix-dev): make prompt_ids/mask and completion_ids/mask required after
589590
# SequencePackedBatchAssembler refactor is done.
@@ -596,7 +597,9 @@ class RLTrainerPayload(TrainerPayload):
596597
sampler_is_weights: ArrayLike | None = None
597598
returns: ArrayLike | None = None
598599
old_values: ArrayLike | None = None
599-
metadata: dict[str, Any] = dataclasses.field(default_factory=dict)
600+
metadata: dict[str, Any] = flax.struct.field(
601+
default_factory=dict, pytree_node=False
602+
)
600603
# TODO(tunix-dev): add ppo specific fields in PPORLTrainerPayload.
601604

602605

tunix/experimental/examples/math_gsm8k_dist/run_gsm8k_dist_grpo.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ def _grpo_model_input(
263263
pad_id: int,
264264
eos_id: int,
265265
) -> dict[str, Any]:
266-
"""Maps a TrainExample microbatch to algo_core.grpo_loss_fn kwargs."""
266+
"""Maps an RLTrainerPayload microbatch to algo_core.grpo_loss_fn kwargs."""
267267
return {
268268
"train_example": train_example,
269269
"algo_config": algo_config,
@@ -592,7 +592,7 @@ def accept_worker(hostname: str, _: int, metadata: bytes) -> None:
592592
dataset=_iter_prompt_items(args),
593593
max_steps=args.max_steps,
594594
reward_fns=reward_fns,
595-
assembler=batch_assembly.GRPOTrainExampleAssembler(
595+
assembler=batch_assembly.PaddedBatchAssembler(
596596
batch_size=args.train_micro_batch_size,
597597
max_prompt_length=args.max_prompt_length,
598598
max_response_length=args.max_response_length,

0 commit comments

Comments
 (0)