Skip to content

Commit 6336d4c

Browse files
committed
Implement MLPerf v6.1 RCP logging and deferred offline evaluation for DeepSWE
1 parent 22f5d90 commit 6336d4c

18 files changed

Lines changed: 925 additions & 22 deletions

File tree

‎tests/examples/deepswe_mllog_utils_test.py‎

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import shutil
2323
import tempfile
2424
import types
25+
from typing import Any
2526
from unittest import mock
2627

2728
import numpy as np
@@ -30,6 +31,15 @@
3031
from tunix.utils import mllog_utils
3132

3233

34+
def _read_mllog_events(path: str) -> list[dict[str, Any]]:
35+
with open(path, "r", encoding="utf-8") as f:
36+
return [
37+
json.loads(line.split(":::MLLOG ", 1)[1])
38+
for line in f
39+
if ":::MLLOG " in line
40+
]
41+
42+
3343
@absltest.skipIf(mllog_utils.mllogger is None, "mlperf_logging is not installed")
3444
class MllogUtilsTest(absltest.TestCase):
3545

@@ -64,6 +74,7 @@ def test_file_logging_with_metric_logger_dir(self):
6474
tpu_topology="v5p-64",
6575
rollout_engine="vllm",
6676
target_accuracy=0.69,
77+
model_id="",
6778
)
6879

6980
mllog_utils.init_start(args)
@@ -221,6 +232,7 @@ def test_end_to_end_mlperf_logging_with_train_configs(self):
221232
seed=1,
222233
learning_rate=1e-6,
223234
eval_every_n_steps=5,
235+
model_id="",
224236
)
225237

226238
mock_train_dataset = [None] * 5480
@@ -602,7 +614,151 @@ def test_train_stop(self):
602614
content = f.read()
603615

604616
self.assertIn('"key": "block_stop"', content)
605-
self.assertIn('"key": "run_stop"', content)
617+
# run_stop is emitted by the offline evaluator, not by train_stop.
618+
self.assertNotIn('"key": "run_stop"', content)
619+
620+
def test_compute_val_start_step(self):
621+
self.assertEqual(mllog_utils.compute_val_start_step(256), 18)
622+
self.assertEqual(mllog_utils.compute_val_start_step(512), 10)
623+
self.assertEqual(mllog_utils.compute_val_start_step(1024), 7)
624+
self.assertEqual(mllog_utils.compute_val_start_step(256, 1), 1)
625+
self.assertEqual(mllog_utils.compute_val_start_step(256, 0), 18)
626+
with self.assertRaises(ValueError):
627+
mllog_utils.compute_val_start_step(0)
628+
629+
def test_append_checkpoint_manifest_upserts_sorted_records(self):
630+
manifest_path = os.path.join(
631+
self.test_dir, "mllog", "eval_checkpoints.jsonl"
632+
)
633+
for step, ts_ms in ((19, 2000), (18, 1000), (18, 1500)):
634+
mllog_utils.append_checkpoint_manifest(
635+
manifest_path,
636+
{
637+
"step": step,
638+
"checkpoint_path": f"gs://ckpt/{step}/model_params",
639+
"timestamp_ms": ts_ms,
640+
"samples_count": step * 256,
641+
"val_start_at": 18,
642+
},
643+
)
644+
with open(manifest_path, "r", encoding="utf-8") as f:
645+
records = [json.loads(line) for line in f]
646+
self.assertEqual([r["step"] for r in records], [18, 19])
647+
self.assertEqual([r["timestamp_ms"] for r in records], [1500, 2000])
648+
649+
def test_configure_logger_downloads_existing_gcs_log_before_config(self):
650+
calls = mock.MagicMock()
651+
fake_mllogger = mock.MagicMock()
652+
fake_mllogger.logger.handlers = []
653+
with (
654+
mock.patch.object(mllog_utils, "mllog", calls.mllog),
655+
mock.patch.object(mllog_utils, "mllogger", fake_mllogger),
656+
mock.patch.object(mllog_utils, "_is_master_process", return_value=True),
657+
mock.patch.object(mllog_utils, "_gcs_target_path", None),
658+
mock.patch.object(mllog_utils, "_local_log_path", None),
659+
mock.patch.object(
660+
mllog_utils, "_download_from_gcs_if_exists", calls.download
661+
),
662+
):
663+
mllog_utils.configure_logger(metric_logger_dir="gs://b/mllog", seed=42)
664+
local_path = mllog_utils._local_log_path # pylint: disable=protected-access
665+
666+
self.assertEqual(
667+
[c[0] for c in calls.mock_calls], ["download", "mllog.config"]
668+
)
669+
calls.download.assert_called_once_with("gs://b/mllog/seed_42.out", local_path)
670+
self.assertEqual(
671+
os.path.abspath(calls.mllog.config.call_args.kwargs["filename"]),
672+
local_path,
673+
)
674+
675+
def test_offline_eval_rcp_sequence_converged(self):
676+
log_dir = self.test_dir
677+
args = types.SimpleNamespace(batch_size=16, num_generations=16)
678+
mllog_utils.configure_logger(metric_logger_dir=log_dir, seed=42)
679+
mllog_utils.train_stop(args, step=19, time_ms=2000)
680+
self.assertFalse(
681+
mllog_utils.log_offline_eval_step(
682+
step=18,
683+
samples_count=4608,
684+
eval_accuracy=0.65,
685+
checkpoint_timestamp_ms=1000,
686+
)
687+
)
688+
self.assertTrue(
689+
mllog_utils.log_offline_eval_step(
690+
step=19,
691+
samples_count=4864,
692+
eval_accuracy=0.70,
693+
checkpoint_timestamp_ms=2000,
694+
is_last_checkpoint=True,
695+
)
696+
)
697+
698+
events = _read_mllog_events(os.path.join(log_dir, "seed_42.out"))
699+
self.assertEqual(
700+
[e["key"] for e in events],
701+
["block_stop"] + ["eval_start", "eval_accuracy", "eval_stop"] * 2
702+
+ ["run_stop"],
703+
)
704+
self.assertEqual(events[0]["time_ms"], 2000)
705+
self.assertEqual(events[0]["metadata"]["step"], 19)
706+
self.assertEqual([events[2]["value"], events[5]["value"]], [0.65, 0.70])
707+
run_stop = events[-1]
708+
self.assertEqual(run_stop["time_ms"], 2000)
709+
self.assertEqual(run_stop["metadata"]["status"], "success")
710+
self.assertEqual(run_stop["metadata"]["samples_count"], 4864)
711+
712+
def test_offline_eval_rcp_sequence_not_converged(self):
713+
log_dir = self.test_dir
714+
mllog_utils.configure_logger(metric_logger_dir=log_dir, seed=42)
715+
for step, ts_ms in ((18, 1000), (19, 2000), (20, 3000)):
716+
self.assertFalse(
717+
mllog_utils.log_offline_eval_step(
718+
step=step,
719+
samples_count=step * 256,
720+
eval_accuracy=0.5,
721+
checkpoint_timestamp_ms=ts_ms,
722+
is_last_checkpoint=step == 20,
723+
)
724+
)
725+
726+
events = _read_mllog_events(os.path.join(log_dir, "seed_42.out"))
727+
run_stops = [e for e in events if e["key"] == "run_stop"]
728+
self.assertLen(run_stops, 1)
729+
self.assertEqual(events[-1]["key"], "run_stop")
730+
self.assertEqual(run_stops[0]["time_ms"], 3000)
731+
self.assertEqual(run_stops[0]["metadata"]["status"], "aborted")
732+
self.assertEqual(run_stops[0]["metadata"]["samples_count"], 5120)
733+
734+
def test_mlperf_6_1_0_init_print_disclosures(self):
735+
fake_mllogger = mock.MagicMock()
736+
with (
737+
mock.patch.object(mllog_utils, "mllogger", fake_mllogger),
738+
mock.patch.object(mllog_utils, "_is_master_process", return_value=True),
739+
mock.patch.object(mllog_utils, "_flush_to_gcs_if_needed"),
740+
):
741+
args = types.SimpleNamespace(
742+
batch_size=16,
743+
num_generations=16,
744+
max_prompt_length=4096,
745+
max_response_length=61440,
746+
model_id="",
747+
)
748+
mllog_utils.init_print(args)
749+
emitted = {
750+
c.kwargs["key"]: c.kwargs["value"]
751+
for c in fake_mllogger.event.call_args_list
752+
}
753+
self.assertEqual(emitted["eval_samples"], 251)
754+
self.assertEqual(emitted["max_sequence_length"], 65536)
755+
for key in (
756+
"lowest_numerical_precision_in_linear",
757+
"lowest_numerical_precision_in_attn",
758+
"lowest_numerical_precision_in_comm",
759+
):
760+
self.assertEqual(emitted[key], "bfloat16")
761+
self.assertEqual(emitted["config_filename"], "qwen35_397b_grpo")
606762

