Skip to content

eval hardening: pinned revision enforcement, pair-level dedup, multi-shard sampling - #3

Open
ClarenceDan wants to merge 3 commits into
unarbos:mainfrom
ClarenceDan:optimize/progressive-eval-v1
Open

eval hardening: pinned revision enforcement, pair-level dedup, multi-shard sampling#3
ClarenceDan wants to merge 3 commits into
unarbos:mainfrom
ClarenceDan:optimize/progressive-eval-v1

Conversation

@ClarenceDan

Copy link
Copy Markdown

This pull request introduces several important improvements to evaluation robustness, security, and flexibility for the model evaluation server and validator. Key changes include strict commit hash verification to prevent model-swap attacks, support for evaluating across multiple data shards, and more reliable state tracking for evaluation attempts and failures.

Security and Robustness Improvements:

  • Strict commit hash verification: Added verify_commit_hash (and related logic) to ensure that only pinned, 40-character commit SHAs are accepted for model revisions, preventing model-swap attacks by verifying the actual commit on HuggingFace matches the expected SHA. This is enforced in both the eval server and validator. (eval_torch.py, eval_server.py, validator.py) [1] [2] [3] [4] [5] [6] [7]

  • Input validation: The eval server now rejects evaluation requests that do not provide a valid commit SHA for both king and challenger revisions, or that lack at least one shard key. (eval_server.py) [1] [2] [3]

Evaluation Flexibility and Data Handling:

  • Multi-shard evaluation: The evaluation pipeline now supports evaluating over multiple data shards. run_bootstrap_test and the API accept a list of shard keys, distribute the evaluation set proportionally across available shards, and shuffle sequences across shards to ensure fair sampling. (eval_torch.py, eval_server.py) [1] [2] [3] [4]

Validator State and Retry Handling:

  • Per-eval-pair tracking: The validator now tracks failed evaluation pairs and evaluation counts for each unique combination of king and challenger repo/revision. This enables more granular retry and deduplication logic, and persists this state across restarts. (validator.py) [1] [2] [3]

Other Improvements:

  • Refactoring and normalization: Several functions and classes have been refactored for clarity, including normalization of revision handling, improved logging, and more robust error handling for state persistence. (eval_server.py, validator.py) [1] [2] [3]

These changes significantly improve the security, reliability, and flexibility of the evaluation infrastructure.

…port

1. eval_torch.py: add verify_commit_hash() using HfApi SHA check to prevent
   model-swap attacks between validator pin and eval server download.
   run_bootstrap_test() now accepts list of shard_keys with proportional
   sequence allocation and cross-shard shuffle.

2. eval_server.py: call verify_commit_hash() in _ensure_king and
   _load_challenger before model loading. EvalRequest accepts shard_keys
   list with backward-compat single shard_key fallback.

3. validator.py: replace evaluated_repos set with eval_counts dict keyed by
   (repo, commit) tuple. TEUTONIC_EVAL_MAX_RETRIES (default 1) controls max
   evals per commit. TEUTONIC_EVAL_SHARD_COUNT (default 1) selects K shards
   via blake2b with personalization salts for each shard index.
- require 40-char commit SHAs for model verification
- persist eval dedup and validation failures by king/challenger commit pair
- refuse insecure startup when king revision is unpinned
- dedupe shard keys and reject unpinned revisions at eval server ingress
- harden multi-shard sampling edge cases for zero/leftover allocation
- migrate legacy king state without king_revision to pinned HF SHAs
- retry revision recovery during liveness checks instead of hard-failing startup
- preserve strict pinned-revision enforcement for new evals once migrated
roykollensvendsen added a commit to roykollensvendsen/teutonic that referenced this pull request May 3, 2026
Spec source: signature (State(r2)) + __init__ field defaults + the R2
contract (r2.get(key) returns parsed JSON or None, r2.put(key, data)
persists). State persists to keys including king/current.json,
state/queue.json, state/seen_hotkeys.json, state/validator_state.json.

Outcome: confirms-behavior plus one resolved spec-ambiguity. 7 tests
cover empty-storage load, per-field round-trip for king/counter/
last_weight_block/seen, missing-keys tolerance, and the intentional
non-persistence of failed_repos / evaluated_repos.

Spec-ambiguity resolved: I assumed failed_repos and evaluated_repos
would persist alongside `seen` in seen_hotkeys.json. They don't —
that key is named for hotkeys (its content), and these two sets hold
repo names used only for in-memory dedup during a single validator
run. The test now documents this as intentional design.

Test design intentionally avoids full-dict equality — assertions are
per-field round-trip identity. This keeps tests resilient to upstream
PR #3 (unarbos#3) which adds new State fields.

Mocking: dict-backed r2_mock fixture stands in for validator.R2.
get returns the stored value (or None), put writes it.
roykollensvendsen added a commit to roykollensvendsen/teutonic that referenced this pull request May 3, 2026
Spec source: signature (State(r2)) + __init__ field defaults + the R2
contract (r2.get(key) returns parsed JSON or None, r2.put(key, data)
persists). State persists to keys including king/current.json,
state/queue.json, state/seen_hotkeys.json, state/validator_state.json.

Outcome: confirms-behavior plus one resolved spec-ambiguity. 7 tests
cover empty-storage load, per-field round-trip for king/counter/
last_weight_block/seen, missing-keys tolerance, and the intentional
non-persistence of failed_repos / evaluated_repos.

