chore(semantics): add v6 release gate pipeline

- Add reproducible v6 boundary, blessing, and consensus-adjudication
  corpora, plus the tiny-transformer trainer and v6 release-gate
  evaluator that gate every candidate on the deployed baselines.
- Wire consensus-label merging, product-policy anchor evaluation, and
  sealed blessing benchmark review with their pytest coverage.
- Refresh open-training corpus generation, iterative retraining runner,
  and random-holdout evaluation so v6 candidates can be benchmarked
  end-to-end.
This commit is contained in:
Rocky
2026-08-29 11:51:42 +08:00
parent b275b6b0d9
commit aa37067f79
50 changed files with 12107 additions and 197 deletions
@@ -0,0 +1,569 @@
#!/usr/bin/env xcrun swift
import CoreML
import Darwin
import Foundation
import NaturalLanguage
private enum BenchmarkError: LocalizedError {
case invalidArguments(String)
case invalidManifest(String)
case invalidCorpus(String)
case missingFile(String)
var errorDescription: String? {
switch self {
case .invalidArguments(let message),
.invalidManifest(let message),
.invalidCorpus(let message),
.missingFile(let message):
return message
}
}
}
private struct Arguments {
let modelDirectory: URL
let corpus: URL
let report: URL
let maximumSamples: Int
let warmRounds: Int
static let usage = """
Usage: benchmark_v6_models.swift \
--model-directory <directory> \
--corpus <corpus.jsonl> \
--report <report.json> \
[--max-samples <positive integer>] \
[--warm-rounds <positive integer>]
"""
static func parse(_ rawArguments: [String]) throws -> Arguments {
var values: [String: String] = [:]
var index = 0
let supportedFlags = Set([
"--model-directory",
"--corpus",
"--report",
"--max-samples",
"--warm-rounds"
])
while index < rawArguments.count {
let flag = rawArguments[index]
guard supportedFlags.contains(flag) else {
throw BenchmarkError.invalidArguments("Unknown argument: \(flag)\n\(usage)")
}
guard index + 1 < rawArguments.count,
!rawArguments[index + 1].hasPrefix("--") else {
throw BenchmarkError.invalidArguments("Missing value for \(flag)\n\(usage)")
}
guard values[flag] == nil else {
throw BenchmarkError.invalidArguments("Duplicate argument: \(flag)\n\(usage)")
}
values[flag] = rawArguments[index + 1]
index += 2
}
let requiredFlags = ["--model-directory", "--corpus", "--report"]
for flag in requiredFlags where values[flag] == nil {
throw BenchmarkError.invalidArguments("Missing required argument: \(flag)\n\(usage)")
}
let maximumSamples = try positiveInteger(
values["--max-samples"] ?? "120",
flag: "--max-samples"
)
let warmRounds = try positiveInteger(
values["--warm-rounds"] ?? "5",
flag: "--warm-rounds"
)
let currentDirectory = URL(
fileURLWithPath: FileManager.default.currentDirectoryPath,
isDirectory: true
)
return Arguments(
modelDirectory: resolvedURL(values["--model-directory"]!, relativeTo: currentDirectory),
corpus: resolvedURL(values["--corpus"]!, relativeTo: currentDirectory),
report: resolvedURL(values["--report"]!, relativeTo: currentDirectory),
maximumSamples: maximumSamples,
warmRounds: warmRounds
)
}
private static func positiveInteger(_ value: String, flag: String) throws -> Int {
guard let result = Int(value), result > 0 else {
throw BenchmarkError.invalidArguments(
"\(flag) must be a positive integer, received: \(value)"
)
}
return result
}
private static func resolvedURL(_ path: String, relativeTo baseURL: URL) -> URL {
URL(fileURLWithPath: path, relativeTo: baseURL).standardizedFileURL
}
}
private struct Manifest: Decodable {
let schemaVersion: Int
let classifiers: [ManifestClassifier]
}
private struct ManifestClassifier: Decodable {
let id: String
let modelFile: String
let algorithm: String
let labels: [String]
let positiveLabel: String?
}
private struct CorpusRecord: Decodable {
let text: String
let split: String
}
private struct MemorySnapshot: Encodable {
let currentRSSBytes: UInt64?
let peakRSSBytes: UInt64?
}
private struct TimingDistribution: Encodable {
let rounds: Int
let samplesPerRound: Int
let measurementCount: Int
let averageMilliseconds: Double
let p50Milliseconds: Double
let p95Milliseconds: Double
let minimumMilliseconds: Double
let maximumMilliseconds: Double
}
private struct ModelBenchmark: Encodable {
let id: String
let modelFile: String
let algorithm: String
let labels: [String]
let positiveLabel: String?
let modelBytes: UInt64
let compiledModelBytes: UInt64
let compileMilliseconds: Double
let coldLoadMilliseconds: Double
let firstPredictionMilliseconds: Double
let warmPrediction: TimingDistribution
let predictedLabelCounts: [String: Int]
let memoryAfterCompile: MemorySnapshot
let memoryAfterLoad: MemorySnapshot
let memoryAfterPredictions: MemorySnapshot
}
private struct CorpusSummary: Encodable {
let path: String
let eligibleSplits: [String]
let maximumSamples: Int
let selectedSamples: Int
let selectedSamplesBySplit: [String: Int]
let selectionPolicy: String
}
private struct BenchmarkReport: Encodable {
let schemaVersion: Int
let generatedAt: String
let manifestSchemaVersion: Int
let modelDirectory: String
let corpus: CorpusSummary
let warmRounds: Int
let clock: String
let percentileMethod: String
let memoryAtStart: MemorySnapshot
let memoryAtEnd: MemorySnapshot
let models: [ModelBenchmark]
}
private let fileManager = FileManager.default
private let eligibleSplits = Set(["validation", "test", "golden"])
private func milliseconds(_ duration: Duration) -> Double {
Double(duration.components.seconds) * 1_000
+ Double(duration.components.attoseconds) / 1_000_000_000_000_000
}
private func rounded(_ value: Double, places: Int = 6) -> Double {
guard value.isFinite else { return 0 }
let scale = pow(10, Double(places))
return (value * scale).rounded() / scale
}
private func currentRSSBytes() -> UInt64? {
var info = mach_task_basic_info()
var count = mach_msg_type_number_t(
MemoryLayout<mach_task_basic_info>.size / MemoryLayout<natural_t>.size
)
let result = withUnsafeMutablePointer(to: &info) { pointer in
pointer.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { rebound in
task_info(
mach_task_self_,
task_flavor_t(MACH_TASK_BASIC_INFO),
rebound,
&count
)
}
}
guard result == KERN_SUCCESS else { return nil }
return UInt64(info.resident_size)
}
private func peakRSSBytes() -> UInt64? {
var usage = rusage()
guard getrusage(RUSAGE_SELF, &usage) == 0, usage.ru_maxrss >= 0 else {
return nil
}
// Darwin reports ru_maxrss in bytes; Linux reports KiB.
#if os(macOS)
return UInt64(usage.ru_maxrss)
#else
return UInt64(usage.ru_maxrss) * 1_024
#endif
}
private func memorySnapshot() -> MemorySnapshot {
MemorySnapshot(
currentRSSBytes: currentRSSBytes(),
peakRSSBytes: peakRSSBytes()
)
}
private func validateReadableFile(_ url: URL, description: String) throws {
var isDirectory: ObjCBool = false
guard fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory),
!isDirectory.boolValue,
fileManager.isReadableFile(atPath: url.path) else {
throw BenchmarkError.missingFile("\(description) is not a readable file: \(url.path)")
}
}
private func validateModelDirectory(_ url: URL) throws {
var isDirectory: ObjCBool = false
guard fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory),
isDirectory.boolValue else {
throw BenchmarkError.missingFile("Model directory does not exist: \(url.path)")
}
}
private func modelURL(for modelFile: String, in directory: URL) throws -> URL {
guard !modelFile.isEmpty else {
throw BenchmarkError.invalidManifest("Manifest contains an empty modelFile")
}
let baseURL = directory.resolvingSymlinksInPath().standardizedFileURL
let candidateURL = directory
.appendingPathComponent(modelFile)
.resolvingSymlinksInPath()
.standardizedFileURL
let basePrefix = baseURL.path.hasSuffix("/") ? baseURL.path : baseURL.path + "/"
guard candidateURL.path.hasPrefix(basePrefix) else {
throw BenchmarkError.invalidManifest(
"Model file resolves outside --model-directory: \(modelFile)"
)
}
try validateReadableFile(candidateURL, description: "Model")
return candidateURL
}
private func loadManifest(from modelDirectory: URL) throws -> Manifest {
let manifestURL = modelDirectory.appendingPathComponent(
"clipboard-semantic-models.json"
)
try validateReadableFile(manifestURL, description: "Manifest")
let manifest = try JSONDecoder().decode(
Manifest.self,
from: Data(contentsOf: manifestURL)
)
guard (1...4).contains(manifest.schemaVersion) else {
throw BenchmarkError.invalidManifest(
"Unsupported manifest schema \(manifest.schemaVersion); expected 1...4"
)
}
guard !manifest.classifiers.isEmpty else {
throw BenchmarkError.invalidManifest("Manifest has no classifiers")
}
let classifierIDs = manifest.classifiers.map(\.id)
guard Set(classifierIDs).count == classifierIDs.count else {
throw BenchmarkError.invalidManifest("Manifest contains duplicate classifier IDs")
}
for classifier in manifest.classifiers {
guard !classifier.id.isEmpty, !classifier.labels.isEmpty else {
throw BenchmarkError.invalidManifest(
"Manifest classifier IDs and labels must not be empty"
)
}
if classifier.id == "domain", classifier.positiveLabel != nil {
throw BenchmarkError.invalidManifest(
"The multiclass domain classifier must not define positiveLabel"
)
}
}
return manifest
}
private func loadCorpus(from url: URL, maximumSamples: Int) throws -> [CorpusRecord] {
try validateReadableFile(url, description: "Corpus")
let content = try String(contentsOf: url, encoding: .utf8)
let decoder = JSONDecoder()
var records: [CorpusRecord] = []
for (offset, line) in content.split(separator: "\n").enumerated() {
let record: CorpusRecord
do {
record = try decoder.decode(CorpusRecord.self, from: Data(line.utf8))
} catch {
throw BenchmarkError.invalidCorpus(
"Invalid JSONL record at line \(offset + 1): \(error.localizedDescription)"
)
}
guard eligibleSplits.contains(record.split) else { continue }
records.append(record)
if records.count == maximumSamples {
break
}
}
guard !records.isEmpty else {
throw BenchmarkError.invalidCorpus(
"Corpus has no records in validation, test, or golden splits"
)
}
return records
}
private func recursiveSize(of url: URL) throws -> UInt64 {
let resourceValues = try url.resourceValues(
forKeys: [.isDirectoryKey, .isRegularFileKey, .fileSizeKey]
)
if resourceValues.isRegularFile == true {
return UInt64(resourceValues.fileSize ?? 0)
}
guard resourceValues.isDirectory == true else { return 0 }
guard let enumerator = fileManager.enumerator(
at: url,
includingPropertiesForKeys: [.isRegularFileKey, .fileSizeKey],
options: [.skipsHiddenFiles]
) else {
return 0
}
var total: UInt64 = 0
for case let childURL as URL in enumerator {
let childValues = try childURL.resourceValues(
forKeys: [.isRegularFileKey, .fileSizeKey]
)
if childValues.isRegularFile == true {
total += UInt64(childValues.fileSize ?? 0)
}
}
return total
}
private func compileModel(sourceURL: URL, outputDirectory: URL) throws -> URL {
let generatedURL = try MLModel.compileModel(at: sourceURL)
let destinationURL = outputDirectory
.appendingPathComponent(sourceURL.deletingPathExtension().lastPathComponent)
.appendingPathExtension("mlmodelc")
if fileManager.fileExists(atPath: destinationURL.path) {
try fileManager.removeItem(at: destinationURL)
}
try fileManager.moveItem(at: generatedURL, to: destinationURL)
return destinationURL
}
private func percentile(_ sortedValues: [Double], fraction: Double) -> Double {
guard !sortedValues.isEmpty else { return 0 }
let rank = max(1, Int(ceil(fraction * Double(sortedValues.count))))
return sortedValues[min(rank - 1, sortedValues.count - 1)]
}
private func timingDistribution(
values: [Double],
rounds: Int,
samplesPerRound: Int
) -> TimingDistribution {
let sortedValues = values.sorted()
let average = values.reduce(0, +) / Double(values.count)
return TimingDistribution(
rounds: rounds,
samplesPerRound: samplesPerRound,
measurementCount: values.count,
averageMilliseconds: rounded(average),
p50Milliseconds: rounded(percentile(sortedValues, fraction: 0.50)),
p95Milliseconds: rounded(percentile(sortedValues, fraction: 0.95)),
minimumMilliseconds: rounded(sortedValues.first ?? 0),
maximumMilliseconds: rounded(sortedValues.last ?? 0)
)
}
private func benchmark(
classifier: ManifestClassifier,
modelDirectory: URL,
temporaryDirectory: URL,
records: [CorpusRecord],
warmRounds: Int
) throws -> ModelBenchmark {
let sourceURL = try modelURL(for: classifier.modelFile, in: modelDirectory)
let sourceBytes = try recursiveSize(of: sourceURL)
let compileStartedAt = ContinuousClock.now
let compiledURL = try compileModel(
sourceURL: sourceURL,
outputDirectory: temporaryDirectory
)
let compileMilliseconds = milliseconds(compileStartedAt.duration(to: .now))
let memoryAfterCompile = memorySnapshot()
let compiledBytes = try recursiveSize(of: compiledURL)
let loadStartedAt = ContinuousClock.now
let model = try NLModel(contentsOf: compiledURL)
let loadMilliseconds = milliseconds(loadStartedAt.duration(to: .now))
let memoryAfterLoad = memorySnapshot()
let firstPredictionStartedAt = ContinuousClock.now
_ = model.predictedLabel(for: records[0].text)
let firstPredictionMilliseconds = milliseconds(
firstPredictionStartedAt.duration(to: .now)
)
var warmMeasurements: [Double] = []
warmMeasurements.reserveCapacity(records.count * warmRounds)
var predictedLabelCounts: [String: Int] = [:]
for _ in 0..<warmRounds {
for record in records {
let predictionStartedAt = ContinuousClock.now
let predictedLabel = model.predictedLabel(for: record.text) ?? "__noPrediction__"
warmMeasurements.append(
milliseconds(predictionStartedAt.duration(to: .now))
)
predictedLabelCounts[predictedLabel, default: 0] += 1
}
}
return ModelBenchmark(
id: classifier.id,
modelFile: classifier.modelFile,
algorithm: classifier.algorithm,
labels: classifier.labels,
positiveLabel: classifier.positiveLabel,
modelBytes: sourceBytes,
compiledModelBytes: compiledBytes,
compileMilliseconds: rounded(compileMilliseconds),
coldLoadMilliseconds: rounded(loadMilliseconds),
firstPredictionMilliseconds: rounded(firstPredictionMilliseconds),
warmPrediction: timingDistribution(
values: warmMeasurements,
rounds: warmRounds,
samplesPerRound: records.count
),
predictedLabelCounts: predictedLabelCounts,
memoryAfterCompile: memoryAfterCompile,
memoryAfterLoad: memoryAfterLoad,
memoryAfterPredictions: memorySnapshot()
)
}
private func writeReport(_ report: BenchmarkReport, to url: URL) throws {
try fileManager.createDirectory(
at: url.deletingLastPathComponent(),
withIntermediateDirectories: true
)
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
try encoder.encode(report).write(to: url, options: .atomic)
}
private func formattedMiB(_ bytes: UInt64?) -> String {
guard let bytes else { return "unavailable" }
return String(format: "%.1f MiB", Double(bytes) / 1_048_576)
}
private func run() throws {
let arguments = try Arguments.parse(Array(CommandLine.arguments.dropFirst()))
try validateModelDirectory(arguments.modelDirectory)
let manifest = try loadManifest(from: arguments.modelDirectory)
let records = try loadCorpus(
from: arguments.corpus,
maximumSamples: arguments.maximumSamples
)
let splitCounts = Dictionary(grouping: records, by: \.split).mapValues(\.count)
let memoryAtStart = memorySnapshot()
let temporaryDirectory = fileManager.temporaryDirectory.appendingPathComponent(
"osg-v6-model-benchmark-\(UUID().uuidString)",
isDirectory: true
)
try fileManager.createDirectory(
at: temporaryDirectory,
withIntermediateDirectories: true
)
defer { try? fileManager.removeItem(at: temporaryDirectory) }
var modelBenchmarks: [ModelBenchmark] = []
modelBenchmarks.reserveCapacity(manifest.classifiers.count)
for classifier in manifest.classifiers {
modelBenchmarks.append(
try benchmark(
classifier: classifier,
modelDirectory: arguments.modelDirectory,
temporaryDirectory: temporaryDirectory,
records: records,
warmRounds: arguments.warmRounds
)
)
}
let report = BenchmarkReport(
schemaVersion: 1,
generatedAt: ISO8601DateFormatter().string(from: Date()),
manifestSchemaVersion: manifest.schemaVersion,
modelDirectory: arguments.modelDirectory.path,
corpus: CorpusSummary(
path: arguments.corpus.path,
eligibleSplits: eligibleSplits.sorted(),
maximumSamples: arguments.maximumSamples,
selectedSamples: records.count,
selectedSamplesBySplit: splitCounts,
selectionPolicy: "first eligible records in corpus order"
),
warmRounds: arguments.warmRounds,
clock: "ContinuousClock",
percentileMethod: "nearest-rank",
memoryAtStart: memoryAtStart,
memoryAtEnd: memorySnapshot(),
models: modelBenchmarks
)
try writeReport(report, to: arguments.report)
let totalCompile = modelBenchmarks.reduce(0) { $0 + $1.compileMilliseconds }
let totalLoad = modelBenchmarks.reduce(0) { $0 + $1.coldLoadMilliseconds }
let warmAverage = modelBenchmarks.reduce(0) {
$0 + $1.warmPrediction.averageMilliseconds
} / Double(modelBenchmarks.count)
print(
String(
format: "V6 benchmark: %d models, %d samples × %d rounds; compile %.3f ms, cold load %.3f ms, warm avg %.3f ms, peak RSS %@; report %@",
modelBenchmarks.count,
records.count,
arguments.warmRounds,
totalCompile,
totalLoad,
warmAverage,
formattedMiB(report.memoryAtEnd.peakRSSBytes),
arguments.report.path
)
)
}
do {
try run()
} catch {
let message = "benchmark_v6_models: \(error.localizedDescription)\n"
FileHandle.standardError.write(Data(message.utf8))
exit(EXIT_FAILURE)
}