import hashlib import json import math from pathlib import Path import pytest import torch import mcss.engine as engine_module from mcss.cli import main from mcss.config import load_config from mcss.engine import ( _random_state, _restore_random_state, _ScalarTracker, _scheduled_learning_rate, ) def _write_config( path: Path, output_dir: Path, *, device: str = "cpu", amp: bool = False, amp_dtype: str = "float16", ) -> None: path.write_text( f""" seed: 3 device: {device} dataset: name: synthetic root: null image_size: [8, 8] context_views: 2 target_views: 1 length: 2 model: mode: fixed voxel_resolution: 8 image_feature_dim: 8 state_feature_dim: 8 refinement_blocks: 1 n_samples: 8 ray_chunk_size: 128 training: output_dir: {output_dir.as_posix()} batch_size: 1 learning_rate: 0.001 max_steps: 1 num_workers: 0 amp: {str(amp).lower()} amp_dtype: {amp_dtype} log_every: 1 checkpoint_every: 1 measurements: [rgb, depth, normal, point, visibility] lr_schedule: warmup_cosine warmup_steps: 1 min_lr_ratio: 0.1 ema_decay: 0.9 """.strip(), encoding="utf-8", ) def _write_context_subset_training_config( path: Path, output_dir: Path, *, device: str = "cpu", amp: bool = False, amp_dtype: str = "float16", ) -> None: path.write_text( f""" seed: 17 device: {device} dataset: name: synthetic root: null image_size: [8, 8] context_views: 4 target_views: 1 length: 2 model: mode: fixed state_architecture: evidence_residual voxel_resolution: [8, 8, 8] image_feature_dim: 8 state_feature_dim: 8 refinement_blocks: 1 evidence_temperature: 0.1 observed_residual_floor: 0.1 completion_residual_scale: 4.0 appearance_resolution_scale: 1 n_samples: 8 ray_chunk_size: 128 training: output_dir: {output_dir.as_posix()} batch_size: 1 learning_rate: 0.001 max_steps: 2 num_workers: 0 amp: {str(amp).lower()} amp_dtype: {amp_dtype} log_every: 1 checkpoint_every: 2 measurements: [rgb, depth, visibility] lr_schedule: warmup_cosine warmup_steps: 2 context_subset_geometry_weight: 0.1 """.strip(), encoding="utf-8", ) def test_cli_trains_and_evaluates_synthetic_smoke(tmp_path: Path) -> None: config_path = tmp_path / "smoke.yaml" output_dir = tmp_path / "outputs" _write_config(config_path, output_dir) assert main(["train", "--config", str(config_path)]) == 0 checkpoint = output_dir / "checkpoints" / "step_000001.pt" assert checkpoint.is_file() assert (output_dir / "train.jsonl").is_file() record = json.loads((output_dir / "train.jsonl").read_text(encoding="utf-8").splitlines()[-1]) expected_fields = { "step", "epoch", "epoch_progress", "batch_in_epoch", "batches_per_epoch", "lr", "grad/norm", "amp/scale", "optimizer/step", "optimizer/updated", "optimizer/skipped", "loss", "window/loss", "ema/loss", "time/step_seconds", "scene_ids", "valid/support", "valid/depth", } assert expected_fields <= set(record) assert record["optimizer/updated"] is True assert record["optimizer/skipped"] is False assert record["scene_ids"][0].startswith("synthetic_") payload = torch.load(checkpoint, map_location="cpu", weights_only=False) assert payload["optimizer_step"] == 1 assert "telemetry" in payload assert main(["evaluate", "--config", str(config_path), "--checkpoint", str(checkpoint)]) == 0 assert (output_dir / "evaluation.json").is_file() report_path = output_dir / "evaluation_report.json" assert report_path.is_file() report = json.loads(report_path.read_text(encoding="utf-8")) assert report["schema_version"] == "mcss.evaluation.v1" assert report["checkpoint"]["absolute_path"] == str(checkpoint.resolve()) assert report["checkpoint"]["sha256"] == hashlib.sha256(checkpoint.read_bytes()).hexdigest() assert report["checkpoint"]["step"] == 1 assert report["resolved_config"]["dataset"]["name"] == "synthetic" assert report["evaluation"]["sample_count"] == 2 assert report["evaluation"]["scene_count"] == 2 assert report["legacy_window_macro_metrics"] assert report["valid_count_weighted_metrics"] assert len(report["per_scene_metrics"]) == 2 assert report["finite_window_counts"] assert report["aggregation_definitions"] def test_disabled_context_subset_path_is_not_called_or_checkpointed( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: config_path = tmp_path / "disabled.yaml" output_dir = tmp_path / "disabled_outputs" _write_config(config_path, output_dir) def fail_if_called(*args: object, **kwargs: object) -> None: raise AssertionError("disabled context-subset path was executed") monkeypatch.setattr(engine_module, "drop_context_view", fail_if_called) assert main(["train", "--config", str(config_path)]) == 0 record = json.loads((output_dir / "train.jsonl").read_text(encoding="utf-8")) payload = torch.load( output_dir / "checkpoints" / "step_000001.pt", map_location="cpu", weights_only=False, ) assert "context_subset" not in record assert "loss/context_subset_geometry" not in record assert "context_subset_generator_state" not in payload def test_enabled_context_subset_path_logs_auditable_metadata(tmp_path: Path) -> None: config_path = tmp_path / "enabled.yaml" output_dir = tmp_path / "enabled_outputs" _write_context_subset_training_config(config_path, output_dir) assert main(["train", "--config", str(config_path)]) == 0 records = [ json.loads(line) for line in (output_dir / "train.jsonl").read_text(encoding="utf-8").splitlines() ] assert [record["context_subset/effective_weight"] for record in records] == [0.05, 0.1] for record in records: assert math.isfinite(record["loss/context_subset_geometry"]) assert math.isfinite(record["loss/context_subset_depth"]) assert math.isfinite(record["loss/context_subset_visibility"]) assert math.isfinite(record["context_subset/residual_abs_diff"]) assert math.isfinite(record["context_subset/full_residual_abs_p50"]) metadata = record["context_subset"] assert metadata["dropped_index"] in {0, 1, 2, 3} assert len(metadata["retained_indices"]) == 3 assert metadata["dropped_index"] not in metadata["retained_indices"] assert len(metadata["per_scene"]) == 1 assert metadata["per_scene"][0]["scene_id"].startswith("synthetic_") checkpoint = output_dir / "checkpoints" / "step_000002.pt" payload = torch.load(checkpoint, map_location="cpu", weights_only=False) assert "context_subset_generator_state" in payload assert payload["context_subset_generator_state"].dtype == torch.uint8 def test_context_subset_checkpoint_restores_next_dropped_view(tmp_path: Path) -> None: config_path = tmp_path / "resume_enabled.yaml" output_dir = tmp_path / "resume_enabled_outputs" _write_context_subset_training_config(config_path, output_dir) config = load_config(config_path) trainer = engine_module.Trainer(config) assert trainer.context_subset_generator is not None torch.randint(4, (1,), generator=trainer.context_subset_generator) trainer.step = 1 checkpoint = trainer.save_checkpoint() expected_next = int( torch.randint(4, (1,), generator=trainer.context_subset_generator).item() ) resumed = engine_module.Trainer(config) resumed.load_checkpoint(checkpoint, load_optimizer=True) assert resumed.context_subset_generator is not None actual_next = int( torch.randint(4, (1,), generator=resumed.context_subset_generator).item() ) assert actual_next == expected_next def test_cli_evaluate_overrides_are_explicit_and_immutable(tmp_path: Path) -> None: config_path = tmp_path / "smoke.yaml" training_output = tmp_path / "training" evaluation_output = tmp_path / "evaluation" _write_config(config_path, training_output) assert main(["train", "--config", str(config_path)]) == 0 checkpoint = training_output / "checkpoints" / "step_000001.pt" command = [ "evaluate", "--config", str(config_path), "--checkpoint", str(checkpoint), "--output-dir", str(evaluation_output), "--n-samples", "12", "--ray-chunk-size", "16", ] assert main(command) == 0 report_path = evaluation_output / "evaluation_report.json" report = json.loads(report_path.read_text(encoding="utf-8")) assert report["resolved_config"]["training"]["output_dir"] == str( evaluation_output.resolve() ) assert report["resolved_config"]["model"]["n_samples"] == 12 assert report["resolved_config"]["model"]["ray_chunk_size"] == 16 original = report_path.read_bytes() with pytest.raises(FileExistsError, match="refusing to overwrite"): main(command) assert report_path.read_bytes() == original def test_cli_download_dry_run_does_not_create_data_root(tmp_path: Path) -> None: root = tmp_path / "hypersim" assert ( main( [ "download", "hypersim", "--root", str(root), "--scenes", "ai_001_001", "--dry-run", ] ) == 0 ) assert not root.exists() def test_restore_random_state_accepts_cuda_mapped_cpu_rng_state() -> None: if not torch.cuda.is_available(): return state = _random_state() state["torch"] = state["torch"].to("cuda") state["cuda"] = [cuda_state.to("cuda") for cuda_state in state["cuda"]] _restore_random_state(state) def test_warmup_cosine_learning_rate_has_explicit_endpoints() -> None: base = 1e-3 assert _scheduled_learning_rate(base, 1, 6, 2, 0.1) == pytest.approx(0.5e-3) assert _scheduled_learning_rate(base, 2, 6, 2, 0.1) == pytest.approx(1e-3) assert _scheduled_learning_rate(base, 4, 6, 2, 0.1) == pytest.approx(0.55e-3) assert _scheduled_learning_rate(base, 6, 6, 2, 0.1) == pytest.approx(0.1e-3) def test_scalar_tracker_reports_window_means_and_resume_safe_ema() -> None: tracker = _ScalarTracker(ema_decay=0.5) tracker.update({"loss": 2.0, "loss/depth": 1.0}) tracker.update({"loss": 4.0, "loss/depth": 3.0}) first = tracker.snapshot(reset_window=True) state = tracker.state_dict() resumed = _ScalarTracker(ema_decay=0.5) resumed.load_state_dict(state) resumed.update({"loss": 6.0, "loss/depth": 5.0}) second = resumed.snapshot(reset_window=True) assert first["window/loss"] == 3.0 assert first["ema/loss"] == 3.0 assert first["window/loss/depth"] == 2.0 assert second["window/loss"] == 6.0 assert second["ema/loss"] == 4.5 def test_bfloat16_cuda_training_updates_without_scaler_skip(tmp_path: Path) -> None: if not torch.cuda.is_available(): return config_path = tmp_path / "cuda_bfloat16.yaml" output_dir = tmp_path / "cuda_outputs" _write_config( config_path, output_dir, device="cuda", amp=True, amp_dtype="bfloat16", ) assert main(["train", "--config", str(config_path)]) == 0 record = json.loads((output_dir / "train.jsonl").read_text(encoding="utf-8")) assert record["optimizer/updated"] is True assert record["optimizer/skipped"] is False assert math.isfinite(record["grad/norm"]) def test_context_subset_bfloat16_cuda_training_is_finite(tmp_path: Path) -> None: if not torch.cuda.is_available(): pytest.skip("CUDA is not available") if not torch.cuda.is_bf16_supported(): pytest.skip("CUDA device does not support bfloat16") config_path = tmp_path / "context_subset_cuda_bfloat16.yaml" output_dir = tmp_path / "context_subset_cuda_outputs" _write_context_subset_training_config( config_path, output_dir, device="cuda", amp=True, amp_dtype="bfloat16", ) assert main(["train", "--config", str(config_path)]) == 0 records = [ json.loads(line) for line in (output_dir / "train.jsonl").read_text(encoding="utf-8").splitlines() ] assert records[-1]["optimizer/updated"] is True assert records[-1]["optimizer/skipped"] is False for record in records: assert math.isfinite(record["loss/context_subset_geometry"]) assert math.isfinite(record["loss/context_subset_depth"]) assert math.isfinite(record["loss/context_subset_visibility"]) assert math.isfinite(record["grad/norm"])