Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions specs/port/trios/crates/trios-trainer/src/bin/trios-train.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// trios-train — the CLI entry point for IGLA training.
// Port of gHashTag/trios:crates/trios-trainer/src/bin/trios-train.rs
// (at 8b229e9e9489ee, 95 lines) — Roadmap Stage 6, goal #4548, target
// t27c gen-rust.
//
// The original is the training CLI: three flags --config (required,
// TOML), --seed and --steps (optional overrides), and --dry-run. The
// flow: the config loads from the given path; the CLI seed and steps
// OVERRIDE the config's training values when present; the LR must pass
// the INV-8 phi-band validation ([0.001, 0.01] by the error message)
// or the run bails; a dry run prints the validated shape (seed, steps,
// LR, d_model, n_layers, checkpoint interval) and exits BEFORE any
// training; otherwise training runs and the Gate-2 verdict answers by
// the best BPB: below 1.50 the VICTORY CANDIDATE, below 1.85 the
// above-target evidence, else the below-target evidence.
//
// The clap parsing, the TOML load and the training run are the
// callers' plumbing; the decisions — the three-flag shape with its
// override rule, the INV-8 LR gate with its band, the dry-run
// early-exit shape, and the three-band Gate-2 verdict — are ported
// here as pure functions and tested (issue #7770, "Port the decision,
// not the plumbing").

module port::trios_trainer_bin;

// ---------------------------------------------------------------------------
// Original lines 20-36: `struct Args` — three flags: --config required,
// --seed and --steps optional overrides, --dry-run a switch.
// ---------------------------------------------------------------------------

pub fn cli_flag_count() -> usize {
return 4;
}

pub fn cli_flag(i: usize) -> str {
if (i == 0) { return "--config"; }
if (i == 1) { return "--seed"; }
if (i == 2) { return "--steps"; }
if (i == 3) { return "--dry-run"; }
return "";
}

// The override rule (lines 48-56): a present CLI value replaces the
// config's value; an absent flag leaves the config untouched.
pub fn apply_override(flag_present: bool, flag_value: u64, config_value: u64) -> u64 {
if (flag_present) {
return flag_value;
}
return config_value;
}

// ---------------------------------------------------------------------------
// Original lines 59-61: the INV-8 LR gate — the LR must sit in the
// phi band [0.001, 0.01] (the error message names the bounds).
// ---------------------------------------------------------------------------

pub fn inv8_lr_band_low() -> f64 {
return 0.001;
}

pub fn inv8_lr_band_high() -> f64 {
return 0.01;
}

pub fn validate_lr_phi_band(lr: f64) -> bool {
return lr >= inv8_lr_band_low() and lr <= inv8_lr_band_high();
}

pub fn inv8_error_band() -> str {
return "must be in [0.001, 0.01]";
}

// ---------------------------------------------------------------------------
// Original lines 65-74: the dry-run shape — the validated config
// prints (seed, steps, LR, d_model, n_layers, checkpoint interval) and
// the run EXITS before any training.
// ---------------------------------------------------------------------------

pub fn dry_run_field_count() -> usize {
return 6;
}

pub fn dry_run_field(i: usize) -> str {
if (i == 0) { return "seed"; }
if (i == 1) { return "steps"; }
if (i == 2) { return "lr"; }
if (i == 3) { return "d_model"; }
if (i == 4) { return "n_layers"; }
if (i == 5) { return "checkpoint_interval"; }
return "";
}

pub fn dry_run_exits_before_training() -> bool {
return true;
}

// ---------------------------------------------------------------------------
// Original lines 87-93: the Gate-2 verdict — a three-band decision on
// the BEST BPB: below 1.50 the victory candidate, below 1.85 the
// above-target evidence, else the below-target evidence.
// ---------------------------------------------------------------------------

pub fn gate2_victory_threshold() -> f64 {
return 1.50;
}

pub fn gate2_above_target_threshold() -> f64 {
return 1.85;
}

pub fn gate2_verdict(best_bpb: f64) -> str {
if (best_bpb < gate2_victory_threshold()) {
return "victory candidate";
}
if (best_bpb < gate2_above_target_threshold()) {
return "above target evidence";
}
return "below target evidence";
}

// ---------------------------------------------------------------------------
// The flow of main (lines 38-95): overrides first, then the INV-8
// gate, then the dry-run branch, then training with the verdict. The
// port composes the same order over the caller's answers.
// ---------------------------------------------------------------------------

pub fn main(lr_valid: bool, dry_run: bool) -> str {
if (!lr_valid) {
return "inv8 violation";
}
if (dry_run) {
return "dry run";
}
return "train";
}

// ---------------------------------------------------------------------------
// Tests: every decision above asserted against values read from the
// original.
// ---------------------------------------------------------------------------

test cli_flag_table {
assert(cli_flag_count() == 4);
assert(cli_flag(0) == "--config");
assert(cli_flag(1) == "--seed");
assert(cli_flag(2) == "--steps");
assert(cli_flag(3) == "--dry-run");
}

test override_rule {
assert(apply_override(true, 43, 1) == 43);
assert(apply_override(true, 500, 1000) == 500);
assert(apply_override(false, 43, 7) == 7);
assert(apply_override(false, 500, 1000) == 1000);
}

test inv8_lr_band {
assert(validate_lr_phi_band(0.001));
assert(validate_lr_phi_band(0.003));
assert(validate_lr_phi_band(0.01));
assert(!validate_lr_phi_band(0.0009));
assert(!validate_lr_phi_band(0.011));
assert(!validate_lr_phi_band(0.05));
assert(inv8_lr_band_low() == 0.001);
assert(inv8_lr_band_high() == 0.01);
assert(inv8_error_band() == "must be in [0.001, 0.01]");
}

test dry_run_shape {
assert(dry_run_field_count() == 6);
assert(dry_run_field(0) == "seed");
assert(dry_run_field(2) == "lr");
assert(dry_run_field(5) == "checkpoint_interval");
assert(dry_run_exits_before_training());
}

test gate2_verdict_bands {
assert(gate2_victory_threshold() == 1.50);
assert(gate2_above_target_threshold() == 1.85);
assert(gate2_verdict(1.49) == "victory candidate");
assert(gate2_verdict(1.50) != "victory candidate");
assert(gate2_verdict(1.84) == "above target evidence");
assert(gate2_verdict(1.85) == "below target evidence");
assert(gate2_verdict(2.716) == "below target evidence");
}

test main_flow_order {
assert(main(false, false) == "inv8 violation");
assert(main(false, true) == "inv8 violation");
assert(main(true, true) == "dry run");
assert(main(true, false) == "train");
}
Loading