// Author: Simon-Pierre Boucher — contact@spboucher.ai // // Parsers for Forge's two output channels (RESEARCH.md §3): // - log.csv: header-driven, the structured metrics source of truth. // Handles both the 7-column (elapsed_s) and legacy 6-column layouts. // - stdout: events the CSV doesn't carry (checkpoint saved, fmodel commit, // run banner), parsed defensively — garbage lines are ignored, never fatal. import Foundation struct LogParser { // ---- log.csv -------------------------------------------------------- struct CSVSchema: Equatable { var columns: [String] static let current = CSVSchema(columns: [ "step", "loss", "lr", "grad_norm", "tokens_per_sec", "val_loss", "elapsed_s", ]) } private(set) var schema: CSVSchema? /// Feed one CSV line (header or data). Returns a point for data lines. mutating func parseCSVLine(_ line: String) -> MetricPoint? { let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return nil } if trimmed.hasPrefix("step,") { schema = CSVSchema(columns: trimmed.split(separator: ",").map(String.init)) return nil } let cols = schema?.columns ?? CSVSchema.current.columns let parts = trimmed.split(separator: ",", omittingEmptySubsequences: false) guard parts.count >= 5 else { return nil } func f(_ name: String) -> Double? { guard let i = cols.firstIndex(of: name), i < parts.count else { return nil } return Double(parts[i]) } guard let step = f("step").map({ Int($0) }), let loss = f("loss") else { return nil } let val = f("val_loss") return MetricPoint( step: step, trainLoss: loss, lr: f("lr") ?? 0, gradNorm: f("grad_norm") ?? 0, tokensPerSec: f("tokens_per_sec") ?? 0, valLoss: (val ?? -1) >= 0 ? val : nil, elapsedS: f("elapsed_s")) } // ---- stdout events --------------------------------------------------- enum StdoutEvent: Equatable { case banner(params: Int, steps: Int, tokensPerStep: Int, backend: String) case checkpointSaved(path: String) case forgeCommit(manifest: String) } static func parseStdout(_ line: String) -> StdoutEvent? { if line.hasPrefix("checkpoint saved: ") { return .checkpointSaved(path: String(line.dropFirst("checkpoint saved: ".count))) } if line.hasPrefix("fmodel: ") { let rest = line.dropFirst("fmodel: ".count) let manifest = rest.split(separator: " ").first.map(String.init) ?? "" return .forgeCommit(manifest: manifest) } if line.hasPrefix("training ") { // "training :

params, steps, tokens/step, backend=…" // The model name may contain digits — only parse after the colon. guard let colon = line.range(of: ": ") else { return nil } let tail = line[colon.upperBound...] let numbers = tail.split(whereSeparator: { !"0123456789".contains($0) }) .compactMap { Int($0) } let backend = line.range(of: "backend=").map { String(line[$0.upperBound...].prefix(while: { $0 != "," })) } if numbers.count >= 3 { return .banner(params: numbers[0], steps: numbers[1], tokensPerStep: numbers[2], backend: backend ?? "?") } } return nil } }