/**
* @copyright Sister Software
* @license AGPL-3.0
* @author Teffen Ellis, et al.
*
* Accuracy cost of the #244 coarse-placer int8 quantization (milestone 3). Runs the held-out test
* split through the fp32 model and the int8 model (dequantized inline) and reports overall + per-
* class accuracy for each, the delta, prediction-agreement rate, and confidence MAE. Gate: int8
* within ~1pp of fp32 (the milestone target).
*
* Run: `mailwoman placer eval quant-compare [--fp32
] [--int8 ] [--abstain 0.5]`
*/
import { readFileSync } from "node:fs"
import * as path from "node:path"
import { JSONSpliterator } from "spliterator"
import { parseJSONStrict } from "#objects"
import { dataRootPath, repoRootPath } from "#utils"
import { CoarsePlacer, type CoarsePlacerMeta } from "../coarse-placer.ts"
interface TestRow {
raw: string
country: string
}
/**
* Options for {@linkcode evalQuantCompare}.
*/
export interface EvalQuantCompareOptions {
/**
* Fp32 artifact dir. Default `$MAILWOMAN_DATA_ROOT/coarse-placer/model`.
*/
fp32?: string
/**
* Int8 artifact dir. Default `$MAILWOMAN_DATA_ROOT/coarse-placer/model-int8`.
*/
int8?: string
/**
* Abstention threshold. Default 0.5.
*/
abstain?: number
/**
* Dataset dir (`test.jsonl`). Default `/data/coarse-placer`.
*/
data?: string
}
/**
* Result of {@linkcode evalQuantCompare}.
*/
export interface EvalQuantCompareResult {
n: number
/**
* Fp32 overall accuracy in percent.
*/
accFp32: number
/**
* Int8 overall accuracy in percent.
*/
accInt8: number
/**
* Whether int8 is within 1pp of fp32 (the gate).
*/
pass: boolean
}
/**
* Coarse-placer int8-vs-fp32 comparison — see the module doc. Emits the report to stdout.
*/
export async function evalQuantCompare(options: EvalQuantCompareOptions = {}): Promise {
const fp32Dir = options.fp32 || dataRootPath("coarse-placer", "model")
const int8Dir = options.int8 || dataRootPath("coarse-placer", "model-int8")
const abstainBelow = options.abstain ?? 0.5
const dataDir = options.data || repoRootPath("data", "coarse-placer")
function loadFp32(dir: string): CoarsePlacer {
const meta = parseJSONStrict(readFileSync(path.join(dir, "meta.json"), "utf8"))
const buf = readFileSync(path.join(dir, "weights.bin"))
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
const weights = new Float32Array(ab)
return new CoarsePlacer({ ...meta, weights }, { abstainBelow })
}
function loadInt8(dir: string): CoarsePlacer {
const meta = parseJSONStrict(readFileSync(path.join(dir, "meta.json"), "utf8"))
const buf = readFileSync(path.join(dir, "weights.bin"))
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
const int8 = new Int8Array(ab)
const C = meta.classes.length
const dim = meta.featureDim
const scales = meta.scales!
const weights = new Float32Array(C * dim)
for (let c = 0; c < C; c++) {
const s = scales[c]!
const base = c * dim
for (let i = 0; i < dim; i++) {
weights[base + i] = int8[base + i]! * s
}
}
return new CoarsePlacer({ ...meta, weights }, { abstainBelow })
}
const fp32 = loadFp32(fp32Dir)
const int8 = loadInt8(int8Dir)
const test = await Array.fromAsync(JSONSpliterator.fromAsync(path.join(dataDir, "test.jsonl")))
const classes = parseJSONStrict(readFileSync(path.join(fp32Dir, "meta.json"), "utf8")).classes
let okF = 0
let okI = 0
let agree = 0
let confMae = 0
const perF: Record = {}
const perI: Record = {}
for (const r of test) {
const pf = fp32.predict(r.raw)
const pi = int8.predict(r.raw)
const cf = pf.country ?? "(abstain)"
const ci = pi.country ?? "(abstain)"
;(perF[r.country] ??= { n: 0, ok: 0 }).n++
;(perI[r.country] ??= { n: 0, ok: 0 }).n++
if (cf === r.country) {
okF++
perF[r.country]!.ok++
}
if (ci === r.country) {
okI++
perI[r.country]!.ok++
}
if (cf === ci) {
agree++
}
confMae += Math.abs(pf.confidence - pi.confidence)
}
const N = test.length
const accF = (100 * okF) / N
const accI = (100 * okI) / N
console.log(`coarse-placer int8 vs fp32 — test n=${N} (abstain ${abstainBelow})`)
console.log(
` overall accuracy: fp32 ${accF.toFixed(2)}% int8 ${accI.toFixed(2)}% Δ ${(accI - accF >= 0 ? "+" : "") + (accI - accF).toFixed(2)}pp`
)
console.log(` prediction agreement (same top class): ${((100 * agree) / N).toFixed(2)}%`)
console.log(` confidence MAE: ${(confMae / N).toFixed(4)}`)
console.log(` per-class recall (fp32 → int8):`)
for (const c of classes) {
const f = perF[c]
const i = perI[c]!
if (!f) continue
const rf = (100 * f.ok) / f.n
const ri = (100 * i.ok) / i.n
console.log(
` ${c.padEnd(6)} ${rf.toFixed(1)}% → ${ri.toFixed(1)}% (Δ ${(ri - rf >= 0 ? "+" : "") + (ri - rf).toFixed(1)}pp, n=${f.n})`
)
}
const pass = Math.abs(accI - accF) <= 1
console.log(` gate: ${pass ? "PASS (within 1pp)" : "FAIL (>1pp drop)"}`)
return { n: N, accFp32: accF, accInt8: accI, pass }
}