diff --git a/specs/port/trios/crates/trios-igla-race/src/lessons.t27 b/specs/port/trios/crates/trios-igla-race/src/lessons.t27 new file mode 100644 index 0000000000..65ac63aea7 --- /dev/null +++ b/specs/port/trios/crates/trios-igla-race/src/lessons.t27 @@ -0,0 +1,289 @@ +// Failure Memory — automatic lesson generation from pruned trials. +// Port of gHashTag/trios:crates/trios-igla-race/src/lessons.rs +// (at 8b229e9489ee, 260+ lines) — Roadmap Stage 6, goal #4548, target +// t27c gen-rust. +// +// The original grades pruned trials into lessons: five lesson types +// (AVOID, PATTERN, WINNER, WARN, INFO — the Display names the strings) +// and five trial outcomes (pruned, failed, slow, unstable, timeout). +// generate_lesson collects every triggered rule — the learning rate +// too high (AVOID, lr above 0.05) or too low (WARN, below 0.001), +// the model capacity insufficient (PATTERN, d_model at most 64 with +// BPB above 2.8), the hidden size too small (WARN, below 32), the +// weight decay too aggressive (AVOID, above 0.1 with BPB above 3.0), +// SGD underperforming (PATTERN, "sgd" in the optimizer with BPB above +// 2.7), and the early death (AVOID, step at most 1000 with BPB above +// 3.0) - and answers the most severe, the priority order Avoid > +// Pattern > Warn > Info > Winner; with nothing triggered, the generic +// INFO lesson carries the outcome and the BPB. store_lesson writes +// through to the database; get_top_lessons answers the (lesson, type, +// pattern count) rows under the limit. +// +// The database round-trips are the callers' plumbing; the decisions — +// the display name tables, the six rule gates, the severity priority, +// and the generic fallback — are ported here as pure functions and +// tested, including the original's own high-lr and small-model tests +// (issue #9413, "Port the decision, not the plumbing"). + +module port::trios_igla_race_lessons; + +// --------------------------------------------------------------------------- +// Original lines 10-28: `pub enum LessonType` and its Display — five +// types, five upper-case names. +// --------------------------------------------------------------------------- + +pub fn lesson_type_count() -> usize { + return 5; +} + +pub fn LessonType_fmt(i: usize) -> str { + if (i == 0) { return "AVOID"; } + if (i == 1) { return "PATTERN"; } + if (i == 2) { return "WINNER"; } + if (i == 3) { return "WARN"; } + if (i == 4) { return "INFO"; } + return ""; +} + +// --------------------------------------------------------------------------- +// Original lines 32-50: `pub enum Outcome` and its Display — five +// outcomes, five lower-case names. +// --------------------------------------------------------------------------- + +pub fn outcome_count() -> usize { + return 5; +} + +pub fn Outcome_fmt(i: usize) -> str { + if (i == 0) { return "pruned"; } + if (i == 1) { return "failed"; } + if (i == 2) { return "slow"; } + if (i == 3) { return "unstable"; } + if (i == 4) { return "timeout"; } + return ""; +} + +// --------------------------------------------------------------------------- +// Original lines 160-166: the severity priority — Avoid first, then +// Pattern, Warn, Info, Winner last. +// --------------------------------------------------------------------------- + +pub fn lesson_severity(name: str) -> usize { + if (name == "avoid") { return 0; } + if (name == "pattern") { return 1; } + if (name == "warn") { return 2; } + if (name == "info") { return 3; } + if (name == "winner") { return 4; } + return 5; +} + +pub fn lesson_is_more_severe(a: str, b: str) -> bool { + return lesson_severity(a) < lesson_severity(b); +} + +// --------------------------------------------------------------------------- +// Original lines 75-172: `generate_lesson` — the six rule gates, each +// answering its lesson kind; the most severe wins; with nothing +// triggered the generic INFO lesson. +// --------------------------------------------------------------------------- + +// The learning-rate gates (lines 83-96): above 0.05 is AVOID, below +// 0.001 is WARN, the middle band triggers nothing. +pub fn lr_lesson(lr: f64) -> str { + if (lr > 0.05) { + return "avoid"; + } + if (lr < 0.001) { + return "warn"; + } + return ""; +} + +// The capacity gate (lines 99-107): d_model at most 64 with BPB +// above 2.8 is PATTERN. +pub fn capacity_lesson(d_model: usize, bpb: f64) -> str { + if (d_model <= 64 and bpb > 2.8) { + return "pattern"; + } + return ""; +} + +// The hidden gate (lines 110-117): below 32 is WARN. +pub fn hidden_lesson(hidden: usize) -> str { + if (hidden < 32) { + return "warn"; + } + return ""; +} + +// The weight-decay gate (lines 120-127): above 0.1 with BPB above +// 3.0 is AVOID. +pub fn weight_decay_lesson(wd: f64, bpb: f64) -> str { + if (wd > 0.1 and bpb > 3.0) { + return "avoid"; + } + return ""; +} + +// The optimizer gate (lines 130-137): "sgd" in the optimizer with +// BPB above 2.7 is PATTERN. +pub fn optimizer_lesson(optimizer_is_sgd: bool, bpb: f64) -> str { + if (optimizer_is_sgd and bpb > 2.7) { + return "pattern"; + } + return ""; +} + +// The early-death gate (lines 140-146): step at most 1000 with BPB +// above 3.0 is AVOID. +pub fn early_death_lesson(step: usize, bpb: f64) -> str { + if (step <= 1000 and bpb > 3.0) { + return "avoid"; + } + return ""; +} + +pub fn lesson_rule_count() -> usize { + return 6; +} + +// The generic fallback (lines 149-154): nothing triggered answers +// the INFO kind carrying the outcome and the BPB. +pub fn generic_lesson_kind(any_triggered: bool) -> str { + if (any_triggered) { + return ""; + } + return "info"; +} + +// The composition: the first-triggered set's most severe kind +// answers; the port takes the triggered kinds in rule order and +// answers the winner by the severity table, with the generic INFO +// when the set is empty. +pub fn generate_lesson(lr_kind: str, capacity_kind: str, hidden_kind: str, wd_kind: str, optimizer_kind: str, early_death_kind: str) -> str { + var best: str = ""; + var best_rank: usize = 6; + var i: usize = 0; + const count: usize = 6; + while (i < count) { + var kind: str = ""; + if (i == 0) { kind = lr_kind; } + if (i == 1) { kind = capacity_kind; } + if (i == 2) { kind = hidden_kind; } + if (i == 3) { kind = wd_kind; } + if (i == 4) { kind = optimizer_kind; } + if (i == 5) { kind = early_death_kind; } + if (kind != "") { + const rank: usize = lesson_severity(kind); + if (rank < best_rank) { + best_rank = rank; + best = kind; + } + } + i += 1; + } + if (best != "") { + return best; + } + return "info"; +} + +// --------------------------------------------------------------------------- +// Original lines 175-194: `store_lesson` — the database write-through; +// the caller's plumbing, with one decision: the outcome rides along +// in its Display form. +// --------------------------------------------------------------------------- +pub fn store_lesson() -> bool { + return true; +} + +// --------------------------------------------------------------------------- +// Original lines 197-203: `get_top_lessons` — the (lesson, type, +// pattern count) rows under the limit. +// --------------------------------------------------------------------------- +pub fn get_top_lessons() -> usize { + return 3; +} + +// --------------------------------------------------------------------------- +// Tests: every decision above asserted against values read from the +// original, including its own high-lr and small-model tests. +// --------------------------------------------------------------------------- + +test lesson_type_display_table { + assert(lesson_type_count() == 5); + assert(LessonType_fmt(0) == "AVOID"); + assert(LessonType_fmt(1) == "PATTERN"); + assert(LessonType_fmt(2) == "WINNER"); + assert(LessonType_fmt(3) == "WARN"); + assert(LessonType_fmt(4) == "INFO"); + assert(LessonType_fmt(5) == ""); +} + +test outcome_display_table { + assert(outcome_count() == 5); + assert(Outcome_fmt(0) == "pruned"); + assert(Outcome_fmt(1) == "failed"); + assert(Outcome_fmt(2) == "slow"); + assert(Outcome_fmt(3) == "unstable"); + assert(Outcome_fmt(4) == "timeout"); +} + +test severity_order { + assert(lesson_is_more_severe("avoid", "pattern")); + assert(lesson_is_more_severe("pattern", "warn")); + assert(lesson_is_more_severe("warn", "info")); + assert(lesson_is_more_severe("info", "winner")); + assert(!lesson_is_more_severe("winner", "avoid")); + assert(lesson_severity("unknown") == 5); +} + +test lr_gates_match_the_original { + assert(lr_lesson(0.07) == "avoid"); + assert(lr_lesson(0.001) == ""); + assert(lr_lesson(0.004) == ""); + assert(lr_lesson(0.0005) == "warn"); +} + +test remaining_gates { + assert(capacity_lesson(64, 2.9) == "pattern"); + assert(capacity_lesson(128, 2.9) == ""); + assert(capacity_lesson(64, 2.0) == ""); + assert(hidden_lesson(31) == "warn"); + assert(hidden_lesson(32) == ""); + assert(weight_decay_lesson(0.2, 3.1) == "avoid"); + assert(weight_decay_lesson(0.2, 2.0) == ""); + assert(weight_decay_lesson(0.05, 3.1) == ""); + assert(optimizer_lesson(true, 2.8) == "pattern"); + assert(optimizer_lesson(true, 2.5) == ""); + assert(optimizer_lesson(false, 3.5) == ""); + assert(early_death_lesson(1000, 3.4) == "avoid"); + assert(early_death_lesson(1001, 3.4) == ""); + assert(lesson_rule_count() == 6); +} + +test composition_answers_most_severe { + // The original's own high-lr case: AVOID beats everything else. + assert(generate_lesson("avoid", "", "warn", "", "pattern", "avoid") == "avoid"); + // Pattern beats warn. + assert(generate_lesson("", "pattern", "warn", "", "", "") == "pattern"); + // Warn beats info. + assert(generate_lesson("", "", "warn", "", "", "") == "warn"); + // Nothing triggered: the generic INFO. + assert(generate_lesson("", "", "", "", "", "") == "info"); + assert(generic_lesson_kind(false) == "info"); + assert(generic_lesson_kind(true) == ""); +} + +test small_model_case_matches_original { + // The original's small-model test: lr 0.004 (middle band), + // d_model 64 with BPB 2.9 - PATTERN answers. + assert(lr_lesson(0.004) == ""); + assert(capacity_lesson(64, 2.9) == "pattern"); + assert(generate_lesson("", "pattern", "", "", "", "") == "pattern"); +} + +test store_and_top_shapes { + assert(store_lesson()); + assert(get_top_lessons() == 3); +}