Spec-ambiguity resolved: I assumed failed_repos and evaluated_repos
would persist alongside `seen` in seen_hotkeys.json. They don't —
that key is named for hotkeys (its content), and these two sets hold
repo names used only for in-memory dedup during a single validator
run. The test now documents this as intentional design.

Test design intentionally avoids full-dict equality — assertions are
per-field round-trip identity. This keeps tests resilient to upstream
PR #3 (unarbos#3) which adds new State fields.

Mocking: dict-backed r2_mock fixture stands in for validator.R2.
get returns the stored value (or None), put writes it.
roykollensvendsen added a commit to roykollensvendsen/teutonic that referenced this pull request May 5, 2026
Spec source: signature (State(r2)) + __init__ field defaults + the R2
contract (r2.get(key) returns parsed JSON or None, r2.put(key, data)
persists). State persists to keys including king/current.json,
state/queue.json, state/seen_hotkeys.json, state/validator_state.json.

Outcome: confirms-behavior plus one resolved spec-ambiguity. 7 tests
cover empty-storage load, per-field round-trip for king/counter/
last_weight_block/seen, missing-keys tolerance, and the intentional
non-persistence of failed_repos / evaluated_repos.

Spec-ambiguity resolved: I assumed failed_repos and evaluated_repos
would persist alongside `seen` in seen_hotkeys.json. They don't —
that key is named for hotkeys (its content), and these two sets hold
repo names used only for in-memory dedup during a single validator
run. The test now documents this as intentional design.

Test design intentionally avoids full-dict equality — assertions are
per-field round-trip identity. This keeps tests resilient to upstream
PR #3 (unarbos#3) which adds new State fields.

Mocking: dict-backed r2_mock fixture stands in for validator.R2.
get returns the stored value (or None), put writes it.
roykollensvendsen added a commit to roykollensvendsen/teutonic that referenced this pull request May 6, 2026
Spec source: signature (State(r2)) + __init__ field defaults + the R2
contract (r2.get(key) returns parsed JSON or None, r2.put(key, data)
persists). State persists to keys including king/current.json,
state/queue.json, state/seen_hotkeys.json, state/validator_state.json.

Outcome: confirms-behavior plus one resolved spec-ambiguity. 7 tests
cover empty-storage load, per-field round-trip for king/counter/
last_weight_block/seen, missing-keys tolerance, and the intentional
non-persistence of failed_repos / evaluated_repos.

Spec-ambiguity resolved: I assumed failed_repos and evaluated_repos
would persist alongside `seen` in seen_hotkeys.json. They don't —
that key is named for hotkeys (its content), and these two sets hold
repo names used only for in-memory dedup during a single validator
run. The test now documents this as intentional design.

Test design intentionally avoids full-dict equality — assertions are
per-field round-trip identity. This keeps tests resilient to upstream
PR #3 (unarbos#3) which adds new State fields.

Mocking: dict-backed r2_mock fixture stands in for validator.R2.
get returns the stored value (or None), put writes it.
roykollensvendsen added a commit to roykollensvendsen/teutonic that referenced this pull request May 8, 2026
Spec source: signature (State(r2)) + __init__ field defaults + the R2
contract (r2.get(key) returns parsed JSON or None, r2.put(key, data)
persists). State persists to keys including king/current.json,
state/queue.json, state/seen_hotkeys.json, state/validator_state.json.

Outcome: confirms-behavior plus one resolved spec-ambiguity. 7 tests
cover empty-storage load, per-field round-trip for king/counter/
last_weight_block/seen, missing-keys tolerance, and the intentional
non-persistence of failed_repos / evaluated_repos.

Spec-ambiguity resolved: I assumed failed_repos and evaluated_repos
would persist alongside `seen` in seen_hotkeys.json. They don't —
that key is named for hotkeys (its content), and these two sets hold
repo names used only for in-memory dedup during a single validator
run. The test now documents this as intentional design.

Test design intentionally avoids full-dict equality — assertions are
per-field round-trip identity. This keeps tests resilient to upstream
PR #3 (unarbos#3) which adds new State fields.

Mocking: dict-backed r2_mock fixture stands in for validator.R2.
get returns the stored value (or None), put writes it.
roykollensvendsen added a commit to roykollensvendsen/teutonic that referenced this pull request May 8, 2026
Spec source: signature (State(r2)) + __init__ field defaults + the R2
contract (r2.get(key) returns parsed JSON or None, r2.put(key, data)
persists). State persists to keys including king/current.json,
state/queue.json, state/seen_hotkeys.json, state/validator_state.json.

Outcome: confirms-behavior plus one resolved spec-ambiguity. 7 tests
cover empty-storage load, per-field round-trip for king/counter/
last_weight_block/seen, missing-keys tolerance, and the intentional
non-persistence of failed_repos / evaluated_repos.

Spec-ambiguity resolved: I assumed failed_repos and evaluated_repos
would persist alongside `seen` in seen_hotkeys.json. They don't —
that key is named for hotkeys (its content), and these two sets hold
repo names used only for in-memory dedup during a single validator
run. The test now documents this as intentional design.

Test design intentionally avoids full-dict equality — assertions are
per-field round-trip identity. This keeps tests resilient to upstream
PR #3 (unarbos#3) which adds new State fields.

Mocking: dict-backed r2_mock fixture stands in for validator.R2.
get returns the stored value (or None), put writes it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant