|
22 | 22 | import shutil |
23 | 23 | import tempfile |
24 | 24 | import types |
| 25 | +from typing import Any |
25 | 26 | from unittest import mock |
26 | 27 |
|
27 | 28 | import numpy as np |
|
30 | 31 | from tunix.utils import mllog_utils |
31 | 32 |
|
32 | 33 |
|
| 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 | + |
33 | 43 | @absltest.skipIf(mllog_utils.mllogger is None, "mlperf_logging is not installed") |
34 | 44 | class MllogUtilsTest(absltest.TestCase): |
35 | 45 |
|
@@ -64,6 +74,7 @@ def test_file_logging_with_metric_logger_dir(self): |
64 | 74 | tpu_topology="v5p-64", |
65 | 75 | rollout_engine="vllm", |
66 | 76 | target_accuracy=0.69, |
| 77 | + model_id="", |
67 | 78 | ) |
68 | 79 |
|
69 | 80 | mllog_utils.init_start(args) |
@@ -221,6 +232,7 @@ def test_end_to_end_mlperf_logging_with_train_configs(self): |
221 | 232 | seed=1, |
222 | 233 | learning_rate=1e-6, |
223 | 234 | eval_every_n_steps=5, |
| 235 | + model_id="", |
224 | 236 | ) |
225 | 237 |
|
226 | 238 | mock_train_dataset = [None] * 5480 |
@@ -602,7 +614,151 @@ def test_train_stop(self): |
602 | 614 | content = f.read() |
603 | 615 |
|
604 | 616 | 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") |
606 | 762 |
|
607 | 763 |
|
608 | 764 | if __name__ == "__main__": |
|
0 commit comments