607763

608764
if __name__ == "__main__":

‎tests/experimental/examples/deepswe_dist/eval_deepswe_test.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,21 @@ def test_missing_and_failed_attempts_are_not_dropped(self):
168168
with self.assertRaisesRegex(ValueError, "duplicate"):
169169
eval_lib.summarize(rows + [rows[0]], ["a", "b"], 4)
170170

171+
def test_pass_at_4_is_reported_with_more_attempts(self):
172+
rows = [
173+
dict(
174+
instance_id="a",
175+
attempt=i,
176+
reward=float(i == 5),
177+
resolved=i == 5,
178+
status="SUCCEEDED",
179+
)
180+
for i in range(8)
181+
]
182+
summary = eval_lib.summarize(rows, ["a"], 8)
183+
# Unbiased pass@k = 1 - C(n - c, k) / C(n, k) with n=8, c=1.
184+
self.assertEqual(summary["pass_at_k"], {"1": 1 / 8, "4": 1 / 2, "8": 1.0})
185+
171186
def test_reward_comes_from_trajectory_not_completion_status(self):
172187
response = types.SimpleNamespace(
173188
error=None,
@@ -182,6 +197,21 @@ def test_reward_comes_from_trajectory_not_completion_status(self):
182197
response.error = "infrastructure failure"
183198
self.assertFalse(eval_lib.compact_result(response)["resolved"])
184199

200+
def test_rcp_logging_fails_fast_without_mlperf_logging(self):
201+
from tunix.utils import mllog_utils # pylint: disable=g-import-not-at-top
202+
203+
log_file = "gs://bucket/mllog/seed_42.out"
204+
a = self.args("--rcp_logging=true", "--metric_logger_dir", log_file)
205+
with mock.patch.object(mllog_utils, "configure_logger") as configure:
206+
with mock.patch.object(mllog_utils, "mllogger", None):
207+
with self.assertRaisesRegex(RuntimeError, "mlperf_logging"):
208+
eval_lib.setup_rcp_logging(a)
209+
configure.assert_not_called()
210+
with mock.patch.object(mllog_utils, "mllogger", object()):
211+
eval_lib.setup_rcp_logging(a)
212+
eval_lib.setup_rcp_logging(self.args("--rcp_logging=false"))
213+
configure.assert_called_once_with(metric_logger_dir=log_file, seed=42)
214+
185215
def test_individual_results_are_persisted(self):
186216
with tempfile.TemporaryDirectory() as directory:
187217
writer = eval_lib.ResultWriter(directory)

‎tests/experimental/orchestrator/rl_program_test.py‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,48 @@ async def _run():
14801480

14811481
asyncio.run(_run())
14821482

1483+
def test_train_stage_on_checkpoint_saved_gets_path_from_response(self):
1484+
async def _run():
1485+
self.mock_algo.num_generations = 1
1486+
self.mock_algo.mini_batch_size = 1
1487+
self.mock_engine.save_checkpoint.return_value = datatypes.Response(
1488+
metadata={
1489+
"checkpoint_saved": True,
1490+
"checkpoint_path": "gs://ckpt/1/model_params",
1491+
}
1492+
)
1493+
saved = []
1494+
program = self._create_program(
1495+
batch_size=1, on_checkpoint_saved=saved.append
1496+
)
1497+
program.engine = self.mock_engine
1498+
1499+
payload = datatypes.RLTrainerPayload(
1500+
prompt_ids=np.array([1, 2], dtype=np.int32),
1501+
prompt_mask=np.array([1.0, 1.0], dtype=np.float32),
1502+
completion_ids=np.array([3, 4], dtype=np.int32),
1503+
completion_mask=np.array([1.0, 1.0], dtype=np.float32),
1504+
advantages=np.array([1.0, 1.0], dtype=np.float32),
1505+
)
1506+
item = datatypes.TrajectoryItem(
1507+
group_index=0,
1508+
prompt_id="prompt_0",
1509+
start_step=0,
1510+
traj={"trajectory_reward": 1.0},
1511+
)
1512+
item.payload = payload
1513+
await program.scored_q.put(item)
1514+
await program.scored_q.close()
1515+
1516+
await program.train_stage()
1517+
1518+
self.assertLen(saved, 1)
1519+
self.assertEqual(saved[0]["step"], 1)
1520+
self.assertEqual(saved[0]["checkpoint_path"], "gs://ckpt/1/model_params")
1521+
self.assertEqual(saved[0]["timestamp_ms"], program.last_step_timestamp_ms)
1522+
1523+
asyncio.run(_run())
1524+
14831525
def test_train_stage_sequence_packed_final_batch_broken_down_into_multiple_microbatches(
14841526
self,
14851527
):

‎tests/experimental/worker/trainer_worker_test.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ def __init__(self):
4444
self.step_count = 10
4545
self.target_state = None
4646
self.gen_model_input_fn = None
47+
self._checkpoint_dir = None
48+
49+
@property
50+
def checkpoint_dir(self) -> str | None:
51+
return self._checkpoint_dir
4752

4853
def compile(self, dummy_data=None):
4954
pass
@@ -152,6 +157,19 @@ def test_update_returns_step_count(self):
152157
step = self.worker.update()
153158
self.assertEqual(step, 11)
154159

160+
def test_save_checkpoint_returns_checkpoint_path_from_checkpoint_dir(self):
161+
resp_empty = self.worker.save_checkpoint(metadata={"step": 5})
162+
self.assertTrue(resp_empty.metadata["checkpoint_saved"])
163+
self.assertEqual(resp_empty.metadata["checkpoint_path"], "")
164+
165+
self.fake_trainer._checkpoint_dir = "gs://bucket/checkpoints"
166+
resp = self.worker.save_checkpoint(metadata={"step": 5})
167+
self.assertTrue(resp.metadata["checkpoint_saved"])
168+
self.assertEqual(
169+
resp.metadata["checkpoint_path"],
170+
"gs://bucket/checkpoints/5/model_params",
171+
)
172+
155173
def test_set_target_state_configures_trainer(self):
156174
target_state = {"params": np.zeros((4, 4))}
157175
resp = self.worker.set_target_state(target_state=target_state)

‎tests/utils/maxtext_utils_test.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,7 @@ def _init_state(self):
913913
mock_cfg = mock.MagicMock()
914914
mock_cfg.weight_dtype = "bfloat16"
915915
mock_cfg.float32_gate_logits = True
916+
mock_cfg.checkpoint_dir = "/tmp/ckpts"
916917
mock_mesh = mock.MagicMock()
917918

918919
with mock.patch.object(
@@ -927,6 +928,7 @@ def _init_state(self):
927928
self.assertIsNot(captured["optimizer_cls_during_init"], FakeOptimizer)
928929
self.assertEqual(FakeNNX.Optimizer, FakeOptimizer)
929930
self.assertIsNone(engine._weight_converter._direct.target_dtype)
931+
self.assertEqual(engine.checkpoint_dir, "/tmp/ckpts")
930932

931933

932934
if __name__ == "__main__":

0 commit comments

Comments
 (0)