tests/test_cli_smoke.py
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"])