From 5670e5162ef24c407faf38a7dac6a9c6c2b941d5 Mon Sep 17 00:00:00 2001 From: echobt <154886644+echobt@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:33:18 +0000 Subject: [PATCH 1/2] feat(od): activate od-base preset, remove od-tiny; light CI excludes od-marked tests --- .github/workflows/ci.yml | 4 +-- docs/schemas/RunManifest.json | 1 - pyproject.toml | 1 + .../models/opendecision/__init__.py | 5 ++-- src/hypertrain/protocol/messages.py | 3 +-- src/hypertrain/protocol/messages_v2.py | 5 +--- tests/auditor/test_island_bisect_v2.py | 4 +-- tests/auditor/test_island_replay_v2.py | 6 ++++- tests/auditor/test_od_bisect.py | 2 ++ tests/challenge/test_authority_snapshot_v2.py | 2 ++ tests/challenge/test_carry_genesis_v2.py | 2 ++ tests/e2e/test_service_network_v2_e2e.py | 1 + tests/layout/test_od_island_v2.py | 2 ++ tests/miner/test_island_launch_v2.py | 1 + tests/models/test_opendecision.py | 2 ++ tests/protocol/test_network_v2.py | 2 ++ tests/protocol/test_od_fields.py | 26 +++++++++---------- tests/relay/test_transport_v2.py | 3 +++ tests/trainer/test_arch_dispatch.py | 2 +- 19 files changed, 44 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4240f8c..5c065cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: done printf 'shard %s/%s -> %s files\n' "$SHARD" "$SHARDS" "${#SEL[@]}" if [ "${#SEL[@]}" -eq 0 ]; then echo "no files in shard"; exit 0; fi - uv run --frozen pytest -q --durations=25 \ + uv run --frozen pytest -q --durations=25 -m "not od" \ --deselect=tests/challenge/test_capacity_outer_v2.py::test_actual_bounded_outer_child_matches_original_signed_replay \ --deselect=tests/challenge/test_service_capacity_v2.py::test_real_capacity_launch_failure_keeps_debit_and_prevents_restart_rerun \ "--deselect=tests/miner/test_capacity_runtime_v2.py::test_actual_small_kernel_attempt_and_cleanup[success]" \ @@ -113,4 +113,4 @@ jobs: enable-cache: true cache-dependency-glob: "uv.lock" - run: uv sync --locked --all-extras --group dev - - run: uv run --frozen pytest -q --durations=25 tests/${{ matrix.suite }} + - run: uv run --frozen pytest -q --durations=25 -m "not od" tests/${{ matrix.suite }} diff --git a/docs/schemas/RunManifest.json b/docs/schemas/RunManifest.json index 9d14279..529f447 100644 --- a/docs/schemas/RunManifest.json +++ b/docs/schemas/RunManifest.json @@ -680,7 +680,6 @@ }, "preset": { "enum": [ - "od-tiny", "od-base", "od-large" ], diff --git a/pyproject.toml b/pyproject.toml index 00f62cd..db62419 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ testpaths = ["tests"] # the `pytest` console script does not put the rootdir on sys.path, `python -m pytest` does. pythonpath = ["."] addopts = "-ra --strict-markers" +markers = ["od: builds a real od-base OpenDecision model (excluded from PR CI)"] [tool.ruff] target-version = "py312" diff --git a/src/hypertrain/models/opendecision/__init__.py b/src/hypertrain/models/opendecision/__init__.py index 10ca07b..f2cb0ae 100644 --- a/src/hypertrain/models/opendecision/__init__.py +++ b/src/hypertrain/models/opendecision/__init__.py @@ -88,9 +88,8 @@ def check_manifest(m: RunManifest | RunManifestV2) -> None: if ms.param_count != want: raise ValueError(f"param_count {ms.param_count} != {want} trainable for {od.objective}") n = PRESETS[od.preset].max_len - # A6 pins N = max_len (1024) for real presets; od-tiny (tests only) may use any N <= max_len. - tiny_ok = od.preset == "od-tiny" and ms.seq_len <= n - if od.objective == "mlm" and ms.seq_len != n and not tiny_ok: + # A6 pins N = max_len (1024). + if od.objective == "mlm" and ms.seq_len != n: raise ValueError(f"mlm seq_len must be {n} for {od.preset} (A6)") diff --git a/src/hypertrain/protocol/messages.py b/src/hypertrain/protocol/messages.py index 404ba59..239d7c5 100644 --- a/src/hypertrain/protocol/messages.py +++ b/src/hypertrain/protocol/messages.py @@ -50,7 +50,6 @@ def _omit_none(v: object) -> bool: OD_PRESETS: dict[str, tuple[int, int, int, int, int]] = { # preset: (d_model, n_layers, n_heads, head_layers, vocab) - "od-tiny": (64, 2, 4, 1, 259), "od-base": (768, 12, 12, 2, 50432), "od-large": (1024, 24, 16, 4, 50432), } @@ -70,7 +69,7 @@ def record_len(self) -> int: class ODSpec(_M): - preset: Literal["od-tiny", "od-base", "od-large"] + preset: Literal["od-base", "od-large"] head_layers: Pos objective: Literal["mlm", "distill", "decision"] mask_ratio: F32 diff --git a/src/hypertrain/protocol/messages_v2.py b/src/hypertrain/protocol/messages_v2.py index eb23577..2511b90 100644 --- a/src/hypertrain/protocol/messages_v2.py +++ b/src/hypertrain/protocol/messages_v2.py @@ -151,10 +151,7 @@ def _assign_unit_and_od(self) -> TrainingManifestV2: raise ValueError("OD record required iff objective is not MLM") if od.record is not None and ms.seq_len != od.record.record_len() - 1: raise ValueError("OD record length must equal seq_len+1") - if od.objective == "mlm" and ( - (od.preset == "od-tiny" and ms.seq_len > 64) - or (od.preset != "od-tiny" and ms.seq_len != 1024) - ): + if od.objective == "mlm" and ms.seq_len != 1024: raise ValueError("OD MLM sequence length differs from pinned preset") if ds.sample_format != "u16[seq_len+1] token ids": raise ValueError("OD requires u16 records") diff --git a/tests/auditor/test_island_bisect_v2.py b/tests/auditor/test_island_bisect_v2.py index b0575f7..d1aba1f 100644 --- a/tests/auditor/test_island_bisect_v2.py +++ b/tests/auditor/test_island_bisect_v2.py @@ -61,9 +61,7 @@ def get(i: int): (2, 1, None), (2, 2, None), (4, 2, None), - (2, 1, "mlm"), - (2, 1, "decision"), - (2, 1, "distill"), + *(pytest.param(2, 1, o, marks=pytest.mark.od) for o in ("mlm", "decision", "distill")), ], ) def test_honest_traces_when_real_island_hooks(n: int, ep: int, objective: str | None) -> None: diff --git a/tests/auditor/test_island_replay_v2.py b/tests/auditor/test_island_replay_v2.py index a2b255e..70815d9 100644 --- a/tests/auditor/test_island_replay_v2.py +++ b/tests/auditor/test_island_replay_v2.py @@ -146,7 +146,10 @@ def fixture(objective: str | None = None, state_policy: str = "carry"): return job, cache, blobs, get, result, key -@pytest.mark.parametrize("objective", [None, "mlm", "decision", "distill"]) +@pytest.mark.parametrize( + "objective", + [None, *(pytest.param(o, marks=pytest.mark.od) for o in ("mlm", "decision", "distill"))], +) def test_full_carry_and_window_equality(objective: str | None) -> None: job, cache, blobs, get, expected, _ = fixture(objective) outputs = emulate(2, lambda c: audit_island(job, c, get, *blobs, cache, now_round=2)) @@ -373,6 +376,7 @@ def test_missing_predecessor_reconstructs_once_under_two_h_budget() -> None: ) +@pytest.mark.od def test_verified_anchor_restart_corruption_and_od_warm_start(tmp_path: Path) -> None: from hypertrain.auditor.replay import tensor_root from hypertrain.models.opendecision import extend_params diff --git a/tests/auditor/test_od_bisect.py b/tests/auditor/test_od_bisect.py index 2347b51..130713d 100644 --- a/tests/auditor/test_od_bisect.py +++ b/tests/auditor/test_od_bisect.py @@ -15,6 +15,8 @@ from hypertrain.trainer.loop import train_round from hypertrain.trainer.model import init_params +pytestmark = pytest.mark.od + N = 2 HON = Keypair(b"\x07" * 32).ss58 CHEAT = Keypair(b"\x08" * 32).ss58 diff --git a/tests/challenge/test_authority_snapshot_v2.py b/tests/challenge/test_authority_snapshot_v2.py index 65a8c5b..79ae943 100644 --- a/tests/challenge/test_authority_snapshot_v2.py +++ b/tests/challenge/test_authority_snapshot_v2.py @@ -18,6 +18,8 @@ from hypertrain.protocol.hashing import sha256_hex from hypertrain.protocol.jcs import canonicalize +pytestmark = pytest.mark.od + ROOT = Path(__file__).resolve().parents[2] spec = importlib.util.spec_from_file_location( "portable_authority_adapter", ROOT / "scripts/network_authority_snapshot.py" diff --git a/tests/challenge/test_carry_genesis_v2.py b/tests/challenge/test_carry_genesis_v2.py index f388cc6..5af8c15 100644 --- a/tests/challenge/test_carry_genesis_v2.py +++ b/tests/challenge/test_carry_genesis_v2.py @@ -18,6 +18,8 @@ from hypertrain.trainer.config import TrainConfig from hypertrain.trainer.model import init_params +pytestmark = pytest.mark.od + spec = importlib.util.spec_from_file_location( "carry_genesis_fixture", Path(__file__).parents[1] / "e2e/test_service_network_v2_e2e.py" ) diff --git a/tests/e2e/test_service_network_v2_e2e.py b/tests/e2e/test_service_network_v2_e2e.py index dde9da3..8b4980a 100644 --- a/tests/e2e/test_service_network_v2_e2e.py +++ b/tests/e2e/test_service_network_v2_e2e.py @@ -39,6 +39,7 @@ def carry_setup(manifest, policy, admission_policy, rows, tree): yield from fixtures.network.__wrapped__(tmp_path, request) +@pytest.mark.od @pytest.mark.parametrize("od_carry_network", ["mlm", "decision", "distill"], indirect=True) def test_od_carry_admission_reference_requires_real_full_state(od_carry_network): """First production prerequisite: genuine corresponding-manifest graduation work.""" diff --git a/tests/layout/test_od_island_v2.py b/tests/layout/test_od_island_v2.py index daa5189..1797cb5 100644 --- a/tests/layout/test_od_island_v2.py +++ b/tests/layout/test_od_island_v2.py @@ -20,6 +20,8 @@ from hypertrain.trainer.loop import Assignment from hypertrain.trainer.model import init_params +pytestmark = pytest.mark.od + def od_manifest(objective: str, n: int, zero1: bool, dtype: str = "fp32") -> RunManifestV2: spec = importlib.util.spec_from_file_location( diff --git a/tests/miner/test_island_launch_v2.py b/tests/miner/test_island_launch_v2.py index 94a84cd..b39486a 100644 --- a/tests/miner/test_island_launch_v2.py +++ b/tests/miner/test_island_launch_v2.py @@ -1026,6 +1026,7 @@ def test_d2_signed_reservation_rejects_before_dispatch(tmp_path, fault): assert len(lifecycle.j.all("network_plan_intake")) == (1 if fault == "replay" else 0) +@pytest.mark.od @pytest.mark.parametrize("objective", ["mlm", "decision", "distill"]) def test_real_od_two_rank_worker_paths(tmp_path: Path, objective: str) -> None: import importlib.util diff --git a/tests/models/test_opendecision.py b/tests/models/test_opendecision.py index 622688f..06627e2 100644 --- a/tests/models/test_opendecision.py +++ b/tests/models/test_opendecision.py @@ -36,6 +36,8 @@ from hypertrain.trainer.loop import replay, train_round from hypertrain.trainer.model import forward as dispatched_forward +pytestmark = pytest.mark.od + HEAD_PREFIXES = ("head.", "scorer.", "type_emb.", "unknown", "log_temp.") diff --git a/tests/protocol/test_network_v2.py b/tests/protocol/test_network_v2.py index d71b0ae..40eaeb4 100644 --- a/tests/protocol/test_network_v2.py +++ b/tests/protocol/test_network_v2.py @@ -124,6 +124,7 @@ def commit(tokens: int = 7680) -> CommitV2: ) +@pytest.mark.od @pytest.mark.parametrize("n", [1, 2, 4, 8]) @pytest.mark.parametrize("objective", ["mlm", "decision", "distill"]) @pytest.mark.parametrize("zero1", [False, True]) @@ -147,6 +148,7 @@ def test_od_general_n_when_v2(n: int, objective: str, zero1: bool) -> None: RunManifest.model_validate(body) +@pytest.mark.od def test_assign_unit_when_only_island_batch_is_divisible() -> None: # Given body = od_body() diff --git a/tests/protocol/test_od_fields.py b/tests/protocol/test_od_fields.py index e21fa36..d19cd17 100644 --- a/tests/protocol/test_od_fields.py +++ b/tests/protocol/test_od_fields.py @@ -27,20 +27,20 @@ def od_body() -> dict[str, Any]: b = copy.deepcopy(example_manifest().body()) b["model"] |= { "arch": "od-encoder", - "n_layers": 2, - "d_model": 64, - "n_heads": 4, - "n_kv_heads": 4, - "d_ff": 256, - "vocab": 259, - "seq_len": 64, - "param_count": 187_140, + "n_layers": 12, + "d_model": 768, + "n_heads": 12, + "n_kv_heads": 12, + "d_ff": 3072, + "vocab": 50432, + "seq_len": 1024, + "param_count": 123_753_984, "compute_dtype": "bf16", "capacity_factor": f32hex(1.0), "aux_loss_coef": f32hex(0.0), "od": { - "preset": "od-tiny", - "head_layers": 1, + "preset": "od-base", + "head_layers": 2, "objective": "mlm", "mask_ratio": f32hex(0.3), "mask_seed": 7, @@ -121,8 +121,8 @@ def test_unit_root_without_unit_rejected() -> None: lambda b: b["reference_spec"].update(profile="od-bf16-det-v1"), lambda b: b["reference_spec"].pop("profile"), lambda b: b["reference_spec"]["layout"].update(zero1=True), - lambda b: b["model"].update(vocab=260), - lambda b: b["model"]["od"].update(head_layers=2), + lambda b: b["model"].update(vocab=50433), + lambda b: b["model"]["od"].update(head_layers=1), lambda b: b["model"]["od"].update(objective="decision"), lambda b: b["model"]["od"].update(tokenizer_offset=4), ], @@ -143,7 +143,7 @@ def test_decision_record_len_pins_seq_len() -> None: b = od_body() b["model"]["od"] |= {"objective": "decision", "record": rec} b["model"]["seq_len"] = 1835 - # d_model etc. still od-tiny; only seq_len/record coupling is under test + # d_model etc. still od-base; only seq_len/record coupling is under test RunManifest.model_validate(b) b["model"]["seq_len"] = 1834 with pytest.raises(ValidationError): diff --git a/tests/relay/test_transport_v2.py b/tests/relay/test_transport_v2.py index 2ad3c3d..79f363a 100644 --- a/tests/relay/test_transport_v2.py +++ b/tests/relay/test_transport_v2.py @@ -1039,6 +1039,7 @@ def command(*args, **kwargs): assert outcome["provider_after"] is None and outcome["proof_status"] == "CENSORED" +@pytest.mark.od def test_provider_fix_pinned_kind_version_and_fail_closed( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: @@ -1140,6 +1141,7 @@ def test_provider_fix_pinned_kind_version_and_fail_closed( module.require_capped_node(journal, "missing-marker") +@pytest.mark.od def test_shim_raw_bytes_exit_and_pinned_kind_empty_preflight(tmp_path: Path) -> None: import json import os @@ -1239,6 +1241,7 @@ def test_shim_raw_bytes_exit_and_pinned_kind_empty_preflight(tmp_path: Path) -> assert not any(c[0] in {"run", "create", "start"} for c in calls) +@pytest.mark.od @pytest.mark.parametrize("kernel_mode", ["valid", "wrong", "missing", "foreign", "forged"]) def test_pinned_kind_contract_through_capped_start(tmp_path: Path, kernel_mode: str) -> None: import json diff --git a/tests/trainer/test_arch_dispatch.py b/tests/trainer/test_arch_dispatch.py index 3a150e8..fc406fe 100644 --- a/tests/trainer/test_arch_dispatch.py +++ b/tests/trainer/test_arch_dispatch.py @@ -66,7 +66,7 @@ def od_cfg() -> TrainConfig: def test_config_fills_od_fields(od_cfg: TrainConfig) -> None: m = od_cfg.model assert (m.arch, m.profile) == ("od-encoder", "od-bf16-det-eager-v1") - assert m.od is not None and m.od.preset == "od-tiny" + assert m.od is not None and m.od.preset == "od-base" hash(m) # frozen ODSpec keeps ModelConfig hashable for the adapter cache From a1158c250d6d26b606ebb809bfa39e55153e070d Mon Sep 17 00:00:00 2001 From: echobt <154886644+echobt@users.noreply.github.com> Date: Tue, 13 Oct 2026 17:21:35 +0000 Subject: [PATCH 2/2] test(od): mark e2e OD-network tests od (excluded from light CI) --- tests/e2e/test_network_service_proof.py | 5 +++++ tests/e2e/test_service_network_v2_e2e.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/tests/e2e/test_network_service_proof.py b/tests/e2e/test_network_service_proof.py index 9447a99..14c09b9 100644 --- a/tests/e2e/test_network_service_proof.py +++ b/tests/e2e/test_network_service_proof.py @@ -499,6 +499,7 @@ def respond(request): ) +@pytest.mark.od def test_decoder_probe_consumer_real_routes_receipt_and_debit_metadata(tmp_path, monkeypatch): """One real reference/proof transition; metadata oracle, not independent gradients.""" from types import SimpleNamespace @@ -1328,6 +1329,7 @@ def test_endpoint_requires_explicit_https_without_ambient_credentials(tmp_path, driver.Settings.model_validate(body) +@pytest.mark.od def test_digest_authority_rejects_before_service_write(tmp_path, signed_package): directory, _, _, _, _, _ = signed_package cfg = settings(tmp_path).model_copy(update={"snapshot": directory}) @@ -1336,6 +1338,7 @@ def test_digest_authority_rejects_before_service_write(tmp_path, signed_package) assert not cfg.evidence.exists() +@pytest.mark.od def test_wrong_role_rejects_before_live_write(tmp_path, monkeypatch, signed_package): directory, digest, roles, template, _, _ = signed_package cfg = settings(tmp_path).model_copy(update={"snapshot": directory, "snapshot_digest": digest}) @@ -1409,6 +1412,7 @@ def forbidden(*args, **kwargs): assert not (tmp_path / "none.log").exists() +@pytest.mark.od def test_missing_service_adapter_rejects_before_evidence(tmp_path, monkeypatch, signed_package): directory, digest, _, _, _, _ = signed_package monkeypatch.setattr(driver, "durable_destination", lambda path: True) @@ -1583,6 +1587,7 @@ def fail_copy(*args, **kwargs): assert result["errors"] and not index["export_complete"] +@pytest.mark.od @pytest.mark.parametrize("fault", ["offset", "beacon", "valid"]) def test_prepare_clock_probe_rejects_before_any_output( tmp_path, monkeypatch, fault, signed_package diff --git a/tests/e2e/test_service_network_v2_e2e.py b/tests/e2e/test_service_network_v2_e2e.py index 8b4980a..32f0421 100644 --- a/tests/e2e/test_service_network_v2_e2e.py +++ b/tests/e2e/test_service_network_v2_e2e.py @@ -146,6 +146,7 @@ def test_actual_shadow_trial_does_not_mutate_production(network): assert network.store._services(network.manifest.run_id())[0].balances().issued == 40000 +@pytest.mark.od @pytest.mark.parametrize("network", ["mlm", "decision", "distill"], indirect=True) def test_od_two_rank_signed_service_reference_and_proof(network): # All OD objectives use the real signed admission/reference/proof/finality routes, @@ -1379,6 +1380,7 @@ def _graduate_four(network): assert network.store._services(network.manifest.run_id())[0].balances().issued == 40000 +@pytest.mark.od @pytest.mark.parametrize("od_carry_network", ["mlm", "decision", "distill"], indirect=True) def test_od_four_active_actual_client_two_round_carry_checkpoint(od_carry_network, monkeypatch): """Production service path with same-manifest admission and actual HTTP/TLS miners.""" @@ -1866,6 +1868,7 @@ async def now(): assert verify_network_checkpoint(checkpoint, fixtures.COORD.ss58) == [] +@pytest.mark.od @pytest.mark.parametrize("od_carry_network", ["mlm"], indirect=True) def test_public_watch_layer_over_actual_fastapi_tls(od_carry_network, monkeypatch): """Funded original audits; real public CLI/TLS routes and committed long-poll wake."""