From 256001ca806f9d7e302bea967850db214f3d8e4e Mon Sep 17 00:00:00 2001 From: Tianxin Wei Date: Fri, 2 Oct 2026 00:35:22 +0000 Subject: [PATCH 01/10] Add training-free choice readout evaluation matrix --- jevany/letter_predictor.py | 158 ++++++-- jevany/letter_readout.py | 35 +- scripts/evaluate_choice_readout_matrix.py | 359 +++++++++++++++++++ scripts/fit_choice_ensemble.py | 298 +++++++++++++++ tests/test_evaluate_choice_readout_matrix.py | 129 +++++++ tests/test_fit_choice_ensemble.py | 65 ++++ tests/test_letter_predictor.py | 65 ++++ tests/test_letter_readout.py | 25 +- 8 files changed, 1073 insertions(+), 61 deletions(-) create mode 100644 scripts/evaluate_choice_readout_matrix.py create mode 100644 scripts/fit_choice_ensemble.py create mode 100644 tests/test_evaluate_choice_readout_matrix.py create mode 100644 tests/test_fit_choice_ensemble.py diff --git a/jevany/letter_predictor.py b/jevany/letter_predictor.py index f5c9e4b..e0856f9 100644 --- a/jevany/letter_predictor.py +++ b/jevany/letter_predictor.py @@ -23,7 +23,7 @@ from .data import api_request, materialize from .device import sync from .letter_readout import ( - LETTERS, + CHOICE_SYMBOLS, SYSTEM_PROMPT, build_prompt, letter_token_ids, @@ -61,17 +61,19 @@ def _safe_chat_text(tokenizer, text: str) -> str: return escaped -def _release_unused_output_head(decision_model) -> bool: - """Drop references to the full-vocabulary head unused by letter readout.""" +def _release_unused_output_head(decision_model, *, retain_native: bool = False) -> bool: + """Drop the full-vocabulary head unless a native LM-token pass needs it.""" adapter = getattr(decision_model, "adapter", None) adapter_head = getattr(adapter, "_output_embeddings", None) native_head = getattr(decision_model, "lm_head", None) + if retain_native: + return False if adapter_head is None and native_head is None: return False - # The exact letter rows are copied immediately after this call. Native - # lm-token inference is never invoked by this predictor, so both aliases - # can be detached even for direct-token checkpoints. + # Exact choice rows are copied from the tied embedding table or loaded + # independently from safetensors. With no LM-token native pass, both + # aliases can therefore be detached to release the full vocabulary head. if native_head is not None: decision_model.lm_head = None if adapter_head is not None: @@ -223,9 +225,10 @@ class LetterReadoutPredictor: """Benchmark predictor for an exact constrained first-token readout. Give either ``base`` for a frozen training-free model, or ``checkpoint`` - to apply a JevAny LoRA before the same readout. ``pointer_weight > 0`` - additionally pools the checkpoint's native pointer probabilities in log - space; this costs one extra pointer-formatted prefill per request. + to apply a JevAny LoRA before the same readout. ``native_weight > 0`` + additionally pools the checkpoint's native probabilities in log space; + this costs one extra native-formatted prefill per request. The legacy + ``pointer_weight`` spelling remains an alias for ``native_weight``. """ def __init__( @@ -237,7 +240,10 @@ def __init__( options: LoadOptions | None = None, revision: str | None = None, temperature: float = 1.0, - pointer_weight: float = 0.0, + pointer_weight: float | None = None, + native_weight: float | None = None, + return_components: bool = False, + exact_kernels: bool = True, max_tokens: int = 16_384, ) -> None: if (base is None) == (checkpoint is None): @@ -247,11 +253,32 @@ def __init__( self.temperature = float(temperature) if not math.isfinite(self.temperature) or self.temperature <= 0: raise ValueError("temperature must be finite and positive") + if pointer_weight is not None and native_weight is not None: + raise ValueError("give at most one of native_weight or pointer_weight") + weight_source = ( + "native_weight" if native_weight is not None + else "pointer_weight" if pointer_weight is not None + else "default" + ) + selected_weight = ( + native_weight if native_weight is not None + else pointer_weight if pointer_weight is not None + else 0.0 + ) # Reuse the same validation as the actual combination path. - geometric_blend([1.0], [1.0], pointer_weight) - self.pointer_weight = float(pointer_weight) - if checkpoint is None and self.pointer_weight: - raise ValueError("pointer_weight requires a JevAny checkpoint") + geometric_blend([1.0], [1.0], selected_weight) + self.native_weight = float(selected_weight) + # Public compatibility alias for existing runtime/CLI integrations. + self.pointer_weight = self.native_weight + if checkpoint is None and self.native_weight: + raise ValueError("native_weight requires a JevAny checkpoint") + if type(return_components) is not bool: + raise TypeError("return_components must be a boolean") + if type(exact_kernels) is not bool: + raise TypeError("exact_kernels must be a boolean") + self.return_components = return_components + self.exact_kernels = exact_kernels + self.exact_cuda_kernels_applied = device == "cuda" and exact_kernels if type(max_tokens) is not int or max_tokens < 2: raise ValueError("max_tokens must be an integer >= 2") self.max_tokens = max_tokens @@ -261,7 +288,22 @@ def __init__( raise ValueError("native-head temperature is not used by letter readout; use temperature") if self.options.cuda_graphs: raise ValueError("CUDA graph capture is available only for native readout") + if self.exact_cuda_kernels_applied: + # Match LocalPredictor: benchmark evaluation disables approximate + # TF32 and fused SDPA kernels, while exact_kernels=False retains + # the serving-style PyTorch defaults. + torch.backends.cuda.matmul.allow_tf32 = False + torch.backends.cudnn.allow_tf32 = False + torch.backends.cuda.enable_flash_sdp(False) + torch.backends.cuda.enable_mem_efficient_sdp(False) self.checkpoint: Checkpoint | None = None + self.native_model = None + self.native_decision_mode = None + self._needs_native = checkpoint is not None and bool( + self.native_weight or self.return_components + ) + # Compatibility alias retained for callers predating generalized + # native blending. self.pointer_model = None if checkpoint is not None: @@ -270,10 +312,10 @@ def __init__( loaded = self.checkpoint = Checkpoint(checkpoint) if loaded.meta.special_embeddings: raise ValueError("letter readout is not validated for checkpoints with trained special embeddings") - if self.pointer_weight and loaded.meta.decision_mode != "pointer": - raise ValueError("pointer_weight requires a native pointer checkpoint") self.preprocessor, decision_model = loaded.load(device, self.options) + self.native_model = decision_model self.pointer_model = decision_model + self.native_decision_mode = loaded.meta.decision_mode self.language_model = decision_model.lm canonical_base = loaded.meta.base canonical_revision = loaded.meta.base_revision @@ -305,7 +347,10 @@ def __init__( projection_source, projection_revision = source, source_revision checkpoint_id, adapter_scale, adapter_applied = None, 0.0, False - released_output_head = _release_unused_output_head(decision_model) + released_output_head = _release_unused_output_head( + decision_model, + retain_native=(self._needs_native and self.native_decision_mode == "lm_token"), + ) self.language_model.eval() override_config = ( Path(self.options.base_load_path) / "config.json" @@ -339,7 +384,7 @@ def __init__( hashlib.sha256(chat_template.encode()).hexdigest() if isinstance(chat_template, str) else None ) - self.alias_token_ids = letter_token_ids(self.tokenizer, LETTERS) + self.alias_token_ids = letter_token_ids(self.tokenizer, CHOICE_SYMBOLS) flat_token_ids, alias_rows = [], {} for letter, token_ids in self.alias_token_ids.items(): start = len(flat_token_ids) @@ -368,12 +413,12 @@ def __init__( if not math.isfinite(self.output_softcap) or self.output_softcap <= 0: raise ValueError("final_logit_softcapping must be finite and positive") self.provenance = { - "method": "exact option-letter choice projection", - "constraint_emulation": "exact decoded uppercase letters", - "prompt": "Cygnet-compatible", + "method": "training-free exact choice-token projection", + "constraint_emulation": "exact decoded one-character option IDs", + "prompt": "Cygnet-compatible A-Z; extended with a-z above 26 options", "system_prompt_sha256": hashlib.sha256(SYSTEM_PROMPT.encode()).hexdigest(), "chat_template_sha256": self.chat_template_sha256, - "prompt_format_version": 1, + "prompt_format_version": 2, "canonical_base": canonical_base, "canonical_revision": canonical_revision, "checkpoint": checkpoint_id, @@ -384,10 +429,19 @@ def __init__( "letter_choice_token_rows": len(flat_token_ids), "output_softcap": self.output_softcap, "temperature": self.temperature, + "native_weight": self.native_weight, + "native_weight_argument": weight_source, + "native_decision_mode": self.native_decision_mode, "pointer_weight": self.pointer_weight, + "native_temperature": ( + float(self.native_model.temperature) if self._needs_native else None + ), "pointer_temperature": ( - float(self.pointer_model.temperature) if self.pointer_weight else None + float(self.native_model.temperature) if self._needs_native else None ), + "return_components": self.return_components, + "exact_kernels": self.exact_kernels, + "exact_cuda_kernels_applied": self.exact_cuda_kernels_applied, "max_tokens": self.max_tokens, "backbone_context_window": self.backbone_context_window, "effective_max_tokens": self.effective_max_tokens, @@ -417,8 +471,10 @@ def _chat_ids(self, prompt: str) -> torch.Tensor: def _letter_question(self, state, question: dict) -> tuple[list[str], list[float], list[float], int]: keys, descriptions = question_options(question) - if len(keys) > len(LETTERS): - raise ValueError(f"exact letter readout supports at most {len(LETTERS)} options") + if len(keys) > len(CHOICE_SYMBOLS): + raise ValueError( + f"exact choice-token readout supports at most {len(CHOICE_SYMBOLS)} options" + ) prompt = build_prompt(state, question.get("instructions") or "", descriptions) ids = self._chat_ids(prompt) output = self.language_model( @@ -434,15 +490,17 @@ def _letter_question(self, state, question: dict) -> tuple[list[str], list[float if self.output_softcap is not None: selected_logits = self.output_softcap * torch.tanh(selected_logits / self.output_softcap) selected_logits = selected_logits.cpu() - aliases = {letter: self.alias_rows[letter] for letter in LETTERS[:len(keys)]} + aliases = { + symbol: self.alias_rows[symbol] for symbol in CHOICE_SYMBOLS[:len(keys)] + } readout = read_letter_distribution(selected_logits, aliases, self.temperature) probabilities = [readout.calibrated_probabilities[letter] for letter in aliases] calibrated_logits = [readout.raw_log_masses[letter] / self.temperature for letter in aliases] return keys, probabilities, calibrated_logits, int(ids.shape[1]) - def _pointer(self, record: dict) -> tuple[list[list[float]], int]: + def _native(self, record: dict) -> tuple[list[list[float]], int]: internal = materialize(record) - encoded = self.pointer_model.encode( + encoded = self.native_model.encode( self.preprocessor, internal, max_state=self.effective_max_tokens, @@ -451,11 +509,16 @@ def _pointer(self, record: dict) -> tuple[list[list[float]], int]: ) if len(encoded["ids"]) > self.effective_max_tokens: raise ContextLengthError( - f"pointer request needs {len(encoded['ids'])} packed tokens; " + f"native request needs {len(encoded['ids'])} packed tokens; " f"limit {self.effective_max_tokens}" ) return [F.softmax(logits, -1).float().cpu().tolist() - for logits in self.pointer_model.forward(encoded)], len(encoded["ids"]) + for logits in self.native_model.forward(encoded)], len(encoded["ids"]) + + def _pointer(self, record: dict) -> tuple[list[list[float]], int]: + """Compatibility alias for the generalized native checkpoint pass.""" + + return self._native(record) @torch.inference_mode() def __call__(self, record: dict) -> dict: @@ -469,37 +532,54 @@ def __call__(self, record: dict) -> dict: self._letter_question(request["state"], question) for question in request["questions"].values() ] - pointer_rows, pointer_tokens = (None, 0) - if self.pointer_weight: - pointer_rows, pointer_tokens = self._pointer(record) - if len(pointer_rows) != len(letter_rows): - raise ValueError("pointer and letter question counts differ") + native_rows, native_tokens = (None, 0) + if self._needs_native: + native_rows, native_tokens = self._native(record) + if len(native_rows) != len(letter_rows): + raise ValueError("native and choice question counts differ") probabilities, logits = {}, {} + choice_probabilities, native_probabilities = {}, {} for index, (question_id, (keys, letter_p, letter_z, _tokens)) in enumerate( zip(request["questions"], letter_rows, strict=True) ): - if pointer_rows is None: + choice_probabilities[question_id] = dict(zip(keys, letter_p, strict=True)) + if native_rows is not None: + if len(native_rows[index]) != len(keys): + raise ValueError( + f"native and choice option counts differ for question {question_id!r}" + ) + native_probabilities[question_id] = dict( + zip(keys, native_rows[index], strict=True) + ) + if native_rows is None or not self.native_weight: selected_p, selected_z = letter_p, letter_z else: selected_p, selected_z = geometric_blend( - letter_p, pointer_rows[index], self.pointer_weight + letter_p, native_rows[index], self.native_weight ) probabilities[question_id] = dict(zip(keys, selected_p, strict=True)) logits[question_id] = dict(zip(keys, selected_z, strict=True)) sync(self.device) - return { + result = { "probabilities": probabilities, "logits": logits, "inference_temperature": self.temperature, "latency_ms": (time.perf_counter() - started) * 1000, - "input_tokens": sum(row[3] for row in letter_rows) + pointer_tokens, + "input_tokens": sum(row[3] for row in letter_rows) + native_tokens, "readout": { "method": self.provenance["method"], "adapter_applied": self.provenance["adapter_applied"], + "native_weight": self.native_weight, + "native_decision_mode": self.native_decision_mode, "pointer_weight": self.pointer_weight, }, } + if self.return_components: + result["component_probabilities"] = {"choice": choice_probabilities} + if native_rows is not None: + result["component_probabilities"]["native"] = native_probabilities + return result __all__ = ["LetterReadoutPredictor", "geometric_blend", "question_options"] diff --git a/jevany/letter_readout.py b/jevany/letter_readout.py index 0b857f7..f38ac57 100644 --- a/jevany/letter_readout.py +++ b/jevany/letter_readout.py @@ -1,4 +1,4 @@ -"""One-token option-letter readout primitives for frozen causal LMs. +"""One-token option-ID readout primitives for frozen causal LMs. The Cygnet-compatible prompt and letter aggregation semantics are adapted from the MIT-licensed ``blockbrain-ai/cygnet-recipe`` shim at commit ``3cf591c`` @@ -18,11 +18,18 @@ from typing import Any -LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +CYGNET_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +# Preserve Cygnet's exact A-Z assignment for its supported range, then extend +# with distinct lowercase one-character IDs for larger decision sets. This +# covers JevJudge's 28-30-option text requests without dropping records. +CHOICE_SYMBOLS = CYGNET_LETTERS + "abcdefghijklmnopqrstuvwxyz" +# Backward-compatible implementation name. Public documentation calls these +# option IDs and the method training-free choice readout. +LETTERS = CHOICE_SYMBOLS SYSTEM_PROMPT = ( "You are a calibration engine. You never answer in prose. You are given a state, a question and " "a numbered set of options, and you choose exactly one option. You reply with that option's " - "LETTER and nothing else — a single character, no words, no punctuation, no explanation." + "case-sensitive ID and nothing else — a single character, no words, no punctuation, no explanation." ) _NEGATIVE_INFINITY = float("-inf") @@ -63,7 +70,7 @@ def _ordered_descriptions(options: Mapping[Any, Any] | Sequence[Any]) -> list[An def build_prompt(state: Any, instructions: Any, options: Mapping[Any, Any] | Sequence[Any]) -> str: - """Build Cygnet's measured user prompt, assigning options A through Z. + """Build the measured user prompt, assigning one-character option IDs. Mapping insertion order or sequence order is the option order. Structured state is rendered with ``indent=1`` as in Cygnet. This function only @@ -78,22 +85,22 @@ def build_prompt(state: Any, instructions: Any, options: Mapping[Any, Any] | Seq ) lines = [state_text.rstrip(), "", instruction_text.rstrip(), "", "Options:"] lines.extend(f"{LETTERS[index]}. {description}" for index, description in enumerate(descriptions)) - lines.extend(("", "Answer with the letter of exactly one option, and nothing else:")) + lines.extend(("", "Answer with the case-sensitive ID of exactly one option, and nothing else:")) return "\n".join(lines) def _validate_letters(letters: Sequence[str]) -> tuple[str, ...]: if isinstance(letters, (bytes, bytearray)): - raise TypeError("letters must be a sequence of A-Z strings") + raise TypeError("option IDs must be a sequence of one-character strings") selected = tuple(letters) if not selected: - raise ValueError("letters must not be empty") + raise ValueError("option IDs must not be empty") if len(selected) > len(LETTERS): raise ValueError(f"letters may contain at most {len(LETTERS)} entries") - if any(type(letter) is not str or len(letter) != 1 or letter not in LETTERS for letter in selected): - raise ValueError("letters must contain only single uppercase A-Z strings") + if any(type(letter) is not str or len(letter) != 1 or letter not in CHOICE_SYMBOLS for letter in selected): + raise ValueError("option IDs must be distinct single A-Z/a-z characters") if len(set(selected)) != len(selected): - raise ValueError("letters must not contain duplicates") + raise ValueError("option IDs must not contain duplicates") return selected @@ -113,12 +120,12 @@ def _decode_token(tokenizer: Any, token_id: int) -> str: def letter_token_ids(tokenizer: Any, letters: Sequence[str]) -> dict[str, tuple[int, ...]]: - """Scan the vocabulary for every token ID decoding to each exact letter. + """Scan the vocabulary for every token ID decoding to each exact option ID. Token text is intentionally never used as a dictionary key: distinct token IDs may decode to identical text, and every such ID contributes probability - mass. Tokens decoding to ``" A"``, ``"A."``, or ``"a"`` are excluded: - Cygnet's structured-output choice admits the exact uppercase string only. + mass. Tokens decoding to ``" A"`` or ``"A."`` are excluded. Case is + significant: ``A`` and ``a`` are different option IDs when both are used. ``len(tokenizer)`` must describe the full vocabulary, including added tokens. """ @@ -338,6 +345,8 @@ def read_letter_distribution( __all__ = [ + "CHOICE_SYMBOLS", + "CYGNET_LETTERS", "LETTERS", "SYSTEM_PROMPT", "LetterReadout", diff --git a/scripts/evaluate_choice_readout_matrix.py b/scripts/evaluate_choice_readout_matrix.py new file mode 100644 index 0000000..7406c6e --- /dev/null +++ b/scripts/evaluate_choice_readout_matrix.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python3 +"""Evaluate training-free choice readout and a checkpoint's native readout. + +The script loads one model once, then scores the same frozen model on JevBench, +Transfer-v9, Typed Decisions and JevJudge's text-only subset. Checkpoint runs +save both readout components so ensemble weights can be selected offline on a +separate calibration split without repeating inference or looking at test +labels. Frozen-base runs save the training-free choice component only. +""" + +from __future__ import annotations + +import argparse +import json +import platform +import statistics +import subprocess +import time +from collections import defaultdict +from pathlib import Path +import math + +import numpy as np +import pyarrow +import pyarrow.parquet as pq +import peft +import safetensors +import torch +import transformers + +from jevany.api import question_keys +from jevany.benchmark import prediction_rows, summarize +from jevany.checkpoint import LoadOptions +from jevany.data import api_request +from jevany.letter_predictor import LetterReadoutPredictor +from jevany.suite import ( + ENCODING, + digest, + load_split, + record_digest, + write_json, +) + + +def _typed_rows(dataset: Path, split: str) -> list[dict]: + path = dataset / "all" / f"{split}-00000-of-00001.parquet" + return pq.read_table(path).to_pylist() + + +def _typed_record(row: dict) -> dict: + state = json.loads(row["state"]) + questions = json.loads(row["questions"]) + gold = json.loads(row["gold"]) + converted = {} + for qid, question in questions.items(): + answer = gold[qid] + value = dict(question) + keys = question_keys(value["type"], value.get("criteria")) + # Typed Decisions accuracy is ordered argmax agreement with the soft + # teacher distribution. The dataset's convenience `label` differs on + # tied rows, so derive the target exactly as the benchmark scorer does. + label_key = max(keys, key=lambda key: float(answer["probabilities"][key])) + if value["type"] == "choice": + value["label"] = label_key + elif value["type"] == "noul": + value["label"] = label_key == "true" + else: + value["label"] = int(label_key) + value["target"] = answer["probabilities"] + value["src"] = f"typed_decisions/{row['workflow']}/{qid}" + converted[qid] = value + return { + "state": state, + "questions": converted, + "_meta": { + "id": row["id"], + "group_id": row["id"], + "source": "LocalLLaMA/typed-decisions", + "variant": "clean", + "split": row["split"], + "workflow": row["workflow"], + }, + } + + +def _attach_soft_gold(rows: list[dict], record: dict) -> None: + questions = record["questions"] + for row in rows: + target = questions[row["question"]].get("target") + if target is not None: + row["gold"] = [float(target[key]) for key in row["keys"]] + + +def _component_prediction(probabilities: dict) -> dict: + return {"probabilities": probabilities} + + +def _typed_soft_metrics(rows: list[dict], bins: int = 15) -> dict: + totals = [0] * bins + correct = [0] * bins + confidence = [0.0] * bins + kl, brier = 0.0, 0.0 + for row in rows: + p = [float(value) for value in row["p"]] + gold = [float(value) for value in row["gold"]] + p_total, gold_total = sum(p), sum(gold) + p = [value / p_total for value in p] + gold = [value / gold_total for value in gold] + kl += sum(g * math.log(max(g, 1e-12) / max(value, 1e-12)) + for g, value in zip(gold, p, strict=True)) + brier += sum((value - g) ** 2 for value, g in zip(p, gold, strict=True)) + predicted = max(range(len(p)), key=p.__getitem__) + conf = max(p) + bucket = min(bins - 1, int(conf * bins)) + totals[bucket] += 1 + correct[bucket] += int(predicted == row["label"]) + confidence[bucket] += conf + n = len(rows) + return { + "n": n, + "correct": sum(correct), + "accuracy": sum(correct) / n, + "kl_from_gold": kl / n, + "brier": brier / n, + "ece": sum( + totals[index] / n * abs( + correct[index] / totals[index] - confidence[index] / totals[index] + ) + for index in range(bins) if totals[index] + ), + "mean_confidence": sum(confidence) / n, + } + + +def evaluate(name: str, records: list[dict], predictor, destination: Path) -> dict: + destination.mkdir(parents=True, exist_ok=False) + component_rows: dict[str, list[dict]] = defaultdict(list) + latencies = [] + started = time.time() + with (destination / "predictions.jsonl").open("w", encoding=ENCODING) as stream: + for index, record in enumerate(records, 1): + result = predictor(record) + components = result.get("component_probabilities") or { + "choice": result["probabilities"] + } + saved = {} + for component, probabilities in components.items(): + if probabilities is None: + continue + rows = prediction_rows(record, _component_prediction(probabilities)) + _attach_soft_gold(rows, record) + component_rows[component].extend(rows) + saved[component] = probabilities + stream.write(json.dumps({ + "id": record["_meta"]["id"], + "request_sha256": record_digest(api_request(record)), + "component_probabilities": saved, + "latency_ms": result["latency_ms"], + "input_tokens": result["input_tokens"], + }, allow_nan=False) + "\n") + stream.flush() + latencies.append(float(result["latency_ms"])) + if index % 25 == 0: + print(f"{name}: {index}/{len(records)}", flush=True) + reports = {} + for component, rows in component_rows.items(): + write_json(destination / f"{component}-rows.json", rows) + reports[component] = summarize(rows) + if name == "typed_test": + reports[component]["typed_soft_metrics"] = _typed_soft_metrics(rows) + report = { + "dataset": name, + "records": len(records), + "questions": sum(len(record["questions"]) for record in records), + "components": reports, + "components_executed": sorted(component_rows), + "latency_scope": ( + "end-to-end predictor call; checkpoint calls execute both choice and native " + "components, so these values are not per-component efficiency measurements" + ), + "latency_ms": { + "median": statistics.median(latencies), + "p95": sorted(latencies)[min(len(latencies) - 1, int(.95 * len(latencies)))], + }, + "wall_seconds": time.time() - started, + } + write_json(destination / "report.json", report) + return report + + +def _correct(report: dict, component: str) -> int: + clean = report["components"][component]["clean"] + return round(clean["n"] * clean["acc"]) + + +def _code_revision() -> str | None: + root = Path(__file__).resolve().parents[1] + result = subprocess.run( + ["git", "rev-parse", "HEAD"], cwd=root, text=True, + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=False, + ) + return result.stdout.strip() if result.returncode == 0 else None + + +def _checkpoint_artifacts(predictor) -> dict | None: + checkpoint = predictor.checkpoint + if checkpoint is None: + return None + root = Path(checkpoint.path) + files = {} + for name in ("adapter_model.safetensors", "head.pt", "adapter_config.json"): + path = root / name + if path.is_file(): + files[name] = {"sha256": digest(path), "bytes": path.stat().st_size} + return {"requested": checkpoint.requested, "resolved": str(root), "files": files} + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + source = parser.add_mutually_exclusive_group(required=True) + source.add_argument("--base") + source.add_argument("--checkpoint") + parser.add_argument("--base-load-path", required=True) + parser.add_argument("--revision") + parser.add_argument("--model-name", required=True) + parser.add_argument("--out", type=Path, required=True) + parser.add_argument("--jevbench", type=Path, required=True) + parser.add_argument("--transfer", type=Path, required=True) + parser.add_argument("--typed", type=Path, required=True) + parser.add_argument("--jevjudge", type=Path, required=True) + parser.add_argument("--expected-jevbench-native-correct", type=int) + parser.add_argument("--expected-transfer-native-correct", type=int) + parser.add_argument("--device", default="cuda") + parser.add_argument("--dtype", choices=("fp32", "bf16"), default="bf16") + parser.add_argument("--attn", choices=("eager", "sdpa"), default="sdpa") + parser.add_argument("--max-tokens", type=int, default=65_536) + args = parser.parse_args() + if args.revision and args.checkpoint: + parser.error("--revision accompanies --base, not --checkpoint") + if args.base and (args.expected_jevbench_native_correct is not None + or args.expected_transfer_native_correct is not None): + parser.error("native parity assertions require --checkpoint") + + args.out.mkdir(parents=True, exist_ok=False) + options = LoadOptions( + dtype={"fp32": torch.float32, "bf16": torch.bfloat16}[args.dtype], + merge=False, + temperature=None, + base_load_path=args.base_load_path, + attn=args.attn, + ) + predictor = LetterReadoutPredictor( + base=args.base, + checkpoint=args.checkpoint, + revision=args.revision, + device=args.device, + options=options, + temperature=1.0, + native_weight=0.5 if args.checkpoint else 0.0, + return_components=bool(args.checkpoint), + exact_kernels=True, + max_tokens=args.max_tokens, + ) + + parity_datasets = { + "jevbench_public": load_split(args.jevbench, "development"), + "transfer_calibration": load_split(args.transfer, "development"), + } + reports = { + name: evaluate(name, records, predictor, args.out / name) + for name, records in parity_datasets.items() + } + if args.checkpoint: + checks = ( + ("jevbench_public", args.expected_jevbench_native_correct), + ("transfer_calibration", args.expected_transfer_native_correct), + ) + for dataset, expected in checks: + if expected is not None: + actual = _correct(reports[dataset], "native") + if actual != expected: + raise RuntimeError( + f"{dataset} native parity failed: expected {expected}, got {actual}" + ) + heldout_datasets = { + "transfer_test": load_split(args.transfer, "test", allow_test=True), + "typed_test": [_typed_record(row) for row in _typed_rows(args.typed, "test")], + "jevjudge_text": [ + record for record in load_split(args.jevjudge, "test", allow_test=True) + if record["_meta"]["modality"] == "text" + ], + } + reports.update({ + name: evaluate(name, records, predictor, args.out / name) + for name, records in heldout_datasets.items() + }) + + typed_manifest = json.loads((args.typed / ".hf-fetch.json").read_text()) + manifest = { + "model": args.model_name, + "source": {"base": args.base, "checkpoint": args.checkpoint}, + "checkpoint_artifacts": _checkpoint_artifacts(predictor), + "base_loading": predictor.base_loading, + "predictor": predictor.provenance, + "runtime": { + "python": platform.python_version(), + "torch": torch.__version__, + "cuda": torch.version.cuda, + "transformers": transformers.__version__, + "peft": peft.__version__, + "numpy": np.__version__, + "pyarrow": pyarrow.__version__, + "safetensors": safetensors.__version__, + "code_revision": _code_revision(), + }, + "protocol": { + "kernel_policy": "LocalPredictor-compatible exact CUDA policy", + "dtype": args.dtype, + "attn": args.attn, + "temperature": 1.0, + "blend_selection": ( + "not performed here; components are saved for fitting on Transfer-v9 " + "development only" + ), + "efficiency_scope": ( + "base calls execute choice only; checkpoint calls execute choice and native. " + "Do not compare component efficiency from these end-to-end timings" + ), + }, + "datasets": { + "jevbench_development_sha256": digest(args.jevbench / "development.jsonl"), + "jevbench_manifest_sha256": digest(args.jevbench / "manifest.json"), + "transfer_manifest_sha256": digest(args.transfer / "manifest.json"), + "transfer_development_sha256": digest(args.transfer / "development.jsonl"), + "transfer_test_sha256": digest(args.transfer / "test.jsonl"), + "typed_revision": typed_manifest["resolved_revision"], + "typed_test_sha256": digest( + args.typed / "all" / "test-00000-of-00001.parquet" + ), + "jevjudge_test_sha256": digest(args.jevjudge / "test.jsonl"), + "jevjudge_manifest_sha256": digest(args.jevjudge / "manifest.json"), + }, + "reports": reports, + } + write_json(args.out / "manifest.json", manifest) + print(json.dumps({ + "model": args.model_name, + "results": { + name: { + component: report["clean"]["acc"] + for component, report in value["components"].items() + } + for name, value in reports.items() + }, + }, indent=2), flush=True) + + +if __name__ == "__main__": + main() diff --git a/scripts/fit_choice_ensemble.py b/scripts/fit_choice_ensemble.py new file mode 100644 index 0000000..c4ae147 --- /dev/null +++ b/scripts/fit_choice_ensemble.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python3 +"""Fit choice/native pooling on Transfer-v9 development and score frozen tests. + +The blend weight and temperature are selected only from the deterministic +Transfer-v9 development partition emitted by ``evaluate_choice_readout_matrix``. +Those fixed values are then applied unchanged to every held-out dataset. +""" + +from __future__ import annotations + +import argparse +import json +import math +from pathlib import Path + +import numpy as np + +from jevany.suite import read_json, write_json + + +EPS = 1e-12 + + +def load_components(directory: Path) -> tuple[list[dict], list[dict] | None]: + choice = read_json(directory / "choice-rows.json") + native_path = directory / "native-rows.json" + native = read_json(native_path) if native_path.exists() else None + if native is not None: + left = [(row["id"], row["question"], row["keys"], row["label"]) for row in choice] + right = [(row["id"], row["question"], row["keys"], row["label"]) for row in native] + if left != right: + raise ValueError(f"component rows are not aligned: {directory}") + return choice, native + + +def headline_indices(rows: list[dict]) -> list[int]: + """Indices in the benchmark's accuracy cohort. + + Transfer-v9 also contains non-clean robustness variants and unknowable + questions. The benchmark never counts either in headline accuracy, so + neither may influence weight or temperature selection. + """ + + return [ + index for index, row in enumerate(rows) + if row.get("variant", "clean") == "clean" + and row.get("source") != "unknowable" + ] + + +def select_indices(rows: list[dict], indices: list[int]) -> list[dict]: + return [rows[index] for index in indices] + + +def targets(rows: list[dict]) -> list[np.ndarray]: + values = [] + for row in rows: + if "gold" in row: + target = np.asarray(row["gold"], dtype=np.float64) + target /= target.sum() + else: + target = np.zeros(len(row["p"]), dtype=np.float64) + target[row["label"]] = 1.0 + values.append(target) + return values + + +def pooled_logits(choice: list[dict], native: list[dict] | None, weight: float) -> list[np.ndarray]: + if native is None and weight: + raise ValueError("native weight requires native component rows") + result = [] + for index, row in enumerate(choice): + left = np.log(np.maximum(np.asarray(row["p"], dtype=np.float64), EPS)) + if native is None: + result.append(left) + else: + right = np.log(np.maximum(np.asarray(native[index]["p"], dtype=np.float64), EPS)) + result.append((1 - weight) * left + weight * right) + return result + + +def probabilities(logits: list[np.ndarray], temperature: float) -> list[np.ndarray]: + result = [] + for values in logits: + scaled = values / temperature + scaled -= scaled.max() + p = np.exp(scaled) + result.append(p / p.sum()) + return result + + +def soft_nll(rows: list[dict], ps: list[np.ndarray]) -> float: + gold = targets(rows) + return float(np.mean([ + -float(np.sum(target * np.log(np.maximum(p, EPS)))) + for target, p in zip(gold, ps, strict=True) + ])) + + +def fit_temperature(rows: list[dict], logits: list[np.ndarray]) -> tuple[float, float]: + """Golden-section search in log-temperature space with fixed bounds.""" + + lo, hi = math.log(.05), math.log(20.0) + ratio = (math.sqrt(5) - 1) / 2 + x1, x2 = hi - ratio * (hi - lo), lo + ratio * (hi - lo) + + def objective(log_temperature: float) -> float: + return soft_nll(rows, probabilities(logits, math.exp(log_temperature))) + + f1, f2 = objective(x1), objective(x2) + for _ in range(80): + if f1 <= f2: + hi, x2, f2 = x2, x1, f1 + x1 = hi - ratio * (hi - lo) + f1 = objective(x1) + else: + lo, x1, f1 = x1, x2, f2 + x2 = lo + ratio * (hi - lo) + f2 = objective(x2) + point = (lo + hi) / 2 + return math.exp(point), objective(point) + + +def fit(rows: list[dict], choice: list[dict], native: list[dict] | None) -> dict: + candidates = [0.0] if native is None else [index / 100 for index in range(101)] + best = None + trace = [] + for weight in candidates: + logits = pooled_logits(choice, native, weight) + temperature, nll = fit_temperature(rows, logits) + t1 = probabilities(logits, 1.0) + correct = sum( + int(int(p.argmax()) == row["label"]) + for row, p in zip(rows, t1, strict=True) + ) + item = { + "native_weight": weight, + "temperature": temperature, + "correct": correct, + "accuracy": correct / len(rows), + "nll": nll, + } + trace.append(item) + if best is None or ( + -correct, nll, abs(weight - .5) + ) < ( + -best["correct"], best["nll"], abs(best["native_weight"] - .5) + ): + best = item + return {"selected": best, "grid": trace} + + +def ece(rows: list[dict], ps: list[np.ndarray], bins: int = 15) -> float: + totals = np.zeros(bins) + correct = np.zeros(bins) + confidence = np.zeros(bins) + for row, p in zip(rows, ps, strict=True): + conf = float(p.max()) + bucket = min(bins - 1, int(conf * bins)) + totals[bucket] += 1 + correct[bucket] += int(int(p.argmax()) == row["label"]) + confidence[bucket] += conf + n = len(rows) + return float(sum( + totals[index] / n * abs(correct[index] / totals[index] - confidence[index] / totals[index]) + for index in range(bins) if totals[index] + )) + + +def metrics(rows: list[dict], ps: list[np.ndarray], *, ece_bins: int = 10) -> dict: + gold = targets(rows) + cross_entropy = soft_nll(rows, ps) + gold_entropy = float(np.mean([ + -float(np.sum(target * np.log(np.maximum(target, EPS)))) for target in gold + ])) + return { + "n": len(rows), + "correct": sum(int(int(p.argmax()) == row["label"]) for row, p in zip(rows, ps, strict=True)), + "accuracy": float(np.mean([ + int(int(p.argmax()) == row["label"]) + for row, p in zip(rows, ps, strict=True) + ])), + "cross_entropy_from_gold": cross_entropy, + "gold_entropy": gold_entropy, + "kl_from_gold": cross_entropy - gold_entropy, + "brier": float(np.mean([ + float(np.sum((p - target) ** 2)) + for target, p in zip(gold, ps, strict=True) + ])), + "ece": ece(rows, ps, bins=ece_bins), + "mean_confidence": float(np.mean([p.max() for p in ps])), + } + + +def evaluate(directory: Path, selected: dict) -> dict: + choice, native = load_components(directory) + configurations = { + "choice_t1": (0.0, 1.0), + "transfer_dev_calibrated_choice": (0.0, selected["choice_temperature"]), + } + if native is not None: + configurations.update({ + "native_shipped": (1.0, 1.0), + "fixed_blend_0_5": (.5, 1.0), + "transfer_dev_tuned_blend": ( + selected["native_weight"], selected["blend_temperature"] + ), + }) + indices = headline_indices(choice) + ece_bins = 15 if directory.name == "typed_test" else 10 + results = {} + for name, (weight, temperature) in configurations.items(): + ps = probabilities(pooled_logits(choice, native, weight), temperature) + headline = metrics( + select_indices(choice, indices), + [ps[index] for index in indices], + ece_bins=ece_bins, + ) + headline["cohort"] = "clean and knowable" + headline["excluded_non_headline_rows"] = len(choice) - len(indices) + headline["ece_bins"] = ece_bins + results[name] = headline + return results + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--run", type=Path, required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + manifest = read_json(args.run / "manifest.json") + calibration_dir = args.run / "transfer_calibration" + choice, native = load_components(calibration_dir) + indices = headline_indices(choice) + calibration_choice = select_indices(choice, indices) + calibration_native = select_indices(native, indices) if native is not None else None + choice_fit = fit(calibration_choice, calibration_choice, None) + blend_fit = fit(calibration_choice, calibration_choice, calibration_native) + selected = { + "choice_temperature": choice_fit["selected"]["temperature"], + "native_weight": blend_fit["selected"]["native_weight"], + "blend_temperature": blend_fit["selected"]["temperature"], + "selection_objective": ( + "hard-label accuracy on the Transfer-v9 development clean/knowable " + "cohort; calibrated NLL and distance from 0.5 break ties" + ), + } + datasets = {} + for name in ( + "transfer_calibration", "transfer_test", "typed_test", "jevbench_public", + "jevjudge_text" + ): + datasets[name] = evaluate(args.run / name, selected) + result = { + "model": manifest["model"], + "source_manifest": str(args.run / "manifest.json"), + "protocol": { + "selection_split": ( + "Transfer-v9 development clean/knowable accuracy cohort; never any " + "reported test panel" + ), + "selection_rows": len(calibration_choice), + "weight_grid": "0.00 to 1.00 inclusive, step 0.01", + "pool": "log-linear/geometric", + "temperature_fit": "bounded scalar NLL minimization, T in [0.05, 20]", + "ece": "15 equal-width bins for Typed Decisions; 10 for every other dataset", + "selection_objective": "accuracy; calibrated NLL then distance from 0.5 break ties", + "transfer": "the selected weight and temperature are frozen for every held-out dataset", + "native_shipped": ( + "the checkpoint's native probabilities, including its shipped inference " + "temperature; no additional temperature is applied" + ), + "fixed_blend_0_5": ( + "equal log-linear pooling of T=1 choice probabilities and shipped native " + "probabilities; no additional temperature is applied" + ), + "transfer_dev_tuned_blend": ( + "the Transfer-dev-selected native weight followed by the reported scalar " + "additional temperature" + ), + }, + "selected": selected, + "selection": {"choice": choice_fit, "blend": blend_fit}, + "datasets": datasets, + } + write_json(args.out, result) + print(json.dumps({ + "model": result["model"], + "selected": selected, + "test_accuracy": { + dataset: {name: values["accuracy"] for name, values in scores.items()} + for dataset, scores in datasets.items() if dataset != "transfer_calibration" + }, + }, indent=2), flush=True) + + +if __name__ == "__main__": + main() diff --git a/tests/test_evaluate_choice_readout_matrix.py b/tests/test_evaluate_choice_readout_matrix.py new file mode 100644 index 0000000..a600a21 --- /dev/null +++ b/tests/test_evaluate_choice_readout_matrix.py @@ -0,0 +1,129 @@ +import json +import math + +import pytest + +from scripts.evaluate_choice_readout_matrix import ( + _attach_soft_gold, + _typed_record, + _typed_soft_metrics, + evaluate, +) + + +def test_typed_record_uses_ordered_argmax_when_soft_gold_is_tied(): + row = { + "id": "tie-case", + "split": "test", + "workflow": "unit", + "state": json.dumps({"context": "A tied teacher distribution"}), + "questions": json.dumps({ + "decision": { + "type": "choice", + "criteria": {"first": "First option", "second": "Second option"}, + } + }), + # The convenience label intentionally disagrees with the benchmark's + # ordered argmax rule and must not determine the converted target. + "gold": json.dumps({ + "decision": { + "label": "second", + "probabilities": {"first": 0.5, "second": 0.5}, + } + }), + } + + record = _typed_record(row) + + assert record["questions"]["decision"]["label"] == "first" + assert record["questions"]["decision"]["target"] == { + "first": 0.5, + "second": 0.5, + } + + +def test_typed_soft_metrics_normalize_distributions_and_use_hard_labels(): + rows = [ + {"p": [8.0, 2.0], "gold": [3.0, 1.0], "label": 0}, + {"p": [4.0, 6.0], "gold": [1.0, 1.0], "label": 0}, + ] + + result = _typed_soft_metrics(rows, bins=2) + + expected_kl = ( + 0.75 * math.log(0.75 / 0.8) + + 0.25 * math.log(0.25 / 0.2) + + 0.5 * math.log(0.5 / 0.4) + + 0.5 * math.log(0.5 / 0.6) + ) / 2 + assert result["n"] == 2 + assert result["correct"] == 1 + assert result["accuracy"] == pytest.approx(0.5) + assert result["kl_from_gold"] == pytest.approx(expected_kl) + assert result["brier"] == pytest.approx(0.0125) + assert result["ece"] == pytest.approx(0.2) + assert result["mean_confidence"] == pytest.approx(0.7) + + +def test_attach_soft_gold_aligns_values_to_each_prediction_rows_key_order(): + record = { + "questions": { + "choice": {"target": {"alpha": 0.1, "beta": 0.9}}, + "score": {"target": {"0": 0.2, "1": 0.3, "2": 0.5}}, + } + } + rows = [ + {"question": "choice", "keys": ["beta", "alpha"]}, + {"question": "score", "keys": ["2", "0", "1"]}, + ] + + _attach_soft_gold(rows, record) + + assert rows[0]["gold"] == [0.9, 0.1] + assert rows[1]["gold"] == [0.5, 0.2, 0.3] + + +def test_evaluate_base_only_result_saves_choice_component(tmp_path): + record = { + "state": "Choose the right option.", + "questions": { + "decision": { + "type": "choice", + "criteria": {"wrong": "Wrong", "right": "Right"}, + "label": "right", + "target": {"right": 0.8, "wrong": 0.2}, + "src": "unit/base-only", + } + }, + "_meta": { + "id": "base-only", + "group_id": "base-only", + "source": "unit", + "variant": "clean", + }, + } + + def predictor(_record): + return { + # Reverse insertion order relative to criteria to exercise keyed + # probability alignment in the full evaluation path. + "probabilities": {"decision": {"right": 0.9, "wrong": 0.1}}, + "latency_ms": 2.0, + "input_tokens": 12, + } + + destination = tmp_path / "base" + report = evaluate("typed_test", [record], predictor, destination) + + assert set(report["components"]) == {"choice"} + assert report["components"]["choice"]["clean"]["acc"] == pytest.approx(1.0) + assert not (destination / "native-rows.json").exists() + rows = json.loads((destination / "choice-rows.json").read_text()) + assert rows[0]["keys"] == ["wrong", "right"] + assert rows[0]["p"] == pytest.approx([0.1, 0.9]) + assert rows[0]["gold"] == pytest.approx([0.2, 0.8]) + prediction = json.loads( + (destination / "predictions.jsonl").read_text().strip() + ) + assert set(prediction["component_probabilities"]) == {"choice"} + diff --git a/tests/test_fit_choice_ensemble.py b/tests/test_fit_choice_ensemble.py new file mode 100644 index 0000000..4e54d9a --- /dev/null +++ b/tests/test_fit_choice_ensemble.py @@ -0,0 +1,65 @@ +import pytest + +from scripts.fit_choice_ensemble import ( + fit, + headline_indices, + metrics, + pooled_logits, + probabilities, +) + + +def row(identity, p, label, gold=None): + value = { + "id": identity, + "question": "decision", + "keys": ["left", "right"], + "label": label, + "p": p, + } + if gold is not None: + value["gold"] = gold + return value + + +def test_fit_selects_native_weight_on_calibration_accuracy_before_nll(): + choice = [row("a", [.9, .1], 1), row("b", [.8, .2], 1)] + native = [row("a", [.1, .9], 1), row("b", [.2, .8], 1)] + + result = fit(choice, choice, native) + + assert result["selected"]["correct"] == 2 + assert result["selected"]["native_weight"] > .5 + assert len(result["grid"]) == 101 + + +def test_soft_gold_metrics_use_ordered_labels_and_report_kl(): + rows = [row("a", [.75, .25], 0, gold=[.5, .5])] + ps = probabilities(pooled_logits(rows, None, 0), 1.0) + + result = metrics(rows, ps) + + assert result["accuracy"] == 1.0 + assert result["brier"] == pytest.approx(.125) + assert result["kl_from_gold"] == pytest.approx( + result["cross_entropy_from_gold"] - result["gold_entropy"] + ) + + +def test_choice_only_fit_has_no_native_weight(): + rows = [row("a", [.7, .3], 0), row("b", [.4, .6], 1)] + + result = fit(rows, rows, None) + + assert result["selected"]["native_weight"] == 0.0 + assert len(result["grid"]) == 1 + + +def test_transfer_selection_uses_only_clean_knowable_headline_rows(): + rows = [ + {**row("headline", [.7, .3], 0), "variant": "clean", "source": "task"}, + {**row("variant", [.7, .3], 0), "variant": "permuted", "source": "task"}, + {**row("unknown", [.7, .3], 0), "variant": "clean", "source": "unknowable"}, + ] + + assert headline_indices(rows) == [0] diff --git a/tests/test_letter_predictor.py b/tests/test_letter_predictor.py index 85cb3d3..ca33be9 100644 --- a/tests/test_letter_predictor.py +++ b/tests/test_letter_predictor.py @@ -26,6 +26,21 @@ def test_letter_predictor_rejects_native_only_load_options_before_loading(): ) +def test_letter_predictor_validates_generalized_native_arguments_before_loading(): + with pytest.raises(ValueError, match="at most one"): + LetterReadoutPredictor( + checkpoint="unused", device="cpu", native_weight=0.5, pointer_weight=0.5, + ) + with pytest.raises(ValueError, match="requires a JevAny checkpoint"): + LetterReadoutPredictor(base="unused", device="cpu", native_weight=0.5) + with pytest.raises(TypeError, match="return_components"): + LetterReadoutPredictor(checkpoint="unused", device="cpu", return_components=1) + with pytest.raises(TypeError, match="exact_kernels"): + LetterReadoutPredictor(checkpoint="unused", device="cpu", exact_kernels=1) + with pytest.raises(ValueError, match="pointer weight"): + LetterReadoutPredictor(checkpoint="unused", device="cpu", native_weight=1.01) + + def test_one_row_input_ids_normalizes_template_shapes_and_mappings(): assert _one_row_input_ids(torch.tensor([1, 2])).tolist() == [[1, 2]] assert _one_row_input_ids({"input_ids": [[3, 4]]}).tolist() == [[3, 4]] @@ -113,6 +128,56 @@ def test_release_unused_output_head_drops_lm_token_aliases(): assert _release_unused_output_head(direct_token) is False +def test_release_unused_output_head_retains_direct_token_native_head(): + head = object() + direct_token = type("Model", (), { + "adapter": type("Adapter", (), {"_output_embeddings": head})(), + "lm_head": head, + })() + + assert _release_unused_output_head(direct_token, retain_native=True) is False + assert direct_token.lm_head is head + assert direct_token.adapter._output_embeddings is head + + +def test_return_components_exposes_unblended_choice_and_native_probabilities(): + predictor = object.__new__(LetterReadoutPredictor) + predictor.device = "cpu" + predictor.temperature = 1.0 + predictor.native_weight = 0.5 + predictor.pointer_weight = 0.5 + predictor.return_components = True + predictor._needs_native = True + predictor.native_decision_mode = "lm_token" + predictor.provenance = { + "method": "exact option-letter choice projection", + "adapter_applied": True, + } + predictor._letter_question = lambda state, question: ( + ["left", "right"], [0.8, 0.2], [4.0, 1.0], 11, + ) + predictor._native = lambda record: ([[0.2, 0.8]], 7) + record = { + "state": "state", + "questions": {"q": { + "type": "choice", + "instructions": "choose", + "criteria": {"left": "Left", "right": "Right"}, + "label": "left", + }}, + } + + result = predictor(record) + + assert result["component_probabilities"] == { + "choice": {"q": {"left": 0.8, "right": 0.2}}, + "native": {"q": {"left": 0.2, "right": 0.8}}, + } + assert result["probabilities"]["q"] == pytest.approx({"left": 0.5, "right": 0.5}) + assert result["input_tokens"] == 18 + assert result["readout"]["native_decision_mode"] == "lm_token" + + def test_question_options_preserve_choice_and_score_order_and_fix_noul_order(): assert question_options({ "type": "choice", diff --git a/tests/test_letter_readout.py b/tests/test_letter_readout.py index 9553ee9..c3dd823 100644 --- a/tests/test_letter_readout.py +++ b/tests/test_letter_readout.py @@ -4,6 +4,8 @@ import pytest from jevany.letter_readout import ( + CHOICE_SYMBOLS, + CYGNET_LETTERS, LETTERS, SYSTEM_PROMPT, build_prompt, @@ -45,21 +47,26 @@ def test_build_prompt_matches_cygnet_structured_rendering_and_option_order(): "A. Refund the card", "B. Issue store credit", "", - "Answer with the letter of exactly one option, and nothing else:", + "Answer with the case-sensitive ID of exactly one option, and nothing else:", ]) assert prompt == expected assert prompt.index("A. Refund") < prompt.index("B. Issue") assert "thinking" not in prompt.lower() - assert "LETTER and nothing else" in SYSTEM_PROMPT + assert "case-sensitive ID and nothing else" in SYSTEM_PROMPT -def test_prompt_accepts_all_letters_and_rejects_invalid_option_counts(): - prompt = build_prompt("state", "choose", [f"option {index}" for index in range(26)]) - assert f"{LETTERS[-1]}. option 25" in prompt +def test_prompt_preserves_cygnet_az_then_extends_to_lowercase_ids(): + prompt = build_prompt( + "state", "choose", [f"option {index}" for index in range(len(CHOICE_SYMBOLS))] + ) + assert LETTERS == CHOICE_SYMBOLS + assert f"{CYGNET_LETTERS[-1]}. option 25" in prompt + assert f"{CHOICE_SYMBOLS[26]}. option 26" in prompt + assert f"{CHOICE_SYMBOLS[-1]}. option 51" in prompt with pytest.raises(ValueError, match="at least one"): build_prompt("state", "choose", []) - with pytest.raises(ValueError, match="at most 26"): - build_prompt("state", "choose", list(range(27))) + with pytest.raises(ValueError, match="at most 52"): + build_prompt("state", "choose", list(range(53))) with pytest.raises(TypeError, match="ordered mapping"): build_prompt("state", "choose", "not an option sequence") @@ -161,8 +168,8 @@ def test_invalid_probability_distributions_are_rejected(probabilities): def test_invalid_letters_token_maps_and_logits_are_rejected(): tokenizer = FakeTokenizer(["A", "not B"]) - with pytest.raises(ValueError, match="only single uppercase"): - letter_token_ids(tokenizer, ("A", "b")) + with pytest.raises(ValueError, match="single A-Z/a-z"): + letter_token_ids(tokenizer, ("A", "1")) with pytest.raises(ValueError, match="duplicates"): letter_token_ids(tokenizer, ("A", "A")) with pytest.raises(ValueError, match="no one-token aliases for: B"): From ae891ed97147b57b41a78a4bb4960b018db3bf42 Mon Sep 17 00:00:00 2001 From: Tianxin Wei Date: Fri, 2 Oct 2026 00:49:09 +0000 Subject: [PATCH 02/10] Handle long-context choice evaluation and resume runs --- jevany/letter_predictor.py | 62 +++++++++++-- scripts/evaluate_choice_readout_matrix.py | 96 +++++++++++++++++++- tests/test_evaluate_choice_readout_matrix.py | 31 +++++++ tests/test_letter_predictor.py | 14 +++ 4 files changed, 191 insertions(+), 12 deletions(-) diff --git a/jevany/letter_predictor.py b/jevany/letter_predictor.py index e0856f9..1459d74 100644 --- a/jevany/letter_predictor.py +++ b/jevany/letter_predictor.py @@ -9,6 +9,7 @@ from __future__ import annotations from collections.abc import Mapping +from contextlib import nullcontext import hashlib import json import math @@ -17,6 +18,7 @@ import torch import torch.nn.functional as F +from torch.nn.attention import SDPBackend, sdpa_kernel from .api import SystemOneRequest, question_keys from .checkpoint import Checkpoint, LoadOptions @@ -244,6 +246,7 @@ def __init__( native_weight: float | None = None, return_components: bool = False, exact_kernels: bool = True, + efficient_long_context_tokens: int | None = None, max_tokens: int = 16_384, ) -> None: if (base is None) == (checkpoint is None): @@ -278,7 +281,16 @@ def __init__( raise TypeError("exact_kernels must be a boolean") self.return_components = return_components self.exact_kernels = exact_kernels - self.exact_cuda_kernels_applied = device == "cuda" and exact_kernels + self.exact_cuda_kernels_applied = str(device).startswith("cuda") and exact_kernels + if ( + efficient_long_context_tokens is not None + and ( + type(efficient_long_context_tokens) is not int + or efficient_long_context_tokens < 2 + ) + ): + raise ValueError("efficient_long_context_tokens must be an integer >= 2 or None") + self.efficient_long_context_tokens = efficient_long_context_tokens if type(max_tokens) is not int or max_tokens < 2: raise ValueError("max_tokens must be an integer >= 2") self.max_tokens = max_tokens @@ -442,6 +454,13 @@ def __init__( "return_components": self.return_components, "exact_kernels": self.exact_kernels, "exact_cuda_kernels_applied": self.exact_cuda_kernels_applied, + "efficient_long_context_tokens": self.efficient_long_context_tokens, + "long_context_attention": ( + "fused SDPA (Flash/Efficient, no math fallback) at or above the " + "recorded threshold; exact math SDPA below it" + if self.efficient_long_context_tokens is not None + else None + ), "max_tokens": self.max_tokens, "backbone_context_window": self.backbone_context_window, "effective_max_tokens": self.effective_max_tokens, @@ -449,6 +468,26 @@ def __init__( "device": device, } + def _uses_efficient_attention(self, token_count: int) -> bool: + return ( + str(self.device).startswith("cuda") + and getattr(self, "efficient_long_context_tokens", None) is not None + and token_count >= self.efficient_long_context_tokens + ) + + def _attention_context(self, token_count: int): + if not self._uses_efficient_attention(token_count): + return nullcontext() + # Exact math SDPA materializes an O(sequence^2) attention matrix. A + # few public JevJudge text records exceed 30K tokens, which can require + # roughly 60 GiB for that matrix alone. Release cached short-record + # workspaces and force a memory-linear fused backend for those records. + torch.cuda.empty_cache() + return sdpa_kernel( + [SDPBackend.FLASH_ATTENTION, SDPBackend.EFFICIENT_ATTENTION], + set_priority=True, + ) + def _chat_ids(self, prompt: str) -> torch.Tensor: messages = [ {"role": "system", "content": SYSTEM_PROMPT}, @@ -477,11 +516,12 @@ def _letter_question(self, state, question: dict) -> tuple[list[str], list[float ) prompt = build_prompt(state, question.get("instructions") or "", descriptions) ids = self._chat_ids(prompt) - output = self.language_model( - input_ids=ids, - attention_mask=torch.ones_like(ids), - use_cache=False, - ) + with self._attention_context(int(ids.shape[1])): + output = self.language_model( + input_ids=ids, + attention_mask=torch.ones_like(ids), + use_cache=False, + ) hidden = output.last_hidden_state[0, -1] projection = self.letter_projection selected_logits = F.linear( @@ -512,8 +552,10 @@ def _native(self, record: dict) -> tuple[list[list[float]], int]: f"native request needs {len(encoded['ids'])} packed tokens; " f"limit {self.effective_max_tokens}" ) - return [F.softmax(logits, -1).float().cpu().tolist() - for logits in self.native_model.forward(encoded)], len(encoded["ids"]) + with self._attention_context(len(encoded["ids"])): + rows = [F.softmax(logits, -1).float().cpu().tolist() + for logits in self.native_model.forward(encoded)] + return rows, len(encoded["ids"]) def _pointer(self, record: dict) -> tuple[list[list[float]], int]: """Compatibility alias for the generalized native checkpoint pass.""" @@ -567,6 +609,10 @@ def __call__(self, record: dict) -> dict: "inference_temperature": self.temperature, "latency_ms": (time.perf_counter() - started) * 1000, "input_tokens": sum(row[3] for row in letter_rows) + native_tokens, + "efficient_long_context_attention_used": ( + any(self._uses_efficient_attention(row[3]) for row in letter_rows) + or self._uses_efficient_attention(native_tokens) + ), "readout": { "method": self.provenance["method"], "adapter_applied": self.provenance["adapter_applied"], diff --git a/scripts/evaluate_choice_readout_matrix.py b/scripts/evaluate_choice_readout_matrix.py index 7406c6e..fba7833 100644 --- a/scripts/evaluate_choice_readout_matrix.py +++ b/scripts/evaluate_choice_readout_matrix.py @@ -37,6 +37,7 @@ ENCODING, digest, load_split, + read_json, record_digest, write_json, ) @@ -136,10 +137,14 @@ def evaluate(name: str, records: list[dict], predictor, destination: Path) -> di destination.mkdir(parents=True, exist_ok=False) component_rows: dict[str, list[dict]] = defaultdict(list) latencies = [] + efficient_long_context_records = 0 started = time.time() with (destination / "predictions.jsonl").open("w", encoding=ENCODING) as stream: for index, record in enumerate(records, 1): result = predictor(record) + efficient_long_context_records += int( + result.get("efficient_long_context_attention_used", False) + ) components = result.get("component_probabilities") or { "choice": result["probabilities"] } @@ -174,6 +179,7 @@ def evaluate(name: str, records: list[dict], predictor, destination: Path) -> di "questions": sum(len(record["questions"]) for record in records), "components": reports, "components_executed": sorted(component_rows), + "efficient_long_context_records": efficient_long_context_records, "latency_scope": ( "end-to-end predictor call; checkpoint calls execute both choice and native " "components, so these values are not per-component efficiency measurements" @@ -188,6 +194,38 @@ def evaluate(name: str, records: list[dict], predictor, destination: Path) -> di return report +def reuse_completed( + name: str, + records: list[dict], + predictor, + destination: Path, +) -> dict: + """Load and validate one completed dataset from an interrupted matrix run.""" + + report_path = destination / "report.json" + if not report_path.is_file(): + raise RuntimeError( + f"cannot resume incomplete dataset directory: {destination}; preserve or " + "move that directory aside before retrying" + ) + report = read_json(report_path) + expected_questions = sum(len(record["questions"]) for record in records) + expected_components = {"choice", "native"} if predictor.return_components else {"choice"} + actual_components = set(report.get("components", {})) + if ( + report.get("dataset") != name + or report.get("records") != len(records) + or report.get("questions") != expected_questions + or actual_components != expected_components + ): + raise RuntimeError(f"completed report does not match requested resume dataset: {destination}") + for component in expected_components: + rows_path = destination / f"{component}-rows.json" + if not rows_path.is_file() or len(read_json(rows_path)) != expected_questions: + raise RuntimeError(f"completed component rows are missing or incomplete: {rows_path}") + return report + + def _correct(report: dict, component: str) -> int: clean = report["components"][component]["clean"] return round(clean["n"] * clean["acc"]) @@ -234,6 +272,21 @@ def main() -> None: parser.add_argument("--dtype", choices=("fp32", "bf16"), default="bf16") parser.add_argument("--attn", choices=("eager", "sdpa"), default="sdpa") parser.add_argument("--max-tokens", type=int, default=65_536) + parser.add_argument( + "--efficient-long-context-tokens", type=int, default=8_192, + help=( + "use memory-linear fused SDPA at or above this many tokens; short " + "parity panels retain exact math SDPA" + ), + ) + parser.add_argument( + "--resume", action="store_true", + help="reuse validated completed dataset directories in an interrupted run", + ) + parser.add_argument( + "--preexisting-code-revision", + help="code revision that produced reused reports (required when any are reused)", + ) args = parser.parse_args() if args.revision and args.checkpoint: parser.error("--revision accompanies --base, not --checkpoint") @@ -241,7 +294,13 @@ def main() -> None: or args.expected_transfer_native_correct is not None): parser.error("native parity assertions require --checkpoint") - args.out.mkdir(parents=True, exist_ok=False) + if args.resume: + if not args.out.is_dir(): + parser.error("--resume requires an existing --out directory") + else: + if args.preexisting_code_revision: + parser.error("--preexisting-code-revision requires --resume") + args.out.mkdir(parents=True, exist_ok=False) options = LoadOptions( dtype={"fp32": torch.float32, "bf16": torch.bfloat16}[args.dtype], merge=False, @@ -259,15 +318,31 @@ def main() -> None: native_weight=0.5 if args.checkpoint else 0.0, return_components=bool(args.checkpoint), exact_kernels=True, + efficient_long_context_tokens=args.efficient_long_context_tokens, max_tokens=args.max_tokens, ) + current_code_revision = _code_revision() + dataset_code_revisions = {} + reused_datasets = [] + + def run_dataset(name: str, records: list[dict]) -> dict: + destination = args.out / name + if args.resume and destination.exists(): + report = reuse_completed(name, records, predictor, destination) + reused_datasets.append(name) + dataset_code_revisions[name] = args.preexisting_code_revision + return report + report = evaluate(name, records, predictor, destination) + dataset_code_revisions[name] = current_code_revision + return report + parity_datasets = { "jevbench_public": load_split(args.jevbench, "development"), "transfer_calibration": load_split(args.transfer, "development"), } reports = { - name: evaluate(name, records, predictor, args.out / name) + name: run_dataset(name, records) for name, records in parity_datasets.items() } if args.checkpoint: @@ -291,9 +366,11 @@ def main() -> None: ], } reports.update({ - name: evaluate(name, records, predictor, args.out / name) + name: run_dataset(name, records) for name, records in heldout_datasets.items() }) + if reused_datasets and not args.preexisting_code_revision: + parser.error("--preexisting-code-revision is required when reports are reused") typed_manifest = json.loads((args.typed / ".hf-fetch.json").read_text()) manifest = { @@ -311,7 +388,7 @@ def main() -> None: "numpy": np.__version__, "pyarrow": pyarrow.__version__, "safetensors": safetensors.__version__, - "code_revision": _code_revision(), + "code_revision": current_code_revision, }, "protocol": { "kernel_policy": "LocalPredictor-compatible exact CUDA policy", @@ -326,6 +403,17 @@ def main() -> None: "base calls execute choice only; checkpoint calls execute choice and native. " "Do not compare component efficiency from these end-to-end timings" ), + "long_context_attention": ( + "exact math SDPA below the configured threshold; memory-linear fused SDPA " + "without a math fallback at or above it" + ), + "efficient_long_context_tokens": args.efficient_long_context_tokens, + "resume": { + "enabled": args.resume, + "reused_datasets": reused_datasets, + "preexisting_code_revision": args.preexisting_code_revision, + "dataset_code_revisions": dataset_code_revisions, + }, }, "datasets": { "jevbench_development_sha256": digest(args.jevbench / "development.jsonl"), diff --git a/tests/test_evaluate_choice_readout_matrix.py b/tests/test_evaluate_choice_readout_matrix.py index a600a21..4dc48c5 100644 --- a/tests/test_evaluate_choice_readout_matrix.py +++ b/tests/test_evaluate_choice_readout_matrix.py @@ -8,6 +8,7 @@ _typed_record, _typed_soft_metrics, evaluate, + reuse_completed, ) @@ -127,3 +128,33 @@ def predictor(_record): ) assert set(prediction["component_probabilities"]) == {"choice"} + +def test_reuse_completed_validates_report_and_component_rows(tmp_path): + destination = tmp_path / "typed_test" + record = { + "state": "state", + "questions": {"q": { + "type": "choice", "criteria": {"a": None, "b": None}, + "label": "a", "target": {"a": .75, "b": .25}, "src": "unit/reuse", + }}, + "_meta": { + "id": "reuse", "group_id": "reuse", "source": "unit", "variant": "clean", + }, + } + + def predictor(_record): + return { + "probabilities": {"q": {"a": .75, "b": .25}}, + "latency_ms": 1.0, + "input_tokens": 4, + "efficient_long_context_attention_used": False, + } + + expected = evaluate("typed_test", [record], predictor, destination) + owner = type("Predictor", (), {"return_components": False})() + + assert reuse_completed("typed_test", [record], owner, destination) == expected + + (destination / "choice-rows.json").unlink() + with pytest.raises(RuntimeError, match="missing or incomplete"): + reuse_completed("typed_test", [record], owner, destination) diff --git a/tests/test_letter_predictor.py b/tests/test_letter_predictor.py index ca33be9..19af8e5 100644 --- a/tests/test_letter_predictor.py +++ b/tests/test_letter_predictor.py @@ -39,6 +39,10 @@ def test_letter_predictor_validates_generalized_native_arguments_before_loading( LetterReadoutPredictor(checkpoint="unused", device="cpu", exact_kernels=1) with pytest.raises(ValueError, match="pointer weight"): LetterReadoutPredictor(checkpoint="unused", device="cpu", native_weight=1.01) + with pytest.raises(ValueError, match="efficient_long_context_tokens"): + LetterReadoutPredictor( + checkpoint="unused", device="cpu", efficient_long_context_tokens=1, + ) def test_one_row_input_ids_normalizes_template_shapes_and_mappings(): @@ -105,6 +109,16 @@ def test_safe_chat_text_escapes_gemma_and_bracket_style_controls(): assert "[INST]" in escaped +def test_long_context_attention_threshold_is_cuda_only(): + predictor = object.__new__(LetterReadoutPredictor) + predictor.efficient_long_context_tokens = 16 + predictor.device = "cpu" + assert not predictor._uses_efficient_attention(16) + predictor.device = "cuda" + assert not predictor._uses_efficient_attention(15) + assert predictor._uses_efficient_attention(16) + + def test_release_unused_output_head_drops_pointer_adapter_reference(): head = object() model = type("Model", (), { From 277cab9f3962bfc37366e30c061edabce144ac4d Mon Sep 17 00:00:00 2001 From: Tianxin Wei Date: Fri, 2 Oct 2026 00:49:09 +0000 Subject: [PATCH 03/10] Handle long-context choice evaluation and resume runs --- jevany/letter_predictor.py | 62 +++++++++++-- scripts/evaluate_choice_readout_matrix.py | 96 +++++++++++++++++++- tests/test_evaluate_choice_readout_matrix.py | 31 +++++++ tests/test_letter_predictor.py | 14 +++ 4 files changed, 191 insertions(+), 12 deletions(-) diff --git a/jevany/letter_predictor.py b/jevany/letter_predictor.py index e0856f9..1459d74 100644 --- a/jevany/letter_predictor.py +++ b/jevany/letter_predictor.py @@ -9,6 +9,7 @@ from __future__ import annotations from collections.abc import Mapping +from contextlib import nullcontext import hashlib import json import math @@ -17,6 +18,7 @@ import torch import torch.nn.functional as F +from torch.nn.attention import SDPBackend, sdpa_kernel from .api import SystemOneRequest, question_keys from .checkpoint import Checkpoint, LoadOptions @@ -244,6 +246,7 @@ def __init__( native_weight: float | None = None, return_components: bool = False, exact_kernels: bool = True, + efficient_long_context_tokens: int | None = None, max_tokens: int = 16_384, ) -> None: if (base is None) == (checkpoint is None): @@ -278,7 +281,16 @@ def __init__( raise TypeError("exact_kernels must be a boolean") self.return_components = return_components self.exact_kernels = exact_kernels - self.exact_cuda_kernels_applied = device == "cuda" and exact_kernels + self.exact_cuda_kernels_applied = str(device).startswith("cuda") and exact_kernels + if ( + efficient_long_context_tokens is not None + and ( + type(efficient_long_context_tokens) is not int + or efficient_long_context_tokens < 2 + ) + ): + raise ValueError("efficient_long_context_tokens must be an integer >= 2 or None") + self.efficient_long_context_tokens = efficient_long_context_tokens if type(max_tokens) is not int or max_tokens < 2: raise ValueError("max_tokens must be an integer >= 2") self.max_tokens = max_tokens @@ -442,6 +454,13 @@ def __init__( "return_components": self.return_components, "exact_kernels": self.exact_kernels, "exact_cuda_kernels_applied": self.exact_cuda_kernels_applied, + "efficient_long_context_tokens": self.efficient_long_context_tokens, + "long_context_attention": ( + "fused SDPA (Flash/Efficient, no math fallback) at or above the " + "recorded threshold; exact math SDPA below it" + if self.efficient_long_context_tokens is not None + else None + ), "max_tokens": self.max_tokens, "backbone_context_window": self.backbone_context_window, "effective_max_tokens": self.effective_max_tokens, @@ -449,6 +468,26 @@ def __init__( "device": device, } + def _uses_efficient_attention(self, token_count: int) -> bool: + return ( + str(self.device).startswith("cuda") + and getattr(self, "efficient_long_context_tokens", None) is not None + and token_count >= self.efficient_long_context_tokens + ) + + def _attention_context(self, token_count: int): + if not self._uses_efficient_attention(token_count): + return nullcontext() + # Exact math SDPA materializes an O(sequence^2) attention matrix. A + # few public JevJudge text records exceed 30K tokens, which can require + # roughly 60 GiB for that matrix alone. Release cached short-record + # workspaces and force a memory-linear fused backend for those records. + torch.cuda.empty_cache() + return sdpa_kernel( + [SDPBackend.FLASH_ATTENTION, SDPBackend.EFFICIENT_ATTENTION], + set_priority=True, + ) + def _chat_ids(self, prompt: str) -> torch.Tensor: messages = [ {"role": "system", "content": SYSTEM_PROMPT}, @@ -477,11 +516,12 @@ def _letter_question(self, state, question: dict) -> tuple[list[str], list[float ) prompt = build_prompt(state, question.get("instructions") or "", descriptions) ids = self._chat_ids(prompt) - output = self.language_model( - input_ids=ids, - attention_mask=torch.ones_like(ids), - use_cache=False, - ) + with self._attention_context(int(ids.shape[1])): + output = self.language_model( + input_ids=ids, + attention_mask=torch.ones_like(ids), + use_cache=False, + ) hidden = output.last_hidden_state[0, -1] projection = self.letter_projection selected_logits = F.linear( @@ -512,8 +552,10 @@ def _native(self, record: dict) -> tuple[list[list[float]], int]: f"native request needs {len(encoded['ids'])} packed tokens; " f"limit {self.effective_max_tokens}" ) - return [F.softmax(logits, -1).float().cpu().tolist() - for logits in self.native_model.forward(encoded)], len(encoded["ids"]) + with self._attention_context(len(encoded["ids"])): + rows = [F.softmax(logits, -1).float().cpu().tolist() + for logits in self.native_model.forward(encoded)] + return rows, len(encoded["ids"]) def _pointer(self, record: dict) -> tuple[list[list[float]], int]: """Compatibility alias for the generalized native checkpoint pass.""" @@ -567,6 +609,10 @@ def __call__(self, record: dict) -> dict: "inference_temperature": self.temperature, "latency_ms": (time.perf_counter() - started) * 1000, "input_tokens": sum(row[3] for row in letter_rows) + native_tokens, + "efficient_long_context_attention_used": ( + any(self._uses_efficient_attention(row[3]) for row in letter_rows) + or self._uses_efficient_attention(native_tokens) + ), "readout": { "method": self.provenance["method"], "adapter_applied": self.provenance["adapter_applied"], diff --git a/scripts/evaluate_choice_readout_matrix.py b/scripts/evaluate_choice_readout_matrix.py index 7406c6e..fba7833 100644 --- a/scripts/evaluate_choice_readout_matrix.py +++ b/scripts/evaluate_choice_readout_matrix.py @@ -37,6 +37,7 @@ ENCODING, digest, load_split, + read_json, record_digest, write_json, ) @@ -136,10 +137,14 @@ def evaluate(name: str, records: list[dict], predictor, destination: Path) -> di destination.mkdir(parents=True, exist_ok=False) component_rows: dict[str, list[dict]] = defaultdict(list) latencies = [] + efficient_long_context_records = 0 started = time.time() with (destination / "predictions.jsonl").open("w", encoding=ENCODING) as stream: for index, record in enumerate(records, 1): result = predictor(record) + efficient_long_context_records += int( + result.get("efficient_long_context_attention_used", False) + ) components = result.get("component_probabilities") or { "choice": result["probabilities"] } @@ -174,6 +179,7 @@ def evaluate(name: str, records: list[dict], predictor, destination: Path) -> di "questions": sum(len(record["questions"]) for record in records), "components": reports, "components_executed": sorted(component_rows), + "efficient_long_context_records": efficient_long_context_records, "latency_scope": ( "end-to-end predictor call; checkpoint calls execute both choice and native " "components, so these values are not per-component efficiency measurements" @@ -188,6 +194,38 @@ def evaluate(name: str, records: list[dict], predictor, destination: Path) -> di return report +def reuse_completed( + name: str, + records: list[dict], + predictor, + destination: Path, +) -> dict: + """Load and validate one completed dataset from an interrupted matrix run.""" + + report_path = destination / "report.json" + if not report_path.is_file(): + raise RuntimeError( + f"cannot resume incomplete dataset directory: {destination}; preserve or " + "move that directory aside before retrying" + ) + report = read_json(report_path) + expected_questions = sum(len(record["questions"]) for record in records) + expected_components = {"choice", "native"} if predictor.return_components else {"choice"} + actual_components = set(report.get("components", {})) + if ( + report.get("dataset") != name + or report.get("records") != len(records) + or report.get("questions") != expected_questions + or actual_components != expected_components + ): + raise RuntimeError(f"completed report does not match requested resume dataset: {destination}") + for component in expected_components: + rows_path = destination / f"{component}-rows.json" + if not rows_path.is_file() or len(read_json(rows_path)) != expected_questions: + raise RuntimeError(f"completed component rows are missing or incomplete: {rows_path}") + return report + + def _correct(report: dict, component: str) -> int: clean = report["components"][component]["clean"] return round(clean["n"] * clean["acc"]) @@ -234,6 +272,21 @@ def main() -> None: parser.add_argument("--dtype", choices=("fp32", "bf16"), default="bf16") parser.add_argument("--attn", choices=("eager", "sdpa"), default="sdpa") parser.add_argument("--max-tokens", type=int, default=65_536) + parser.add_argument( + "--efficient-long-context-tokens", type=int, default=8_192, + help=( + "use memory-linear fused SDPA at or above this many tokens; short " + "parity panels retain exact math SDPA" + ), + ) + parser.add_argument( + "--resume", action="store_true", + help="reuse validated completed dataset directories in an interrupted run", + ) + parser.add_argument( + "--preexisting-code-revision", + help="code revision that produced reused reports (required when any are reused)", + ) args = parser.parse_args() if args.revision and args.checkpoint: parser.error("--revision accompanies --base, not --checkpoint") @@ -241,7 +294,13 @@ def main() -> None: or args.expected_transfer_native_correct is not None): parser.error("native parity assertions require --checkpoint") - args.out.mkdir(parents=True, exist_ok=False) + if args.resume: + if not args.out.is_dir(): + parser.error("--resume requires an existing --out directory") + else: + if args.preexisting_code_revision: + parser.error("--preexisting-code-revision requires --resume") + args.out.mkdir(parents=True, exist_ok=False) options = LoadOptions( dtype={"fp32": torch.float32, "bf16": torch.bfloat16}[args.dtype], merge=False, @@ -259,15 +318,31 @@ def main() -> None: native_weight=0.5 if args.checkpoint else 0.0, return_components=bool(args.checkpoint), exact_kernels=True, + efficient_long_context_tokens=args.efficient_long_context_tokens, max_tokens=args.max_tokens, ) + current_code_revision = _code_revision() + dataset_code_revisions = {} + reused_datasets = [] + + def run_dataset(name: str, records: list[dict]) -> dict: + destination = args.out / name + if args.resume and destination.exists(): + report = reuse_completed(name, records, predictor, destination) + reused_datasets.append(name) + dataset_code_revisions[name] = args.preexisting_code_revision + return report + report = evaluate(name, records, predictor, destination) + dataset_code_revisions[name] = current_code_revision + return report + parity_datasets = { "jevbench_public": load_split(args.jevbench, "development"), "transfer_calibration": load_split(args.transfer, "development"), } reports = { - name: evaluate(name, records, predictor, args.out / name) + name: run_dataset(name, records) for name, records in parity_datasets.items() } if args.checkpoint: @@ -291,9 +366,11 @@ def main() -> None: ], } reports.update({ - name: evaluate(name, records, predictor, args.out / name) + name: run_dataset(name, records) for name, records in heldout_datasets.items() }) + if reused_datasets and not args.preexisting_code_revision: + parser.error("--preexisting-code-revision is required when reports are reused") typed_manifest = json.loads((args.typed / ".hf-fetch.json").read_text()) manifest = { @@ -311,7 +388,7 @@ def main() -> None: "numpy": np.__version__, "pyarrow": pyarrow.__version__, "safetensors": safetensors.__version__, - "code_revision": _code_revision(), + "code_revision": current_code_revision, }, "protocol": { "kernel_policy": "LocalPredictor-compatible exact CUDA policy", @@ -326,6 +403,17 @@ def main() -> None: "base calls execute choice only; checkpoint calls execute choice and native. " "Do not compare component efficiency from these end-to-end timings" ), + "long_context_attention": ( + "exact math SDPA below the configured threshold; memory-linear fused SDPA " + "without a math fallback at or above it" + ), + "efficient_long_context_tokens": args.efficient_long_context_tokens, + "resume": { + "enabled": args.resume, + "reused_datasets": reused_datasets, + "preexisting_code_revision": args.preexisting_code_revision, + "dataset_code_revisions": dataset_code_revisions, + }, }, "datasets": { "jevbench_development_sha256": digest(args.jevbench / "development.jsonl"), diff --git a/tests/test_evaluate_choice_readout_matrix.py b/tests/test_evaluate_choice_readout_matrix.py index a600a21..4dc48c5 100644 --- a/tests/test_evaluate_choice_readout_matrix.py +++ b/tests/test_evaluate_choice_readout_matrix.py @@ -8,6 +8,7 @@ _typed_record, _typed_soft_metrics, evaluate, + reuse_completed, ) @@ -127,3 +128,33 @@ def predictor(_record): ) assert set(prediction["component_probabilities"]) == {"choice"} + +def test_reuse_completed_validates_report_and_component_rows(tmp_path): + destination = tmp_path / "typed_test" + record = { + "state": "state", + "questions": {"q": { + "type": "choice", "criteria": {"a": None, "b": None}, + "label": "a", "target": {"a": .75, "b": .25}, "src": "unit/reuse", + }}, + "_meta": { + "id": "reuse", "group_id": "reuse", "source": "unit", "variant": "clean", + }, + } + + def predictor(_record): + return { + "probabilities": {"q": {"a": .75, "b": .25}}, + "latency_ms": 1.0, + "input_tokens": 4, + "efficient_long_context_attention_used": False, + } + + expected = evaluate("typed_test", [record], predictor, destination) + owner = type("Predictor", (), {"return_components": False})() + + assert reuse_completed("typed_test", [record], owner, destination) == expected + + (destination / "choice-rows.json").unlink() + with pytest.raises(RuntimeError, match="missing or incomplete"): + reuse_completed("typed_test", [record], owner, destination) diff --git a/tests/test_letter_predictor.py b/tests/test_letter_predictor.py index ca33be9..19af8e5 100644 --- a/tests/test_letter_predictor.py +++ b/tests/test_letter_predictor.py @@ -39,6 +39,10 @@ def test_letter_predictor_validates_generalized_native_arguments_before_loading( LetterReadoutPredictor(checkpoint="unused", device="cpu", exact_kernels=1) with pytest.raises(ValueError, match="pointer weight"): LetterReadoutPredictor(checkpoint="unused", device="cpu", native_weight=1.01) + with pytest.raises(ValueError, match="efficient_long_context_tokens"): + LetterReadoutPredictor( + checkpoint="unused", device="cpu", efficient_long_context_tokens=1, + ) def test_one_row_input_ids_normalizes_template_shapes_and_mappings(): @@ -105,6 +109,16 @@ def test_safe_chat_text_escapes_gemma_and_bracket_style_controls(): assert "[INST]" in escaped +def test_long_context_attention_threshold_is_cuda_only(): + predictor = object.__new__(LetterReadoutPredictor) + predictor.efficient_long_context_tokens = 16 + predictor.device = "cpu" + assert not predictor._uses_efficient_attention(16) + predictor.device = "cuda" + assert not predictor._uses_efficient_attention(15) + assert predictor._uses_efficient_attention(16) + + def test_release_unused_output_head_drops_pointer_adapter_reference(): head = object() model = type("Model", (), { From a88bfa91844127f1b14178e20c5195c0338643e4 Mon Sep 17 00:00:00 2001 From: Tianxin Wei Date: Fri, 2 Oct 2026 00:46:33 +0000 Subject: [PATCH 04/10] Expose choice readout as the public API --- jevany/__init__.py | 6 +- jevany/benchmark.py | 42 ++++---- jevany/cli.py | 20 ++-- jevany/demos/static/app.js | 3 +- jevany/letter_predictor.py | 13 +-- jevany/letter_runtime.py | 40 ++++---- jevany/readout.py | 160 +++++++++++++++++++++++------- jevany/runtime.py | 46 +++++---- jevany/serve.py | 56 ++++++----- tests/test_playground_workflow.py | 13 ++- tests/test_readout_integration.py | 130 ++++++++++++++++-------- 11 files changed, 355 insertions(+), 174 deletions(-) diff --git a/jevany/__init__.py b/jevany/__init__.py index d999df1..a3ca0e6 100644 --- a/jevany/__init__.py +++ b/jevany/__init__.py @@ -5,8 +5,12 @@ from .api import Choice, Noul, Score, SystemOneRequest from .client import JevClient from .inference import InferenceOptions +from .readout import ChoiceReadoutOptions -__all__ = ["Choice", "Noul", "Score", "SystemOneRequest", "JevClient", "JevModel", "InferenceOptions"] +__all__ = [ + "Choice", "Noul", "Score", "SystemOneRequest", "JevClient", "JevModel", + "InferenceOptions", "ChoiceReadoutOptions", +] def __getattr__(name: str): diff --git a/jevany/benchmark.py b/jevany/benchmark.py index 0a361ca..5ae3a5c 100644 --- a/jevany/benchmark.py +++ b/jevany/benchmark.py @@ -26,7 +26,7 @@ from jevany.metrics import EPSILON, grouped_metrics, metrics, unknowable_report from jevany.model import ContextLengthError from jevany.predictors import LocalPredictor, RemotePredictor -from jevany.readout import add_readout_arguments, letter_options_from_args +from jevany.readout import add_readout_arguments, choice_options_from_args, normalize_readout from jevany.suite import ENCODING, digest, load_split, read_manifest, record_digest, write_json @@ -170,7 +170,7 @@ def evaluate_records(records, predictor, directory, temperature=1.0, heldout_sou EXAMPLES = """examples: jevany eval --run runs/my-jev --data data/starter/development.jsonl --out runs/my-jev/eval jevany eval --run SimpleJev/JevAny-Qwen3.5-4B-LoRA --suite data/eval-suite --out runs/eval - jevany eval --run SimpleJev/JevAny-Qwen3.5-4B-LoRA --readout letter --suite data/eval-suite --out runs/letter + jevany eval --run SimpleJev/JevAny-Qwen3.5-4B-LoRA --readout choice --suite data/eval-suite --out runs/choice jevany eval --remote http://127.0.0.1:8008 --data my-labelled.jsonl --out runs/remote-eval --data scores your own labelled JSONL (jevany.data.load_records); --suite scores a @@ -195,12 +195,12 @@ def main(argv=None, prog=None): ap.add_argument("--date_facts", action="store_true", help="apply jevany.api.with_date_facts to every state before scoring (the opt-in serving preprocessor); reported in report.json") a = ap.parse_args(argv) try: - letter_options = letter_options_from_args(a) + choice_options = choice_options_from_args(a) except ValueError as error: ap.error(str(error)) if bool(a.run) == bool(a.remote): ap.error("give exactly one of --run or --remote") - if a.remote and letter_options is not None: - ap.error("--readout letter is a local model setting; configure it on the remote server") + if a.remote and choice_options is not None: + ap.error("--readout choice is a local model setting; configure it on the remote server") if bool(a.suite) == bool(a.data): ap.error("give exactly one of --suite or --data") if a.data: records, heldout, split, source_hash = load_records(a.data), [], "custom", digest(Path(a.data)) @@ -212,34 +212,40 @@ def main(argv=None, prog=None): records = [{**r, "state": with_date_facts(r["state"])} for r in records] if a.remote: predictor = RemotePredictor(a.remote, a.remote_model, os.environ.get("JEVANY_REMOTE_API_KEY", "local")) - elif letter_options is not None: + elif choice_options is not None: from jevany.letter_predictor import LetterReadoutPredictor predictor = LetterReadoutPredictor( checkpoint=a.run, device=a.device, options=LoadOptions.from_env(), - temperature=letter_options.temperature, - pointer_weight=letter_options.pointer_weight, - max_tokens=letter_options.max_tokens, + temperature=choice_options.temperature, + native_weight=choice_options.native_weight, + max_tokens=choice_options.max_tokens, ) else: predictor = LocalPredictor(a.run, a.device, LoadOptions.from_env()) report, _ = evaluate_records(records, predictor, a.out, heldout_sources=tuple(heldout), skip_overlong=bool(a.data)) - pointer_temperature = (getattr(predictor.pointer_model, "temperature", None) - if letter_options is not None and predictor.pointer_weight else None) + native_temperature = (getattr(predictor.native_model, "temperature", None) + if choice_options is not None and predictor.native_weight else None) calibration_applied = (None if a.remote else predictor.temperature != 1.0 - or pointer_temperature not in (None, 1.0)) + or native_temperature not in (None, 1.0)) report.update(suite_sha256=source_hash, data=a.data, date_facts=a.date_facts, run=a.run or a.remote, split=split, calibration_applied=calibration_applied, base_loading=getattr(predictor, "base_loading", None), remote={"base_url": a.remote, "requested_model": a.remote_model, "served_model": predictor.served_model} if a.remote else None) if not a.remote: - report["readout"] = a.readout - if letter_options is not None: - report["letter_readout"] = dict(predictor.provenance) - if pointer_temperature is not None: - report["letter_readout"]["pointer_temperature"] = pointer_temperature - report["calibration"]["pointer_temperature"] = pointer_temperature + report["readout"] = normalize_readout(a.readout) + if choice_options is not None: + choice_readout = dict(predictor.provenance) + if native_temperature is not None: + choice_readout["native_temperature"] = native_temperature + choice_readout["pointer_temperature"] = native_temperature + report["calibration"]["native_temperature"] = native_temperature + report["calibration"]["pointer_temperature"] = native_temperature + report["choice_readout"] = choice_readout + # Kept as an output alias for consumers of reports written before the + # public readout name changed to ``choice``. + report["letter_readout"] = dict(choice_readout) write_json(Path(a.out) / "report.json", report) print(json.dumps({"objective": report["objective"], "clean": report["clean"], "coverage": report["coverage"]}, indent=2)) diff --git a/jevany/cli.py b/jevany/cli.py index 055a321..a04a0d7 100644 --- a/jevany/cli.py +++ b/jevany/cli.py @@ -45,7 +45,7 @@ def decide_main(argv: list[str]) -> None: from dataclasses import fields from .inference import InferenceOptions, add_inference_arguments, inference_options_from_args from .placement import add_placement_arguments - from .readout import add_readout_arguments, letter_options_from_args + from .readout import add_readout_arguments, choice_options_from_args parser = argparse.ArgumentParser(prog="jevany decide") parser.add_argument("request", help="JSON request file; - reads stdin") @@ -63,7 +63,7 @@ def decide_main(argv: list[str]) -> None: add_inference_arguments(parser) args = parser.parse_args(argv) try: - letter_options = letter_options_from_args(args) + choice_options = choice_options_from_args(args) except ValueError as error: parser.error(str(error)) content = sys.stdin.read() if args.request == "-" else Path(args.request).read_text(encoding="utf-8") @@ -73,12 +73,12 @@ def decide_main(argv: list[str]) -> None: from dataclasses import replace from .checkpoint import LoadOptions, load_options_from_args from .runtime import JevModel - if letter_options is not None and ( + if choice_options is not None and ( args.cuda_graphs or args.cuda_graph_max_tokens is not None or any(getattr(args, item.name) is not None for item in fields(InferenceOptions)) ): - parser.error("CUDA graph and native inference-limit flags cannot be used with --readout letter; " - "use --letter-max-tokens") + parser.error("CUDA graph and native inference-limit flags cannot be used with --readout choice; " + "use --choice-max-tokens") options = load_options_from_args(args) if args.cuda_graphs or args.cuda_graph_max_tokens is not None: options = options or LoadOptions.from_env() @@ -87,11 +87,11 @@ def decide_main(argv: list[str]) -> None: if args.cuda_graph_max_tokens is not None else options.cuda_graph_max_tokens)) settings = ({ - "readout": "letter", - "letter_temperature": letter_options.temperature, - "letter_pointer_weight": letter_options.pointer_weight, - "letter_max_tokens": letter_options.max_tokens, - } if letter_options is not None else { + "readout": "choice", + "choice_temperature": choice_options.temperature, + "choice_native_weight": choice_options.native_weight, + "choice_max_tokens": choice_options.max_tokens, + } if choice_options is not None else { "inference_options": inference_options_from_args(args), }) client = JevModel.from_pretrained( diff --git a/jevany/demos/static/app.js b/jevany/demos/static/app.js index b7a51d1..2f43c48 100644 --- a/jevany/demos/static/app.js +++ b/jevany/demos/static/app.js @@ -217,7 +217,8 @@ async function liveStep(action) { } function renderConnection() { const media = link.media || {}, served = link.served; - const readout = served && served.readout === "letter" ? "letter" : served && served.decision_mode; + const readout = served && (served.readout === "choice" || served.readout === "letter") + ? "choice" : served && served.decision_mode; $("connect-url").disabled = $("connect-model").disabled = !link.editable; if (!$("connect-url").value) $("connect-url").value = link.base_url || link.default_base_url || ""; if (!$("connect-model").value && link.model && link.model !== "jevany-latest") $("connect-model").value = link.model; diff --git a/jevany/letter_predictor.py b/jevany/letter_predictor.py index 1459d74..078bba4 100644 --- a/jevany/letter_predictor.py +++ b/jevany/letter_predictor.py @@ -1,4 +1,4 @@ -"""Training-free option-letter inference over a frozen base or JevAny adapter. +"""Training-free choice-token inference over a frozen base or JevAny adapter. This is the in-process backend for :mod:`jevany.letter_readout`. It keeps the existing pointer path unchanged: a JevAny checkpoint can instead be applied to @@ -297,7 +297,7 @@ def __init__( self.device = device self.options = options or LoadOptions() if self.options.temperature is not None: - raise ValueError("native-head temperature is not used by letter readout; use temperature") + raise ValueError("native-head temperature is not used by choice readout; use temperature") if self.options.cuda_graphs: raise ValueError("CUDA graph capture is available only for native readout") if self.exact_cuda_kernels_applied: @@ -323,7 +323,7 @@ def __init__( raise ValueError("revision accompanies a base; pin checkpoint revisions in owner/repo@revision") loaded = self.checkpoint = Checkpoint(checkpoint) if loaded.meta.special_embeddings: - raise ValueError("letter readout is not validated for checkpoints with trained special embeddings") + raise ValueError("choice readout is not validated for checkpoints with trained special embeddings") self.preprocessor, decision_model = loaded.load(device, self.options) self.native_model = decision_model self.pointer_model = decision_model @@ -406,7 +406,7 @@ def __init__( if tied: embeddings = self.language_model.get_input_embeddings().weight if max(flat_token_ids) >= embeddings.shape[0]: - raise ValueError("letter choice token ID exceeds the tied vocabulary projection") + raise ValueError("choice token ID exceeds the tied vocabulary projection") index = torch.tensor(flat_token_ids, dtype=torch.long, device=embeddings.device) projection = embeddings.index_select(0, index).detach().clone() projection_bias = None @@ -438,6 +438,7 @@ def __init__( "adapter_scale": adapter_scale, "tied_output_embeddings": tied, "unused_full_output_head_released": released_output_head, + "choice_token_rows": len(flat_token_ids), "letter_choice_token_rows": len(flat_token_ids), "output_softcap": self.output_softcap, "temperature": self.temperature, @@ -503,7 +504,7 @@ def _chat_ids(self, prompt: str) -> torch.Tensor: ids = _one_row_input_ids(ids) if ids.shape[1] + 1 > self.effective_max_tokens: raise ContextLengthError( - f"letter prompt needs {ids.shape[1] + 1} tokens including the answer; " + f"choice prompt needs {ids.shape[1] + 1} tokens including the answer; " f"limit {self.effective_max_tokens}" ) return ids.to(self.device) @@ -567,7 +568,7 @@ def __call__(self, record: dict) -> dict: request = api_request(record) SystemOneRequest.model_validate(request) if request.get("media"): - raise ValueError("letter readout is text-only and does not accept media") + raise ValueError("choice readout is text-only and does not accept media") sync(self.device) started = time.perf_counter() letter_rows = [ diff --git a/jevany/letter_runtime.py b/jevany/letter_runtime.py index 2f41022..1ea0637 100644 --- a/jevany/letter_runtime.py +++ b/jevany/letter_runtime.py @@ -1,4 +1,4 @@ -"""System One runtime adapter for the training-free option-letter predictor.""" +"""System One runtime adapter for the training-free choice-token predictor.""" from __future__ import annotations @@ -7,6 +7,7 @@ from typing import Any from .api import SystemOneRequest, output_tokens, to_answers, to_record, validate_response +from .letter_readout import CHOICE_SYMBOLS def _unlabelled_record(request: SystemOneRequest) -> dict[str, Any]: @@ -41,7 +42,7 @@ def __post_init__(self) -> None: if not isinstance(self.model_id, str) or not self.model_id.strip(): raise ValueError("model_name must be a nonempty string") if self.predictor.checkpoint is None: - raise ValueError("serving letter readout requires a JevAny checkpoint") + raise ValueError("serving choice readout requires a JevAny checkpoint") @property def checkpoint(self): @@ -52,25 +53,26 @@ def aliases(self) -> list[str]: return [] if self.model_id == "jevany-latest" else ["jevany-latest"] def clear_cache(self) -> None: - """Letter readout currently keeps no mutable prefix cache.""" + """Choice readout currently keeps no mutable prefix cache.""" def describe(self) -> dict[str, Any]: - """Describe the effective letter readout and its checkpoint overlay.""" + """Describe the effective choice readout and its checkpoint overlay.""" checkpoint = self.checkpoint - pointer_model = self.predictor.pointer_model - capabilities = pointer_model.inference_capabilities + native_model = self.predictor.native_model + capabilities = native_model.inference_capabilities context_window = capabilities.context_window effective_window = self.predictor.effective_max_tokens - acceleration = getattr(pointer_model, "inference_acceleration", { + acceleration = getattr(native_model, "inference_acceleration", { "compile_mode": None, "lora_merged": False, "approximate_bf16_merge": False, "cuda_graphs": None, }) - letter_readout = dict(self.predictor.provenance) - if self.predictor.pointer_weight: - letter_readout["pointer_temperature"] = pointer_model.temperature + choice_readout = dict(self.predictor.provenance) + if self.predictor.native_weight: + choice_readout["native_temperature"] = native_model.temperature + choice_readout["pointer_temperature"] = native_model.temperature with self.lock: return { "id": self.model_id, @@ -79,13 +81,15 @@ def describe(self) -> dict[str, Any]: "base": checkpoint.meta.base, "lora": checkpoint.meta.lora, "device": self.predictor.device, - "device_map": getattr(pointer_model, "device_map", None), - "devices": getattr(pointer_model, "devices", [self.predictor.device]), + "device_map": getattr(native_model, "device_map", None), + "devices": getattr(native_model, "devices", [self.predictor.device]), "temperature": self.predictor.temperature, "decision_mode": checkpoint.meta.decision_mode, - "readout": "letter", - "letter_readout": letter_readout, - "backbone_adapter": pointer_model.backbone_adapter, + "readout": "choice", + "choice_readout": choice_readout, + # Legacy descriptor alias retained for older clients. + "letter_readout": dict(choice_readout), + "backbone_adapter": native_model.backbone_adapter, "branch_mode": "chat", "acceleration": acceleration, "capabilities": { @@ -98,7 +102,7 @@ def describe(self) -> dict[str, Any]: "state_tokens": effective_window, "branch_tokens": effective_window, "packed_tokens": effective_window, - "choices": 26, + "choices": len(CHOICE_SYMBOLS), }, "prefix_cache": { "enabled": False, @@ -111,12 +115,12 @@ def describe(self) -> dict[str, Any]: } def answer(self, request: SystemOneRequest) -> dict[str, Any]: - """Run letter inference and return a validated System One response.""" + """Run choice-token inference and return a validated System One response.""" if request.model not in (self.model_id, *self.aliases): raise ValueError(f"unknown model {request.model!r}; this deployment serves {self.model_id!r}") if request.media: - raise ValueError("letter readout does not support media requests") + raise ValueError("choice readout does not support media requests") _, metadata = to_record(request) with self.lock: prediction = self.predictor(_unlabelled_record(request)) diff --git a/jevany/readout.py b/jevany/readout.py index 99f8b45..387a1a1 100644 --- a/jevany/readout.py +++ b/jevany/readout.py @@ -11,69 +11,159 @@ import math -READOUTS = ("native", "letter") +# ``letter`` is accepted indefinitely as the legacy spelling, but is omitted +# from generated usage/help so new integrations consistently say ``choice``. +READOUTS = ("native", "choice") +_ACCEPTED_READOUTS = (*READOUTS, "letter") + + +def normalize_readout(readout: str) -> str: + """Return the public readout name while accepting the legacy alias.""" + + if readout == "letter": + return "choice" + if readout not in READOUTS: + raise ValueError("readout must be native or choice") + return readout @dataclass(frozen=True) -class LetterReadoutOptions: - """Runtime settings for the training-free option-letter readout.""" +class ChoiceReadoutOptions: + """Runtime settings for the training-free choice-token readout.""" temperature: float = 1.0 - pointer_weight: float = 0.0 + native_weight: float = 0.0 max_tokens: int = 16_384 def __post_init__(self) -> None: - for name in ("temperature", "pointer_weight"): + for name in ("temperature", "native_weight"): value = getattr(self, name) if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value): - raise ValueError(f"letter {name.replace('_', ' ')} must be finite and numeric") + raise ValueError(f"choice {name.replace('_', ' ')} must be finite and numeric") if self.temperature <= 0: - raise ValueError("letter temperature must be positive") - if not 0 <= self.pointer_weight <= 1: - raise ValueError("letter pointer weight must be in [0, 1]") + raise ValueError("choice temperature must be positive") + if not 0 <= self.native_weight <= 1: + raise ValueError("choice native weight must be in [0, 1]") if type(self.max_tokens) is not int or self.max_tokens < 2: - raise ValueError("letter max tokens must be an integer >= 2") + raise ValueError("choice max tokens must be an integer >= 2") + + @property + def pointer_weight(self) -> float: + """Legacy name for :attr:`native_weight`.""" + + return self.native_weight + + +@dataclass(frozen=True) +class LetterReadoutOptions: + """Legacy spelling of :class:`ChoiceReadoutOptions`.""" + + temperature: float = 1.0 + pointer_weight: float = 0.0 + max_tokens: int = 16_384 + + def __post_init__(self) -> None: + # Validate through the public representation so both APIs have exactly + # the same accepted values. + ChoiceReadoutOptions(self.temperature, self.pointer_weight, self.max_tokens) + + @property + def native_weight(self) -> float: + return self.pointer_weight + + def as_choice(self) -> ChoiceReadoutOptions: + return ChoiceReadoutOptions(self.temperature, self.pointer_weight, self.max_tokens) def add_readout_arguments(parser: argparse.ArgumentParser) -> None: - """Add the same readout selector and letter-only knobs to a CLI parser.""" + """Add shared readout arguments, with legacy flags accepted but hidden.""" parser.add_argument( - "--readout", choices=READOUTS, default="native", - help="native checkpoint head (default) or training-free option-letter readout", + "--readout", choices=_ACCEPTED_READOUTS, metavar="{native,choice}", default="native", + help="native checkpoint head (default) or training-free choice-token readout", ) parser.add_argument( - "--letter-temperature", type=float, - help="temperature for letter probability calibration (letter readout only; default 1.0)", + "--choice-temperature", type=float, + help="probability temperature for choice readout (default 1.0)", ) parser.add_argument( - "--letter-pointer-weight", type=float, - help="log-linear weight on the native pointer distribution (letter readout only; default 0)", + "--choice-native-weight", type=float, + help="log-linear weight on the checkpoint's native distribution (default 0)", ) parser.add_argument( - "--letter-max-tokens", type=int, - help="maximum chat-prompt tokens including the one-letter answer (letter readout only; default 16384)", + "--choice-max-tokens", type=int, + help="maximum chat-prompt tokens including the one-token answer (default 16384)", + ) + # Backward-compatible spellings. argparse.SUPPRESS keeps the preferred + # surface compact without removing existing scripts. + parser.add_argument("--letter-temperature", type=float, help=argparse.SUPPRESS) + parser.add_argument("--letter-pointer-weight", type=float, help=argparse.SUPPRESS) + parser.add_argument("--letter-max-tokens", type=int, help=argparse.SUPPRESS) + + +def resolve_readout_options( + readout: str = "native", *, + choice_temperature: float | None = None, + choice_native_weight: float | None = None, + choice_max_tokens: int | None = None, + letter_temperature: float | None = None, + letter_pointer_weight: float | None = None, + letter_max_tokens: int | None = None, +) -> tuple[str, ChoiceReadoutOptions | None]: + """Normalize aliases and resolve preferred/legacy setting spellings.""" + + normalized = normalize_readout(readout) + pairs = ( + ("temperature", "--choice-temperature", choice_temperature, + "--letter-temperature", letter_temperature), + ("native_weight", "--choice-native-weight", choice_native_weight, + "--letter-pointer-weight", letter_pointer_weight), + ("max_tokens", "--choice-max-tokens", choice_max_tokens, + "--letter-max-tokens", letter_max_tokens), + ) + values, used = {}, [] + for name, preferred_flag, preferred, legacy_flag, legacy in pairs: + if preferred is not None and legacy is not None: + raise ValueError(f"give at most one of {preferred_flag} and legacy {legacy_flag}") + if preferred is not None: + values[name] = preferred + used.append(preferred_flag) + elif legacy is not None: + values[name] = legacy + used.append(legacy_flag) + if normalized == "native": + if used: + raise ValueError(f"{', '.join(used)} require --readout choice") + return normalized, None + return normalized, ChoiceReadoutOptions(**values) + + +def choice_options_from_args(args: argparse.Namespace) -> ChoiceReadoutOptions | None: + """Return preferred choice settings from a shared CLI namespace.""" + + _, options = resolve_readout_options( + getattr(args, "readout", "native"), + choice_temperature=getattr(args, "choice_temperature", None), + choice_native_weight=getattr(args, "choice_native_weight", None), + choice_max_tokens=getattr(args, "choice_max_tokens", None), + letter_temperature=getattr(args, "letter_temperature", None), + letter_pointer_weight=getattr(args, "letter_pointer_weight", None), + letter_max_tokens=getattr(args, "letter_max_tokens", None), ) + return options def letter_options_from_args(args: argparse.Namespace) -> LetterReadoutOptions | None: - """Return letter settings, rejecting letter-only flags with native readout.""" - - values = { - "temperature": getattr(args, "letter_temperature", None), - "pointer_weight": getattr(args, "letter_pointer_weight", None), - "max_tokens": getattr(args, "letter_max_tokens", None), - } - if getattr(args, "readout", "native") == "native": - used = ["--letter-" + name.replace("_", "-") for name, value in values.items() if value is not None] - if used: - raise ValueError(f"{', '.join(used)} require --readout letter") - return None - return LetterReadoutOptions(**{ - name: value for name, value in values.items() if value is not None - }) + """Legacy wrapper returning the historical options type.""" + + options = choice_options_from_args(args) + return (None if options is None else LetterReadoutOptions( + options.temperature, options.native_weight, options.max_tokens, + )) __all__ = [ - "READOUTS", "LetterReadoutOptions", "add_readout_arguments", "letter_options_from_args", + "READOUTS", "ChoiceReadoutOptions", "LetterReadoutOptions", + "add_readout_arguments", "choice_options_from_args", "letter_options_from_args", + "normalize_readout", "resolve_readout_options", ] diff --git a/jevany/runtime.py b/jevany/runtime.py index 728f43b..1d1205e 100644 --- a/jevany/runtime.py +++ b/jevany/runtime.py @@ -14,6 +14,7 @@ from .device import default_device, sync from .inference import InferenceOptions from .model import DecisionModel +from .readout import resolve_readout_options DEFAULT_CHECKPOINT = "SimpleJev/JevAny-Qwen3.8-27B-LoRA" @@ -165,7 +166,9 @@ def from_pretrained( device: str | None = None, dtype: str | None = None, model_name: str | None = None, options: LoadOptions | None = None, inference_options: InferenceOptions | None = None, - readout: str = "native", letter_temperature: float | None = None, + readout: str = "native", choice_temperature: float | None = None, + choice_native_weight: float | None = None, choice_max_tokens: int | None = None, + letter_temperature: float | None = None, letter_pointer_weight: float | None = None, letter_max_tokens: int | None = None, ) -> "JevModel": """Load a local run or Hugging Face adapter ID (optionally ``owner/repo@revision``). @@ -173,20 +176,25 @@ def from_pretrained( The full backbone must fit on the selected device unless ``options.device_map`` (or JEVANY_DEVICE_MAP) splits it over the visible GPUs. ``dtype`` accepts fp32, fp16 or bf16; omission uses the checkpoint/environment settings. - ``readout='letter'`` replaces the checkpoint head with a training-free - option-letter projection; its temperature, pointer blend, and prompt - limit are deployment settings, not checkpoint metadata. Files used by - native media requests are trusted local paths. + ``readout='choice'`` replaces the checkpoint head with a training-free + choice-token projection; its temperature, native blend, and prompt + limit are deployment settings, not checkpoint metadata. ``letter`` and + ``letter_*`` remain accepted legacy aliases. Files used by native media + requests are trusted local paths. """ import torch - if readout not in ("native", "letter"): - raise ValueError("readout must be native or letter") - letter_settings = (letter_temperature, letter_pointer_weight, letter_max_tokens) - if readout == "native" and any(value is not None for value in letter_settings): - raise ValueError("letter readout options require readout='letter'") - if readout == "letter" and inference_options is not None: - raise ValueError("inference_options apply only to native readout; use letter_max_tokens") + readout, choice_options = resolve_readout_options( + readout, + choice_temperature=choice_temperature, + choice_native_weight=choice_native_weight, + choice_max_tokens=choice_max_tokens, + letter_temperature=letter_temperature, + letter_pointer_weight=letter_pointer_weight, + letter_max_tokens=letter_max_tokens, + ) + if readout == "choice" and inference_options is not None: + raise ValueError("inference_options apply only to native readout; use choice_max_tokens") device = default_device() if device is None else device if device not in ("cpu", "mps", "cuda"): @@ -203,21 +211,21 @@ def from_pretrained( options = replace(options, dtype=dtypes[dtype]) if device == "mps" and options.attn is None: options = replace(options, attn="sdpa") - if readout == "letter" and options.cuda_graphs: + if readout == "choice" and options.cuda_graphs: raise ValueError("CUDA graph capture is available only for native readout") - if readout == "letter" and options.temperature is not None: - raise ValueError("JEVANY_TEMPERATURE applies to the native head; use letter_temperature") + if readout == "choice" and options.temperature is not None: + raise ValueError("JEVANY_TEMPERATURE applies to the native head; use choice_temperature") predictor = None - if readout == "letter": + if readout == "choice": from .letter_predictor import LetterReadoutPredictor predictor = LetterReadoutPredictor( checkpoint=checkpoint, device=device, options=options, - temperature=1.0 if letter_temperature is None else letter_temperature, - pointer_weight=0.0 if letter_pointer_weight is None else letter_pointer_weight, - max_tokens=16_384 if letter_max_tokens is None else letter_max_tokens, + temperature=choice_options.temperature, + native_weight=choice_options.native_weight, + max_tokens=choice_options.max_tokens, ) loaded = predictor.checkpoint else: diff --git a/jevany/serve.py b/jevany/serve.py index a93d798..49944ff 100644 --- a/jevany/serve.py +++ b/jevany/serve.py @@ -3,7 +3,7 @@ """FastAPI server for prefill-only decisions. Run: uv run --extra serve python -m jevany.serve --run runs/rlcr --port 8008 -Use ``--readout letter`` for the training-free option-letter deployment path. +Use ``--readout choice`` for the training-free choice-token deployment path. TypeSafe-compatible: POST /v1/systemone and GET /v1/models (no auth). JEVANY_PREFIX_CACHE / JEVANY_PREFIX_MIN_TOKENS size the state-prefix cache; JEVANY_DATE_FACTS=1 enables deterministic date preprocessing. @@ -18,7 +18,13 @@ from .api import SystemOneRequest, with_date_facts from .checkpoint import LoadOptions, add_placement_arguments, load_options_from_args from .inference import InferenceOptions, add_inference_arguments, inference_options_from_args -from .readout import LetterReadoutOptions, add_readout_arguments, letter_options_from_args +from .readout import ( + ChoiceReadoutOptions, + LetterReadoutOptions, + add_readout_arguments, + choice_options_from_args, + normalize_readout, +) from .runtime import DEFAULT_CHECKPOINT, DecisionRuntime, JevModel DATE_FACTS = os.environ.get("JEVANY_DATE_FACTS", "0") == "1" MEDIA_ROOT = os.environ.get("JEVANY_MEDIA_ROOT") @@ -148,7 +154,8 @@ def create_app( model: JevModel | None = None, device: str | None = None, dtype: str | None = None, model_name: str | None = None, options: LoadOptions | None = None, inference_options: InferenceOptions | None = None, - readout: str = "native", letter_options: LetterReadoutOptions | None = None, + readout: str = "native", choice_options: ChoiceReadoutOptions | None = None, + letter_options: LetterReadoutOptions | None = None, ) -> FastAPI: """Build an isolated app, loading one checkpoint during ASGI startup. @@ -156,18 +163,21 @@ def create_app( and cache with Python callers. Loading options cannot accompany an injected model. Each worker loads its own full model; use one worker per device. """ - if model is not None and (readout != "native" or letter_options is not None or any( + readout = normalize_readout(readout) + if choice_options is not None and letter_options is not None: + raise ValueError("give at most one of choice_options and legacy letter_options") + if letter_options is not None: + choice_options = letter_options.as_choice() + if model is not None and (readout != "native" or choice_options is not None or any( value is not None for value in (checkpoint, device, dtype, model_name, options, inference_options) )): raise ValueError("pass either a loaded model or checkpoint loading options") - if readout not in ("native", "letter"): - raise ValueError("readout must be native or letter") - if readout == "native" and letter_options is not None: - raise ValueError("letter_options require readout='letter'") - if readout == "letter" and inference_options is not None: - raise ValueError("inference_options apply only to native readout; use letter_options.max_tokens") - if readout == "letter" and letter_options is None: - letter_options = LetterReadoutOptions() + if readout == "native" and choice_options is not None: + raise ValueError("choice_options require readout='choice'") + if readout == "choice" and inference_options is not None: + raise ValueError("inference_options apply only to native readout; use choice_options.max_tokens") + if readout == "choice" and choice_options is None: + choice_options = ChoiceReadoutOptions() @asynccontextmanager async def lifespan(application: FastAPI): @@ -175,11 +185,11 @@ async def lifespan(application: FastAPI): local = model else: settings = ({ - "readout": "letter", - "letter_temperature": letter_options.temperature, - "letter_pointer_weight": letter_options.pointer_weight, - "letter_max_tokens": letter_options.max_tokens, - } if letter_options is not None else { + "readout": "choice", + "choice_temperature": choice_options.temperature, + "choice_native_weight": choice_options.native_weight, + "choice_max_tokens": choice_options.max_tokens, + } if choice_options is not None else { "inference_options": inference_options, }) local = JevModel.from_pretrained( @@ -221,15 +231,15 @@ def main(argv=None): ap.add_argument("--port", type=int, default=8008) a = ap.parse_args(argv) try: - letter_options = letter_options_from_args(a) + choice_options = choice_options_from_args(a) except ValueError as error: ap.error(str(error)) - if letter_options is not None and ( + if choice_options is not None and ( a.cuda_graphs or a.cuda_graph_max_tokens is not None or any(getattr(a, item.name) is not None for item in fields(InferenceOptions)) ): - ap.error("CUDA graph and native inference-limit flags cannot be used with --readout letter; " - "use --letter-max-tokens") + ap.error("CUDA graph and native inference-limit flags cannot be used with --readout choice; " + "use --choice-max-tokens") options = load_options_from_args(a) if a.cuda_graphs or a.cuda_graph_max_tokens is not None: options = options or LoadOptions.from_env() @@ -239,8 +249,8 @@ def main(argv=None): application = create_app(a.run, device=a.device, dtype=a.dtype, model_name=a.model_name, options=options, inference_options=(inference_options_from_args(a) - if letter_options is None else None), - readout=a.readout, letter_options=letter_options) + if choice_options is None else None), + readout=normalize_readout(a.readout), choice_options=choice_options) import uvicorn uvicorn.run(application, host=a.host, port=a.port) diff --git a/tests/test_playground_workflow.py b/tests/test_playground_workflow.py index 46cbf36..9058463 100644 --- a/tests/test_playground_workflow.py +++ b/tests/test_playground_workflow.py @@ -140,12 +140,19 @@ def test_connecting_reports_identity_and_capabilities_from_v1_models(app, endpoi app.set_images(True) -def test_playground_displays_letter_deployment_readout(app, endpoint): +def test_playground_displays_choice_deployment_readout(app, endpoint): + endpoint.models[0]["readout"] = "choice" + assert app.connect(endpoint.url)["served"]["readout"] == "choice" + source = (Path(__file__).parents[1] / "jevany" / "demos" / "static" / "app.js").read_text() + assert 'served.readout === "choice" || served.readout === "letter"' in source + assert 'readout ? `${readout} readout`' in source + + +def test_playground_labels_legacy_letter_descriptor_as_choice(app, endpoint): endpoint.models[0]["readout"] = "letter" assert app.connect(endpoint.url)["served"]["readout"] == "letter" source = (Path(__file__).parents[1] / "jevany" / "demos" / "static" / "app.js").read_text() - assert 'served.readout === "letter" ? "letter"' in source - assert 'readout ? `${readout} readout`' in source + assert 'served.readout === "choice" || served.readout === "letter"' in source def test_playground_accepts_older_model_descriptors_without_readout(app, endpoint): diff --git a/tests/test_readout_integration.py b/tests/test_readout_integration.py index 6f8efcf..dee9c36 100644 --- a/tests/test_readout_integration.py +++ b/tests/test_readout_integration.py @@ -5,12 +5,14 @@ import pytest +from jevany import ChoiceReadoutOptions from jevany.api import Choice, Noul, Score, SystemOneRequest from jevany.inference import InferenceOptions from jevany.letter_runtime import LetterDecisionRuntime, _unlabelled_record from jevany.readout import ( LetterReadoutOptions, add_readout_arguments, + choice_options_from_args, letter_options_from_args, ) @@ -34,15 +36,18 @@ def __init__(self): }, device_map=None, devices=["cpu"], backbone_adapter="test", temperature=1.2, ) + self.native_model = self.pointer_model self.device = "cpu" self.temperature = 1.0 self.pointer_weight = 0.25 + self.native_weight = 0.25 self.max_tokens = 2048 self.effective_max_tokens = 2048 self.tokenizer = FakeTokenizer() self.provenance = { "method": "exact option-letter alias projection", "adapter_applied": True, + "native_weight": 0.25, "pointer_weight": 0.25, } self.last_record = None @@ -72,25 +77,38 @@ def decision_request(): ) -def test_letter_cli_options_are_shared_and_native_rejects_letter_flags(): +def test_choice_cli_is_public_and_legacy_letter_flags_remain_compatible(): parser = argparse.ArgumentParser() add_readout_arguments(parser) - assert letter_options_from_args(parser.parse_args([])) is None - actual = letter_options_from_args(parser.parse_args([ + help_text = parser.format_help() + assert "--readout {native,choice}" in help_text + assert "--choice-native-weight" in help_text + assert "--letter-temperature" not in help_text + assert choice_options_from_args(parser.parse_args([])) is None + actual = choice_options_from_args(parser.parse_args([ + "--readout", "choice", "--choice-temperature", "1.5", + "--choice-native-weight", "0.25", "--choice-max-tokens", "2048", + ])) + assert actual == ChoiceReadoutOptions(temperature=1.5, native_weight=0.25, max_tokens=2048) + legacy = letter_options_from_args(parser.parse_args([ "--readout", "letter", "--letter-temperature", "1.5", "--letter-pointer-weight", "0.25", "--letter-max-tokens", "2048", ])) - assert actual == LetterReadoutOptions(temperature=1.5, pointer_weight=0.25, max_tokens=2048) - with pytest.raises(ValueError, match="require --readout letter"): - letter_options_from_args(parser.parse_args(["--letter-temperature", "2"])) + assert legacy == LetterReadoutOptions(temperature=1.5, pointer_weight=0.25, max_tokens=2048) + with pytest.raises(ValueError, match="require --readout choice"): + choice_options_from_args(parser.parse_args(["--letter-temperature", "2"])) + with pytest.raises(ValueError, match="at most one"): + choice_options_from_args(parser.parse_args([ + "--readout", "choice", "--choice-temperature", "1", "--letter-temperature", "1", + ])) for arguments, message in [ - (["--readout", "letter", "--letter-temperature", "nan"], "finite"), - (["--readout", "letter", "--letter-temperature", "0"], "positive"), - (["--readout", "letter", "--letter-pointer-weight", "1.1"], r"\[0, 1\]"), - (["--readout", "letter", "--letter-max-tokens", "1"], ">= 2"), + (["--readout", "choice", "--choice-temperature", "nan"], "finite"), + (["--readout", "choice", "--choice-temperature", "0"], "positive"), + (["--readout", "choice", "--choice-native-weight", "1.1"], r"\[0, 1\]"), + (["--readout", "choice", "--choice-max-tokens", "1"], ">= 2"), ]: with pytest.raises(ValueError, match=message): - letter_options_from_args(parser.parse_args(arguments)) + choice_options_from_args(parser.parse_args(arguments)) def test_letter_runtime_maps_requests_and_reports_effective_readout(decision_request): @@ -107,12 +125,14 @@ def test_letter_runtime_maps_requests_and_reports_effective_readout(decision_req assert predictor.last_record["state"] == {"signal": "green"} assert [q["label"] for q in predictor.last_record["questions"].values()] == ["left", False, 0] description = runtime.describe() - assert description["readout"] == "letter" - assert description["letter_readout"]["pointer_weight"] == 0.25 - assert description["letter_readout"]["pointer_temperature"] == 1.2 + assert description["readout"] == "choice" + assert description["choice_readout"]["native_weight"] == 0.25 + assert description["choice_readout"]["pointer_weight"] == 0.25 + assert description["choice_readout"]["native_temperature"] == 1.2 + assert description["letter_readout"] == description["choice_readout"] assert description["limits"] == { "state_tokens": 2048, "branch_tokens": 2048, - "packed_tokens": 2048, "choices": 26, + "packed_tokens": 2048, "choices": 52, } assert description["capabilities"]["media_types"] == [] assert not description["prefix_cache"]["enabled"] @@ -137,7 +157,7 @@ def test_unlabelled_record_adds_only_encoder_placeholders(decision_request): assert record["questions"]["score"]["label"] == 0 -def test_public_loader_dispatches_to_letter_predictor(monkeypatch): +def test_public_loader_dispatches_choice_settings_to_predictor(monkeypatch): from jevany import letter_predictor from jevany.runtime import JevModel @@ -152,34 +172,61 @@ def load(**kwargs): monkeypatch.setattr(letter_predictor, "LetterReadoutPredictor", load) local = JevModel.from_pretrained( "owner/checkpoint", device="cpu", model_name="letter-model", - readout="letter", letter_temperature=1.5, - letter_pointer_weight=0.25, letter_max_tokens=2048, + readout="choice", choice_temperature=1.5, + choice_native_weight=0.25, choice_max_tokens=2048, ) assert isinstance(local.runtime, LetterDecisionRuntime) assert seen["checkpoint"] == "owner/checkpoint" assert seen["temperature"] == 1.5 - assert seen["pointer_weight"] == 0.25 + assert seen["native_weight"] == 0.25 assert seen["max_tokens"] == 2048 - with pytest.raises(ValueError, match="require readout='letter'"): - JevModel.from_pretrained("unused", device="cpu", letter_temperature=2) + with pytest.raises(ValueError, match="require --readout choice"): + JevModel.from_pretrained("unused", device="cpu", choice_temperature=2) with pytest.raises(ValueError, match="only to native readout"): JevModel.from_pretrained( - "unused", device="cpu", readout="letter", + "unused", device="cpu", readout="choice", inference_options=InferenceOptions(), ) +def test_public_loader_accepts_legacy_letter_api(monkeypatch): + from jevany import letter_predictor + from jevany.runtime import JevModel + + predictor = FakeLetterPredictor() + predictor.checkpoint.path = "/tmp/checkpoint" + seen = {} + + def load(**kwargs): + seen.update(kwargs) + return predictor + + monkeypatch.setattr(letter_predictor, "LetterReadoutPredictor", load) + local = JevModel.from_pretrained( + "owner/checkpoint", device="cpu", model_name="letter-model", + readout="letter", letter_temperature=1.5, + letter_pointer_weight=0.25, letter_max_tokens=2048, + ) + + assert isinstance(local.runtime, LetterDecisionRuntime) + assert seen["temperature"] == 1.5 + assert seen["native_weight"] == 0.25 + assert seen["max_tokens"] == 2048 + + def test_create_app_rejects_cross_readout_settings(): pytest.importorskip("fastapi") from jevany.serve import create_app - with pytest.raises(ValueError, match="require readout='letter'"): - create_app(readout="native", letter_options=LetterReadoutOptions()) + with pytest.raises(ValueError, match="require readout='choice'"): + create_app(readout="native", choice_options=ChoiceReadoutOptions()) with pytest.raises(ValueError, match="only to native readout"): - create_app(readout="letter", inference_options=InferenceOptions()) + create_app(readout="choice", inference_options=InferenceOptions()) + # The old programmatic spelling still builds the same application path. + assert create_app(readout="letter", letter_options=LetterReadoutOptions()) -def test_decide_cli_forwards_letter_settings(tmp_path, monkeypatch, capsys): +def test_decide_cli_forwards_choice_settings(tmp_path, monkeypatch, capsys): from jevany.cli import decide_main from jevany.runtime import JevModel @@ -207,21 +254,21 @@ def load(*args, **kwargs): monkeypatch.setattr(JevModel, "from_pretrained", load) decide_main([ str(source), "--checkpoint", "owner/checkpoint", "--device", "cpu", - "--readout", "letter", "--letter-temperature", "1.5", - "--letter-pointer-weight", "0.25", "--letter-max-tokens", "2048", + "--readout", "choice", "--choice-temperature", "1.5", + "--choice-native-weight", "0.25", "--choice-max-tokens", "2048", ]) assert json.loads(capsys.readouterr().out)["answers"]["choice"]["choice"] == "right" assert seen["args"] == ("owner/checkpoint",) - assert seen["kwargs"]["readout"] == "letter" - assert seen["kwargs"]["letter_temperature"] == 1.5 - assert seen["kwargs"]["letter_pointer_weight"] == 0.25 - assert seen["kwargs"]["letter_max_tokens"] == 2048 + assert seen["kwargs"]["readout"] == "choice" + assert seen["kwargs"]["choice_temperature"] == 1.5 + assert seen["kwargs"]["choice_native_weight"] == 0.25 + assert seen["kwargs"]["choice_max_tokens"] == 2048 assert "inference_options" not in seen["kwargs"] with pytest.raises(SystemExit): decide_main([str(source), "--letter-temperature", "2"]) -def test_eval_cli_selects_letter_predictor_and_records_provenance(tmp_path, monkeypatch, capsys): +def test_eval_cli_selects_choice_predictor_and_records_provenance(tmp_path, monkeypatch, capsys): from jevany import benchmark, letter_predictor data = tmp_path / "data.jsonl" @@ -237,8 +284,9 @@ def test_eval_cli_selects_letter_predictor_and_records_provenance(tmp_path, monk class Predictor: temperature = 1.5 + native_weight = 0.25 pointer_weight = 0.25 - pointer_model = SimpleNamespace(temperature=1.2) + native_model = SimpleNamespace(temperature=1.2) provenance = {"method": "exact option-letter alias projection"} def __init__(self, **kwargs): @@ -255,18 +303,20 @@ def evaluate(records, predictor, directory, **kwargs): monkeypatch.setattr(letter_predictor, "LetterReadoutPredictor", Predictor) benchmark.main([ "--run", "owner/checkpoint", "--data", str(data), "--out", str(output), - "--device", "cpu", "--readout", "letter", "--letter-temperature", "1.5", - "--letter-pointer-weight", "0.25", "--letter-max-tokens", "2048", + "--device", "cpu", "--readout", "choice", "--choice-temperature", "1.5", + "--choice-native-weight", "0.25", "--choice-max-tokens", "2048", ]) assert json.loads(capsys.readouterr().out)["objective"] == 0.0 assert seen["checkpoint"] == "owner/checkpoint" assert seen["temperature"] == 1.5 - assert seen["pointer_weight"] == 0.25 + assert seen["native_weight"] == 0.25 assert seen["max_tokens"] == 2048 report = json.loads((output / "report.json").read_text()) - assert report["readout"] == "letter" - assert report["letter_readout"] == { - **Predictor.provenance, "pointer_temperature": 1.2, + assert report["readout"] == "choice" + assert report["choice_readout"] == { + **Predictor.provenance, "native_temperature": 1.2, "pointer_temperature": 1.2, } + assert report["letter_readout"] == report["choice_readout"] assert report["calibration_applied"] is True + assert report["calibration"]["native_temperature"] == 1.2 assert report["calibration"]["pointer_temperature"] == 1.2 From 97e242fe7e5a4b18f017296eb2e12c52d9bf6c48 Mon Sep 17 00:00:00 2001 From: Tianxin Wei Date: Fri, 2 Oct 2026 00:44:36 +0000 Subject: [PATCH 05/10] Add reproducible choice readout result builder --- scripts/build_choice_readout_results.py | 852 +++++++++++++++++++++ tests/test_build_choice_readout_results.py | 304 ++++++++ 2 files changed, 1156 insertions(+) create mode 100644 scripts/build_choice_readout_results.py create mode 100644 tests/test_build_choice_readout_results.py diff --git a/scripts/build_choice_readout_results.py b/scripts/build_choice_readout_results.py new file mode 100644 index 0000000..8eb6fcc --- /dev/null +++ b/scripts/build_choice_readout_results.py @@ -0,0 +1,852 @@ +#!/usr/bin/env python3 +"""Validate, summarize, and plot the five-run choice-readout matrix. + +The input directory must contain ``base4``, ``pointer4``, ``direct4``, +``base27``, and ``pointer27`` subdirectories. Each subdirectory contains the +``manifest.json`` emitted by ``evaluate_choice_readout_matrix.py`` and the +``ensemble.json`` emitted by ``fit_choice_ensemble.py``. + +The builder fails closed on incomplete panels, component/count drift, or +cross-run dataset-hash drift. It never substitutes partial coverage or a +media-stripped score for JevJudge full. +""" + +from __future__ import annotations + +import argparse +import copy +import hashlib +import json +import math +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_EXTERNAL = ROOT / "results/external-zero-shot-v1.json" +DEFAULT_JSON_OUT = ROOT / "results/choice-readout-v2.json" +DEFAULT_SVG_OUT = ROOT / "docs/choice-readout-results.svg" + +INK = "#213248" +MUTED = "#64748B" +RULE = "#E4E9EF" +CHOICE = "#278577" +NATIVE = "#8493A6" +TUNED = "#165F55" +EXTERNAL = "#A17BB7" +TYPESAFE = "#C08A42" + +RUN_SPECS = ( + { + "key": "base4", + "family": "Qwen3.5-4B", + "weights": "frozen_base", + "native_readout": None, + "native_decision_mode": None, + }, + { + "key": "pointer4", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "pointer", + "native_decision_mode": "pointer", + }, + { + "key": "direct4", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "direct-token", + "native_decision_mode": "lm_token", + }, + { + "key": "base27", + "family": "Qwen3.8-27B", + "weights": "frozen_base", + "native_readout": None, + "native_decision_mode": None, + }, + { + "key": "pointer27", + "family": "Qwen3.8-27B", + "weights": "jevany_sft", + "native_readout": "pointer", + "native_decision_mode": "pointer", + }, +) + +DATASETS = { + "transfer_calibration": { + "label": "Transfer-v9 development", + "role": "selection_only", + "records": 1_264, + "questions": 1_264, + "headline_n": 1_046, + }, + "transfer_test": { + "label": "Transfer-v9 test", + "role": "held_out_evaluation", + "records": 1_264, + "questions": 1_264, + "headline_n": 1_046, + }, + "typed_test": { + "label": "Typed Decisions test", + "role": "held_out_external_evaluation", + "records": 400, + "questions": 2_000, + "headline_n": 2_000, + }, + "jevbench_public": { + "label": "JevBench public development", + "role": "public_diagnostic_not_used_for_selection", + "records": 231, + "questions": 231, + "headline_n": 231, + }, + "jevjudge_text": { + "label": "JevJudge text", + "role": "held_out_external_evaluation", + "records": 724, + "questions": 724, + "headline_n": 724, + }, +} + +ZERO_SHOT_CONFIGS = ( + "choice_t1", + "native_shipped", + "fixed_blend_0_5", +) +TUNED_CONFIGS = ( + "transfer_dev_calibrated_choice", + "transfer_dev_tuned_blend", +) +BASE_CONFIGS = {"choice_t1", "transfer_dev_calibrated_choice"} +CHECKPOINT_CONFIGS = BASE_CONFIGS | { + "native_shipped", + "fixed_blend_0_5", + "transfer_dev_tuned_blend", +} + + +def _read_object(path: Path) -> dict: + try: + value = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as error: + raise ValueError(f"missing input artifact: {path}") from error + except json.JSONDecodeError as error: + raise ValueError(f"invalid JSON artifact: {path}: {error}") from error + if not isinstance(value, dict): + raise ValueError(f"JSON artifact must be an object: {path}") + return value + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def _display_path(path: Path) -> str: + resolved = path.resolve() + try: + return str(resolved.relative_to(ROOT.resolve())) + except ValueError: + return str(resolved) + + +def _finite(value: Any) -> bool: + return ( + not isinstance(value, bool) + and isinstance(value, (int, float)) + and math.isfinite(value) + ) + + +def _probability(value: Any, context: str) -> float: + if not _finite(value) or not 0 <= value <= 1: + raise ValueError(f"{context} must be a finite number in [0, 1]") + return float(value) + + +def _positive(value: Any, context: str) -> float: + if not _finite(value) or value <= 0: + raise ValueError(f"{context} must be a finite positive number") + return float(value) + + +def _metric(metric: Any, expected_n: int, context: str) -> dict: + if not isinstance(metric, dict): + raise ValueError(f"{context} must be an object") + if metric.get("n") != expected_n: + raise ValueError( + f"{context} expected n={expected_n}, got {metric.get('n')!r}" + ) + correct = metric.get("correct") + if isinstance(correct, bool) or not isinstance(correct, int) or not 0 <= correct <= expected_n: + raise ValueError(f"{context}.correct must be an integer in [0, {expected_n}]") + accuracy = _probability(metric.get("accuracy"), f"{context}.accuracy") + if not math.isclose(accuracy, correct / expected_n, rel_tol=0, abs_tol=1e-12): + raise ValueError(f"{context}.accuracy does not equal correct / n") + for key in ( + "cross_entropy_from_gold", + "gold_entropy", + "kl_from_gold", + "brier", + "ece", + "mean_confidence", + ): + if key in metric and not _finite(metric[key]): + raise ValueError(f"{context}.{key} must be finite") + return copy.deepcopy(metric) + + +def _manifest_component_accuracy(report: dict, component: str, expected_n: int, context: str) -> float: + try: + clean = report["components"][component]["clean"] + except (KeyError, TypeError) as error: + raise ValueError(f"{context}: missing {component} clean report") from error + if clean.get("n") != expected_n: + raise ValueError( + f"{context}: {component} expected clean n={expected_n}, " + f"got {clean.get('n')!r}" + ) + return _probability(clean.get("acc"), f"{context}.{component}.clean.acc") + + +def _validate_source(spec: dict, manifest: dict, context: str) -> None: + source = manifest.get("source") + predictor = manifest.get("predictor") + if not isinstance(source, dict) or not isinstance(predictor, dict): + raise ValueError(f"{context}: missing source or predictor provenance") + is_base = spec["weights"] == "frozen_base" + if is_base: + if not source.get("base") or source.get("checkpoint") is not None: + raise ValueError(f"{context}: frozen-base run must use source.base only") + if predictor.get("adapter_applied") is not False: + raise ValueError(f"{context}: frozen-base run unexpectedly applied an adapter") + if manifest.get("checkpoint_artifacts") is not None: + raise ValueError(f"{context}: frozen-base run has checkpoint artifacts") + else: + if not source.get("checkpoint") or source.get("base") is not None: + raise ValueError(f"{context}: checkpoint run must use source.checkpoint only") + if predictor.get("adapter_applied") is not True: + raise ValueError(f"{context}: checkpoint run did not apply its adapter") + artifacts = manifest.get("checkpoint_artifacts") + if not isinstance(artifacts, dict) or not isinstance(artifacts.get("files"), dict): + raise ValueError(f"{context}: checkpoint artifact hashes are missing") + if predictor.get("native_decision_mode") != spec["native_decision_mode"]: + raise ValueError( + f"{context}: expected native_decision_mode={spec['native_decision_mode']!r}, " + f"got {predictor.get('native_decision_mode')!r}" + ) + + +def _load_run(run_root: Path, spec: dict) -> dict: + directory = run_root / spec["key"] + manifest_path = directory / "manifest.json" + ensemble_path = directory / "ensemble.json" + manifest = _read_object(manifest_path) + ensemble = _read_object(ensemble_path) + context = spec["key"] + + model = manifest.get("model") + if not isinstance(model, str) or not model.strip(): + raise ValueError(f"{context}: manifest model must be a non-empty string") + if ensemble.get("model") != model: + raise ValueError(f"{context}: manifest and ensemble model names differ") + _validate_source(spec, manifest, context) + + expected_components = {"choice"} + if spec["native_readout"] is not None: + expected_components.add("native") + reports = manifest.get("reports") + datasets = ensemble.get("datasets") + if not isinstance(reports, dict) or not isinstance(datasets, dict): + raise ValueError(f"{context}: missing report or ensemble datasets") + if set(DATASETS) - set(reports) or set(DATASETS) - set(datasets): + raise ValueError(f"{context}: one or more required datasets are missing") + + expected_configs = BASE_CONFIGS if spec["native_readout"] is None else CHECKPOINT_CONFIGS + summarized = {} + for dataset, expected in DATASETS.items(): + report = reports[dataset] + if report.get("records") != expected["records"]: + raise ValueError( + f"{context}/{dataset}: expected {expected['records']} records, " + f"got {report.get('records')!r}" + ) + if report.get("questions") != expected["questions"]: + raise ValueError( + f"{context}/{dataset}: expected {expected['questions']} questions, " + f"got {report.get('questions')!r}" + ) + components = set(report.get("components", {})) + executed = set(report.get("components_executed", components)) + if components != expected_components or executed != expected_components: + raise ValueError( + f"{context}/{dataset}: expected components {sorted(expected_components)}, " + f"got reports={sorted(components)}, executed={sorted(executed)}" + ) + component_accuracy = { + component: _manifest_component_accuracy( + report, component, expected["headline_n"], f"{context}/{dataset}" + ) + for component in sorted(expected_components) + } + + configurations = datasets[dataset] + if not isinstance(configurations, dict) or set(configurations) != expected_configs: + raise ValueError( + f"{context}/{dataset}: expected ensemble configs " + f"{sorted(expected_configs)}, got " + f"{sorted(configurations) if isinstance(configurations, dict) else configurations!r}" + ) + checked = { + name: _metric( + values, + expected["headline_n"], + f"{context}/{dataset}/{name}", + ) + for name, values in configurations.items() + } + expected_excluded = expected["questions"] - expected["headline_n"] + for name, values in checked.items(): + if values.get("excluded_non_headline_rows") != expected_excluded: + raise ValueError( + f"{context}/{dataset}/{name}: expected " + f"excluded_non_headline_rows={expected_excluded}" + ) + if not math.isclose( + checked["choice_t1"]["accuracy"], component_accuracy["choice"], + rel_tol=0, abs_tol=1e-12, + ): + raise ValueError(f"{context}/{dataset}: choice component and ensemble are not aligned") + if checked["choice_t1"]["correct"] != checked["transfer_dev_calibrated_choice"]["correct"]: + raise ValueError( + f"{context}/{dataset}: temperature-only calibration changed hard decisions" + ) + if "native" in expected_components and not math.isclose( + checked["native_shipped"]["accuracy"], component_accuracy["native"], + rel_tol=0, abs_tol=1e-12, + ): + raise ValueError(f"{context}/{dataset}: native component and ensemble are not aligned") + + summarized[dataset] = { + "zero_shot": { + key: checked[key] for key in ZERO_SHOT_CONFIGS if key in checked + }, + "transfer_dev_tuned": { + key: checked[key] for key in TUNED_CONFIGS if key in checked + }, + } + + protocol = ensemble.get("protocol") + selected = ensemble.get("selected") + if not isinstance(protocol, dict) or protocol.get("selection_rows") != 1_046: + raise ValueError(f"{context}: ensemble must select on 1,046 Transfer-dev rows") + if not isinstance(selected, dict): + raise ValueError(f"{context}: missing selected ensemble settings") + choice_temperature = _positive( + selected.get("choice_temperature"), f"{context}.choice_temperature" + ) + blend_temperature = _positive( + selected.get("blend_temperature"), f"{context}.blend_temperature" + ) + native_weight = _probability( + selected.get("native_weight"), f"{context}.native_weight" + ) + if spec["native_readout"] is None and native_weight != 0: + raise ValueError(f"{context}: frozen base selected a native weight") + + dataset_hashes = manifest.get("datasets") + runtime = manifest.get("runtime") + if not isinstance(dataset_hashes, dict) or not dataset_hashes: + raise ValueError(f"{context}: dataset hashes are missing") + if not isinstance(runtime, dict) or not runtime.get("code_revision"): + raise ValueError(f"{context}: runtime/code revision is missing") + + return { + "key": spec["key"], + "model": model, + "family": spec["family"], + "weights": spec["weights"], + "native_readout": spec["native_readout"], + "artifacts": { + "manifest": { + "path": _display_path(manifest_path), + "sha256": _sha256(manifest_path), + }, + "ensemble": { + "path": _display_path(ensemble_path), + "sha256": _sha256(ensemble_path), + }, + }, + "source": copy.deepcopy(manifest.get("source")), + "checkpoint_artifacts": copy.deepcopy(manifest.get("checkpoint_artifacts")), + "base_loading": copy.deepcopy(manifest.get("base_loading")), + "predictor": copy.deepcopy(manifest.get("predictor")), + "runtime": copy.deepcopy(runtime), + "dataset_hashes": copy.deepcopy(dataset_hashes), + "evaluation_protocol": copy.deepcopy(manifest.get("protocol")), + "ensemble_protocol": copy.deepcopy(protocol), + "selected": { + **copy.deepcopy(selected), + "choice_temperature": choice_temperature, + "blend_temperature": blend_temperature, + "native_weight": native_weight, + }, + "datasets": summarized, + } + + +def _find_model(rows: Any, name: str, context: str) -> dict: + if not isinstance(rows, list): + raise ValueError(f"{context}: models must be a list") + matches = [row for row in rows if isinstance(row, dict) and row.get("model") == name] + if len(matches) != 1: + raise ValueError(f"{context}: expected exactly one {name!r} row") + _probability(matches[0].get("accuracy"), f"{context}/{name}.accuracy") + return copy.deepcopy(matches[0]) + + +def _load_external(path: Path) -> dict: + external = _read_object(path) + typed = external.get("typed_decisions") + text = external.get("jevjudge_text") + if not isinstance(typed, dict) or not isinstance(text, dict): + raise ValueError("external artifact is missing Typed Decisions or JevJudge text") + + typed_rows = typed.get("models") + if not isinstance(typed_rows, list): + raise ValueError("external Typed Decisions models must be a list") + published = [ + row for row in typed_rows + if isinstance(row, dict) + and row.get("kind") == "published_only" + and _finite(row.get("accuracy")) + ] + if not published: + raise ValueError("external artifact has no published Typed Decisions baseline") + typed_top = copy.deepcopy(max(published, key=lambda row: (row["accuracy"], row["model"]))) + _probability(typed_top.get("accuracy"), "Typed published top accuracy") + typed_typesafe = _find_model( + typed_rows, "Jev 1.13 (OpenRouter)", "external/typed_decisions" + ) + + text_rows = text.get("models") + text_typesafe = _find_model( + text_rows, "Jev 1.13 (OpenRouter)", "external/jevjudge_text" + ) + kev_rows = [ + row for row in text_rows + if isinstance(row, dict) + and row.get("kind") == "open_kev" + and _finite(row.get("accuracy")) + ] if isinstance(text_rows, list) else [] + if not kev_rows: + raise ValueError("external artifact has no scored JevJudge-text Kev baseline") + text_kev = copy.deepcopy(max(kev_rows, key=lambda row: (row["accuracy"], row["model"]))) + for row in (text_typesafe, text_kev): + if (row.get("answered"), row.get("requested")) != (724, 724): + raise ValueError( + f"external/jevjudge_text/{row['model']}: expected 724/724 coverage" + ) + + return { + "artifact": { + "path": _display_path(path), + "sha256": _sha256(path), + "artifact_version": external.get("artifact_version"), + "provenance": copy.deepcopy(external.get("provenance")), + }, + "typed_decisions": [typed_top, typed_typesafe], + "jevjudge_text": [text_typesafe, text_kev], + } + + +def build_results(run_root: Path, external_path: Path = DEFAULT_EXTERNAL) -> dict: + """Build a validated, JSON-serializable result artifact without writing it.""" + + run_root = Path(run_root) + runs = [_load_run(run_root, spec) for spec in RUN_SPECS] + if len({run["model"] for run in runs}) != len(runs): + raise ValueError("the five run manifests must have unique model labels") + + reference_hashes = runs[0]["dataset_hashes"] + for run in runs[1:]: + if run["dataset_hashes"] != reference_hashes: + raise ValueError( + f"{run['key']}: dataset hashes differ from {runs[0]['key']}" + ) + revisions = {run["runtime"].get("code_revision") for run in runs} + if len(revisions) != 1: + raise ValueError("all five runs must use one code revision") + + external = _load_external(Path(external_path)) + result_datasets = {} + for dataset, expected in DATASETS.items(): + result_datasets[dataset] = { + **copy.deepcopy(expected), + "runs": [ + { + "run": run["key"], + "model": run["model"], + "family": run["family"], + "weights": run["weights"], + "native_readout": run["native_readout"], + **copy.deepcopy(run["datasets"][dataset]), + } + for run in runs + ], + } + result_datasets["typed_test"]["external_baselines"] = copy.deepcopy( + external["typed_decisions"] + ) + result_datasets["jevjudge_text"]["external_baselines"] = copy.deepcopy( + external["jevjudge_text"] + ) + result_datasets["jevjudge_full"] = { + "label": "JevJudge full", + "records": 3_220, + "status": "unsupported", + "accuracy": None, + "reason": ( + "Training-free choice-token readout is text-only; image/video records " + "are not stripped or relabeled as a full-suite result." + ), + "native_checkpoint_results": "results/external-zero-shot-v1.json#jevjudge_full", + } + + public_runs = [] + for run in runs: + public_runs.append({key: copy.deepcopy(value) for key, value in run.items() if key != "datasets"}) + + return { + "schema_version": 2, + "artifact": "choice-readout-v2", + "title": "Training-free choice-token readout evaluation", + "generated_by": "scripts/build_choice_readout_results.py", + "method": { + "readout": ( + "Constrain the next token to exact one-character option IDs and " + "renormalize their probability mass; this can change argmax decisions." + ), + "training": "No additional training for the choice-token readout.", + "option_ids": "A-Z followed by a-z; at most 52 options.", + "calibration": ( + "Scalar temperature changes probabilities but not argmax accuracy." + ), + "ensemble": ( + "Native + choice configurations use log-linear/geometric pooling." + ), + }, + "protocol_groups": { + "zero_shot": { + "meaning": ( + "No Typed, JevJudge, JevBench, or Transfer-test labels tune the " + "readout configuration." + ), + "configurations": list(ZERO_SHOT_CONFIGS), + }, + "transfer_dev_tuned": { + "meaning": ( + "Weight and additional temperature are selected only on Transfer-v9 " + "development, then frozen for every displayed evaluation panel." + ), + "configurations": list(TUNED_CONFIGS), + "accuracy_note": ( + "Temperature-only calibrated choice has the same hard accuracy as " + "choice_t1; only a changed blend weight can change its argmax." + ), + }, + }, + "expected_counts": copy.deepcopy(DATASETS), + "dataset_hashes": copy.deepcopy(reference_hashes), + "code_revision": next(iter(revisions)), + "source_runs": public_runs, + "external_source": external["artifact"], + "datasets": result_datasets, + "legacy_results": { + "status": "retained unchanged", + "artifact": "results/letter-readout-v1.json", + }, + } + + +def write_results(path: Path, artifact: dict) -> None: + path = Path(path) + path.parent.mkdir(parents=True, exist_ok=True) + payload = json.dumps( + artifact, indent=2, ensure_ascii=False, allow_nan=False + ) + "\n" + path.write_text(payload, encoding="utf-8") + + +def _run_result(artifact: dict, dataset: str, run_key: str) -> dict: + matches = [ + row for row in artifact["datasets"][dataset]["runs"] + if row["run"] == run_key + ] + if len(matches) != 1: + raise ValueError(f"{dataset}: expected exactly one {run_key} result") + return matches[0] + + +def _plot_rows(artifact: dict, dataset: str) -> list[dict]: + def accuracy(run: str, group: str, config: str) -> float: + return _run_result(artifact, dataset, run)[group][config]["accuracy"] + + rows = [ + { + "label": "Frozen Qwen3.5 4B · Choice", + "group": "zero_shot", + "kind": "choice", + "accuracy": accuracy("base4", "zero_shot", "choice_t1"), + }, + { + "label": "JevAny 4B Pointer · Native", + "group": "zero_shot", + "kind": "native", + "accuracy": accuracy("pointer4", "zero_shot", "native_shipped"), + }, + { + "label": "JevAny 4B Pointer · Choice", + "group": "zero_shot", + "kind": "choice", + "accuracy": accuracy("pointer4", "zero_shot", "choice_t1"), + }, + { + "label": "JevAny 4B Direct-Token · Native", + "group": "zero_shot", + "kind": "native", + "accuracy": accuracy("direct4", "zero_shot", "native_shipped"), + }, + { + "label": "JevAny 4B Direct-Token · Choice", + "group": "zero_shot", + "kind": "choice", + "accuracy": accuracy("direct4", "zero_shot", "choice_t1"), + }, + { + "label": "Frozen Qwen3.8 27B · Choice", + "group": "zero_shot", + "kind": "choice", + "accuracy": accuracy("base27", "zero_shot", "choice_t1"), + }, + { + "label": "JevAny 27B Pointer · Native", + "group": "zero_shot", + "kind": "native", + "accuracy": accuracy("pointer27", "zero_shot", "native_shipped"), + }, + { + "label": "JevAny 27B Pointer · Choice", + "group": "zero_shot", + "kind": "choice", + "accuracy": accuracy("pointer27", "zero_shot", "choice_t1"), + }, + { + "label": "JevAny 4B Pointer · Tuned blend", + "group": "transfer_dev_tuned", + "kind": "tuned", + "accuracy": accuracy( + "pointer4", "transfer_dev_tuned", "transfer_dev_tuned_blend" + ), + }, + { + "label": "JevAny 4B Direct-Token · Tuned blend", + "group": "transfer_dev_tuned", + "kind": "tuned", + "accuracy": accuracy( + "direct4", "transfer_dev_tuned", "transfer_dev_tuned_blend" + ), + }, + { + "label": "JevAny 27B Pointer · Tuned blend", + "group": "transfer_dev_tuned", + "kind": "tuned", + "accuracy": accuracy( + "pointer27", "transfer_dev_tuned", "transfer_dev_tuned_blend" + ), + }, + ] + baselines = artifact["datasets"][dataset]["external_baselines"] + for baseline in baselines: + is_typesafe = baseline["model"] == "Jev 1.13 (OpenRouter)" + label = ( + "TypeSafe Jev 1.13" + if is_typesafe + else f"{baseline['model']} · " + + ("published" if dataset == "typed_test" else "open") + ) + rows.append({ + "label": label, + "group": "external", + "kind": "typesafe" if is_typesafe else "external", + "accuracy": baseline["accuracy"], + "published": dataset == "typed_test" and not is_typesafe, + }) + return rows + + +def render_svg(artifact: dict, output: Path) -> None: + """Render the compact two-panel README figure from a validated artifact.""" + + try: + import matplotlib + except ImportError as error: + raise RuntimeError("matplotlib is required to render the SVG") from error + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + from matplotlib.patches import Patch + + plt.rcParams.update({ + "font.family": ["DejaVu Sans", "sans-serif"], + "svg.fonttype": "none", + "svg.hashsalt": "jevany-choice-readout-v2", + "text.color": INK, + "hatch.linewidth": 0.7, + }) + panels = ( + ("typed_test", "Typed Decisions", "2,000 decisions · accuracy (%)"), + ("jevjudge_text", "JevJudge text", "724 records · accuracy (%)"), + ) + fig, axes = plt.subplots(1, 2, figsize=(18, 9.3), dpi=110, facecolor="white") + fig.subplots_adjust(left=0.19, right=0.988, bottom=0.12, top=0.78, wspace=0.30) + fig.text(0.035, 0.955, "Training-free choice readout", fontsize=24, weight="bold") + fig.text( + 0.035, + 0.914, + "Zero-shot readouts and Transfer-dev-tuned blends · no Typed or JevJudge labels used for tuning", + fontsize=12.2, + color=MUTED, + ) + fig.legend( + handles=[ + Patch(facecolor=CHOICE, label="Choice T=1 · zero-shot"), + Patch(facecolor=NATIVE, label="Native shipped · zero-shot"), + Patch(facecolor=TUNED, hatch="///", label="Transfer-dev-tuned blend"), + Patch(facecolor=TYPESAFE, label="TypeSafe Jev"), + Patch(facecolor=EXTERNAL, hatch="////", label="Published / open baseline"), + ], + loc="upper right", + bbox_to_anchor=(0.988, 0.982), + ncol=3, + frameon=False, + fontsize=10.1, + handlelength=1.2, + columnspacing=1.25, + ) + + description_parts = [] + colors = { + "choice": CHOICE, + "native": NATIVE, + "tuned": TUNED, + "external": EXTERNAL, + "typesafe": TYPESAFE, + } + positions = list(range(8)) + list(range(9, 12)) + list(range(13, 15)) + for ax, (dataset, title, scope) in zip(axes, panels, strict=True): + rows = _plot_rows(artifact, dataset) + if len(rows) != len(positions): + raise ValueError(f"{dataset}: plot requires exactly 15 rows") + values = [row["accuracy"] * 100 for row in rows] + xmax = min(100, max(70, int(math.ceil((max(values) + 4) / 10) * 10))) + ax.axhspan(-0.7, 7.65, color="#F7FAFC", zorder=0) + ax.axhspan(8.35, 11.65, color="#EDF7F4", zorder=0) + ax.axhspan(12.35, 14.65, color="#FAF7FC", zorder=0) + for y, row, value in zip(positions, rows, values, strict=True): + bars = ax.barh( + y, + value, + height=0.64, + color=colors[row["kind"]], + edgecolor="#805A2B" if row.get("published") else "none", + linewidth=0.7 if row.get("published") else 0, + zorder=3, + ) + if row["kind"] == "tuned": + bars[0].set_hatch("///") + elif row.get("published"): + bars[0].set_hatch("////") + ax.text( + min(value + xmax * 0.015, xmax * 0.985), + y, + f"{value:.1f}", + ha="right" if value > xmax * 0.91 else "left", + va="center", + fontsize=9.2, + color=INK, + weight="bold" if row["group"] != "external" else "normal", + ) + ax.text(0, -0.78, "ZERO-SHOT", fontsize=9.0, color=MUTED, weight="bold") + ax.text(0, 8.22, "TRANSFER-DEV-TUNED", fontsize=9.0, color=TUNED, weight="bold") + ax.text(0, 12.22, "EXTERNAL REFERENCE", fontsize=9.0, color=MUTED, weight="bold") + ax.set_title(title, loc="left", fontsize=17, color=INK, weight="bold", pad=28) + ax.text(0, 1.015, scope, transform=ax.transAxes, fontsize=10.2, color=MUTED) + ax.set_xlim(0, xmax) + ticks = list(range(0, xmax + 1, 20)) + ax.set_xticks(ticks) + ax.set_xticklabels([str(value) for value in ticks], fontsize=9.2, color=MUTED) + ax.set_yticks(positions) + ax.set_yticklabels([row["label"] for row in rows], fontsize=9.0, color=INK) + for tick, row in zip(ax.get_yticklabels(), rows, strict=True): + if row["group"] != "external": + tick.set_weight("bold" if row["kind"] == "tuned" else "normal") + ax.set_ylim(15.0, -1.1) + ax.tick_params(axis="x", length=0, pad=7) + ax.tick_params(axis="y", length=0, pad=7) + ax.set_axisbelow(True) + ax.grid(axis="x", color=RULE, linewidth=0.8) + for spine in ax.spines.values(): + spine.set_visible(False) + ax.axvline(0, color=RULE, linewidth=1) + description_parts.append( + f"{title}: " + ", ".join( + f"{row['label']} {row['accuracy'] * 100:.2f}%" for row in rows + ) + ) + + fig.text( + 0.035, + 0.044, + "JevJudge full is unsupported for this text-only readout. Choice temperature calibration is omitted here because it cannot change accuracy.", + fontsize=10.1, + color=MUTED, + ) + output = Path(output) + output.parent.mkdir(parents=True, exist_ok=True) + metadata = { + "Date": None, + "Title": "Training-free choice readout accuracy", + "Description": " ".join(description_parts), + } + fig.savefig(output, metadata=metadata) + plt.close(fig) + output.write_text( + "\n".join(line.rstrip() for line in output.read_text().splitlines()) + "\n", + encoding="utf-8", + ) + + +def main(argv: list[str] | None = None) -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--run-root", type=Path, required=True) + parser.add_argument("--external", type=Path, default=DEFAULT_EXTERNAL) + parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON_OUT) + parser.add_argument("--svg-out", type=Path, default=DEFAULT_SVG_OUT) + args = parser.parse_args(argv) + + artifact = build_results(args.run_root, args.external) + write_results(args.json_out, artifact) + render_svg(artifact, args.svg_out) + print(f"Wrote {_display_path(args.json_out)} and {_display_path(args.svg_out)}") + + +if __name__ == "__main__": + main() diff --git a/tests/test_build_choice_readout_results.py b/tests/test_build_choice_readout_results.py new file mode 100644 index 0000000..7cf760c --- /dev/null +++ b/tests/test_build_choice_readout_results.py @@ -0,0 +1,304 @@ +import copy +import json +from pathlib import Path + +import pytest + +from scripts.build_choice_readout_results import ( + DATASETS, + RUN_SPECS, + build_results, + render_svg, + write_results, +) + + +def _metric(n, correct): + accuracy = correct / n + return { + "n": n, + "correct": correct, + "accuracy": accuracy, + "cross_entropy_from_gold": 0.5, + "gold_entropy": 0.1, + "kl_from_gold": 0.4, + "brier": 0.2, + "ece": 0.03, + "mean_confidence": 0.7, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15, + } + + +def _write_json(path, value): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(value, indent=2) + "\n") + + +def _external_fixture(path): + value = { + "artifact_version": 5, + "provenance": {"fixture": {"sha256": "e" * 64}}, + "typed_decisions": { + "models": [ + { + "model": "Published runner-up", + "kind": "published_only", + "accuracy": 0.74, + }, + { + "model": "Published top", + "kind": "published_only", + "accuracy": 0.77, + }, + { + "model": "Jev 1.13 (OpenRouter)", + "kind": "external_api", + "accuracy": 0.73, + }, + ] + }, + "jevjudge_text": { + "models": [ + { + "model": "Jev 1.13 (OpenRouter)", + "kind": "external_api", + "accuracy": 0.65, + "answered": 724, + "requested": 724, + }, + { + "model": "Kev-4B", + "kind": "open_kev", + "accuracy": 0.54, + "answered": 724, + "requested": 724, + }, + { + "model": "Kev-27B", + "kind": "open_kev", + "accuracy": 0.64, + "answered": 724, + "requested": 724, + }, + ] + }, + } + _write_json(path, value) + + +def _run_fixture(root, spec, ordinal): + is_base = spec["weights"] == "frozen_base" + components = ["choice"] if is_base else ["choice", "native"] + model = f"fixture-{spec['key']}" + reports = {} + ensemble_datasets = {} + for dataset_index, (dataset, expected) in enumerate(DATASETS.items()): + n = expected["headline_n"] + choice_correct = n // 2 + ordinal + dataset_index + native_correct = choice_correct + 1 + choice_accuracy = choice_correct / n + native_accuracy = native_correct / n + reports[dataset] = { + "dataset": dataset, + "records": expected["records"], + "questions": expected["questions"], + "components_executed": components, + "components": { + "choice": {"clean": {"n": n, "acc": choice_accuracy}}, + }, + } + if not is_base: + reports[dataset]["components"]["native"] = { + "clean": {"n": n, "acc": native_accuracy} + } + choice = _metric(n, choice_correct) + choice["excluded_non_headline_rows"] = expected["questions"] - n + calibrated = copy.deepcopy(choice) + calibrated["cross_entropy_from_gold"] = 0.45 + configurations = { + "choice_t1": choice, + "transfer_dev_calibrated_choice": calibrated, + } + if not is_base: + native = _metric(n, native_correct) + fixed = _metric(n, min(n, native_correct + 1)) + tuned = _metric(n, min(n, native_correct + 2)) + for value in (native, fixed, tuned): + value["excluded_non_headline_rows"] = expected["questions"] - n + configurations.update({ + "native_shipped": native, + "fixed_blend_0_5": fixed, + "transfer_dev_tuned_blend": tuned, + }) + ensemble_datasets[dataset] = configurations + + hashes = { + "jevbench_development_sha256": "1" * 64, + "jevbench_manifest_sha256": "2" * 64, + "transfer_manifest_sha256": "3" * 64, + "transfer_development_sha256": "4" * 64, + "transfer_test_sha256": "5" * 64, + "typed_revision": "6" * 40, + "typed_test_sha256": "7" * 64, + "jevjudge_test_sha256": "8" * 64, + "jevjudge_manifest_sha256": "9" * 64, + } + manifest = { + "model": model, + "source": { + "base": f"fixture/{spec['family']}" if is_base else None, + "checkpoint": None if is_base else f"fixture/{spec['key']}", + }, + "checkpoint_artifacts": None if is_base else { + "requested": f"fixture/{spec['key']}", + "resolved": f"/fixture/{spec['key']}", + "files": { + "adapter_model.safetensors": { + "sha256": f"{ordinal + 1}" * 64, + "bytes": 123, + } + }, + }, + "base_loading": {"requested": spec["family"], "resolved": "/fixture/base"}, + "predictor": { + "method": "training-free exact choice-token projection", + "adapter_applied": not is_base, + "native_decision_mode": spec["native_decision_mode"], + "canonical_base": spec["family"], + "canonical_revision": "a" * 40, + }, + "runtime": { + "python": "3.12.0", + "torch": "2.fixture", + "code_revision": "b" * 40, + }, + "protocol": {"temperature": 1.0, "kernel_policy": "fixture exact"}, + "datasets": hashes, + "reports": reports, + } + ensemble = { + "model": model, + "protocol": { + "selection_rows": 1046, + "selection_split": "Transfer-v9 development", + }, + "selected": { + "choice_temperature": 1.2, + "native_weight": 0.0 if is_base else 0.55, + "blend_temperature": 1.1, + "selection_objective": "fixture", + }, + "datasets": ensemble_datasets, + } + directory = root / spec["key"] + _write_json(directory / "manifest.json", manifest) + _write_json(directory / "ensemble.json", ensemble) + + +@pytest.fixture +def matrix(tmp_path): + root = tmp_path / "runs" + for ordinal, spec in enumerate(RUN_SPECS): + _run_fixture(root, spec, ordinal) + external = tmp_path / "external.json" + _external_fixture(external) + return root, external + + +def test_build_results_preserves_all_five_run_types_and_protocol_groups(matrix): + root, external = matrix + + artifact = build_results(root, external) + + assert artifact["artifact"] == "choice-readout-v2" + assert [run["key"] for run in artifact["source_runs"]] == [ + "base4", "pointer4", "direct4", "base27", "pointer27" + ] + assert {run["weights"] for run in artifact["source_runs"]} == { + "frozen_base", "jevany_sft" + } + assert {run["native_readout"] for run in artifact["source_runs"]} == { + None, "pointer", "direct-token" + } + typed = artifact["datasets"]["typed_test"] + assert typed["questions"] == typed["headline_n"] == 2000 + assert typed["external_baselines"][0]["model"] == "Published top" + assert artifact["datasets"]["jevjudge_text"]["external_baselines"][1]["model"] == "Kev-27B" + assert artifact["datasets"]["jevjudge_full"] == { + "label": "JevJudge full", + "records": 3220, + "status": "unsupported", + "accuracy": None, + "reason": ( + "Training-free choice-token readout is text-only; image/video records " + "are not stripped or relabeled as a full-suite result." + ), + "native_checkpoint_results": "results/external-zero-shot-v1.json#jevjudge_full", + } + base = next(row for row in typed["runs"] if row["run"] == "base4") + direct = next(row for row in typed["runs"] if row["run"] == "direct4") + assert set(base["zero_shot"]) == {"choice_t1"} + assert set(base["transfer_dev_tuned"]) == {"transfer_dev_calibrated_choice"} + assert set(direct["zero_shot"]) == { + "choice_t1", "native_shipped", "fixed_blend_0_5" + } + assert set(direct["transfer_dev_tuned"]) == { + "transfer_dev_calibrated_choice", "transfer_dev_tuned_blend" + } + assert len(artifact["source_runs"][0]["artifacts"]["manifest"]["sha256"]) == 64 + assert artifact["source_runs"][0]["runtime"]["torch"] == "2.fixture" + + +def test_build_results_rejects_component_count_drift(matrix): + root, external = matrix + path = root / "pointer4" / "manifest.json" + manifest = json.loads(path.read_text()) + manifest["reports"]["typed_test"]["components"]["native"]["clean"]["n"] = 1999 + _write_json(path, manifest) + + with pytest.raises(ValueError, match="native expected clean n=2000"): + build_results(root, external) + + +def test_build_results_rejects_native_component_metric_misalignment(matrix): + root, external = matrix + path = root / "direct4" / "ensemble.json" + ensemble = json.loads(path.read_text()) + native = ensemble["datasets"]["jevjudge_text"]["native_shipped"] + native["correct"] += 1 + native["accuracy"] = native["correct"] / native["n"] + _write_json(path, ensemble) + + with pytest.raises(ValueError, match="native component and ensemble are not aligned"): + build_results(root, external) + + +def test_build_results_rejects_cross_run_dataset_hash_drift(matrix): + root, external = matrix + path = root / "direct4" / "manifest.json" + manifest = json.loads(path.read_text()) + manifest["datasets"]["typed_test_sha256"] = "0" * 64 + _write_json(path, manifest) + + with pytest.raises(ValueError, match="dataset hashes differ"): + build_results(root, external) + + +def test_synthetic_artifact_and_svg_are_written_only_to_requested_paths(matrix, tmp_path): + pytest.importorskip("matplotlib") + root, external = matrix + artifact = build_results(root, external) + json_out = tmp_path / "out" / "choice.json" + svg_out = tmp_path / "out" / "choice.svg" + + write_results(json_out, artifact) + render_svg(artifact, svg_out) + + assert json.loads(json_out.read_text())["schema_version"] == 2 + svg = svg_out.read_text() + assert "Training-free choice readout" in svg + assert "Typed Decisions" in svg + assert "JevJudge text" in svg + assert "TRANSFER-DEV-TUNED" in svg From d46eb271ae19d3a23c77f72c7f37d56c79ca01c2 Mon Sep 17 00:00:00 2001 From: Tianxin Wei Date: Fri, 2 Oct 2026 01:04:54 +0000 Subject: [PATCH 06/10] Add choice readout report appendix builder --- .github/workflows/ci.yml | 18 + docs/EXTERNAL_EVALUATION.md | 29 +- scripts/build_external_report_appendix.py | 467 +++++++++++++++++-- tests/test_build_external_report_appendix.py | 217 +++++++++ 4 files changed, 694 insertions(+), 37 deletions(-) create mode 100644 tests/test_build_external_report_appendix.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecdcd3e..366628c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,3 +64,21 @@ jobs: path: | ci-results.xml ci-dependencies.txt + + report-test: + name: report appendix + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.13' + cache: pip + cache-dependency-path: reports/requirements.txt + - name: Install report dependencies + run: python -m pip install --disable-pip-version-check -r reports/requirements.txt pytest==9.1.1 + - name: Test report artifact and PDF builders + run: >- + python -m pytest -q + tests/test_build_choice_readout_results.py + tests/test_build_external_report_appendix.py diff --git a/docs/EXTERNAL_EVALUATION.md b/docs/EXTERNAL_EVALUATION.md index 731db1d..0366469 100644 --- a/docs/EXTERNAL_EVALUATION.md +++ b/docs/EXTERNAL_EVALUATION.md @@ -152,9 +152,12 @@ and are not used in the figure. [`external-zero-shot-v1.json`](../results/external-zero-shot-v1.json), and the deterministic renderer is [`plot_external_zero_shot.py`](../scripts/plot_external_zero_shot.py). -- The two external-evaluation pages appended to the single merged technical - report are generated by - [`build_external_report_appendix.py`](../scripts/build_external_report_appendix.py). +- Four evaluation pages are appended to the single merged technical report by + [`build_external_report_appendix.py`](../scripts/build_external_report_appendix.py): + two external-evaluation pages in Appendix L and two training-free + choice-token pages in Appendix M. Appendix M reads + `results/choice-readout-v2.json` directly; it does not rasterize or convert + the README SVG. - The tracked artifact contains the aggregate rows needed to regenerate the figure. Raw local run locations and SHA-256 hashes are recorded for audit but are not included in the repository. JevAny full results come from @@ -190,20 +193,24 @@ build/report-repro-env/bin/python scripts/plot_external_zero_shot.py build/report-repro-env/bin/python scripts/build_external_report_appendix.py \ --data results/external-zero-shot-v1.json \ --chart docs/external-zero-shot.png \ - --output build/JevAny_Tech_Report_External_Evaluation_Appendix.pdf \ + --choice-data results/choice-readout-v2.json \ + --output build/JevAny_Tech_Report_Evaluation_Appendices.pdf \ --base-report reports/JevAny_Tech_Report.pdf \ --merged-output reports/JevAny_Tech_Report.pdf \ --base-pages 19 +cp reports/JevAny_Tech_Report.pdf site/files/reports/JevAny_Tech_Report.pdf ``` `--base-pages 19` always takes the original report and agent-harness pages and -discards any previously appended external pages before adding the newly built -two-page appendix. The base and merged paths may therefore be identical without -growing the report on repeated runs. Both outputs are written to temporary -files in their destination directories and installed with `os.replace`; an -interrupted build cannot leave a partial tracked PDF. Appendix and merged PDF -metadata use the fixed creation and modification timestamp -`D:20261001000000Z`. With the pinned inputs and dependencies, repeated commands +discards any previously appended evaluation pages before rebuilding Appendices +L and M. The result is exactly 23 pages: 19 preserved pages plus four generated +pages. The base and merged paths may therefore be identical without growing the +report on repeated runs. Both outputs are written to temporary files in their +destination directories and installed with `os.replace`; an interrupted build +cannot leave a partial tracked PDF. `reports/` contains only one PDF—the merged +report; the `site/files/` copy is its byte-identical deployment mirror. Appendix +and merged PDF metadata use the fixed creation and modification timestamp +`D:20261002000000Z`. With the pinned inputs and dependencies, repeated commands produce byte-identical appendix and merged files. ### JevBench v1.5.4 diff --git a/scripts/build_external_report_appendix.py b/scripts/build_external_report_appendix.py index 435746f..5885e4c 100644 --- a/scripts/build_external_report_appendix.py +++ b/scripts/build_external_report_appendix.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 -"""Build and reproducibly merge the two-page external-evaluation appendix.""" +"""Build and reproducibly merge the external and choice-readout appendices.""" import argparse from contextlib import contextmanager from datetime import datetime, timezone import json +import math import os from pathlib import Path import tempfile @@ -25,21 +26,29 @@ RULE = "#DCE4EC" TEAL = "#278577" PALE = "#F3F7FA" -PDF_TIMESTAMP = datetime(2026, 10, 1, tzinfo=timezone.utc) -PDF_DATE_LITERAL = "D:20261001000000Z" +PDF_TIMESTAMP = datetime(2026, 10, 2, tzinfo=timezone.utc) +PDF_DATE_LITERAL = "D:20261002000000Z" +EXTERNAL_APPENDIX_PAGES = 2 +CHOICE_APPENDIX_PAGES = 2 +APPENDIX_PAGES = EXTERNAL_APPENDIX_PAGES + CHOICE_APPENDIX_PAGES +REPORT_BASE_PAGES = 19 +MERGED_REPORT_PAGES = REPORT_BASE_PAGES + APPENDIX_PAGES APPENDIX_METADATA = { - "Title": "JevAny Technical Report — External Evaluation Appendix", + "Title": "JevAny Technical Report — Evaluation Appendices", "Author": "SimpleJev", - "Subject": "Typed Decisions and JevJudge-Public v0.3 evaluation", + "Subject": "External evaluation and training-free choice-token readout", "Creator": "scripts/build_external_report_appendix.py", "Producer": "Matplotlib PDF backend", "CreationDate": PDF_TIMESTAMP, "ModDate": PDF_TIMESTAMP, } MERGED_METADATA = { - "/Title": "JevAny: Toward General Decision Intelligence — with Agent-Harness and External-Evaluation Appendices", + "/Title": "JevAny: Toward General Decision Intelligence — merged technical report", "/Author": "SimpleJev", - "/Subject": "JevAny model, bounded decision harness, and external decision-suite evaluation", + "/Subject": ( + "JevAny model, bounded decision harness, external evaluation, and " + "training-free choice-token readout" + ), "/Creator": "scripts/build_external_report_appendix.py", "/Producer": "pypdf", "/CreationDate": PDF_DATE_LITERAL, @@ -67,15 +76,24 @@ def atomic_destination(destination: Path): temporary.unlink(missing_ok=True) -def header(fig, title: str, subtitle: str, page: int) -> None: - fig.text(0.065, 0.955, "SimpleJev · TECHNICAL REPORT · APPENDIX L", color=TEAL, +def header( + fig, + title: str, + subtitle: str, + page: int, + *, + appendix: str = "L", + section: str = "External evaluation", + pages: int = EXTERNAL_APPENDIX_PAGES, +) -> None: + fig.text(0.065, 0.955, f"SimpleJev · TECHNICAL REPORT · APPENDIX {appendix}", color=TEAL, fontsize=8.5, weight="bold") fig.text(0.065, 0.905, title, color=INK, fontsize=22, weight="bold") fig.text(0.065, 0.872, subtitle, color=MUTED, fontsize=9.5) fig.lines.append(plt.Line2D([0.065, 0.935], [0.845, 0.845], transform=fig.transFigure, color=RULE, linewidth=1)) fig.text(0.065, 0.035, "JevAny · Toward General Decision Intelligence", color=MUTED, fontsize=7.5) - fig.text(0.935, 0.035, f"External evaluation · {page}/2", color=MUTED, + fig.text(0.935, 0.035, f"{section} · {page}/{pages}", color=MUTED, fontsize=7.5, ha="right") @@ -173,15 +191,407 @@ def results_page(pdf: PdfPages, data: dict) -> None: plt.close(fig) -def build_appendix(data_path: Path, chart_path: Path, output: Path) -> None: - data = json.loads(data_path.read_text()) +CHOICE_RUN_SPECS = { + "base4": ("Qwen3.5-4B", "frozen_base", None), + "pointer4": ("Qwen3.5-4B", "jevany_sft", "pointer"), + "direct4": ("Qwen3.5-4B", "jevany_sft", "direct-token"), + "base27": ("Qwen3.8-27B", "frozen_base", None), + "pointer27": ("Qwen3.8-27B", "jevany_sft", "pointer"), +} +CHOICE_DATASET_SPECS = ( + ("jevbench_public", "JevBench\npublic", 231, 231, 231), + ("transfer_test", "Transfer-v9\ntest", 1_264, 1_264, 1_046), + ("typed_test", "Typed\ntest", 400, 2_000, 2_000), + ("jevjudge_text", "JevJudge\ntext", 724, 724, 724), +) + + +def _read_json_object(path: Path, name: str) -> dict: + try: + value = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as error: + raise ValueError(f"missing {name}: {path}") from error + except json.JSONDecodeError as error: + raise ValueError(f"invalid {name}: {path}: {error}") from error + if not isinstance(value, dict): + raise ValueError(f"{name} must be a JSON object: {path}") + return value + + +def _choice_metric(row: dict, group: str, config: str, n: int, context: str) -> dict: + try: + metric = row[group][config] + except (KeyError, TypeError) as error: + raise ValueError(f"{context}: missing {group}/{config}") from error + if not isinstance(metric, dict) or metric.get("n") != n: + raise ValueError(f"{context}/{group}/{config}: expected n={n}") + correct = metric.get("correct") + accuracy = metric.get("accuracy") + if isinstance(correct, bool) or not isinstance(correct, int) or not 0 <= correct <= n: + raise ValueError(f"{context}/{group}/{config}: invalid correct count") + if ( + isinstance(accuracy, bool) + or not isinstance(accuracy, (int, float)) + or not math.isfinite(accuracy) + or not math.isclose(float(accuracy), correct / n, rel_tol=0, abs_tol=1e-12) + ): + raise ValueError(f"{context}/{group}/{config}: accuracy does not equal correct / n") + return metric + + +def load_choice_artifact(path: Path) -> dict: + """Load the validated v2 matrix consumed by Appendix M.""" + + artifact = _read_json_object(path, "choice-readout artifact") + if artifact.get("schema_version") != 2 or artifact.get("artifact") != "choice-readout-v2": + raise ValueError("choice-readout artifact must use schema_version=2") + method = artifact.get("method") + if not isinstance(method, dict) or method.get("option_ids") != ( + "A-Z followed by a-z; at most 52 options." + ): + raise ValueError("choice-readout artifact does not declare the v2 52-ID method") + + source_rows = artifact.get("source_runs") + if not isinstance(source_rows, list): + raise ValueError("choice-readout artifact has no source_runs list") + source_by_key = { + row.get("key"): row for row in source_rows if isinstance(row, dict) + } + if set(source_by_key) != set(CHOICE_RUN_SPECS) or len(source_rows) != len(source_by_key): + raise ValueError("choice-readout artifact must contain the five required source runs") + for key, (family, weights, native_readout) in CHOICE_RUN_SPECS.items(): + row = source_by_key[key] + if (row.get("family"), row.get("weights"), row.get("native_readout")) != ( + family, weights, native_readout + ): + raise ValueError(f"choice-readout source run {key!r} has inconsistent identity") + selected = row.get("selected") + if not isinstance(selected, dict): + raise ValueError(f"choice-readout source run {key!r} has no selected settings") + for field in ("native_weight", "choice_temperature", "blend_temperature"): + value = selected.get(field) + if ( + isinstance(value, bool) + or not isinstance(value, (int, float)) + or not math.isfinite(value) + ): + raise ValueError(f"choice-readout source run {key!r} has invalid {field}") + if not 0 <= selected["native_weight"] <= 1: + raise ValueError(f"choice-readout source run {key!r} has invalid native_weight") + if selected["choice_temperature"] <= 0 or selected["blend_temperature"] <= 0: + raise ValueError(f"choice-readout source run {key!r} has invalid temperature") + if weights == "frozen_base" and selected["native_weight"] != 0: + raise ValueError(f"frozen-base run {key!r} selected a native weight") + ensemble_protocol = row.get("ensemble_protocol") + if not isinstance(ensemble_protocol, dict) or ( + ensemble_protocol.get("selection_rows") != 1_046 + ): + raise ValueError(f"choice-readout source run {key!r} has invalid selection rows") + + datasets = artifact.get("datasets") + if not isinstance(datasets, dict): + raise ValueError("choice-readout artifact has no datasets object") + for dataset, _label, records, questions, n in CHOICE_DATASET_SPECS: + panel = datasets.get(dataset) + if not isinstance(panel, dict) or ( + panel.get("records"), panel.get("questions"), panel.get("headline_n") + ) != (records, questions, n): + raise ValueError( + f"choice-readout dataset {dataset!r} must declare " + f"records/questions/headline_n={records}/{questions}/{n}" + ) + rows = panel.get("runs") + if not isinstance(rows, list): + raise ValueError(f"choice-readout dataset {dataset!r} has no runs list") + by_key = {row.get("run"): row for row in rows if isinstance(row, dict)} + if set(by_key) != set(CHOICE_RUN_SPECS) or len(rows) != len(by_key): + raise ValueError(f"choice-readout dataset {dataset!r} must contain five runs") + for key, (family, weights, native_readout) in CHOICE_RUN_SPECS.items(): + row = by_key[key] + if (row.get("family"), row.get("weights"), row.get("native_readout")) != ( + family, weights, native_readout + ): + raise ValueError(f"{dataset}/{key}: inconsistent run identity") + _choice_metric(row, "zero_shot", "choice_t1", n, f"{dataset}/{key}") + if native_readout is not None: + _choice_metric(row, "zero_shot", "native_shipped", n, f"{dataset}/{key}") + _choice_metric( + row, + "transfer_dev_tuned", + "transfer_dev_tuned_blend", + n, + f"{dataset}/{key}", + ) + + full = datasets.get("jevjudge_full") + if not isinstance(full, dict) or ( + full.get("records"), full.get("status"), full.get("accuracy") + ) != (3_220, "unsupported", None): + raise ValueError("choice-token JevJudge full must be explicitly unsupported") + return artifact + + +def _rounded_box(fig, x, y, width, height, *, facecolor=PALE, edgecolor=RULE): + patch = FancyBboxPatch( + (x, y), width, height, transform=fig.transFigure, + boxstyle="round,pad=0.010,rounding_size=0.010", + facecolor=facecolor, edgecolor=edgecolor, linewidth=0.8, + ) + fig.patches.append(patch) + + +def choice_method_page(pdf: PdfPages, data: dict) -> None: + fig = plt.figure(figsize=(8.5, 11), facecolor="white") + header( + fig, + "Training-free choice-token readout", + "A constrained readout, probability calibration, and stacking are different operations", + 1, + appendix="M", + section="Choice-token readout", + pages=CHOICE_APPENDIX_PAGES, + ) + fig.text( + 0.065, 0.815, + "The frozen language model scores one exact option ID at the answer position. " + "No explanation is generated and no additional training is required for this readout.", + color=INK, fontsize=10.0, wrap=True, linespacing=1.45, + ) + + pipeline = ( + ("1", "Ordered options", "Preserve candidate order"), + ("2", "52 exact IDs", "A–Z followed by a–z"), + ("3", "One-token projection", "Read next-token mass"), + ("4", "Decision distribution", "Renormalize over IDs"), + ) + for index, (number, title, body) in enumerate(pipeline): + x = 0.065 + index * 0.222 + _rounded_box(fig, x, 0.685, 0.185, 0.095, facecolor="#F5F9F8") + fig.text(x + 0.014, 0.750, number, color=TEAL, fontsize=9, weight="bold") + fig.text(x + 0.042, 0.750, title, color=INK, fontsize=9.2, weight="bold") + fig.text(x + 0.014, 0.712, body, color=MUTED, fontsize=7.8) + if index < len(pipeline) - 1: + fig.text(x + 0.198, 0.730, "→", color=TEAL, fontsize=14, weight="bold") + + semantic_cards = ( + ( + "Choice-token · T=1", + "Training-free constrained\nprojection. This is a readout,\n" + "not temperature calibration;\nit can disagree with native.", + ), + ( + "Temperature calibration", + "Fit scalar T on separate\ndevelopment rows. T changes\n" + "probabilities; it cannot change\nargmax or accuracy.", + ), + ( + "Native + choice stack", + "Log-linear pooling can change\nrankings. Select native weight\n" + "and additional temperature on\nTransfer-v9 development only.", + ), + ) + for index, (title, body) in enumerate(semantic_cards): + x = 0.065 + index * 0.299 + _rounded_box(fig, x, 0.475, 0.270, 0.150) + fig.text(x + 0.016, 0.590, title, color=INK, fontsize=9.6, weight="bold") + fig.text( + x + 0.016, 0.558, body, color=MUTED, fontsize=7.8, + va="top", linespacing=1.35, + ) + + sources = {row["key"]: row for row in data["source_runs"]} + table = ( + ("Frozen base", "—", "Choice-token", f"{sources['base4']['family']}, {sources['base27']['family']}"), + ("JevAny SFT", "Pointer", "Choice-token", f"{sources['pointer4']['family']}, {sources['pointer27']['family']}"), + ("JevAny SFT", "Direct-Token", "Choice-token", sources["direct4"]["family"]), + ) + fig.text(0.065, 0.405, "Five-run comparison", color=INK, fontsize=12, weight="bold") + columns = (0.065, 0.245, 0.420, 0.610) + for x, label in zip(columns, ("Weights", "Native reference", "Training-free path", "Backbone family")): + fig.text(x, 0.370, label, color=MUTED, fontsize=8.0, weight="bold") + fig.lines.append(plt.Line2D([0.065, 0.935], [0.357, 0.357], transform=fig.transFigure, + color=RULE, linewidth=1)) + for index, values in enumerate(table): + y = 0.326 - index * 0.055 + for x, value in zip(columns, values): + fig.text(x, y, value, color=INK, fontsize=8.4) + fig.lines.append(plt.Line2D([0.065, 0.935], [y - 0.019, y - 0.019], + transform=fig.transFigure, color="#EEF2F6", linewidth=0.7)) + + _rounded_box(fig, 0.065, 0.095, 0.870, 0.105, facecolor="#F7FAFC") + fig.text(0.083, 0.169, "Scope", color=INK, fontsize=9.5, weight="bold") + fig.text( + 0.083, 0.142, + "Text only · up to 52 option IDs · JevJudge text 724 is supported.\n" + "The full 3,220-record multimodal suite is unsupported; media is never stripped into a score.", + color=MUTED, fontsize=8.2, linespacing=1.35, + ) + fig.text( + 0.083, 0.112, + "The earlier agent-harness appendix is unchanged; these readout experiments do not re-score agent tasks.", + color=MUTED, fontsize=8.2, + ) + pdf.savefig(fig) + plt.close(fig) + + +def _dataset_run(data: dict, dataset: str, run: str) -> dict: + matches = [row for row in data["datasets"][dataset]["runs"] if row["run"] == run] + if len(matches) != 1: + raise ValueError(f"{dataset}: expected one {run} row") + return matches[0] + + +def _choice_matrix_rows(data: dict) -> list[dict]: + sources = {row["key"]: row for row in data["source_runs"]} + definitions = ( + ("Frozen Qwen3.5-4B", "Choice T=1", "base4", "zero_shot", "choice_t1", "—"), + ("JevAny 4B Pointer", "Native", "pointer4", "zero_shot", "native_shipped", "shipped"), + ("", "Choice T=1", "pointer4", "zero_shot", "choice_t1", "—"), + ("", "Tuned stack", "pointer4", "transfer_dev_tuned", "transfer_dev_tuned_blend", None), + ("JevAny 4B Direct-Token", "Native", "direct4", "zero_shot", "native_shipped", "shipped"), + ("", "Choice T=1", "direct4", "zero_shot", "choice_t1", "—"), + ("", "Tuned stack", "direct4", "transfer_dev_tuned", "transfer_dev_tuned_blend", None), + ("Frozen Qwen3.8-27B", "Choice T=1", "base27", "zero_shot", "choice_t1", "—"), + ("JevAny 27B Pointer", "Native", "pointer27", "zero_shot", "native_shipped", "shipped"), + ("", "Choice T=1", "pointer27", "zero_shot", "choice_t1", "—"), + ("", "Tuned stack", "pointer27", "transfer_dev_tuned", "transfer_dev_tuned_blend", None), + ) + rows = [] + for model, readout, run, group, config, weight in definitions: + if weight is None: + weight = f"{sources[run]['selected']['native_weight']:.2f}" + metrics = [] + for dataset, _label, _records, _questions, n in CHOICE_DATASET_SPECS: + metric = _choice_metric( + _dataset_run(data, dataset, run), group, config, n, f"{dataset}/{run}" + ) + metrics.append(metric) + rows.append({ + "model": model, + "readout": readout, + "run": run, + "weight": weight, + "metrics": metrics, + "tuned": readout == "Tuned stack", + "native": readout == "Native", + }) + return rows + + +def choice_results_page(pdf: PdfPages, data: dict) -> None: + fig = plt.figure(figsize=(8.5, 11), facecolor="white") + header( + fig, + "Choice-token accuracy matrix", + "Matched runs · Transfer-dev settings frozen before every displayed evaluation panel", + 2, + appendix="M", + section="Choice-token readout", + pages=CHOICE_APPENDIX_PAGES, + ) + rows = _choice_matrix_rows(data) + ax = fig.add_axes([0.055, 0.285, 0.89, 0.530]) + ax.axis("off") + x_model, x_readout, x_weight = 0.00, 0.315, 0.495 + x_metrics = (0.635, 0.755, 0.875, 0.995) + ax.text(x_model, 1.035, "Model", transform=ax.transAxes, fontsize=8.0, + color=MUTED, weight="bold") + ax.text(x_readout, 1.035, "Readout", transform=ax.transAxes, fontsize=8.0, + color=MUTED, weight="bold") + ax.text(x_weight, 1.035, "w_native", transform=ax.transAxes, fontsize=8.0, + color=MUTED, weight="bold", ha="right") + for x, (_dataset, label, _records, _questions, n) in zip( + x_metrics, CHOICE_DATASET_SPECS, strict=True + ): + ax.text(x, 1.035, f"{label}\n(n={n:,})", transform=ax.transAxes, + fontsize=7.7, color=MUTED, weight="bold", ha="right", va="bottom") + ax.plot([0, 1], [0.995, 0.995], transform=ax.transAxes, color=RULE, linewidth=1) + + group_spans = ((0, 0), (1, 3), (4, 6), (7, 7), (8, 10)) + step, first_y = 0.082, 0.930 + for group_index, (start, end) in enumerate(group_spans): + top = first_y - start * step + 0.034 + bottom = first_y - end * step - 0.034 + if group_index % 2: + patch = FancyBboxPatch( + (-0.012, bottom), 1.022, top - bottom, + transform=ax.transAxes, boxstyle="round,pad=0.004,rounding_size=0.005", + facecolor="#F7FAFC", edgecolor="none", zorder=0, + ) + ax.add_patch(patch) + + maxima = [max(row["metrics"][index]["accuracy"] for row in rows) for index in range(4)] + for row_index, row in enumerate(rows): + y = first_y - row_index * step + color = TEAL if row["tuned"] else INK + ax.text(x_model, y, row["model"], transform=ax.transAxes, fontsize=8.0, + color=INK, va="center", weight="bold" if row["model"] else "normal") + ax.text(x_readout, y, row["readout"], transform=ax.transAxes, fontsize=8.0, + color=color, va="center", weight="bold" if row["tuned"] else "normal") + ax.text(x_weight, y, row["weight"], transform=ax.transAxes, fontsize=8.0, + color=MUTED, va="center", ha="right") + for metric_index, (x, metric) in enumerate(zip(x_metrics, row["metrics"], strict=True)): + best = math.isclose(metric["accuracy"], maxima[metric_index], rel_tol=0, abs_tol=1e-12) + ax.text( + x, y, f"{metric['accuracy'] * 100:.2f}", transform=ax.transAxes, + fontsize=8.1, color=TEAL if best else color, va="center", ha="right", + weight="bold" if best or row["tuned"] else "normal", + ) + ax.plot([0, 1], [y - 0.040, y - 0.040], transform=ax.transAxes, + color="#EEF2F6", linewidth=0.6, zorder=1) + + sources = {row["key"]: row for row in data["source_runs"]} + tuned = [] + for key, label in (("pointer4", "4B Pointer"), ("direct4", "4B Direct-Token"), + ("pointer27", "27B Pointer")): + selected = sources[key]["selected"] + tuned.append( + f"{label}: w={selected['native_weight']:.2f}, T={selected['blend_temperature']:.2f}" + ) + fig.text(0.065, 0.235, "Transfer-dev selection", color=INK, fontsize=10.5, weight="bold") + fig.text(0.065, 0.205, " · ".join(tuned), color=TEAL, fontsize=8.4, weight="bold") + fig.text( + 0.065, 0.169, + "Weights and additional temperatures use only 1,046 clean/knowable Transfer-v9 development rows.\n" + "Scalar T changes probabilities, not the accuracy values above.", + color=MUTED, fontsize=8.2, linespacing=1.35, + ) + fig.text( + 0.065, 0.125, + "Native = matched native rerun at the checkpoint's shipped inference temperature.\n" + "JevBench is a public development diagnostic; Transfer, Typed, and JevJudge text are held-out panels.", + color=MUTED, fontsize=8.2, linespacing=1.35, + ) + fig.text( + 0.065, 0.082, + "Choice-token is text-only: JevJudge text 724 is reported; full JevJudge 3,220 is unsupported.\n" + "Source and complete calibration metrics: results/choice-readout-v2.json.", + color=MUTED, fontsize=8.2, linespacing=1.35, + ) + pdf.savefig(fig) + plt.close(fig) + + +def build_appendix( + data_path: Path, + chart_path: Path, + choice_data_path: Path, + output: Path, +) -> None: + data = _read_json_object(data_path, "external-evaluation artifact") + choice_data = load_choice_artifact(choice_data_path) with atomic_destination(output) as temporary: with PdfPages(temporary, metadata=APPENDIX_METADATA) as pdf: overview_page(pdf, data, chart_path) results_page(pdf, data) + choice_method_page(pdf, choice_data) + choice_results_page(pdf, choice_data) appendix = PdfReader(temporary) - if len(appendix.pages) != 2: - raise RuntimeError(f"expected a two-page appendix, got {len(appendix.pages)} pages") + if len(appendix.pages) != APPENDIX_PAGES: + raise RuntimeError( + f"expected a {APPENDIX_PAGES}-page appendix, got {len(appendix.pages)} pages" + ) def page_invariants(page) -> tuple: @@ -199,16 +609,18 @@ def page_invariants(page) -> tuple: def merge_report(base_report: Path, appendix_report: Path, merged_output: Path, base_pages: int) -> None: - if base_pages < 1: - raise ValueError("--base-pages must be positive") + if base_pages != REPORT_BASE_PAGES: + raise ValueError(f"--base-pages must be exactly {REPORT_BASE_PAGES}") base = PdfReader(base_report) appendix = PdfReader(appendix_report) if len(base.pages) < base_pages: raise ValueError( f"base report has {len(base.pages)} pages, fewer than --base-pages={base_pages}" ) - if len(appendix.pages) != 2: - raise ValueError(f"appendix must have exactly two pages, got {len(appendix.pages)}") + if len(appendix.pages) != APPENDIX_PAGES: + raise ValueError( + f"appendix must have exactly {APPENDIX_PAGES} pages, got {len(appendix.pages)}" + ) writer = PdfWriter() writer.pdf_header = "%PDF-1.7" @@ -222,7 +634,7 @@ def merge_report(base_report: Path, appendix_report: Path, merged_output: Path, with temporary.open("wb") as stream: writer.write(stream) merged = PdfReader(temporary) - expected_pages = base_pages + len(appendix.pages) + expected_pages = MERGED_REPORT_PAGES if len(merged.pages) != expected_pages: raise RuntimeError(f"expected {expected_pages} merged pages, got {len(merged.pages)}") for index in range(base_pages): @@ -230,10 +642,13 @@ def merge_report(base_report: Path, appendix_report: Path, merged_output: Path, raise RuntimeError(f"base page {index + 1} changed during merge") -def main() -> None: +def main(argv: list[str] | None = None) -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--data", type=Path, default=ROOT / "results/external-zero-shot-v1.json") parser.add_argument("--chart", type=Path, default=ROOT / "docs/external-zero-shot.png") + parser.add_argument( + "--choice-data", type=Path, default=ROOT / "results/choice-readout-v2.json" + ) parser.add_argument("--output", type=Path, required=True) parser.add_argument( "--base-report", @@ -245,12 +660,12 @@ def main() -> None: type=Path, help="merged report; may be the same path as --base-report", ) - parser.add_argument("--base-pages", type=int, default=19) - args = parser.parse_args() + parser.add_argument("--base-pages", type=int, default=REPORT_BASE_PAGES) + args = parser.parse_args(argv) if (args.base_report is None) != (args.merged_output is None): parser.error("--base-report and --merged-output must be provided together") - if args.base_pages < 1: - parser.error("--base-pages must be positive") + if args.base_pages != REPORT_BASE_PAGES: + parser.error(f"--base-pages must be exactly {REPORT_BASE_PAGES}") if args.base_report is not None: appendix_path = args.output.expanduser().absolute() base_path = args.base_report.expanduser().absolute() @@ -258,12 +673,12 @@ def main() -> None: if appendix_path in {base_path, merged_path}: parser.error("--output must differ from --base-report and --merged-output") - build_appendix(args.data, args.chart, args.output) + build_appendix(args.data, args.chart, args.choice_data, args.output) print(f"Wrote reproducible appendix: {args.output}") if args.base_report is not None: merge_report(args.base_report, args.output, args.merged_output, args.base_pages) print( - f"Wrote reproducible {args.base_pages + 2}-page merged report: " + f"Wrote reproducible {MERGED_REPORT_PAGES}-page merged report: " f"{args.merged_output}" ) diff --git a/tests/test_build_external_report_appendix.py b/tests/test_build_external_report_appendix.py new file mode 100644 index 0000000..6b6a62a --- /dev/null +++ b/tests/test_build_external_report_appendix.py @@ -0,0 +1,217 @@ +import json + +import pytest + +pytest.importorskip("matplotlib") +pytest.importorskip("pypdf") + +import matplotlib.pyplot as plt +from pypdf import PdfReader, PdfWriter + +from scripts.build_external_report_appendix import ( + CHOICE_DATASET_SPECS, + CHOICE_RUN_SPECS, + build_appendix, + load_choice_artifact, + merge_report, +) + + +def _write_json(path, value): + path.write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8") + + +def _external_artifact(path): + value = { + "typed_decisions": { + "models": [ + {"model": "JevAny-Qwen3.8-27B", "accuracy": 0.728}, + {"model": "Jev 1.13 (OpenRouter)", "accuracy": 0.727}, + ], + }, + "jevjudge_text": { + "models": [ + {"model": "JevAny-Qwen3.8-27B", "accuracy": 0.6644}, + {"model": "Jev 1.13 (OpenRouter)", "accuracy": 0.6506}, + {"model": "Kev-27B", "accuracy": 0.6423}, + ], + }, + "jevjudge_full": { + "models": [ + { + "model": "JevAny-Qwen3.8-27B", + "kind": "ours", + "accuracy": 0.6227, + "skill_role": 0.3555, + "skill_role_ci_95": [0.3284, 0.3818], + "nll": 0.878, + "ece": 0.094, + }, + { + "model": "Jeff-Qwen3.5-2B", + "kind": "open_external", + "accuracy": 0.4823, + "skill_role": 0.1357, + "skill_role_ci_95": [0.1080, 0.1599], + "nll": 1.105, + "ece": 0.137, + }, + ], + }, + } + _write_json(path, value) + + +def _metric(n, correct): + return {"n": n, "correct": correct, "accuracy": correct / n} + + +def _choice_artifact(path): + source_runs = [] + for index, (key, (family, weights, native)) in enumerate(CHOICE_RUN_SPECS.items()): + source_runs.append({ + "key": key, + "model": f"fixture-{key}", + "family": family, + "weights": weights, + "native_readout": native, + "selected": { + "native_weight": 0.0 if native is None else 0.40 + index / 100, + "choice_temperature": 1.20 + index / 100, + "blend_temperature": 1.10 + index / 100, + }, + "ensemble_protocol": {"selection_rows": 1_046}, + }) + + datasets = {} + for dataset_index, (dataset, _label, records, questions, n) in enumerate( + CHOICE_DATASET_SPECS + ): + runs = [] + for run_index, (key, (family, weights, native)) in enumerate( + CHOICE_RUN_SPECS.items() + ): + choice_correct = n // 2 + dataset_index * 2 + run_index + row = { + "run": key, + "model": f"fixture-{key}", + "family": family, + "weights": weights, + "native_readout": native, + "zero_shot": {"choice_t1": _metric(n, choice_correct)}, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": _metric(n, choice_correct), + }, + } + if native is not None: + row["zero_shot"].update({ + "native_shipped": _metric(n, choice_correct + 1), + "fixed_blend_0_5": _metric(n, choice_correct + 2), + }) + row["transfer_dev_tuned"]["transfer_dev_tuned_blend"] = _metric( + n, choice_correct + 3 + ) + runs.append(row) + datasets[dataset] = { + "records": records, + "questions": questions, + "headline_n": n, + "runs": runs, + } + datasets["jevjudge_full"] = { + "label": "JevJudge full", + "records": 3_220, + "status": "unsupported", + "accuracy": None, + "reason": "text-only fixture", + } + value = { + "schema_version": 2, + "artifact": "choice-readout-v2", + "method": { + "option_ids": "A-Z followed by a-z; at most 52 options.", + "calibration": "Scalar temperature changes probabilities but not argmax accuracy.", + }, + "source_runs": source_runs, + "datasets": datasets, + } + _write_json(path, value) + return value + + +@pytest.fixture +def appendix_inputs(tmp_path): + external = tmp_path / "external.json" + choice = tmp_path / "choice.json" + chart = tmp_path / "chart.png" + _external_artifact(external) + _choice_artifact(choice) + plt.imsave(chart, [[0.0, 0.5], [0.75, 1.0]], cmap="viridis") + return external, chart, choice + + +def test_build_appendix_renders_four_pages_directly_from_choice_json( + appendix_inputs, tmp_path +): + external, chart, choice = appendix_inputs + output = tmp_path / "appendices.pdf" + + build_appendix(external, chart, choice, output) + + pdf = PdfReader(output) + assert len(pdf.pages) == 4 + method_text = pdf.pages[2].extract_text() + results_text = pdf.pages[3].extract_text() + assert "APPENDIX M" in method_text + assert "52 exact IDs" in method_text + assert "not temperature calibration" in method_text + assert "Choice-token accuracy matrix" in results_text + assert "Matched runs" in results_text + assert "JevJudge text 724" in results_text + # base4 JevBench fixture: floor(231 / 2) = 115 => 49.78%. + assert "49.78" in results_text + + +def test_merge_report_preserves_first_19_pages_and_writes_exactly_23( + appendix_inputs, tmp_path +): + external, chart, choice = appendix_inputs + appendix = tmp_path / "appendices.pdf" + base = tmp_path / "base.pdf" + merged = tmp_path / "merged.pdf" + build_appendix(external, chart, choice, appendix) + writer = PdfWriter() + for _ in range(21): + writer.add_blank_page(width=612, height=792) + with base.open("wb") as stream: + writer.write(stream) + + merge_report(base, appendix, merged, base_pages=19) + + assert len(PdfReader(merged).pages) == 23 + + +def test_merge_report_rejects_any_base_page_count_other_than_19( + appendix_inputs, tmp_path +): + external, chart, choice = appendix_inputs + appendix = tmp_path / "appendices.pdf" + base = tmp_path / "base.pdf" + build_appendix(external, chart, choice, appendix) + writer = PdfWriter() + writer.add_blank_page(width=612, height=792) + with base.open("wb") as stream: + writer.write(stream) + + with pytest.raises(ValueError, match="exactly 19"): + merge_report(base, appendix, tmp_path / "merged.pdf", base_pages=18) + + +def test_choice_artifact_requires_full_3220_to_be_unsupported(tmp_path): + choice = tmp_path / "choice.json" + value = _choice_artifact(choice) + value["datasets"]["jevjudge_full"]["accuracy"] = 0.5 + _write_json(choice, value) + + with pytest.raises(ValueError, match="must be explicitly unsupported"): + load_choice_artifact(choice) From 01e5081e8a8293efb7d6b1555ae4853e3ae31393 Mon Sep 17 00:00:00 2001 From: Tianxin Wei Date: Fri, 2 Oct 2026 01:14:33 +0000 Subject: [PATCH 07/10] Strengthen report appendix regression tests --- .github/workflows/ci.yml | 6 + scripts/build_external_report_appendix.py | 49 ++- tests/test_build_external_report_appendix.py | 328 ++++++++++++++++++- 3 files changed, 366 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 366628c..985bb4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,13 @@ jobs: cache-dependency-path: reports/requirements.txt - name: Install report dependencies run: python -m pip install --disable-pip-version-check -r reports/requirements.txt pytest==9.1.1 + - name: Verify report environment + run: | + python -m pip check + python -c "import matplotlib, pypdf, pytest; print(matplotlib.__version__, pypdf.__version__, pytest.__version__)" - name: Test report artifact and PDF builders + env: + JEVANY_REPORT_TESTS_REQUIRED: '1' run: >- python -m pytest -q tests/test_build_choice_readout_results.py diff --git a/scripts/build_external_report_appendix.py b/scripts/build_external_report_appendix.py index 5885e4c..a5dddd6 100644 --- a/scripts/build_external_report_appendix.py +++ b/scripts/build_external_report_appendix.py @@ -4,6 +4,7 @@ import argparse from contextlib import contextmanager from datetime import datetime, timezone +import hashlib import json import math import os @@ -18,6 +19,7 @@ from matplotlib.backends.backend_pdf import PdfPages from matplotlib.patches import FancyBboxPatch from pypdf import PdfReader, PdfWriter +from pypdf.generic import IndirectObject ROOT = Path(__file__).resolve().parents[1] @@ -594,17 +596,62 @@ def build_appendix( ) +def _canonical_pdf_object(value, active: set[int] | None = None): + """Resolve PDF references into a stable, object-number-independent value.""" + + if isinstance(value, IndirectObject): + return _canonical_pdf_object(value.get_object(), active) + if active is None: + active = set() + if isinstance(value, dict): + marker = id(value) + if marker in active: + return ("cycle",) + active.add(marker) + try: + items = tuple(sorted( + ( + str(key), + _canonical_pdf_object(item, active), + ) + for key, item in value.items() + if str(key) != "/Length" + )) + stream = None + if hasattr(value, "get_data"): + stream = hashlib.sha256(value.get_data()).hexdigest() + return ("dict", items, stream) + finally: + active.remove(marker) + if isinstance(value, (list, tuple)): + return tuple(_canonical_pdf_object(item, active) for item in value) + if isinstance(value, bytes): + return ("bytes", hashlib.sha256(value).hexdigest()) + if value is None or isinstance(value, (bool, int, float, str)): + return value + return str(value) + + def page_invariants(page) -> tuple: """Return page properties that must survive the merge unchanged.""" contents = page.get_contents() content_bytes = contents.get_data() if contents is not None else b"" + annotations = [] + for reference in page.get("/Annots", []): + annotation = reference.get_object() + # /P is a back-reference to the owning page and would make the + # otherwise stable annotation target depend on PDF object numbers. + annotations.append(_canonical_pdf_object({ + key: value for key, value in annotation.items() if str(key) != "/P" + })) return ( tuple(float(value) for value in page.mediabox), tuple(float(value) for value in page.cropbox), page.rotation, content_bytes, page.extract_text() or "", - len(page.get("/Annots", [])), + _canonical_pdf_object(page.get("/Resources")), + tuple(annotations), ) diff --git a/tests/test_build_external_report_appendix.py b/tests/test_build_external_report_appendix.py index 6b6a62a..43fffe1 100644 --- a/tests/test_build_external_report_appendix.py +++ b/tests/test_build_external_report_appendix.py @@ -1,19 +1,36 @@ import json +import os +import shutil +from datetime import datetime, timezone import pytest -pytest.importorskip("matplotlib") -pytest.importorskip("pypdf") +try: + import matplotlib + import pypdf +except ImportError: + if os.environ.get("JEVANY_REPORT_TESTS_REQUIRED") == "1": + raise + pytest.skip("report dependencies are installed in the report-test job", allow_module_level=True) import matplotlib.pyplot as plt +from matplotlib.backends.backend_pdf import PdfPages from pypdf import PdfReader, PdfWriter from scripts.build_external_report_appendix import ( CHOICE_DATASET_SPECS, CHOICE_RUN_SPECS, + _choice_matrix_rows, build_appendix, load_choice_artifact, merge_report, + page_invariants, +) +from scripts.build_choice_readout_results import ( + DATASETS as PRODUCER_DATASETS, + RUN_SPECS as PRODUCER_RUN_SPECS, + build_results, + write_results, ) @@ -23,17 +40,40 @@ def _write_json(path, value): def _external_artifact(path): value = { + "artifact_version": 5, + "provenance": {"fixture": {"sha256": "e" * 64}}, "typed_decisions": { "models": [ {"model": "JevAny-Qwen3.8-27B", "accuracy": 0.728}, - {"model": "Jev 1.13 (OpenRouter)", "accuracy": 0.727}, + { + "model": "Jev 1.13 (OpenRouter)", + "kind": "external_api", + "accuracy": 0.727, + }, + { + "model": "Published top", + "kind": "published_only", + "accuracy": 0.770, + }, ], }, "jevjudge_text": { "models": [ {"model": "JevAny-Qwen3.8-27B", "accuracy": 0.6644}, - {"model": "Jev 1.13 (OpenRouter)", "accuracy": 0.6506}, - {"model": "Kev-27B", "accuracy": 0.6423}, + { + "model": "Jev 1.13 (OpenRouter)", + "kind": "external_api", + "accuracy": 0.6506, + "answered": 724, + "requested": 724, + }, + { + "model": "Kev-27B", + "kind": "open_kev", + "accuracy": 0.6423, + "answered": 724, + "requested": 724, + }, ], }, "jevjudge_full": { @@ -139,6 +179,161 @@ def _choice_artifact(path): return value +def _producer_metric(n, correct, excluded): + return { + "n": n, + "correct": correct, + "accuracy": correct / n, + "cross_entropy_from_gold": 0.5, + "gold_entropy": 0.1, + "kl_from_gold": 0.4, + "brier": 0.2, + "ece": 0.03, + "mean_confidence": 0.7, + "cohort": "clean and knowable", + "excluded_non_headline_rows": excluded, + "ece_bins": 15, + } + + +def _producer_run(root, spec, ordinal): + is_base = spec["weights"] == "frozen_base" + components = ["choice"] if is_base else ["choice", "native"] + model = f"producer-{spec['key']}" + reports = {} + ensemble_datasets = {} + for dataset_index, (dataset, expected) in enumerate(PRODUCER_DATASETS.items()): + n = expected["headline_n"] + excluded = expected["questions"] - n + choice_correct = n // 2 + ordinal + dataset_index + native_correct = choice_correct + 1 + reports[dataset] = { + "dataset": dataset, + "records": expected["records"], + "questions": expected["questions"], + "components_executed": components, + "components": { + "choice": {"clean": {"n": n, "acc": choice_correct / n}}, + }, + } + if not is_base: + reports[dataset]["components"]["native"] = { + "clean": {"n": n, "acc": native_correct / n} + } + choice = _producer_metric(n, choice_correct, excluded) + calibrated = dict(choice) + calibrated["cross_entropy_from_gold"] = 0.45 + configurations = { + "choice_t1": choice, + "transfer_dev_calibrated_choice": calibrated, + } + if not is_base: + configurations.update({ + "native_shipped": _producer_metric(n, native_correct, excluded), + "fixed_blend_0_5": _producer_metric(n, native_correct + 1, excluded), + "transfer_dev_tuned_blend": _producer_metric( + n, native_correct + 2, excluded + ), + }) + ensemble_datasets[dataset] = configurations + + hashes = { + "jevbench_development_sha256": "1" * 64, + "jevbench_manifest_sha256": "2" * 64, + "transfer_manifest_sha256": "3" * 64, + "transfer_development_sha256": "4" * 64, + "transfer_test_sha256": "5" * 64, + "typed_revision": "6" * 40, + "typed_test_sha256": "7" * 64, + "jevjudge_test_sha256": "8" * 64, + "jevjudge_manifest_sha256": "9" * 64, + } + manifest = { + "model": model, + "source": { + "base": f"fixture/{spec['family']}" if is_base else None, + "checkpoint": None if is_base else f"fixture/{spec['key']}", + }, + "checkpoint_artifacts": None if is_base else { + "requested": f"fixture/{spec['key']}", + "resolved": f"/fixture/{spec['key']}", + "files": { + "adapter_model.safetensors": { + "sha256": f"{ordinal + 1}" * 64, + "bytes": 123, + }, + }, + }, + "base_loading": {"requested": spec["family"], "resolved": "/fixture/base"}, + "predictor": { + "method": "training-free exact choice-token projection", + "adapter_applied": not is_base, + "native_decision_mode": spec["native_decision_mode"], + "canonical_base": spec["family"], + "canonical_revision": "a" * 40, + }, + "runtime": { + "python": "3.13.5", + "torch": "2.fixture", + "code_revision": "b" * 40, + }, + "protocol": {"temperature": 1.0, "kernel_policy": "fixture exact"}, + "datasets": hashes, + "reports": reports, + } + ensemble = { + "model": model, + "protocol": { + "selection_rows": 1_046, + "selection_split": "Transfer-v9 development", + }, + "selected": { + "choice_temperature": 1.2, + "native_weight": 0.0 if is_base else 0.50 + ordinal / 100, + "blend_temperature": 1.0 + ordinal / 10, + "selection_objective": "fixture", + }, + "datasets": ensemble_datasets, + } + directory = root / spec["key"] + directory.mkdir(parents=True) + _write_json(directory / "manifest.json", manifest) + _write_json(directory / "ensemble.json", ensemble) + + +def _base_report(path, pages=21): + metadata = { + "Title": "Synthetic uniquely identified base report", + "CreationDate": datetime(2026, 10, 2, tzinfo=timezone.utc), + "ModDate": datetime(2026, 10, 2, tzinfo=timezone.utc), + } + with PdfPages(path, metadata=metadata) as pdf: + for page_number in range(1, pages + 1): + fig = plt.figure(figsize=(8.5, 11), facecolor="white") + fig.text( + 0.1, + 0.8, + f"UNIQUE BASE PAGE {page_number:02d}", + fontsize=16, + url=f"https://example.test/base/{page_number}", + ) + fig.text(0.1, 0.7, f"resource marker {page_number * 17}", fontsize=9) + pdf.savefig(fig) + plt.close(fig) + + +def _annotation_uris(page): + uris = [] + for reference in page.get("/Annots", []): + annotation = reference.get_object() + action = annotation.get("/A") + if action is not None: + action = action.get_object() + if action.get("/URI") is not None: + uris.append(str(action["/URI"])) + return uris + + @pytest.fixture def appendix_inputs(tmp_path): external = tmp_path / "external.json" @@ -150,6 +345,19 @@ def appendix_inputs(tmp_path): return external, chart, choice +@pytest.fixture +def producer_artifact(tmp_path): + root = tmp_path / "producer-runs" + for ordinal, spec in enumerate(PRODUCER_RUN_SPECS): + _producer_run(root, spec, ordinal) + external = tmp_path / "producer-external.json" + _external_artifact(external) + artifact = build_results(root, external) + choice = tmp_path / "producer-choice.json" + write_results(choice, artifact) + return external, choice + + def test_build_appendix_renders_four_pages_directly_from_choice_json( appendix_inputs, tmp_path ): @@ -172,23 +380,111 @@ def test_build_appendix_renders_four_pages_directly_from_choice_json( assert "49.78" in results_text -def test_merge_report_preserves_first_19_pages_and_writes_exactly_23( +def test_producer_artifact_flows_into_pointer_direct_and_tuned_pdf_rows( + producer_artifact, tmp_path +): + external, choice = producer_artifact + chart = tmp_path / "producer-chart.png" + output = tmp_path / "producer-appendices.pdf" + plt.imsave(chart, [[0.0, 0.5], [0.75, 1.0]], cmap="viridis") + + loaded = load_choice_artifact(choice) + source_by_key = {row["key"]: row for row in loaded["source_runs"]} + assert source_by_key["pointer4"]["native_readout"] == "pointer" + assert source_by_key["direct4"]["native_readout"] == "direct-token" + assert source_by_key["pointer4"]["selected"]["native_weight"] == 0.51 + assert source_by_key["direct4"]["selected"]["native_weight"] == 0.52 + rows = _choice_matrix_rows(loaded) + assert [(row["model"], row["readout"], row["run"]) for row in rows] == [ + ("Frozen Qwen3.5-4B", "Choice T=1", "base4"), + ("JevAny 4B Pointer", "Native", "pointer4"), + ("", "Choice T=1", "pointer4"), + ("", "Tuned stack", "pointer4"), + ("JevAny 4B Direct-Token", "Native", "direct4"), + ("", "Choice T=1", "direct4"), + ("", "Tuned stack", "direct4"), + ("Frozen Qwen3.8-27B", "Choice T=1", "base27"), + ("JevAny 27B Pointer", "Native", "pointer27"), + ("", "Choice T=1", "pointer27"), + ("", "Tuned stack", "pointer27"), + ] + matrix = {(row["run"], row["readout"]): row for row in rows} + assert matrix[("pointer4", "Native")]["metrics"][0]["correct"] == 120 + assert matrix[("pointer4", "Tuned stack")]["metrics"][0]["correct"] == 122 + assert matrix[("direct4", "Native")]["metrics"][0]["correct"] == 121 + assert matrix[("direct4", "Tuned stack")]["metrics"][0]["correct"] == 123 + build_appendix(external, chart, choice, output) + + text = PdfReader(output).pages[3].extract_text() + assert "JevAny 4B Pointer" in text + assert "JevAny 4B Direct-Token" in text + assert text.count("Tuned stack") == 3 + assert "4B Pointer: w=0.51, T=1.10" in text + assert "4B Direct-Token: w=0.52, T=1.20" in text + assert "27B Pointer: w=0.54, T=1.40" in text + # JevBench producer values: Pointer native/tuned and Direct native/tuned. + for accuracy in ("51.95", "52.81", "52.38", "53.25"): + assert accuracy in text + + +def test_appendix_and_in_place_merge_are_deterministic_and_replace_old_pages( appendix_inputs, tmp_path ): external, chart, choice = appendix_inputs - appendix = tmp_path / "appendices.pdf" + appendix_a = tmp_path / "appendices-a.pdf" + appendix_b = tmp_path / "appendices-b.pdf" base = tmp_path / "base.pdf" - merged = tmp_path / "merged.pdf" - build_appendix(external, chart, choice, appendix) - writer = PdfWriter() - for _ in range(21): - writer.add_blank_page(width=612, height=792) - with base.open("wb") as stream: - writer.write(stream) + merged_a = tmp_path / "merged-a.pdf" + merged_b = tmp_path / "merged-b.pdf" + inplace = tmp_path / "inplace.pdf" + build_appendix(external, chart, choice, appendix_a) + build_appendix(external, chart, choice, appendix_b) + assert appendix_a.read_bytes() == appendix_b.read_bytes() + appendix_metadata = PdfReader(appendix_a).metadata + assert appendix_metadata["/CreationDate"] == "D:20261002000000Z" + assert appendix_metadata["/ModDate"] == "D:20261002000000Z" + assert appendix_metadata["/Title"] == "JevAny Technical Report — Evaluation Appendices" + + _base_report(base) + original = PdfReader(base) + original_invariants = [page_invariants(original.pages[index]) for index in range(19)] + assert "UNIQUE BASE PAGE 20" in original.pages[19].extract_text() + assert "UNIQUE BASE PAGE 21" in original.pages[20].extract_text() + merge_report(base, appendix_a, merged_a, base_pages=19) + merge_report(base, appendix_b, merged_b, base_pages=19) + assert merged_a.read_bytes() == merged_b.read_bytes() + + merged = PdfReader(merged_a) + assert len(merged.pages) == 23 + for index in range(19): + assert f"UNIQUE BASE PAGE {index + 1:02d}" in merged.pages[index].extract_text() + assert page_invariants(merged.pages[index]) == original_invariants[index] + assert _annotation_uris(merged.pages[index]) == [ + f"https://example.test/base/{index + 1}" + ] + generated_text = [merged.pages[index].extract_text() for index in range(19, 23)] + assert all("UNIQUE BASE PAGE 20" not in text for text in generated_text) + assert all("UNIQUE BASE PAGE 21" not in text for text in generated_text) + assert "APPENDIX L" in generated_text[0] + assert "APPENDIX L" in generated_text[1] + assert "APPENDIX M" in generated_text[2] + assert "APPENDIX M" in generated_text[3] + assert "Training-free choice-token readout" in generated_text[2] + assert "Choice-token accuracy matrix" in generated_text[3] + merged_metadata = merged.metadata + assert merged_metadata["/CreationDate"] == "D:20261002000000Z" + assert merged_metadata["/ModDate"] == "D:20261002000000Z" + assert merged_metadata["/Title"] == ( + "JevAny: Toward General Decision Intelligence — merged technical report" + ) - merge_report(base, appendix, merged, base_pages=19) + shutil.copyfile(base, inplace) + merge_report(inplace, appendix_a, inplace, base_pages=19) + first_inplace = inplace.read_bytes() + assert len(PdfReader(inplace).pages) == 23 + merge_report(inplace, appendix_a, inplace, base_pages=19) - assert len(PdfReader(merged).pages) == 23 + assert inplace.read_bytes() == first_inplace == merged_a.read_bytes() def test_merge_report_rejects_any_base_page_count_other_than_19( From 57a832aaf742803d15dcb8beba3389d5dd9f25c7 Mon Sep 17 00:00:00 2001 From: Tianxin Wei Date: Fri, 2 Oct 2026 01:32:10 +0000 Subject: [PATCH 08/10] Publish choice readout results and report --- README.md | 59 +- docs/CHOICE_READOUT.md | 198 ++ docs/EVALUATION.md | 35 +- docs/LETTER_READOUT.md | 173 +- docs/choice-readout-results.svg | 928 ++++++++ jevany/letter_predictor.py | 8 +- reports/JevAny_Tech_Report.pdf | Bin 289359 -> 303110 bytes results/choice-readout-v2.json | 2558 +++++++++++++++++++++ site/files/reports/JevAny_Tech_Report.pdf | Bin 289359 -> 303110 bytes 9 files changed, 3762 insertions(+), 197 deletions(-) create mode 100644 docs/CHOICE_READOUT.md create mode 100644 docs/choice-readout-results.svg create mode 100644 results/choice-readout-v2.json diff --git a/README.md b/README.md index 4ca72da..c298c71 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ where Jev helps and when to return control to the LLM. - [🛠️ 1.2 JevAny Training](#training) - [🚀 1.3 JevAny Deployment](#deployment) - [🤗 2. Pretrained Models](#pretrained-models) - - [Training-free letter readout](#letter-readout) + - [Training-free choice-token readout](#choice-readout) - [📊 3. Benchmark Results](#evaluation) - [⏱️ 3.1 Inference efficiency](#efficiency) - [🕹️ 4. Examples & Test Environments](#examples--test-environments) @@ -296,30 +296,43 @@ Pointer and direct-token models share the same API. Pointer supports up to See [readout choices](docs/TRAINING.md#pointer-and-direct-token-readouts) for training and accuracy tradeoffs. -### Training-free letter readout +### Training-free choice-token readout -The experimental letter readout presents up to 26 options as A–Z, then sums -the frozen language model's next-token probability mass for every vocabulary -token that decodes exactly to that uppercase letter. It can run on a base model -without training, apply a JevAny adapter before the same readout, or combine the -letter and native pointer distributions. The letter path is text-only and does -not change the checkpoint metadata. Select it with `--readout letter` in -`jevany eval`, `jevany decide`, or `jevany serve`; the checkpoint-native -readout remains the default. +Choice-token readout assigns the ordered options to the exact one-token IDs +`A–Z, a–z`, scores those 52 rows at the answer position, and renormalizes over +the available options. It needs no readout training and works on a frozen base, +after a JevAny Pointer or Direct-Token adapter, or in a log-linear stack with +the checkpoint-native distribution. It can change the ranking and the selected +answer; scalar temperature calibration only changes confidence. -[Method, limits and evaluation command](docs/LETTER_READOUT.md). +```bash +jevany eval --run SimpleJev/JevAny-Qwen3.5-4B-Direct-Token-LoRA \ + --suite /path/to/suite --out runs/choice --device cuda --readout choice +``` + +[Method, commands, limits and complete tables](docs/CHOICE_READOUT.md) · +[Machine-readable results](results/choice-readout-v2.json) + +[![Training-free choice-token, checkpoint-native, Transfer-dev-tuned blend, and external baseline accuracy on Typed Decisions and JevJudge text](docs/choice-readout-results.svg)](docs/CHOICE_READOUT.md#results) + +- Direct-Token 4B's Transfer-dev-tuned stack reaches **79.83%** on held-out + Transfer, **67.65%** on Typed Decisions, and **59.25%** on JevJudge text: + +0.96, +0.45, and +0.83 points over its native readout. +- Pointer 27B's tuned stack reaches **89.10%** on held-out Transfer and + **73.30%** on Typed, but drops from **66.44% to 64.36%** on JevJudge text. + The target-domain result decides whether stacking is useful. +- The frozen 4B choice path scores 52.75% on Typed; applying the Direct-Token + adapter raises the same choice path to 64.80%. On JevJudge text, the same + adapter slightly lowers it, from 58.70% to 57.60%. -[![Accuracy change from native pointer for letter readout and the fixed blend](docs/letter-readout-results.svg)](docs/LETTER_READOUT.md#results) +**Practical rule** -- The fixed 50/50 blend improved 27B on both diagnostics: +0.43 accuracy - points on JevBench public and +0.67 on Transfer-v9. Neither gain is - statistically significant. -- At 4B, the same blend gained +1.73 points on JevBench public but lost 0.10 - on Transfer-v9. Letter readout is a paired target-domain ablation, not a - universal upgrade. -- Letter-only measured a 1.05x/1.07x median speedup over native at 4B/27B - on one warmed H200 panel. Blending runs both paths and cost 1.81x/1.89x the - native median latency. +- Use choice-token readout when the model can reason over the options and the + bottleneck is extracting a decision; it cannot create missing task ability. +- Blend only when paired development errors show that native and choice rescue + each other, then freeze one weight before the target evaluation. +- Use temperature for probability calibration, not accuracy: it cannot change + the argmax. > **Benchmark units:** Cygnet's official **73.70** on JevBench v1.5.4 is a > four-axis composite over 1,624 open and sealed decisions, not accuracy. Its @@ -329,6 +342,7 @@ readout remains the default. ## 📊 3. Benchmark Results +The release table below uses each checkpoint's native readout. JevAny-Qwen3.8-27B leads both benchmarks and has the lowest NLL and Brier. Among 4B releases, direct-token leads on JevBench; pointer leads on Transfer. @@ -357,7 +371,8 @@ NLL, Brier and ECE are measured on Transfer. [Machine-readable results](results/model-family-v2.json) · [Method and ablation report](reports/JevAny_Tech_Report.pdf) -The external comparison uses the complete Typed Decisions test split, the full +The external comparison below also uses checkpoint-native readouts. It uses the +complete Typed Decisions test split, the full 3,220-record JevJudge multimodal suite, and its 724-record text slice. The same 13 models stay in the same order; `—` means unsupported native input or no matching result. diff --git a/docs/CHOICE_READOUT.md b/docs/CHOICE_READOUT.md new file mode 100644 index 0000000..ba70c79 --- /dev/null +++ b/docs/CHOICE_READOUT.md @@ -0,0 +1,198 @@ +# Training-free choice-token readout + +Choice-token readout turns an ordinary causal language model into a decision +readout without adding or training a head: + +1. Preserve the candidate order and assign the exact one-character IDs + `A–Z, a–z`. +2. Ask for one case-sensitive option ID with thinking disabled. +3. At the answer position, sum the mass of every vocabulary token that decodes + exactly to each available ID. +4. Renormalize over the available IDs and map the distribution back to the + original option keys. + +This operation can change the ranking and the selected answer. It is therefore +not temperature calibration. A scalar temperature changes probability values +but cannot change argmax accuracy. A native + choice stack is a third operation: +it pools two distributions and can change the answer. + +The same readout works on a frozen base model or after loading a JevAny Pointer +or Direct-Token adapter. No additional readout training is performed. + +## Use it + +The checkpoint-native readout remains the default. Select choice-token readout +with `--readout choice`: + +```bash +jevany eval \ + --run SimpleJev/JevAny-Qwen3.5-4B-Direct-Token-LoRA \ + --suite /path/to/jevbench-public-v1.4.2.2 \ + --out runs/choice-readout/qwen35-4b-direct \ + --device cuda --readout choice --choice-temperature 1.0 + +jevany decide request.json \ + --checkpoint SimpleJev/JevAny-Qwen3.5-4B-LoRA \ + --device cuda --dtype bf16 --readout choice + +jevany serve \ + --checkpoint SimpleJev/JevAny-Qwen3.5-4B-LoRA \ + --device cuda --dtype bf16 --readout choice +``` + +Use `--choice-native-weight` to pool the choice and checkpoint-native +distributions. This executes both paths. `--choice-max-tokens` controls the +choice prompt limit. The former `letter` selector and `--letter-*` flags remain +hidden compatibility aliases. + +The matrix evaluator also accepts a frozen base directly: + +```bash +python -m scripts.evaluate_choice_readout_matrix \ + --base Qwen/Qwen3.5-4B \ + --revision 851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a \ + --base-load-path /path/to/Qwen3.5-4B \ + --model-name Frozen-Qwen3.5-4B \ + --out runs/choice-readout/base4 \ + --jevbench /path/to/jevbench-public-v1.4.2.2 \ + --transfer /path/to/transfer-v9 \ + --typed /path/to/typed-decisions \ + --jevjudge /path/to/jevjudge-public +``` + +## Limits + +- Text input only; JevJudge full multimodal is unsupported and is never + converted into a text-only score. +- 1–52 options per question. +- Exact one-token aliases must exist in the tokenizer for every used ID. +- Choice-only runs use one chat-formatted prefill per question. A stack also + executes the checkpoint-native prefill. +- Matrix runs use exact math SDPA below 8,192 tokens and memory-linear fused + SDPA for longer prompts. All 724 JevJudge text records complete; no record is + truncated or rejected. + +## Evaluation protocol + +The v2 matrix uses the same prompt and data for five runs: + +- frozen Qwen3.5-4B; +- JevAny Qwen3.5-4B Pointer; +- JevAny Qwen3.5-4B Direct-Token; +- frozen Qwen3.8-27B; +- JevAny Qwen3.8-27B Pointer. + +Zero-shot rows use T=1 and no evaluation-label tuning. For tuned rows, each +model selects one native weight on the 1,046 clean/knowable Transfer-v9 +development decisions; fitted NLL and distance from 0.5 break accuracy ties. +One additional temperature is then fitted on the same development rows. The +weight and temperature are frozen before Transfer test, Typed Decisions, +JevJudge text, and the JevBench public diagnostic are scored. + +The native parity gates reproduce the released README results before any +held-out panel runs: + +| Checkpoint | Transfer development | JevBench public | +|---|---:|---:| +| JevAny 4B Pointer | 823/1,046 · 78.68% | 185/231 · 80.09% | +| JevAny 4B Direct-Token | 818/1,046 · 78.20% | 187/231 · 80.95% | +| JevAny 27B Pointer | 900/1,046 · 86.04% | 208/231 · 90.04% | + +## Results + +![Training-free choice-token, checkpoint-native, Transfer-dev-tuned blend, and external baseline accuracy on Typed Decisions and JevJudge text](choice-readout-results.svg) + +All values below are accuracy. JevBench is its 231-item public development +diagnostic. Transfer test contains 1,046 clean/knowable held-out decisions. +Typed covers 2,000 decisions in 400 test cases. JevJudge text covers all 724 +text records; it is not the 3,220-record multimodal suite. + +| Weights | Readout | JevBench public | Transfer test | Typed test | JevJudge text | +|---|---|---:|---:|---:|---:| +| Frozen Qwen3.5-4B | Choice T=1 | 79.65% | 68.45% | 52.75% | 58.70% | +| JevAny 4B Pointer | Native shipped | 80.09% | 77.92% | **63.50%** | 58.01% | +| | Choice T=1 | **81.39%** | 73.61% | 57.80% | 52.62% | +| | Tuned stack · w=.78 | 80.95% | **78.01%** | 62.90% | **59.53%** | +| JevAny 4B Direct-Token | Native shipped | 80.95% | 78.87% | 67.20% | 58.43% | +| | Choice T=1 | **81.39%** | 78.59% | 64.80% | 57.60% | +| | Tuned stack · w=.59 | 80.95% | **79.83%** | **67.65%** | **59.25%** | +| Frozen Qwen3.8-27B | Choice T=1 | 87.88% | 80.69% | 68.25% | 61.74% | +| JevAny 27B Pointer | Native shipped | **90.04%** | 87.95% | 72.80% | **66.44%** | +| | Choice T=1 | 89.61% | 86.04% | 72.60% | 57.87% | +| | Tuned stack · w=.52 | **90.04%** | **89.10%** | **73.30%** | 64.36% | + +The fixed, untuned 50/50 stacks remain a separate zero-shot reference: + +| Checkpoint | JevBench public | Transfer test | Typed test | JevJudge text | +|---|---:|---:|---:|---:| +| JevAny 4B Pointer | 81.39% | 77.44% | 60.70% | 59.12% | +| JevAny 4B Direct-Token | 80.95% | 79.45% | 66.75% | 58.98% | +| JevAny 27B Pointer | 90.04% | **89.20%** | 73.20% | 64.23% | + +The selected parameters differ by checkpoint: + +| Checkpoint | Native weight | Additional temperature | +|---|---:|---:| +| JevAny 4B Pointer | .78 | 1.10 | +| JevAny 4B Direct-Token | .59 | 1.12 | +| JevAny 27B Pointer | .52 | .84 | + +### External comparison + +| Model/readout | Typed test | JevJudge text | +|---|---:|---:| +| meraGPT Decider 1 · published | **76.80%** | — | +| JevAny 27B Pointer · native | 72.80% | **66.44%** | +| JevAny 27B Pointer · tuned stack | **73.30%** | 64.36% | +| TypeSafe Jev 1.13 | 72.70% | 65.06% | +| Kev-27B | — | 64.23% | + +The 27B stack beats its native readout by 12 held-out Transfer decisions and +10 Typed decisions, while losing 15 JevJudge text decisions. Direct-Token 4B's +stack improves over native on all three held-out panels. Pointer 4B improves on +Transfer and JevJudge but loses on Typed. No single blend is universally best. + +### Practical principles + +- **Selection bottleneck:** use choice-token readout when the model can reason + over the candidates but the native extraction path is the bottleneck. +- **Complementary errors:** keep a stack only when paired development results + show that each readout rescues errors made by the other. Select one weight on + development data and freeze it. +- **Capability limit:** a different readout cannot supply knowledge, perception, + planning, or instruction-following ability missing from the underlying model. +- **Calibration limit:** temperature can improve NLL, Brier, or ECE, but it + cannot change the selected option or accuracy. +- **Domain shift:** target-domain validation wins over a global rule. The same + 27B stack helps Transfer and Typed, ties JevBench, and hurts JevJudge text. + +Complete metrics, hashes, runtime versions, selected weights, and the unsupported +full-JevJudge marker are in +[`results/choice-readout-v2.json`](../results/choice-readout-v2.json). The +deterministic builder is +[`scripts/build_choice_readout_results.py`](../scripts/build_choice_readout_results.py). + +## Benchmark units + +Cygnet's official `73.70` on JevBench v1.5.4 is a four-axis composite over +1,624 open and sealed decisions, not accuracy. Its separate public-development +result is 203/231 (87.9% accuracy). Every JevBench value on this page is +accuracy over the same 231 public-development items and cannot be compared +numerically with the composite. + +## Historical v1 + +The original prompt-v1 experiment and one-panel latency diagnostic remain +unchanged in [`results/letter-readout-v1.json`](../results/letter-readout-v1.json) +and [`letter-readout-results.svg`](letter-readout-results.svg). Those runs used a +different prompt and runtime, and their matched native reruns did not reproduce +the canonical README rows. They are retained for audit and are not mixed into +the v2 tables. + +## Attribution + +The Cygnet-compatible prompt and exact option-ID aggregation semantics are +adapted from `blockbrain-ai/cygnet-recipe` commit `3cf591c`, Copyright 2026 +Nood Co and contributors, under MIT. Cygnet credits the one-token option-ID +readout to [NInfer](https://github.com/igorls/ninfer), released under Apache-2.0. +JevAny does not incorporate NInfer source code. See [`NOTICE`](../NOTICE). diff --git a/docs/EVALUATION.md b/docs/EVALUATION.md index b6e8999..f858b77 100644 --- a/docs/EVALUATION.md +++ b/docs/EVALUATION.md @@ -2,7 +2,7 @@ ## Model family v2 -This section contains the full results for the five current LoRA SFT releases. +This section contains checkpoint-native results for the five current LoRA SFT releases. Transfer also informed model development and serves as a diagnostic comparison. **Transfer** is the cross-domain evaluation built from @@ -16,7 +16,7 @@ and ECE in the main table are Transfer metrics; every run covers every item. Every JevBench value in this document is public-development accuracy (`correct / 231`). It is not the official JevBench v1.5.4 composite, which combines Intelligence, Calibration, Speed and Cost over 1,624 open and sealed -decisions. See the [letter-readout guide](LETTER_READOUT.md#benchmark-units) for +decisions. See the [choice-token readout guide](CHOICE_READOUT.md#benchmark-units) for the side-by-side definitions. | Model | Transfer ↑ | JevBench ↑ | NLL ↓ | Brier ↓ | ECE ↓ | @@ -95,7 +95,7 @@ a complete local Transfer API run and JevBench's published per-tier accuracy. - **Loss:** cross-entropy, pure InfoNCE, and mixed objectives; CE gave the best Transfer accuracy in the loss sweep, while small contrastive terms mainly improved calibration. The released checkpoints use CE. -- **Readout family:** at 4B, direct-token improves JevBench (80.95% vs 80.09%), +- **Trained checkpoint-native readout family:** at 4B, direct-token improves JevBench (80.95% vs 80.09%), while pointer is slightly stronger on Transfer (78.68% vs 78.20%). ### Reproducibility @@ -140,6 +140,35 @@ excluded. Gemma uses run/checkpoint timestamps because its earlier checkpoint format did not store cumulative elapsed seconds; the other figures come from checkpoint or terminal trainer telemetry. +## Training-free choice-token readout + +Choice-token readout constrains the next token to one of 52 exact option IDs +and renormalizes their mass. It needs no additional training and can change the +selected answer. Scalar temperature fitting is separate: it changes confidence, +not accuracy. A native + choice stack can change both. + +The stack weight and additional temperature below are selected only on 1,046 +clean/knowable Transfer-v9 development decisions and then frozen. Transfer test, +Typed Decisions, and JevJudge text are held out. JevBench is a public diagnostic. +All values are accuracy. + +| Weights | Readout | JevBench public | Transfer test | Typed test | JevJudge text | +|---|---|---:|---:|---:|---:| +| Frozen Qwen3.5-4B | Choice T=1 | 79.65% | 68.45% | 52.75% | 58.70% | +| JevAny 4B Pointer | Native / Choice / Tuned | 80.09 / 81.39 / 80.95 | 77.92 / 73.61 / **78.01** | **63.50** / 57.80 / 62.90 | 58.01 / 52.62 / **59.53** | +| JevAny 4B Direct-Token | Native / Choice / Tuned | 80.95 / **81.39** / 80.95 | 78.87 / 78.59 / **79.83** | 67.20 / 64.80 / **67.65** | 58.43 / 57.60 / **59.25** | +| Frozen Qwen3.8-27B | Choice T=1 | 87.88% | 80.69% | 68.25% | 61.74% | +| JevAny 27B Pointer | Native / Choice / Tuned | **90.04** / 89.61 / **90.04** | 87.95 / 86.04 / **89.10** | 72.80 / 72.60 / **73.30** | **66.44** / 57.87 / 64.36 | + +The 4B Direct-Token stack improves over its native readout on all three held-out +panels. The 27B stack improves Transfer and Typed, ties JevBench, and hurts +JevJudge text. Retain a stack only when paired development errors are +complementary and the target distribution validates it. + +[Full method, fixed 50/50 controls and protocol](CHOICE_READOUT.md) · +[Machine-readable results](../results/choice-readout-v2.json) · +[Merged 23-page technical report](../reports/JevAny_Tech_Report.pdf) + ## Earlier releases and evaluations The [JevBench and Kev comparison](EXTERNAL_EVALUATION.md) evaluates both released diff --git a/docs/LETTER_READOUT.md b/docs/LETTER_READOUT.md index 789f56c..136b12f 100644 --- a/docs/LETTER_READOUT.md +++ b/docs/LETTER_READOUT.md @@ -1,170 +1,7 @@ -# Training-free option-letter readout +# Moved: training-free choice-token readout -JevAny includes an experimental evaluator for a training-free, single-answer-slot -decision readout. It adapts the prompt and probability-aggregation semantics -from [Cygnet](https://github.com/blockbrain-ai/cygnet-recipe/tree/3cf591c692dec649f7c134449814610307c7bb3a): +The public method name is **choice-token readout**. See +[`CHOICE_READOUT.md`](CHOICE_READOUT.md) for the method, commands, limits, +current results, and retained historical v1 links. -1. Render the options in their original order as `A`, `B`, …, `Z`. -2. Ask the model for exactly one option letter with thinking disabled. -3. At the answer position, collect every vocabulary token whose decoded surface - is exactly each available uppercase letter. This emulates Cygnet's - `structured_outputs.choice` mask; leading-space, punctuated, and lowercase - forms are not admitted. -4. Sum duplicate-token mass per letter and normalize over the available options. -5. Optionally apply a temperature fitted for that model and evaluation domain. - -The model produces no explanation and the method needs no additional training. -The same readout can be applied after loading a JevAny LoRA. For pointer -checkpoints, the letter and native pointer distributions can also be combined -log-linearly. The integrated commands call this knob -`--letter-pointer-weight`; the standalone experiment script calls it -`--pointer-weight`. - -## Run the public diagnostic - -Use `--readout letter` to score a JevAny checkpoint on any regular frozen suite -or labelled JSONL. This example keeps temperature at 1 rather than copying a -value fitted for another model: - -```bash -jevany eval \ - --run SimpleJev/JevAny-Qwen3.5-4B-LoRA \ - --suite /path/to/jevbench-public-v1.4.2.2 \ - --out runs/letter-readout/qwen35-4b \ - --device cuda --readout letter --letter-temperature 1.0 -``` - -The same selector works for one local decision or an HTTP deployment. Native -checkpoint readout remains the default when `--readout` is omitted: - -```bash -jevany decide request.json \ - --checkpoint SimpleJev/JevAny-Qwen3.5-4B-LoRA \ - --device cuda --dtype bf16 --readout letter - -jevany serve \ - --checkpoint SimpleJev/JevAny-Qwen3.5-4B-LoRA \ - --device cuda --dtype bf16 --readout letter -``` - -The standalone evaluator additionally supports a frozen base via `--base`, -tier sampling, and explicit LoRA scaling: - -```bash -python scripts/evaluate_letter_readout.py \ - --checkpoint SimpleJev/JevAny-Qwen3.5-4B-LoRA \ - --suite /path/to/jevbench-public-v1.4.2.2 \ - --out runs/letter-readout/qwen35-4b \ - --device cuda --dtype bf16 --temperature 1.0 -``` - -Add `--sample-per-tier 1` for a three-record smoke test. Use -`--lora-scale 0` with a checkpoint to evaluate its exact pinned base without -the adapter. A nonzero `--pointer-weight` requires a native pointer checkpoint -and adds a second, pointer-formatted model pass per request. - -Current limits: - -- text-only model input; -- 1–26 options per question; -- safetensors weights for models with an untied language-model output head; -- one letter-formatted prefill per question, plus one native prefill when - pointer blending is enabled; -- native inference-limit and CUDA-graph flags do not apply to the chat-formatted - letter path; use `--letter-max-tokens` for its prompt limit. - -Temperature changes reported probabilities but does not change the letter-only -argmax. Fit it on a separate calibration split for each model. Cygnet's `3.4` -was fitted for its own Gemma configuration and is not a default for JevAny. - -## Benchmark units - -The official score and the public diagnostic answer different questions: - -| Result | Evaluation set | Unit | -|---|---:|---| -| Cygnet 73.70 | JevBench v1.5.4, 1,624 decisions: 904 open + 720 sealed | Equal-weight harmonic mean of Intelligence, Calibration, Speed and Cost | -| Cygnet 203/231 (87.9%) | Public development set, 231 decisions | Accuracy | -| JevAny results in the current README | Same public development set, 231 decisions | Accuracy | - -Cygnet ranks first among 106 systems on the official v1.5.4 composite and is a -statistical tie with Winnow-12B Q8. The 73.70 composite is not `73.70%` and -cannot be compared numerically with public-set accuracy. See Cygnet's -[official-submission report](https://github.com/blockbrain-ai/cygnet-recipe/pull/4) -and the [JevBench v1.5.4 board](https://benchmarkheaven.com/jev-models/v1.5.4). - -## Results - -All JevAny values below are local pinned-checkpoint runs at temperature 1. The -complete metrics, suite hashes, report hashes, and paired counts are in -[`results/letter-readout-v1.json`](../results/letter-readout-v1.json). -These are separate matched reruns of the pinned released checkpoints for this -ablation; they do not replace the main model-family table, whose frozen -checkpoints and run provenance differ. - -![Accuracy-point change from native pointer for letter and fixed-blend readouts](letter-readout-results.svg) - -JevBench public is a 231-question development diagnostic, not the sealed -v1.5.4 board. `Base + letter` disables the JevAny adapter; the other columns use -the released adapter. Deltas and two-sided exact McNemar p-values compare each -adapter readout with the native pointer on the same questions. - -| Model | Base + letter | Native | Letter | Fixed 50/50 blend | -|---|---:|---:|---:|---:| -| Qwen3.5-4B | 79.65% | 80.09% | 81.39% (+1.30, p=.749) | **81.82%** (+1.73, p=.424) | -| Qwen3.8-27B | 88.31% | 89.61% | 89.61% (+0.00, p=1.000) | **90.04%** (+0.43, p=1.000) | - -Transfer-v9 evaluates all 1,264 requests without rejection or truncation. Its -accuracy headline uses the 1,046 clean knowable decisions. - -| Model | Native | Letter | Fixed 50/50 blend | -|---|---:|---:|---:| -| Qwen3.5-4B | **79.16%** | 75.72% (-3.44, p=.0028) | 79.06% (-0.10, p=1.000) | -| Qwen3.8-27B | 86.23% | 84.23% (-2.01, p=.0375) | **86.90%** (+0.67, p=.337) | - -The 27B blend gets 909/1,046 decisions right versus 902/1,046 for native, but -the seven-question gain is not statistically significant. The fixed blend is -also not a universal improvement: at 4B it gets one fewer answer right. None of -the positive gains in either table is significant at the 0.05 level. The -letter-only Transfer-v9 losses show that a public-diagnostic gain does not by -itself establish transfer. - -## Latency diagnostic - -One warmed run per mode on H200/BF16 measured the same heterogeneous 231-record -panel with 16 warmups, one measured repeat, and concurrency 1. Values are external -end-to-end median / p95 milliseconds; loading and network transport are -excluded. - -| Model | Native | Letter | Fixed 50/50 blend | -|---|---:|---:|---:| -| Qwen3.5-4B | 138.43 / 200.98 | **131.86** / 201.55 (1.05x median speedup) | 250.34 / 388.31 (1.81x median latency) | -| Qwen3.8-27B | 194.70 / 477.67 | **181.58** / 492.51 (1.07x median speedup) | 367.40 / 943.44 (1.89x median latency) | - -Letter-only has a modest median improvement on this panel despite using more -logical input tokens on average (701 versus 602); p95 does not improve. The -blend evaluates both prefills and averages 1,303 logical input tokens. This is -not saturated server throughput or a general speed claim. - -## Practical principles - -- Treat letter readout as a model-and-domain ablation, not a drop-in upgrade. - Keep it only after a paired evaluation on the target decision distribution. -- Blend only when the two readouts make complementary errors. The fixed 50/50 - pool helped 27B on both diagnostics; at 4B it helped JevBench public but not - Transfer-v9. Select the weight on a separate development split. -- Calibrate each model, readout, and domain separately. Cygnet's fitted - temperature `3.4` does not transfer to these Qwen checkpoints. -- Re-measure latency in the deployment runtime and traffic mix. The one-pass - letter path can trim median latency, while blending requires both letter and - native passes and nearly doubles median latency here. - -## Attribution - -The Cygnet-compatible prompt and aggregation semantics are adapted from -`blockbrain-ai/cygnet-recipe` commit -`3cf591c692dec649f7c134449814610307c7bb3a`, Copyright 2026 Nood Co and -contributors, under MIT. Cygnet credits the one-token option-letter readout -method to [NInfer](https://github.com/igorls/ninfer), released under Apache-2.0. -JevAny does not incorporate NInfer source code. See [NOTICE](../NOTICE) for the -full Cygnet license notice. +The former `letter` CLI/API spelling remains a compatibility alias. diff --git a/docs/choice-readout-results.svg b/docs/choice-readout-results.svg new file mode 100644 index 0000000..a32e254 --- /dev/null +++ b/docs/choice-readout-results.svg @@ -0,0 +1,928 @@ + + + + Training-free choice readout accuracy + + + + + Training-free choice readout accuracy + Typed Decisions: Frozen Qwen3.5 4B · Choice 52.75%, JevAny 4B Pointer · Native 63.50%, JevAny 4B Pointer · Choice 57.80%, JevAny 4B Direct-Token · Native 67.20%, JevAny 4B Direct-Token · Choice 64.80%, Frozen Qwen3.8 27B · Choice 68.25%, JevAny 27B Pointer · Native 72.80%, JevAny 27B Pointer · Choice 72.60%, JevAny 4B Pointer · Tuned blend 62.90%, JevAny 4B Direct-Token · Tuned blend 67.65%, JevAny 27B Pointer · Tuned blend 73.30%, meraGPT Decider 1 · published 76.80%, TypeSafe Jev 1.13 72.70% JevJudge text: Frozen Qwen3.5 4B · Choice 58.70%, JevAny 4B Pointer · Native 58.01%, JevAny 4B Pointer · Choice 52.62%, JevAny 4B Direct-Token · Native 58.43%, JevAny 4B Direct-Token · Choice 57.60%, Frozen Qwen3.8 27B · Choice 61.74%, JevAny 27B Pointer · Native 66.44%, JevAny 27B Pointer · Choice 57.87%, JevAny 4B Pointer · Tuned blend 59.53%, JevAny 4B Direct-Token · Tuned blend 59.25%, JevAny 27B Pointer · Tuned blend 64.36%, TypeSafe Jev 1.13 65.06%, Kev-27B · open 64.23% + image/svg+xml + + + Matplotlib v3.11.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + 20 + + + + + + + + + 40 + + + + + + + + + 60 + + + + + + + + + 80 + + + + + + + + Frozen Qwen3.5 4B · Choice + + + + + + JevAny 4B Pointer · Native + + + + + + JevAny 4B Pointer · Choice + + + + + + JevAny 4B Direct-Token · Native + + + + + + JevAny 4B Direct-Token · Choice + + + + + + Frozen Qwen3.8 27B · Choice + + + + + + JevAny 27B Pointer · Native + + + + + + JevAny 27B Pointer · Choice + + + + + + JevAny 4B Pointer · Tuned blend + + + + + + JevAny 4B Direct-Token · Tuned blend + + + + + + JevAny 27B Pointer · Tuned blend + + + + + + meraGPT Decider 1 · published + + + + + + TypeSafe Jev 1.13 + + + + + + + + + + + 52.8 + + + + + + 63.5 + + + + + + 57.8 + + + + + + 67.2 + + + + + + 64.8 + + + + + + 68.2 + + + + + + 72.8 + + + + + + 72.6 + + + + + + 62.9 + + + + + + 67.7 + + + + + + 73.3 + + + + + + 76.8 + + + + + + 72.7 + + + ZERO-SHOT + + + TRANSFER-DEV-TUNED + + + EXTERNAL REFERENCE + + + 2,000 decisions · accuracy (%) + + + Typed Decisions + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + 20 + + + + + + + + + 40 + + + + + + + + + 60 + + + + + + + + + 80 + + + + + + + + Frozen Qwen3.5 4B · Choice + + + + + + JevAny 4B Pointer · Native + + + + + + JevAny 4B Pointer · Choice + + + + + + JevAny 4B Direct-Token · Native + + + + + + JevAny 4B Direct-Token · Choice + + + + + + Frozen Qwen3.8 27B · Choice + + + + + + JevAny 27B Pointer · Native + + + + + + JevAny 27B Pointer · Choice + + + + + + JevAny 4B Pointer · Tuned blend + + + + + + JevAny 4B Direct-Token · Tuned blend + + + + + + JevAny 27B Pointer · Tuned blend + + + + + + TypeSafe Jev 1.13 + + + + + + Kev-27B · open + + + + + + + + + + + 58.7 + + + + + + 58.0 + + + + + + 52.6 + + + + + + 58.4 + + + + + + 57.6 + + + + + + 61.7 + + + + + + 66.4 + + + + + + 57.9 + + + + + + 59.5 + + + + + + 59.3 + + + + + + 64.4 + + + + + + 65.1 + + + + + + 64.2 + + + ZERO-SHOT + + + TRANSFER-DEV-TUNED + + + EXTERNAL REFERENCE + + + 724 records · accuracy (%) + + + JevJudge text + + + + Training-free choice readout + + + Zero-shot readouts and Transfer-dev-tuned blends · no Typed or JevJudge labels used for tuning + + + JevJudge full is unsupported for this text-only readout. Choice temperature calibration is omitted here because it cannot change accuracy. + + + + + + + Choice T=1 · zero-shot + + + + + + Native shipped · zero-shot + + + + + + Transfer-dev-tuned blend + + + + + + TypeSafe Jev + + + + + + Published / open baseline + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jevany/letter_predictor.py b/jevany/letter_predictor.py index 078bba4..ea9c656 100644 --- a/jevany/letter_predictor.py +++ b/jevany/letter_predictor.py @@ -1,9 +1,9 @@ """Training-free choice-token inference over a frozen base or JevAny adapter. -This is the in-process backend for :mod:`jevany.letter_readout`. It keeps the -existing pointer path unchanged: a JevAny checkpoint can instead be applied to -the chat prompt before the frozen vocabulary readout, and its pointer -distribution can optionally be combined with the letter distribution. +This is the in-process backend for :mod:`jevany.letter_readout`. A JevAny +checkpoint can be applied to the chat prompt before the frozen vocabulary +readout, and its Pointer or Direct-Token native distribution can optionally be +combined with the choice distribution. """ from __future__ import annotations diff --git a/reports/JevAny_Tech_Report.pdf b/reports/JevAny_Tech_Report.pdf index de0ad588a7bc9b185964e4d24d4e35e03437c9f8..5e0b4c473188a421332f0f479802cd82f7c515c2 100644 GIT binary patch delta 24235 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 delta 11006 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 diff --git a/results/choice-readout-v2.json b/results/choice-readout-v2.json new file mode 100644 index 0000000..8c1213f --- /dev/null +++ b/results/choice-readout-v2.json @@ -0,0 +1,2558 @@ +{ + "schema_version": 2, + "artifact": "choice-readout-v2", + "title": "Training-free choice-token readout evaluation", + "generated_by": "scripts/build_choice_readout_results.py", + "method": { + "readout": "Constrain the next token to exact one-character option IDs and renormalize their probability mass; this can change argmax decisions.", + "training": "No additional training for the choice-token readout.", + "option_ids": "A-Z followed by a-z; at most 52 options.", + "calibration": "Scalar temperature changes probabilities but not argmax accuracy.", + "ensemble": "Native + choice configurations use log-linear/geometric pooling." + }, + "protocol_groups": { + "zero_shot": { + "meaning": "No Typed, JevJudge, JevBench, or Transfer-test labels tune the readout configuration.", + "configurations": [ + "choice_t1", + "native_shipped", + "fixed_blend_0_5" + ] + }, + "transfer_dev_tuned": { + "meaning": "Weight and additional temperature are selected only on Transfer-v9 development, then frozen for every displayed evaluation panel.", + "configurations": [ + "transfer_dev_calibrated_choice", + "transfer_dev_tuned_blend" + ], + "accuracy_note": "Temperature-only calibrated choice has the same hard accuracy as choice_t1; only a changed blend weight can change its argmax." + } + }, + "expected_counts": { + "transfer_calibration": { + "label": "Transfer-v9 development", + "role": "selection_only", + "records": 1264, + "questions": 1264, + "headline_n": 1046 + }, + "transfer_test": { + "label": "Transfer-v9 test", + "role": "held_out_evaluation", + "records": 1264, + "questions": 1264, + "headline_n": 1046 + }, + "typed_test": { + "label": "Typed Decisions test", + "role": "held_out_external_evaluation", + "records": 400, + "questions": 2000, + "headline_n": 2000 + }, + "jevbench_public": { + "label": "JevBench public development", + "role": "public_diagnostic_not_used_for_selection", + "records": 231, + "questions": 231, + "headline_n": 231 + }, + "jevjudge_text": { + "label": "JevJudge text", + "role": "held_out_external_evaluation", + "records": 724, + "questions": 724, + "headline_n": 724 + } + }, + "dataset_hashes": { + "jevbench_development_sha256": "b6d6eb34fdbbf46ec11be8657da5e513e3538990d7a00ca5582c55d8bd7941c6", + "jevbench_manifest_sha256": "e356ad5a2c1dd3917c126e74bdee58940391568de013502abcdcd902bde3c2a5", + "transfer_manifest_sha256": "3c4f0be94509a3612678bfd3a30fd99a8d0ca3c47ddfe7318075d95b2fa365e4", + "transfer_development_sha256": "53872ba045564315cf3c3dd8fb656e76441c6c514b1a7ff985ff0db3955710cf", + "transfer_test_sha256": "048abea5e12c73c3a622ce5836898c00fa43766e0bf48fb2459e77ee9873497f", + "typed_revision": "d0e2f0c42fef86cc15d1688d25a19f5ba7c85b18", + "typed_test_sha256": "4f294f218ea1da27f3efef936359389c62ea4d3973a41457732990f1d31b647c", + "jevjudge_test_sha256": "5c50f89e9ff1beb2107648b56f2a3cbc19c946e379519caf59afc191700e83cb", + "jevjudge_manifest_sha256": "ee52a15f82629742093a89910a8f72e7ca97248b197aad6ec801f984c2a15b68" + }, + "code_revision": "ae891ed97147b57b41a78a4bb4960b018db3bf42", + "source_runs": [ + { + "key": "base4", + "model": "Frozen-Qwen3.5-4B", + "family": "Qwen3.5-4B", + "weights": "frozen_base", + "native_readout": null, + "artifacts": { + "manifest": { + "path": "/lustre-storage/fsx/tianxinwei/JevAny/runs/choice-readout-v2-20261002-r1/base4/manifest.json", + "sha256": "49e57b70600869d5235a3363a3238161b753b3082f9d348cbd25e810a562418b" + }, + "ensemble": { + "path": "/lustre-storage/fsx/tianxinwei/JevAny/runs/choice-readout-v2-20261002-r1/base4/ensemble.json", + "sha256": "388cf7b00de00c5c5090f27c8436981cadb1a71986f6855f77806c58840b03d2" + } + }, + "source": { + "base": "Qwen/Qwen3.5-4B", + "checkpoint": null + }, + "checkpoint_artifacts": null, + "base_loading": { + "canonical_base": "Qwen/Qwen3.5-4B", + "canonical_base_is_local": false, + "canonical_revision": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a", + "override_used": true, + "override_config_sha256": "ddc63e1c717afa86c865bb5e01313d89d72bb53b97ad4a8a03ba8510c0621670" + }, + "predictor": { + "method": "training-free exact choice-token projection", + "constraint_emulation": "exact decoded one-character option IDs", + "prompt": "Cygnet-compatible A-Z; extended with a-z above 26 options", + "system_prompt_sha256": "e2f0ac9f62eb446d7826b3abc3f7909287f4075175e9b58e4cd3ca72718671fe", + "chat_template_sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715", + "prompt_format_version": 2, + "canonical_base": "Qwen/Qwen3.5-4B", + "canonical_revision": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a", + "checkpoint": null, + "adapter_applied": false, + "adapter_scale": 0.0, + "tied_output_embeddings": true, + "unused_full_output_head_released": false, + "letter_choice_token_rows": 52, + "output_softcap": null, + "temperature": 1.0, + "native_weight": 0.0, + "native_weight_argument": "native_weight", + "native_decision_mode": null, + "pointer_weight": 0.0, + "native_temperature": null, + "pointer_temperature": null, + "return_components": false, + "exact_kernels": true, + "exact_cuda_kernels_applied": true, + "efficient_long_context_tokens": 8192, + "long_context_attention": "fused SDPA (Flash/Efficient, no math fallback) at or above the recorded threshold; exact math SDPA below it", + "max_tokens": 65536, + "backbone_context_window": 262144, + "effective_max_tokens": 65536, + "dtype": "torch.bfloat16", + "device": "cuda" + }, + "runtime": { + "python": "3.12.14", + "torch": "2.8.0+cu128", + "cuda": "12.8", + "transformers": "5.17.0", + "peft": "0.21.0", + "numpy": "2.5.3", + "pyarrow": "25.0.1", + "safetensors": "0.8.0", + "code_revision": "ae891ed97147b57b41a78a4bb4960b018db3bf42" + }, + "dataset_hashes": { + "jevbench_development_sha256": "b6d6eb34fdbbf46ec11be8657da5e513e3538990d7a00ca5582c55d8bd7941c6", + "jevbench_manifest_sha256": "e356ad5a2c1dd3917c126e74bdee58940391568de013502abcdcd902bde3c2a5", + "transfer_manifest_sha256": "3c4f0be94509a3612678bfd3a30fd99a8d0ca3c47ddfe7318075d95b2fa365e4", + "transfer_development_sha256": "53872ba045564315cf3c3dd8fb656e76441c6c514b1a7ff985ff0db3955710cf", + "transfer_test_sha256": "048abea5e12c73c3a622ce5836898c00fa43766e0bf48fb2459e77ee9873497f", + "typed_revision": "d0e2f0c42fef86cc15d1688d25a19f5ba7c85b18", + "typed_test_sha256": "4f294f218ea1da27f3efef936359389c62ea4d3973a41457732990f1d31b647c", + "jevjudge_test_sha256": "5c50f89e9ff1beb2107648b56f2a3cbc19c946e379519caf59afc191700e83cb", + "jevjudge_manifest_sha256": "ee52a15f82629742093a89910a8f72e7ca97248b197aad6ec801f984c2a15b68" + }, + "evaluation_protocol": { + "kernel_policy": "LocalPredictor-compatible exact CUDA policy", + "dtype": "bf16", + "attn": "sdpa", + "temperature": 1.0, + "blend_selection": "not performed here; components are saved for fitting on Transfer-v9 development only", + "efficiency_scope": "base calls execute choice only; checkpoint calls execute choice and native. Do not compare component efficiency from these end-to-end timings", + "long_context_attention": "exact math SDPA below the configured threshold; memory-linear fused SDPA without a math fallback at or above it", + "efficient_long_context_tokens": 8192, + "resume": { + "enabled": true, + "reused_datasets": [ + "jevbench_public", + "transfer_calibration", + "transfer_test", + "typed_test" + ], + "preexisting_code_revision": "256001ca806f9d7e302bea967850db214f3d8e4e", + "dataset_code_revisions": { + "jevbench_public": "256001ca806f9d7e302bea967850db214f3d8e4e", + "transfer_calibration": "256001ca806f9d7e302bea967850db214f3d8e4e", + "transfer_test": "256001ca806f9d7e302bea967850db214f3d8e4e", + "typed_test": "256001ca806f9d7e302bea967850db214f3d8e4e", + "jevjudge_text": "ae891ed97147b57b41a78a4bb4960b018db3bf42" + } + } + }, + "ensemble_protocol": { + "selection_split": "Transfer-v9 development clean/knowable accuracy cohort; never any reported test panel", + "selection_rows": 1046, + "weight_grid": "0.00 to 1.00 inclusive, step 0.01", + "pool": "log-linear/geometric", + "temperature_fit": "bounded scalar NLL minimization, T in [0.05, 20]", + "ece": "15 equal-width bins for Typed Decisions; 10 for every other dataset", + "selection_objective": "accuracy; calibrated NLL then distance from 0.5 break ties", + "transfer": "the selected weight and temperature are frozen for every held-out dataset", + "native_shipped": "the checkpoint's native probabilities, including its shipped inference temperature; no additional temperature is applied", + "fixed_blend_0_5": "equal log-linear pooling of T=1 choice probabilities and shipped native probabilities; no additional temperature is applied", + "transfer_dev_tuned_blend": "the Transfer-dev-selected native weight followed by the reported scalar additional temperature" + }, + "selected": { + "choice_temperature": 1.8514751919432642, + "native_weight": 0.0, + "blend_temperature": 1.8514751919432642, + "selection_objective": "hard-label accuracy on the Transfer-v9 development clean/knowable cohort; calibrated NLL and distance from 0.5 break ties" + } + }, + { + "key": "pointer4", + "model": "JevAny-Qwen3.5-4B-Pointer", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "pointer", + "artifacts": { + "manifest": { + "path": "/lustre-storage/fsx/tianxinwei/JevAny/runs/choice-readout-v2-20261002-r1/pointer4/manifest.json", + "sha256": "11b64050dc31f834c64dbe54c975df1fb365b9eb9465a4089e7de98ba00242b1" + }, + "ensemble": { + "path": "/lustre-storage/fsx/tianxinwei/JevAny/runs/choice-readout-v2-20261002-r1/pointer4/ensemble.json", + "sha256": "0e18398586de8760719ea6a3e8956f221a69e341e75d3364e22fda7df9cb6488" + } + }, + "source": { + "base": null, + "checkpoint": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.5-4B-LoRA/snapshots/1c7aa9bab14ac347aeb917c0bcd757838a8a78ce" + }, + "checkpoint_artifacts": { + "requested": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.5-4B-LoRA/snapshots/1c7aa9bab14ac347aeb917c0bcd757838a8a78ce", + "resolved": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.5-4B-LoRA/snapshots/1c7aa9bab14ac347aeb917c0bcd757838a8a78ce", + "files": { + "adapter_model.safetensors": { + "sha256": "5acbaec8d554517250bf9c626b3f4b9e2ec31fc67957f3486aa72e2c91ee907f", + "bytes": 64993432 + }, + "head.pt": { + "sha256": "e5ea982efd06ecaf71b493045ebccdbfdca39f6fe128de164bf636d73152cd44", + "bytes": 5513315 + }, + "adapter_config.json": { + "sha256": "8a138b2c091d2ba463a8107e9e19182202395188750742047f4a12e8b59e1eda", + "bytes": 1265 + } + } + }, + "base_loading": { + "canonical_base": "Qwen/Qwen3.5-4B", + "canonical_base_is_local": false, + "canonical_revision": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a", + "override_used": true, + "override_config_sha256": "ddc63e1c717afa86c865bb5e01313d89d72bb53b97ad4a8a03ba8510c0621670" + }, + "predictor": { + "method": "training-free exact choice-token projection", + "constraint_emulation": "exact decoded one-character option IDs", + "prompt": "Cygnet-compatible A-Z; extended with a-z above 26 options", + "system_prompt_sha256": "e2f0ac9f62eb446d7826b3abc3f7909287f4075175e9b58e4cd3ca72718671fe", + "chat_template_sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715", + "prompt_format_version": 2, + "canonical_base": "Qwen/Qwen3.5-4B", + "canonical_revision": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a", + "checkpoint": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.5-4B-LoRA/snapshots/1c7aa9bab14ac347aeb917c0bcd757838a8a78ce", + "adapter_applied": true, + "adapter_scale": 1.0, + "tied_output_embeddings": true, + "unused_full_output_head_released": false, + "letter_choice_token_rows": 52, + "output_softcap": null, + "temperature": 1.0, + "native_weight": 0.5, + "native_weight_argument": "native_weight", + "native_decision_mode": "pointer", + "pointer_weight": 0.5, + "native_temperature": 1.0, + "pointer_temperature": 1.0, + "return_components": true, + "exact_kernels": true, + "exact_cuda_kernels_applied": true, + "efficient_long_context_tokens": 8192, + "long_context_attention": "fused SDPA (Flash/Efficient, no math fallback) at or above the recorded threshold; exact math SDPA below it", + "max_tokens": 65536, + "backbone_context_window": 262144, + "effective_max_tokens": 65536, + "dtype": "torch.bfloat16", + "device": "cuda" + }, + "runtime": { + "python": "3.12.14", + "torch": "2.8.0+cu128", + "cuda": "12.8", + "transformers": "5.17.0", + "peft": "0.21.0", + "numpy": "2.5.3", + "pyarrow": "25.0.1", + "safetensors": "0.8.0", + "code_revision": "ae891ed97147b57b41a78a4bb4960b018db3bf42" + }, + "dataset_hashes": { + "jevbench_development_sha256": "b6d6eb34fdbbf46ec11be8657da5e513e3538990d7a00ca5582c55d8bd7941c6", + "jevbench_manifest_sha256": "e356ad5a2c1dd3917c126e74bdee58940391568de013502abcdcd902bde3c2a5", + "transfer_manifest_sha256": "3c4f0be94509a3612678bfd3a30fd99a8d0ca3c47ddfe7318075d95b2fa365e4", + "transfer_development_sha256": "53872ba045564315cf3c3dd8fb656e76441c6c514b1a7ff985ff0db3955710cf", + "transfer_test_sha256": "048abea5e12c73c3a622ce5836898c00fa43766e0bf48fb2459e77ee9873497f", + "typed_revision": "d0e2f0c42fef86cc15d1688d25a19f5ba7c85b18", + "typed_test_sha256": "4f294f218ea1da27f3efef936359389c62ea4d3973a41457732990f1d31b647c", + "jevjudge_test_sha256": "5c50f89e9ff1beb2107648b56f2a3cbc19c946e379519caf59afc191700e83cb", + "jevjudge_manifest_sha256": "ee52a15f82629742093a89910a8f72e7ca97248b197aad6ec801f984c2a15b68" + }, + "evaluation_protocol": { + "kernel_policy": "LocalPredictor-compatible exact CUDA policy", + "dtype": "bf16", + "attn": "sdpa", + "temperature": 1.0, + "blend_selection": "not performed here; components are saved for fitting on Transfer-v9 development only", + "efficiency_scope": "base calls execute choice only; checkpoint calls execute choice and native. Do not compare component efficiency from these end-to-end timings", + "long_context_attention": "exact math SDPA below the configured threshold; memory-linear fused SDPA without a math fallback at or above it", + "efficient_long_context_tokens": 8192, + "resume": { + "enabled": true, + "reused_datasets": [ + "jevbench_public", + "transfer_calibration", + "transfer_test", + "typed_test" + ], + "preexisting_code_revision": "256001ca806f9d7e302bea967850db214f3d8e4e", + "dataset_code_revisions": { + "jevbench_public": "256001ca806f9d7e302bea967850db214f3d8e4e", + "transfer_calibration": "256001ca806f9d7e302bea967850db214f3d8e4e", + "transfer_test": "256001ca806f9d7e302bea967850db214f3d8e4e", + "typed_test": "256001ca806f9d7e302bea967850db214f3d8e4e", + "jevjudge_text": "ae891ed97147b57b41a78a4bb4960b018db3bf42" + } + } + }, + "ensemble_protocol": { + "selection_split": "Transfer-v9 development clean/knowable accuracy cohort; never any reported test panel", + "selection_rows": 1046, + "weight_grid": "0.00 to 1.00 inclusive, step 0.01", + "pool": "log-linear/geometric", + "temperature_fit": "bounded scalar NLL minimization, T in [0.05, 20]", + "ece": "15 equal-width bins for Typed Decisions; 10 for every other dataset", + "selection_objective": "accuracy; calibrated NLL then distance from 0.5 break ties", + "transfer": "the selected weight and temperature are frozen for every held-out dataset", + "native_shipped": "the checkpoint's native probabilities, including its shipped inference temperature; no additional temperature is applied", + "fixed_blend_0_5": "equal log-linear pooling of T=1 choice probabilities and shipped native probabilities; no additional temperature is applied", + "transfer_dev_tuned_blend": "the Transfer-dev-selected native weight followed by the reported scalar additional temperature" + }, + "selected": { + "choice_temperature": 0.8505257300716687, + "native_weight": 0.78, + "blend_temperature": 1.0977784557618349, + "selection_objective": "hard-label accuracy on the Transfer-v9 development clean/knowable cohort; calibrated NLL and distance from 0.5 break ties" + } + }, + { + "key": "direct4", + "model": "JevAny-Qwen3.5-4B-Direct-Token", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "direct-token", + "artifacts": { + "manifest": { + "path": "/lustre-storage/fsx/tianxinwei/JevAny/runs/choice-readout-v2-20261002-r1/direct4/manifest.json", + "sha256": "244ede1e22b445634be5792977e19e594bfaeb2fe783f905a6ccf2a0424078e5" + }, + "ensemble": { + "path": "/lustre-storage/fsx/tianxinwei/JevAny/runs/choice-readout-v2-20261002-r1/direct4/ensemble.json", + "sha256": "e3a2aee6ca34b98a0780807b6b49c86dcf261b55e5559da8fc61dd17b9f03db7" + } + }, + "source": { + "base": null, + "checkpoint": "/lustre-storage/fsx/tianxinwei/JevAny/releases/model-family-v2-20260929/JevAny-Qwen3.5-4B-Direct-Token-LoRA" + }, + "checkpoint_artifacts": { + "requested": "/lustre-storage/fsx/tianxinwei/JevAny/releases/model-family-v2-20260929/JevAny-Qwen3.5-4B-Direct-Token-LoRA", + "resolved": "/lustre-storage/fsx/tianxinwei/JevAny/releases/model-family-v2-20260929/JevAny-Qwen3.5-4B-Direct-Token-LoRA", + "files": { + "adapter_model.safetensors": { + "sha256": "b768d54b9e5e20204b2db9e24c18ae6b10fa310b241d1931bac6b8882f127c53", + "bytes": 64993432 + }, + "head.pt": { + "sha256": "d3909cffc156d077061114627c8aa22f60c7a8f1bdcfd20c489bb3bb009fc4a6", + "bytes": 4699 + }, + "adapter_config.json": { + "sha256": "f84527483265b514d0a5f68d9c44e3d7952825c61d3659a858af8342e6ce9ff2", + "bytes": 1265 + } + } + }, + "base_loading": { + "canonical_base": "Qwen/Qwen3.5-4B", + "canonical_base_is_local": false, + "canonical_revision": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a", + "override_used": true, + "override_config_sha256": "ddc63e1c717afa86c865bb5e01313d89d72bb53b97ad4a8a03ba8510c0621670" + }, + "predictor": { + "method": "training-free exact choice-token projection", + "constraint_emulation": "exact decoded one-character option IDs", + "prompt": "Cygnet-compatible A-Z; extended with a-z above 26 options", + "system_prompt_sha256": "e2f0ac9f62eb446d7826b3abc3f7909287f4075175e9b58e4cd3ca72718671fe", + "chat_template_sha256": "a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715", + "prompt_format_version": 2, + "canonical_base": "Qwen/Qwen3.5-4B", + "canonical_revision": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a", + "checkpoint": "/lustre-storage/fsx/tianxinwei/JevAny/releases/model-family-v2-20260929/JevAny-Qwen3.5-4B-Direct-Token-LoRA", + "adapter_applied": true, + "adapter_scale": 1.0, + "tied_output_embeddings": true, + "unused_full_output_head_released": false, + "letter_choice_token_rows": 52, + "output_softcap": null, + "temperature": 1.0, + "native_weight": 0.5, + "native_weight_argument": "native_weight", + "native_decision_mode": "lm_token", + "pointer_weight": 0.5, + "native_temperature": 1.0, + "pointer_temperature": 1.0, + "return_components": true, + "exact_kernels": true, + "exact_cuda_kernels_applied": true, + "efficient_long_context_tokens": 8192, + "long_context_attention": "fused SDPA (Flash/Efficient, no math fallback) at or above the recorded threshold; exact math SDPA below it", + "max_tokens": 65536, + "backbone_context_window": 262144, + "effective_max_tokens": 65536, + "dtype": "torch.bfloat16", + "device": "cuda" + }, + "runtime": { + "python": "3.12.14", + "torch": "2.8.0+cu128", + "cuda": "12.8", + "transformers": "5.17.0", + "peft": "0.21.0", + "numpy": "2.5.3", + "pyarrow": "25.0.1", + "safetensors": "0.8.0", + "code_revision": "ae891ed97147b57b41a78a4bb4960b018db3bf42" + }, + "dataset_hashes": { + "jevbench_development_sha256": "b6d6eb34fdbbf46ec11be8657da5e513e3538990d7a00ca5582c55d8bd7941c6", + "jevbench_manifest_sha256": "e356ad5a2c1dd3917c126e74bdee58940391568de013502abcdcd902bde3c2a5", + "transfer_manifest_sha256": "3c4f0be94509a3612678bfd3a30fd99a8d0ca3c47ddfe7318075d95b2fa365e4", + "transfer_development_sha256": "53872ba045564315cf3c3dd8fb656e76441c6c514b1a7ff985ff0db3955710cf", + "transfer_test_sha256": "048abea5e12c73c3a622ce5836898c00fa43766e0bf48fb2459e77ee9873497f", + "typed_revision": "d0e2f0c42fef86cc15d1688d25a19f5ba7c85b18", + "typed_test_sha256": "4f294f218ea1da27f3efef936359389c62ea4d3973a41457732990f1d31b647c", + "jevjudge_test_sha256": "5c50f89e9ff1beb2107648b56f2a3cbc19c946e379519caf59afc191700e83cb", + "jevjudge_manifest_sha256": "ee52a15f82629742093a89910a8f72e7ca97248b197aad6ec801f984c2a15b68" + }, + "evaluation_protocol": { + "kernel_policy": "LocalPredictor-compatible exact CUDA policy", + "dtype": "bf16", + "attn": "sdpa", + "temperature": 1.0, + "blend_selection": "not performed here; components are saved for fitting on Transfer-v9 development only", + "efficiency_scope": "base calls execute choice only; checkpoint calls execute choice and native. Do not compare component efficiency from these end-to-end timings", + "long_context_attention": "exact math SDPA below the configured threshold; memory-linear fused SDPA without a math fallback at or above it", + "efficient_long_context_tokens": 8192, + "resume": { + "enabled": true, + "reused_datasets": [ + "jevbench_public", + "transfer_calibration", + "transfer_test", + "typed_test" + ], + "preexisting_code_revision": "256001ca806f9d7e302bea967850db214f3d8e4e", + "dataset_code_revisions": { + "jevbench_public": "256001ca806f9d7e302bea967850db214f3d8e4e", + "transfer_calibration": "256001ca806f9d7e302bea967850db214f3d8e4e", + "transfer_test": "256001ca806f9d7e302bea967850db214f3d8e4e", + "typed_test": "256001ca806f9d7e302bea967850db214f3d8e4e", + "jevjudge_text": "ae891ed97147b57b41a78a4bb4960b018db3bf42" + } + } + }, + "ensemble_protocol": { + "selection_split": "Transfer-v9 development clean/knowable accuracy cohort; never any reported test panel", + "selection_rows": 1046, + "weight_grid": "0.00 to 1.00 inclusive, step 0.01", + "pool": "log-linear/geometric", + "temperature_fit": "bounded scalar NLL minimization, T in [0.05, 20]", + "ece": "15 equal-width bins for Typed Decisions; 10 for every other dataset", + "selection_objective": "accuracy; calibrated NLL then distance from 0.5 break ties", + "transfer": "the selected weight and temperature are frozen for every held-out dataset", + "native_shipped": "the checkpoint's native probabilities, including its shipped inference temperature; no additional temperature is applied", + "fixed_blend_0_5": "equal log-linear pooling of T=1 choice probabilities and shipped native probabilities; no additional temperature is applied", + "transfer_dev_tuned_blend": "the Transfer-dev-selected native weight followed by the reported scalar additional temperature" + }, + "selected": { + "choice_temperature": 1.246278988841639, + "native_weight": 0.59, + "blend_temperature": 1.1240763000234277, + "selection_objective": "hard-label accuracy on the Transfer-v9 development clean/knowable cohort; calibrated NLL and distance from 0.5 break ties" + } + }, + { + "key": "base27", + "model": "Frozen-Qwen3.8-27B", + "family": "Qwen3.8-27B", + "weights": "frozen_base", + "native_readout": null, + "artifacts": { + "manifest": { + "path": "/lustre-storage/fsx/tianxinwei/JevAny/runs/choice-readout-v2-20261002-r1/base27/manifest.json", + "sha256": "5f1d1c6a755bdb8f8ee5d37f011345213cbfa205256457622d08a88f88d81fd2" + }, + "ensemble": { + "path": "/lustre-storage/fsx/tianxinwei/JevAny/runs/choice-readout-v2-20261002-r1/base27/ensemble.json", + "sha256": "c18a65303093f9673f22cc7a9fcbd7de89a73cd46372b04311e935f01277e6fb" + } + }, + "source": { + "base": "Qwen/Qwen3.8-27B", + "checkpoint": null + }, + "checkpoint_artifacts": null, + "base_loading": { + "canonical_base": "Qwen/Qwen3.8-27B", + "canonical_base_is_local": false, + "canonical_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "override_used": true, + "override_config_sha256": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab" + }, + "predictor": { + "method": "training-free exact choice-token projection", + "constraint_emulation": "exact decoded one-character option IDs", + "prompt": "Cygnet-compatible A-Z; extended with a-z above 26 options", + "system_prompt_sha256": "e2f0ac9f62eb446d7826b3abc3f7909287f4075175e9b58e4cd3ca72718671fe", + "chat_template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041", + "prompt_format_version": 2, + "canonical_base": "Qwen/Qwen3.8-27B", + "canonical_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "checkpoint": null, + "adapter_applied": false, + "adapter_scale": 0.0, + "tied_output_embeddings": false, + "unused_full_output_head_released": false, + "letter_choice_token_rows": 52, + "output_softcap": null, + "temperature": 1.0, + "native_weight": 0.0, + "native_weight_argument": "native_weight", + "native_decision_mode": null, + "pointer_weight": 0.0, + "native_temperature": null, + "pointer_temperature": null, + "return_components": false, + "exact_kernels": true, + "exact_cuda_kernels_applied": true, + "efficient_long_context_tokens": 8192, + "long_context_attention": "fused SDPA (Flash/Efficient, no math fallback) at or above the recorded threshold; exact math SDPA below it", + "max_tokens": 65536, + "backbone_context_window": 262144, + "effective_max_tokens": 65536, + "dtype": "torch.bfloat16", + "device": "cuda" + }, + "runtime": { + "python": "3.12.14", + "torch": "2.8.0+cu128", + "cuda": "12.8", + "transformers": "5.17.0", + "peft": "0.21.0", + "numpy": "2.5.3", + "pyarrow": "25.0.1", + "safetensors": "0.8.0", + "code_revision": "ae891ed97147b57b41a78a4bb4960b018db3bf42" + }, + "dataset_hashes": { + "jevbench_development_sha256": "b6d6eb34fdbbf46ec11be8657da5e513e3538990d7a00ca5582c55d8bd7941c6", + "jevbench_manifest_sha256": "e356ad5a2c1dd3917c126e74bdee58940391568de013502abcdcd902bde3c2a5", + "transfer_manifest_sha256": "3c4f0be94509a3612678bfd3a30fd99a8d0ca3c47ddfe7318075d95b2fa365e4", + "transfer_development_sha256": "53872ba045564315cf3c3dd8fb656e76441c6c514b1a7ff985ff0db3955710cf", + "transfer_test_sha256": "048abea5e12c73c3a622ce5836898c00fa43766e0bf48fb2459e77ee9873497f", + "typed_revision": "d0e2f0c42fef86cc15d1688d25a19f5ba7c85b18", + "typed_test_sha256": "4f294f218ea1da27f3efef936359389c62ea4d3973a41457732990f1d31b647c", + "jevjudge_test_sha256": "5c50f89e9ff1beb2107648b56f2a3cbc19c946e379519caf59afc191700e83cb", + "jevjudge_manifest_sha256": "ee52a15f82629742093a89910a8f72e7ca97248b197aad6ec801f984c2a15b68" + }, + "evaluation_protocol": { + "kernel_policy": "LocalPredictor-compatible exact CUDA policy", + "dtype": "bf16", + "attn": "sdpa", + "temperature": 1.0, + "blend_selection": "not performed here; components are saved for fitting on Transfer-v9 development only", + "efficiency_scope": "base calls execute choice only; checkpoint calls execute choice and native. Do not compare component efficiency from these end-to-end timings", + "long_context_attention": "exact math SDPA below the configured threshold; memory-linear fused SDPA without a math fallback at or above it", + "efficient_long_context_tokens": 8192, + "resume": { + "enabled": true, + "reused_datasets": [ + "jevbench_public", + "transfer_calibration", + "transfer_test", + "typed_test" + ], + "preexisting_code_revision": "256001ca806f9d7e302bea967850db214f3d8e4e", + "dataset_code_revisions": { + "jevbench_public": "256001ca806f9d7e302bea967850db214f3d8e4e", + "transfer_calibration": "256001ca806f9d7e302bea967850db214f3d8e4e", + "transfer_test": "256001ca806f9d7e302bea967850db214f3d8e4e", + "typed_test": "256001ca806f9d7e302bea967850db214f3d8e4e", + "jevjudge_text": "ae891ed97147b57b41a78a4bb4960b018db3bf42" + } + } + }, + "ensemble_protocol": { + "selection_split": "Transfer-v9 development clean/knowable accuracy cohort; never any reported test panel", + "selection_rows": 1046, + "weight_grid": "0.00 to 1.00 inclusive, step 0.01", + "pool": "log-linear/geometric", + "temperature_fit": "bounded scalar NLL minimization, T in [0.05, 20]", + "ece": "15 equal-width bins for Typed Decisions; 10 for every other dataset", + "selection_objective": "accuracy; calibrated NLL then distance from 0.5 break ties", + "transfer": "the selected weight and temperature are frozen for every held-out dataset", + "native_shipped": "the checkpoint's native probabilities, including its shipped inference temperature; no additional temperature is applied", + "fixed_blend_0_5": "equal log-linear pooling of T=1 choice probabilities and shipped native probabilities; no additional temperature is applied", + "transfer_dev_tuned_blend": "the Transfer-dev-selected native weight followed by the reported scalar additional temperature" + }, + "selected": { + "choice_temperature": 1.718360083457113, + "native_weight": 0.0, + "blend_temperature": 1.718360083457113, + "selection_objective": "hard-label accuracy on the Transfer-v9 development clean/knowable cohort; calibrated NLL and distance from 0.5 break ties" + } + }, + { + "key": "pointer27", + "model": "JevAny-Qwen3.8-27B-Pointer", + "family": "Qwen3.8-27B", + "weights": "jevany_sft", + "native_readout": "pointer", + "artifacts": { + "manifest": { + "path": "/lustre-storage/fsx/tianxinwei/JevAny/runs/choice-readout-v2-20261002-r1/pointer27/manifest.json", + "sha256": "c2d6dd8bb4b9f2b9e5b9c57088f673e0839af5435acb88b561559ae0a824367a" + }, + "ensemble": { + "path": "/lustre-storage/fsx/tianxinwei/JevAny/runs/choice-readout-v2-20261002-r1/pointer27/ensemble.json", + "sha256": "2270c1a3309b20fbe77bf6e905e2677ba29ecc3a3c9d4251b9932ee887e64826" + } + }, + "source": { + "base": null, + "checkpoint": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.8-27B-LoRA/snapshots/09c9e9102d5b8cc7d56558d25da1202a761b6c0d" + }, + "checkpoint_artifacts": { + "requested": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.8-27B-LoRA/snapshots/09c9e9102d5b8cc7d56558d25da1202a761b6c0d", + "resolved": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.8-27B-LoRA/snapshots/09c9e9102d5b8cc7d56558d25da1202a761b6c0d", + "files": { + "adapter_model.safetensors": { + "sha256": "f4b835f4f7be78a819067dac226feaad2fc7b9eb0b968c5e6e665ad5328e6731", + "bytes": 233584648 + }, + "head.pt": { + "sha256": "f78fa283f7e979ce418c5b137d24459350fecaea73e5766894532c1cd99684c7", + "bytes": 10756195 + }, + "adapter_config.json": { + "sha256": "31d8e262a21a45b80fd409f08d47930effd6cd9bc12156c59ad61e627f50a275", + "bytes": 1266 + } + } + }, + "base_loading": { + "canonical_base": "Qwen/Qwen3.8-27B", + "canonical_base_is_local": false, + "canonical_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "override_used": true, + "override_config_sha256": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab" + }, + "predictor": { + "method": "training-free exact choice-token projection", + "constraint_emulation": "exact decoded one-character option IDs", + "prompt": "Cygnet-compatible A-Z; extended with a-z above 26 options", + "system_prompt_sha256": "e2f0ac9f62eb446d7826b3abc3f7909287f4075175e9b58e4cd3ca72718671fe", + "chat_template_sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041", + "prompt_format_version": 2, + "canonical_base": "Qwen/Qwen3.8-27B", + "canonical_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "checkpoint": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.8-27B-LoRA/snapshots/09c9e9102d5b8cc7d56558d25da1202a761b6c0d", + "adapter_applied": true, + "adapter_scale": 1.0, + "tied_output_embeddings": false, + "unused_full_output_head_released": false, + "letter_choice_token_rows": 52, + "output_softcap": null, + "temperature": 1.0, + "native_weight": 0.5, + "native_weight_argument": "native_weight", + "native_decision_mode": "pointer", + "pointer_weight": 0.5, + "native_temperature": 1.0, + "pointer_temperature": 1.0, + "return_components": true, + "exact_kernels": true, + "exact_cuda_kernels_applied": true, + "efficient_long_context_tokens": 8192, + "long_context_attention": "fused SDPA (Flash/Efficient, no math fallback) at or above the recorded threshold; exact math SDPA below it", + "max_tokens": 65536, + "backbone_context_window": 262144, + "effective_max_tokens": 65536, + "dtype": "torch.bfloat16", + "device": "cuda" + }, + "runtime": { + "python": "3.12.14", + "torch": "2.8.0+cu128", + "cuda": "12.8", + "transformers": "5.17.0", + "peft": "0.21.0", + "numpy": "2.5.3", + "pyarrow": "25.0.1", + "safetensors": "0.8.0", + "code_revision": "ae891ed97147b57b41a78a4bb4960b018db3bf42" + }, + "dataset_hashes": { + "jevbench_development_sha256": "b6d6eb34fdbbf46ec11be8657da5e513e3538990d7a00ca5582c55d8bd7941c6", + "jevbench_manifest_sha256": "e356ad5a2c1dd3917c126e74bdee58940391568de013502abcdcd902bde3c2a5", + "transfer_manifest_sha256": "3c4f0be94509a3612678bfd3a30fd99a8d0ca3c47ddfe7318075d95b2fa365e4", + "transfer_development_sha256": "53872ba045564315cf3c3dd8fb656e76441c6c514b1a7ff985ff0db3955710cf", + "transfer_test_sha256": "048abea5e12c73c3a622ce5836898c00fa43766e0bf48fb2459e77ee9873497f", + "typed_revision": "d0e2f0c42fef86cc15d1688d25a19f5ba7c85b18", + "typed_test_sha256": "4f294f218ea1da27f3efef936359389c62ea4d3973a41457732990f1d31b647c", + "jevjudge_test_sha256": "5c50f89e9ff1beb2107648b56f2a3cbc19c946e379519caf59afc191700e83cb", + "jevjudge_manifest_sha256": "ee52a15f82629742093a89910a8f72e7ca97248b197aad6ec801f984c2a15b68" + }, + "evaluation_protocol": { + "kernel_policy": "LocalPredictor-compatible exact CUDA policy", + "dtype": "bf16", + "attn": "sdpa", + "temperature": 1.0, + "blend_selection": "not performed here; components are saved for fitting on Transfer-v9 development only", + "efficiency_scope": "base calls execute choice only; checkpoint calls execute choice and native. Do not compare component efficiency from these end-to-end timings", + "long_context_attention": "exact math SDPA below the configured threshold; memory-linear fused SDPA without a math fallback at or above it", + "efficient_long_context_tokens": 8192, + "resume": { + "enabled": true, + "reused_datasets": [ + "jevbench_public", + "transfer_calibration", + "transfer_test", + "typed_test" + ], + "preexisting_code_revision": "256001ca806f9d7e302bea967850db214f3d8e4e", + "dataset_code_revisions": { + "jevbench_public": "256001ca806f9d7e302bea967850db214f3d8e4e", + "transfer_calibration": "256001ca806f9d7e302bea967850db214f3d8e4e", + "transfer_test": "256001ca806f9d7e302bea967850db214f3d8e4e", + "typed_test": "256001ca806f9d7e302bea967850db214f3d8e4e", + "jevjudge_text": "ae891ed97147b57b41a78a4bb4960b018db3bf42" + } + } + }, + "ensemble_protocol": { + "selection_split": "Transfer-v9 development clean/knowable accuracy cohort; never any reported test panel", + "selection_rows": 1046, + "weight_grid": "0.00 to 1.00 inclusive, step 0.01", + "pool": "log-linear/geometric", + "temperature_fit": "bounded scalar NLL minimization, T in [0.05, 20]", + "ece": "15 equal-width bins for Typed Decisions; 10 for every other dataset", + "selection_objective": "accuracy; calibrated NLL then distance from 0.5 break ties", + "transfer": "the selected weight and temperature are frozen for every held-out dataset", + "native_shipped": "the checkpoint's native probabilities, including its shipped inference temperature; no additional temperature is applied", + "fixed_blend_0_5": "equal log-linear pooling of T=1 choice probabilities and shipped native probabilities; no additional temperature is applied", + "transfer_dev_tuned_blend": "the Transfer-dev-selected native weight followed by the reported scalar additional temperature" + }, + "selected": { + "choice_temperature": 0.5969253208700946, + "native_weight": 0.52, + "blend_temperature": 0.8352476617411964, + "selection_objective": "hard-label accuracy on the Transfer-v9 development clean/knowable cohort; calibrated NLL and distance from 0.5 break ties" + } + } + ], + "external_source": { + "path": "results/external-zero-shot-v1.json", + "sha256": "8a589a7e5d0d5ae5232fbdf30a71cdce5399cb5f5aacb1fcaf974057be48b3b9", + "artifact_version": 5, + "provenance": { + "typed_existing": { + "path": "results/external-decision-evals-20261001/typed-decisions.json", + "sha256": "a9fbcc06a5e957630e83f18c5dc01d41ff7b3e2289b0d816f2e1459ef814341e" + }, + "typed_open_summary": { + "local_run_path": "runs/external-open-baselines-20261001/summary.json", + "sha256": "4b4f06f8f02ce6cb4a610f110450469c46b917fd9aa1d571077d0c054320eab2" + }, + "jevjudge_full_context_scores": { + "local_run_path": "runs/jevjudge-open-baselines-20261001/scores-full-context", + "protocol": "native inputs, no truncation, 65,536-token shared context ceiling", + "main_cohort_sha256_by_file": { + "bongard-mini.json": "f3fa601f617ed2e70b31d98fe2c589ee8787ee47624233f3fbd8b3d02630daa9", + "jeff-gemma4-e2b.json": "78fd3e26bf01b5630e23656049f2a33e3a77e81a0381d6106c2ad3afce4d0491", + "jeff-qwen35-08b.json": "ea2dc271c6d23c11278e6fdcdf56a9f45a1938404577d98ce101ec6669f58dbd", + "jeff-qwen35-2b.json": "8d76f061b8cd67811d48b1eb5467e9274c1648e47cb3c835974c8b32c647a404", + "jevany-gemma-4b-step2771.json": "88561342bec2909101bb8e3bb90a7f9a73626a35056468fae0d27f4e0389c39a", + "jevany-muse-glimmer-30b-step3324.json": "8022d3ff2cb33c602bf6b3aca55cdc10840f1d0735bd523c53dacb0fb966ba00", + "jevany-qwen35-4b-direct-release.json": "db0b29b095e73e6fe6f0795632686a05e0057f03f7b27eae46121db6854eee02", + "jevany-qwen35-4b-pointer-step13850.json": "c7c70a9b2adda41a597bbdd2c354320f5caf2fe4ce09d24c3fb5731615ff5cc0", + "jevany-qwen38-27b-step44319.json": "fcbdda6cb097f84b8bade8b7272f588dd049d32eb0ccd1f59cb6a76850954661", + "opendecider-small.json": "438b89596d7fe7aa593d1d024ef6576082d074e0b488eebc1c4336ee65a28032" + }, + "supplemental_sha256_by_file": { + "kev-27b.json": "056a42c2fef59f5ecffb40e342690ac4467b273f374cbf5d59ec9dbbe3df0cae", + "kev-4b.json": "c6985eef36f04b962c17bc72ca3bd6d2235bcf0619904db6dda135a649291db1", + "laya.json": "abde9d49d8c100b8c6c09ec6b3aaea442f0f9b2f1350e34701d1b615e1651b90" + }, + "supplemental_protocol": "Mixed audit protocols; these hashes are not covered by the no-truncation main-cohort claim." + }, + "jevjudge_full_jevany": { + "local_run_path": "runs/jevjudge-full-current-20261001", + "summary_sha256": "fcc3025cd29ec057d869067edc5d45f5d154b8596f3281aabc4d0c83996b6609", + "final_audit_sha256": "ad0055609f1a3dd87210738acb45a8fce113c2ad0fc9b507af8ddce8d644d773", + "official_scorer_sha256": "477b39c76dd6e6200f0919fcb56e80045ffd29b66445496d5e6e70c3d1537141", + "bootstrap_scorer_sha256": "8f7413d70290bd1009acd398478bb26cbc98f89ebd19af298fc0930b7a763133", + "protocol": "native text/image/video inputs; 65,536-token ceiling; no token, option, or media truncation; 3,220/3,220 per model" + }, + "jevjudge_full_open": { + "local_run_path": "worktrees/JevAny/jevjudge-full-open-20261001/runs/jevjudge-full-open-20261001-v2", + "original_summary_sha256": "f34fc4265b6cf5847d4f1bd46a5601111cc927f57c4e6178acbf61e496477c51", + "corrected_final_summary_sha256": "3c348c94f0494815ae4236ffb2524e92d6cdd3d237812d21465e64e18ddc00b3", + "compatibility_matrix_sha256": "128cc4d00520fa0515f3f5d5172bc4d704c08ebea63ba8b60f93f225db4ac98e", + "note": "Published headline values use returned-probability NLL and the final source-stratified group bootstrap; the original summary retained a raw-logit NLL diagnostic." + }, + "jevjudge_kev_text": { + "local_run_path": "worktrees/JevAny/kev-jevjudge-20261001/runs/jevjudge-kev-full-context-20261001", + "summary_sha256": "4e1f448503983a13a14df9fc877cd876906c09915ef8173bc676778d52e06598", + "protocol": "724/724 text records; 65,536-token ceiling; 4,096-token chunked-KV prefill; no input truncation" + }, + "jevjudge_jev_113_openrouter": { + "source_commit": "2ff9d3019bd7f910486543ce76513321537a3c6f", + "protocol": "OpenRouter full-context rerun; 724/724 JevJudge text-only records", + "note": "Aggregate metrics were added directly to the public evaluation table; item-level outputs are not tracked in this artifact." + } + } + }, + "datasets": { + "transfer_calibration": { + "label": "Transfer-v9 development", + "role": "selection_only", + "records": 1264, + "questions": 1264, + "headline_n": 1046, + "runs": [ + { + "run": "base4", + "model": "Frozen-Qwen3.5-4B", + "family": "Qwen3.5-4B", + "weights": "frozen_base", + "native_readout": null, + "zero_shot": { + "choice_t1": { + "n": 1046, + "correct": 725, + "accuracy": 0.6931166347992351, + "cross_entropy_from_gold": 0.8799398258908452, + "gold_entropy": 0.0, + "kl_from_gold": 0.8799398258908452, + "brier": 0.43925607559848767, + "ece": 0.12305476565507475, + "mean_confidence": 0.8154458555896062, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 1046, + "correct": 725, + "accuracy": 0.6931166347992351, + "cross_entropy_from_gold": 0.7684499162138351, + "gold_entropy": 0.0, + "kl_from_gold": 0.7684499162138351, + "brier": 0.40380428797912027, + "ece": 0.0442424982205075, + "mean_confidence": 0.7062354250490573, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + } + }, + { + "run": "pointer4", + "model": "JevAny-Qwen3.5-4B-Pointer", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "pointer", + "zero_shot": { + "choice_t1": { + "n": 1046, + "correct": 784, + "accuracy": 0.7495219885277247, + "cross_entropy_from_gold": 0.6801893631325125, + "gold_entropy": 0.0, + "kl_from_gold": 0.6801893631325125, + "brier": 0.3338033580246269, + "ece": 0.054371329145242946, + "mean_confidence": 0.6968489272720685, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "native_shipped": { + "n": 1046, + "correct": 823, + "accuracy": 0.7868068833652008, + "cross_entropy_from_gold": 0.5868780830556992, + "gold_entropy": 0.0, + "kl_from_gold": 0.5868780830556992, + "brier": 0.2965183466659115, + "ece": 0.03479245011625781, + "mean_confidence": 0.805131298589329, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 1046, + "correct": 822, + "accuracy": 0.7858508604206501, + "cross_entropy_from_gold": 0.5717414381657305, + "gold_entropy": 0.0, + "kl_from_gold": 0.5717414381657305, + "brier": 0.2919156848114442, + "ece": 0.03811670878840213, + "mean_confidence": 0.7688884953818541, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 1046, + "correct": 784, + "accuracy": 0.7495219885277247, + "cross_entropy_from_gold": 0.675438096110365, + "gold_entropy": 0.0, + "kl_from_gold": 0.675438096110365, + "brier": 0.3287201057208544, + "ece": 0.030612288328262394, + "mean_confidence": 0.7238557515998861, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 1046, + "correct": 828, + "accuracy": 0.7915869980879541, + "cross_entropy_from_gold": 0.569720964551106, + "gold_entropy": 0.0, + "kl_from_gold": 0.569720964551106, + "brier": 0.2929198506354004, + "ece": 0.05359065555472778, + "mean_confidence": 0.7774203199582407, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + } + }, + { + "run": "direct4", + "model": "JevAny-Qwen3.5-4B-Direct-Token", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "direct-token", + "zero_shot": { + "choice_t1": { + "n": 1046, + "correct": 796, + "accuracy": 0.7609942638623327, + "cross_entropy_from_gold": 0.6010867296757888, + "gold_entropy": 0.0, + "kl_from_gold": 0.6010867296757888, + "brier": 0.3064220873561066, + "ece": 0.04138187687571737, + "mean_confidence": 0.796631394719685, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "native_shipped": { + "n": 1046, + "correct": 818, + "accuracy": 0.7820267686424475, + "cross_entropy_from_gold": 0.5642005514977974, + "gold_entropy": 0.0, + "kl_from_gold": 0.5642005514977974, + "brier": 0.2908559938309828, + "ece": 0.029163506342423436, + "mean_confidence": 0.8005854296720307, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 1046, + "correct": 821, + "accuracy": 0.7848948374760994, + "cross_entropy_from_gold": 0.5635679825981634, + "gold_entropy": 0.0, + "kl_from_gold": 0.5635679825981634, + "brier": 0.28919182401119004, + "ece": 0.03032435006136549, + "mean_confidence": 0.79621407300774, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 1046, + "correct": 796, + "accuracy": 0.7609942638623327, + "cross_entropy_from_gold": 0.5914027552210456, + "gold_entropy": 0.0, + "kl_from_gold": 0.5914027552210456, + "brier": 0.3043378646694837, + "ece": 0.03285794273637177, + "mean_confidence": 0.765631487860452, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 1046, + "correct": 826, + "accuracy": 0.7896749521988528, + "cross_entropy_from_gold": 0.5583414368363817, + "gold_entropy": 0.0, + "kl_from_gold": 0.5583414368363817, + "brier": 0.2874056312419155, + "ece": 0.027602128315946592, + "mean_confidence": 0.7807455671293496, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + } + }, + { + "run": "base27", + "model": "Frozen-Qwen3.8-27B", + "family": "Qwen3.8-27B", + "weights": "frozen_base", + "native_readout": null, + "zero_shot": { + "choice_t1": { + "n": 1046, + "correct": 820, + "accuracy": 0.7839388145315488, + "cross_entropy_from_gold": 0.6672937347936473, + "gold_entropy": 0.0, + "kl_from_gold": 0.6672937347936473, + "brier": 0.31577326969120223, + "ece": 0.08097099874334637, + "mean_confidence": 0.8596910291171252, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 1046, + "correct": 820, + "accuracy": 0.7839388145315488, + "cross_entropy_from_gold": 0.5895245884799086, + "gold_entropy": 0.0, + "kl_from_gold": 0.5895245884799086, + "brier": 0.3023594256806041, + "ece": 0.029495602615825022, + "mean_confidence": 0.7806446298690051, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + } + }, + { + "run": "pointer27", + "model": "JevAny-Qwen3.8-27B-Pointer", + "family": "Qwen3.8-27B", + "weights": "jevany_sft", + "native_readout": "pointer", + "zero_shot": { + "choice_t1": { + "n": 1046, + "correct": 885, + "accuracy": 0.8460803059273423, + "cross_entropy_from_gold": 0.5085522464835983, + "gold_entropy": 0.0, + "kl_from_gold": 0.5085522464835983, + "brier": 0.2411187763545078, + "ece": 0.10827608077576636, + "mean_confidence": 0.7390643592114102, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "native_shipped": { + "n": 1046, + "correct": 900, + "accuracy": 0.8604206500956023, + "cross_entropy_from_gold": 0.3878296713265552, + "gold_entropy": 0.0, + "kl_from_gold": 0.3878296713265552, + "brier": 0.19524463105141282, + "ece": 0.026304156956950205, + "mean_confidence": 0.8776426213905197, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 1046, + "correct": 907, + "accuracy": 0.8671128107074569, + "cross_entropy_from_gold": 0.3773854700281526, + "gold_entropy": 0.0, + "kl_from_gold": 0.3773854700281526, + "brier": 0.19080039228840473, + "ece": 0.030952348286441715, + "mean_confidence": 0.8386108687417092, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 1046, + "correct": 885, + "accuracy": 0.8460803059273423, + "cross_entropy_from_gold": 0.4457110992469971, + "gold_entropy": 0.0, + "kl_from_gold": 0.4457110992469971, + "brier": 0.21999786383788478, + "ece": 0.023323136487035337, + "mean_confidence": 0.8447172036358691, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 1046, + "correct": 909, + "accuracy": 0.8690248565965584, + "cross_entropy_from_gold": 0.3698933820845643, + "gold_entropy": 0.0, + "kl_from_gold": 0.3698933820845643, + "brier": 0.18873769003914254, + "ece": 0.020429096141296538, + "mean_confidence": 0.8663359693950322, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + } + } + ] + }, + "transfer_test": { + "label": "Transfer-v9 test", + "role": "held_out_evaluation", + "records": 1264, + "questions": 1264, + "headline_n": 1046, + "runs": [ + { + "run": "base4", + "model": "Frozen-Qwen3.5-4B", + "family": "Qwen3.5-4B", + "weights": "frozen_base", + "native_readout": null, + "zero_shot": { + "choice_t1": { + "n": 1046, + "correct": 716, + "accuracy": 0.6845124282982792, + "cross_entropy_from_gold": 0.8349623273118204, + "gold_entropy": 0.0, + "kl_from_gold": 0.8349623273118204, + "brier": 0.42656058074391484, + "ece": 0.1303798195616654, + "mean_confidence": 0.8139445762365763, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 1046, + "correct": 716, + "accuracy": 0.6845124282982792, + "cross_entropy_from_gold": 0.7410651656609946, + "gold_entropy": 0.0, + "kl_from_gold": 0.7410651656609946, + "brier": 0.3904408201413376, + "ece": 0.04134673815393105, + "mean_confidence": 0.7051530768940606, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + } + }, + { + "run": "pointer4", + "model": "JevAny-Qwen3.5-4B-Pointer", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "pointer", + "zero_shot": { + "choice_t1": { + "n": 1046, + "correct": 770, + "accuracy": 0.7361376673040153, + "cross_entropy_from_gold": 0.6902431349522291, + "gold_entropy": 0.0, + "kl_from_gold": 0.6902431349522291, + "brier": 0.348668135528129, + "ece": 0.04630103938423926, + "mean_confidence": 0.701833119833475, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "native_shipped": { + "n": 1046, + "correct": 815, + "accuracy": 0.7791586998087954, + "cross_entropy_from_gold": 0.5717563825431627, + "gold_entropy": 0.0, + "kl_from_gold": 0.5717563825431627, + "brier": 0.29765821801656817, + "ece": 0.04239090732927413, + "mean_confidence": 0.8129759944331685, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 1046, + "correct": 810, + "accuracy": 0.7743785850860421, + "cross_entropy_from_gold": 0.5666923610469762, + "gold_entropy": 0.0, + "kl_from_gold": 0.5666923610469762, + "brier": 0.2939336464727867, + "ece": 0.04874813932892946, + "mean_confidence": 0.7776535459474749, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 1046, + "correct": 770, + "accuracy": 0.7361376673040153, + "cross_entropy_from_gold": 0.6888466560394513, + "gold_entropy": 0.0, + "kl_from_gold": 0.6888466560394513, + "brier": 0.34649580012970715, + "ece": 0.03269551339782706, + "mean_confidence": 0.7287518287657895, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 1046, + "correct": 816, + "accuracy": 0.780114722753346, + "cross_entropy_from_gold": 0.5578570106435864, + "gold_entropy": 0.0, + "kl_from_gold": 0.5578570106435864, + "brier": 0.2914736076482713, + "ece": 0.04496393628178446, + "mean_confidence": 0.7852072596268804, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + } + }, + { + "run": "direct4", + "model": "JevAny-Qwen3.5-4B-Direct-Token", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "direct-token", + "zero_shot": { + "choice_t1": { + "n": 1046, + "correct": 822, + "accuracy": 0.7858508604206501, + "cross_entropy_from_gold": 0.5697635579703885, + "gold_entropy": 0.0, + "kl_from_gold": 0.5697635579703885, + "brier": 0.29077517389394053, + "ece": 0.0470678026592797, + "mean_confidence": 0.8092660785307848, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "native_shipped": { + "n": 1046, + "correct": 825, + "accuracy": 0.7887189292543021, + "cross_entropy_from_gold": 0.5304680016857037, + "gold_entropy": 0.0, + "kl_from_gold": 0.5304680016857037, + "brier": 0.2727158680694733, + "ece": 0.040601108067036665, + "mean_confidence": 0.8199335179349373, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 1046, + "correct": 831, + "accuracy": 0.7944550669216062, + "cross_entropy_from_gold": 0.5309958800935447, + "gold_entropy": 0.0, + "kl_from_gold": 0.5309958800935447, + "brier": 0.2725904993128285, + "ece": 0.038203239429849746, + "mean_confidence": 0.8132871591572012, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 1046, + "correct": 822, + "accuracy": 0.7858508604206501, + "cross_entropy_from_gold": 0.5628608839489921, + "gold_entropy": 0.0, + "kl_from_gold": 0.5628608839489921, + "brier": 0.28883932397403955, + "ece": 0.030719110557561026, + "mean_confidence": 0.7766618482775124, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 1046, + "correct": 835, + "accuracy": 0.7982791586998088, + "cross_entropy_from_gold": 0.5262143707831856, + "gold_entropy": 0.0, + "kl_from_gold": 0.5262143707831856, + "brier": 0.26999002433749075, + "ece": 0.027780702772704318, + "mean_confidence": 0.7973056353797929, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + } + }, + { + "run": "base27", + "model": "Frozen-Qwen3.8-27B", + "family": "Qwen3.8-27B", + "weights": "frozen_base", + "native_readout": null, + "zero_shot": { + "choice_t1": { + "n": 1046, + "correct": 844, + "accuracy": 0.8068833652007649, + "cross_entropy_from_gold": 0.6110678829313702, + "gold_entropy": 0.0, + "kl_from_gold": 0.6110678829313702, + "brier": 0.279721544152333, + "ece": 0.06893200869919826, + "mean_confidence": 0.8617564454102284, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 1046, + "correct": 844, + "accuracy": 0.8068833652007649, + "cross_entropy_from_gold": 0.5494987252614483, + "gold_entropy": 0.0, + "kl_from_gold": 0.5494987252614483, + "brier": 0.2709760690574176, + "ece": 0.027612650998887305, + "mean_confidence": 0.7874674967417389, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + } + }, + { + "run": "pointer27", + "model": "JevAny-Qwen3.8-27B-Pointer", + "family": "Qwen3.8-27B", + "weights": "jevany_sft", + "native_readout": "pointer", + "zero_shot": { + "choice_t1": { + "n": 1046, + "correct": 900, + "accuracy": 0.8604206500956023, + "cross_entropy_from_gold": 0.47077064689010384, + "gold_entropy": 0.0, + "kl_from_gold": 0.47077064689010384, + "brier": 0.2114754899927417, + "ece": 0.11231046843501219, + "mean_confidence": 0.7539620937273849, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "native_shipped": { + "n": 1046, + "correct": 920, + "accuracy": 0.8795411089866156, + "cross_entropy_from_gold": 0.3191183572882807, + "gold_entropy": 0.0, + "kl_from_gold": 0.3191183572882807, + "brier": 0.15983357286839844, + "ece": 0.02547180335097676, + "mean_confidence": 0.8916975436746409, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 1046, + "correct": 933, + "accuracy": 0.8919694072657743, + "cross_entropy_from_gold": 0.32150313724237106, + "gold_entropy": 0.0, + "kl_from_gold": 0.32150313724237106, + "brier": 0.1550705009584002, + "ece": 0.043199084741469725, + "mean_confidence": 0.8548688707373127, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 1046, + "correct": 900, + "accuracy": 0.8604206500956023, + "cross_entropy_from_gold": 0.40058733865111656, + "gold_entropy": 0.0, + "kl_from_gold": 0.40058733865111656, + "brier": 0.1880387608542386, + "ece": 0.02034085988544015, + "mean_confidence": 0.8604184603403277, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 1046, + "correct": 932, + "accuracy": 0.8910133843212237, + "cross_entropy_from_gold": 0.3094830481103647, + "gold_entropy": 0.0, + "kl_from_gold": 0.3094830481103647, + "brier": 0.15280535574202198, + "ece": 0.034827378233759226, + "mean_confidence": 0.8807493062406941, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 218, + "ece_bins": 10 + } + } + } + ] + }, + "typed_test": { + "label": "Typed Decisions test", + "role": "held_out_external_evaluation", + "records": 400, + "questions": 2000, + "headline_n": 2000, + "runs": [ + { + "run": "base4", + "model": "Frozen-Qwen3.5-4B", + "family": "Qwen3.5-4B", + "weights": "frozen_base", + "native_readout": null, + "zero_shot": { + "choice_t1": { + "n": 2000, + "correct": 1055, + "accuracy": 0.5275, + "cross_entropy_from_gold": 1.485304034737827, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.7179454791134983, + "brier": 0.32209870787613476, + "ece": 0.21683605197556294, + "mean_confidence": 0.7434521192146655, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 2000, + "correct": 1055, + "accuracy": 0.5275, + "cross_entropy_from_gold": 1.160641706252667, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.3932831506283383, + "brier": 0.22234376212257462, + "ece": 0.0900516370077249, + "mean_confidence": 0.6134237235271172, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + } + } + }, + { + "run": "pointer4", + "model": "JevAny-Qwen3.5-4B-Pointer", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "pointer", + "zero_shot": { + "choice_t1": { + "n": 2000, + "correct": 1156, + "accuracy": 0.578, + "cross_entropy_from_gold": 1.1689837218988208, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.4016251662744921, + "brier": 0.21958081006971433, + "ece": 0.11804038821625913, + "mean_confidence": 0.6283756741526655, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + }, + "native_shipped": { + "n": 2000, + "correct": 1270, + "accuracy": 0.635, + "cross_entropy_from_gold": 1.2461303849511813, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.4787718293268526, + "brier": 0.21769431550066473, + "ece": 0.11542953593789118, + "mean_confidence": 0.7131901462821837, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + }, + "fixed_blend_0_5": { + "n": 2000, + "correct": 1214, + "accuracy": 0.607, + "cross_entropy_from_gold": 1.1474089185651393, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.3800503629408105, + "brier": 0.20385335585187295, + "ece": 0.08838893066306003, + "mean_confidence": 0.6710193141005254, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 2000, + "correct": 1156, + "accuracy": 0.578, + "cross_entropy_from_gold": 1.2283795069938794, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.4610209513695507, + "brier": 0.23872296531625706, + "ece": 0.13219447566436857, + "mean_confidence": 0.6614429063056575, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + }, + "transfer_dev_tuned_blend": { + "n": 2000, + "correct": 1258, + "accuracy": 0.629, + "cross_entropy_from_gold": 1.1499057537598978, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.38254719813556903, + "brier": 0.19860375584665835, + "ece": 0.09685551065650508, + "mean_confidence": 0.6767929224204166, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + } + } + }, + { + "run": "direct4", + "model": "JevAny-Qwen3.5-4B-Direct-Token", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "direct-token", + "zero_shot": { + "choice_t1": { + "n": 2000, + "correct": 1296, + "accuracy": 0.648, + "cross_entropy_from_gold": 1.157150279237127, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.38979172361279835, + "brier": 0.18515075572006146, + "ece": 0.07029919279872957, + "mean_confidence": 0.6765339723262027, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + }, + "native_shipped": { + "n": 2000, + "correct": 1344, + "accuracy": 0.672, + "cross_entropy_from_gold": 1.2022614330547874, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.43490287743045863, + "brier": 0.17877494146152656, + "ece": 0.08278732481632818, + "mean_confidence": 0.7379338187164135, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + }, + "fixed_blend_0_5": { + "n": 2000, + "correct": 1335, + "accuracy": 0.6675, + "cross_entropy_from_gold": 1.1350838501828007, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.3677252945584719, + "brier": 0.1702376402804755, + "ece": 0.06198979447604478, + "mean_confidence": 0.7066262108749651, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 2000, + "correct": 1296, + "accuracy": 0.648, + "cross_entropy_from_gold": 1.083382488931363, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.31602393330703427, + "brier": 0.16593211527952617, + "ece": 0.07601165366691312, + "mean_confidence": 0.6328419411033379, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + }, + "transfer_dev_tuned_blend": { + "n": 2000, + "correct": 1353, + "accuracy": 0.6765, + "cross_entropy_from_gold": 1.0910947225952166, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.3237361669708878, + "brier": 0.15773140989073764, + "ece": 0.06194953110709811, + "mean_confidence": 0.6893724371900853, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + } + } + }, + { + "run": "base27", + "model": "Frozen-Qwen3.8-27B", + "family": "Qwen3.8-27B", + "weights": "frozen_base", + "native_readout": null, + "zero_shot": { + "choice_t1": { + "n": 2000, + "correct": 1365, + "accuracy": 0.6825, + "cross_entropy_from_gold": 1.4486833046492098, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.681324749024881, + "brier": 0.23332950942149694, + "ece": 0.12575488372079707, + "mean_confidence": 0.8052338907255023, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 2000, + "correct": 1365, + "accuracy": 0.6825, + "cross_entropy_from_gold": 1.0976128315315092, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.3302542759071805, + "brier": 0.16553442637117138, + "ece": 0.07868470788264477, + "mean_confidence": 0.7000954995537738, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + } + } + }, + { + "run": "pointer27", + "model": "JevAny-Qwen3.8-27B-Pointer", + "family": "Qwen3.8-27B", + "weights": "jevany_sft", + "native_readout": "pointer", + "zero_shot": { + "choice_t1": { + "n": 2000, + "correct": 1452, + "accuracy": 0.726, + "cross_entropy_from_gold": 0.9201029180583041, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.15274436243397538, + "brier": 0.0785204257840767, + "ece": 0.10839391090387629, + "mean_confidence": 0.6176060890961234, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + }, + "native_shipped": { + "n": 2000, + "correct": 1456, + "accuracy": 0.728, + "cross_entropy_from_gold": 1.0602046256660616, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.2928460700417328, + "brier": 0.13141516555965035, + "ece": 0.05304485014212312, + "mean_confidence": 0.7527105045858369, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + }, + "fixed_blend_0_5": { + "n": 2000, + "correct": 1464, + "accuracy": 0.732, + "cross_entropy_from_gold": 0.9466132871046253, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.17925473148029658, + "brier": 0.09467929687142354, + "ece": 0.034120016906592165, + "mean_confidence": 0.6998051537601903, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 2000, + "correct": 1452, + "accuracy": 0.726, + "cross_entropy_from_gold": 1.0152256258087546, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.2478670701844259, + "brier": 0.11614111181065959, + "ece": 0.020460563111670046, + "mean_confidence": 0.7331494836433007, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + }, + "transfer_dev_tuned_blend": { + "n": 2000, + "correct": 1466, + "accuracy": 0.733, + "cross_entropy_from_gold": 1.004976949424594, + "gold_entropy": 0.7673585556243288, + "kl_from_gold": 0.23761839380026517, + "brier": 0.11476036996840079, + "ece": 0.02738445525330449, + "mean_confidence": 0.740111940631487, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 15 + } + } + } + ], + "external_baselines": [ + { + "model": "meraGPT Decider 1", + "kind": "published_only", + "status": "dataset-card result; not locally rerun", + "accuracy": 0.768 + }, + { + "model": "Jev 1.13 (OpenRouter)", + "canonical_model": "TypeSafe Jev 1.13.0", + "kind": "external_api", + "result_source": "published_only", + "status": "dataset-card result; not locally rerun", + "accuracy": 0.727 + } + ] + }, + "jevbench_public": { + "label": "JevBench public development", + "role": "public_diagnostic_not_used_for_selection", + "records": 231, + "questions": 231, + "headline_n": 231, + "runs": [ + { + "run": "base4", + "model": "Frozen-Qwen3.5-4B", + "family": "Qwen3.5-4B", + "weights": "frozen_base", + "native_readout": null, + "zero_shot": { + "choice_t1": { + "n": 231, + "correct": 184, + "accuracy": 0.7965367965367965, + "cross_entropy_from_gold": 0.46503985941725867, + "gold_entropy": 0.0, + "kl_from_gold": 0.46503985941725867, + "brier": 0.26023857002415907, + "ece": 0.042706500279636225, + "mean_confidence": 0.8277960488256922, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 231, + "correct": 184, + "accuracy": 0.7965367965367965, + "cross_entropy_from_gold": 0.5034935201937853, + "gold_entropy": 0.0, + "kl_from_gold": 0.5034935201937853, + "brier": 0.2731813432864743, + "ece": 0.08846006305082621, + "mean_confidence": 0.7245256388535549, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + } + }, + { + "run": "pointer4", + "model": "JevAny-Qwen3.5-4B-Pointer", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "pointer", + "zero_shot": { + "choice_t1": { + "n": 231, + "correct": 188, + "accuracy": 0.8138528138528138, + "cross_entropy_from_gold": 0.47901846432733636, + "gold_entropy": 0.0, + "kl_from_gold": 0.47901846432733636, + "brier": 0.2561308970527479, + "ece": 0.056124141655517386, + "mean_confidence": 0.7692551472534949, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "native_shipped": { + "n": 231, + "correct": 185, + "accuracy": 0.8008658008658008, + "cross_entropy_from_gold": 0.4554565575008672, + "gold_entropy": 0.0, + "kl_from_gold": 0.4554565575008672, + "brier": 0.2585838755503942, + "ece": 0.03718147002370739, + "mean_confidence": 0.8308520442419092, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 231, + "correct": 188, + "accuracy": 0.8138528138528138, + "cross_entropy_from_gold": 0.4142501461307885, + "gold_entropy": 0.0, + "kl_from_gold": 0.4142501461307885, + "brier": 0.23413224994049325, + "ece": 0.02272572239740828, + "mean_confidence": 0.807826264488957, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 231, + "correct": 188, + "accuracy": 0.8138528138528138, + "cross_entropy_from_gold": 0.46979954158584164, + "gold_entropy": 0.0, + "kl_from_gold": 0.46979954158584164, + "brier": 0.2515032873679713, + "ece": 0.03081010600115095, + "mean_confidence": 0.7956829590845715, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 231, + "correct": 187, + "accuracy": 0.8095238095238095, + "cross_entropy_from_gold": 0.4300189742220573, + "gold_entropy": 0.0, + "kl_from_gold": 0.4300189742220573, + "brier": 0.24497083465715366, + "ece": 0.03200768487813679, + "mean_confidence": 0.8105343163775357, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + } + }, + { + "run": "direct4", + "model": "JevAny-Qwen3.5-4B-Direct-Token", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "direct-token", + "zero_shot": { + "choice_t1": { + "n": 231, + "correct": 188, + "accuracy": 0.8138528138528138, + "cross_entropy_from_gold": 0.4119059642596153, + "gold_entropy": 0.0, + "kl_from_gold": 0.4119059642596153, + "brier": 0.23630630313186377, + "ece": 0.06493498624236703, + "mean_confidence": 0.8264788235127298, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "native_shipped": { + "n": 231, + "correct": 187, + "accuracy": 0.8095238095238095, + "cross_entropy_from_gold": 0.43291971264790263, + "gold_entropy": 0.0, + "kl_from_gold": 0.43291971264790263, + "brier": 0.25567839211017723, + "ece": 0.05126707211644283, + "mean_confidence": 0.8459940130356174, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 231, + "correct": 187, + "accuracy": 0.8095238095238095, + "cross_entropy_from_gold": 0.4013583529376732, + "gold_entropy": 0.0, + "kl_from_gold": 0.4013583529376732, + "brier": 0.23610741709317634, + "ece": 0.04227451187274651, + "mean_confidence": 0.8360951760741018, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 231, + "correct": 188, + "accuracy": 0.8138528138528138, + "cross_entropy_from_gold": 0.419037743510702, + "gold_entropy": 0.0, + "kl_from_gold": 0.419037743510702, + "brier": 0.2361960585013741, + "ece": 0.04983130926953957, + "mean_confidence": 0.7972245036382399, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 231, + "correct": 187, + "accuracy": 0.8095238095238095, + "cross_entropy_from_gold": 0.40445010913159385, + "gold_entropy": 0.0, + "kl_from_gold": 0.40445010913159385, + "brier": 0.23725278784385523, + "ece": 0.04395161391787564, + "mean_confidence": 0.8242603879406017, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + } + }, + { + "run": "base27", + "model": "Frozen-Qwen3.8-27B", + "family": "Qwen3.8-27B", + "weights": "frozen_base", + "native_readout": null, + "zero_shot": { + "choice_t1": { + "n": 231, + "correct": 203, + "accuracy": 0.8787878787878788, + "cross_entropy_from_gold": 0.2864628223866121, + "gold_entropy": 0.0, + "kl_from_gold": 0.2864628223866121, + "brier": 0.1604073338283613, + "ece": 0.022674652746122903, + "mean_confidence": 0.8936596611320321, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 231, + "correct": 203, + "accuracy": 0.8787878787878788, + "cross_entropy_from_gold": 0.32118852252593943, + "gold_entropy": 0.0, + "kl_from_gold": 0.32118852252593943, + "brier": 0.16739722641469756, + "ece": 0.09116510874430303, + "mean_confidence": 0.821229989485614, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + } + }, + { + "run": "pointer27", + "model": "JevAny-Qwen3.8-27B-Pointer", + "family": "Qwen3.8-27B", + "weights": "jevany_sft", + "native_readout": "pointer", + "zero_shot": { + "choice_t1": { + "n": 231, + "correct": 207, + "accuracy": 0.8961038961038961, + "cross_entropy_from_gold": 0.34460785579024505, + "gold_entropy": 0.0, + "kl_from_gold": 0.34460785579024505, + "brier": 0.16729832168502917, + "ece": 0.12307461523799648, + "mean_confidence": 0.7818628405160418, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "native_shipped": { + "n": 231, + "correct": 208, + "accuracy": 0.9004329004329005, + "cross_entropy_from_gold": 0.2650469579600223, + "gold_entropy": 0.0, + "kl_from_gold": 0.2650469579600223, + "brier": 0.14556199524820898, + "ece": 0.02122154456035358, + "mean_confidence": 0.884113426683873, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 231, + "correct": 208, + "accuracy": 0.9004329004329005, + "cross_entropy_from_gold": 0.26310190901965735, + "gold_entropy": 0.0, + "kl_from_gold": 0.26310190901965735, + "brier": 0.1406077982333171, + "ece": 0.05751149881179743, + "mean_confidence": 0.8560666856708948, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 231, + "correct": 207, + "accuracy": 0.8961038961038961, + "cross_entropy_from_gold": 0.26532298607460764, + "gold_entropy": 0.0, + "kl_from_gold": 0.26532298607460764, + "brier": 0.14331108028016937, + "ece": 0.042115349045515435, + "mean_confidence": 0.8714557776895534, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 231, + "correct": 208, + "accuracy": 0.9004329004329005, + "cross_entropy_from_gold": 0.24856632495280404, + "gold_entropy": 0.0, + "kl_from_gold": 0.24856632495280404, + "brier": 0.13696196671048083, + "ece": 0.05151408138171153, + "mean_confidence": 0.8781699403131888, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + } + } + ] + }, + "jevjudge_text": { + "label": "JevJudge text", + "role": "held_out_external_evaluation", + "records": 724, + "questions": 724, + "headline_n": 724, + "runs": [ + { + "run": "base4", + "model": "Frozen-Qwen3.5-4B", + "family": "Qwen3.5-4B", + "weights": "frozen_base", + "native_readout": null, + "zero_shot": { + "choice_t1": { + "n": 724, + "correct": 425, + "accuracy": 0.5870165745856354, + "cross_entropy_from_gold": 0.9990413838295111, + "gold_entropy": 0.0, + "kl_from_gold": 0.9990413838295111, + "brier": 0.5424665612791818, + "ece": 0.124790188004864, + "mean_confidence": 0.7118067625904997, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 724, + "correct": 425, + "accuracy": 0.5870165745856354, + "cross_entropy_from_gold": 0.9193370649594155, + "gold_entropy": 0.0, + "kl_from_gold": 0.9193370649594155, + "brier": 0.5121791926918943, + "ece": 0.030915955188617703, + "mean_confidence": 0.5929232826389871, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + } + }, + { + "run": "pointer4", + "model": "JevAny-Qwen3.5-4B-Pointer", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "pointer", + "zero_shot": { + "choice_t1": { + "n": 724, + "correct": 381, + "accuracy": 0.5262430939226519, + "cross_entropy_from_gold": 1.027944601965405, + "gold_entropy": 0.0, + "kl_from_gold": 1.027944601965405, + "brier": 0.5701372991529206, + "ece": 0.05964150883024191, + "mean_confidence": 0.5692400844020692, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "native_shipped": { + "n": 724, + "correct": 420, + "accuracy": 0.580110497237569, + "cross_entropy_from_gold": 0.9966502097264682, + "gold_entropy": 0.0, + "kl_from_gold": 0.9966502097264682, + "brier": 0.5538821469971053, + "ece": 0.07444449081079427, + "mean_confidence": 0.621924240558197, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 724, + "correct": 428, + "accuracy": 0.5911602209944752, + "cross_entropy_from_gold": 0.9621095236741931, + "gold_entropy": 0.0, + "kl_from_gold": 0.9621095236741931, + "brier": 0.5413174973396784, + "ece": 0.06904408118946087, + "mean_confidence": 0.5874577811451718, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 724, + "correct": 381, + "accuracy": 0.5262430939226519, + "cross_entropy_from_gold": 1.048869475648102, + "gold_entropy": 0.0, + "kl_from_gold": 1.048869475648102, + "brier": 0.5765749498377344, + "ece": 0.0728368732066297, + "mean_confidence": 0.5930575646329146, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 724, + "correct": 431, + "accuracy": 0.5953038674033149, + "cross_entropy_from_gold": 0.9665595951901557, + "gold_entropy": 0.0, + "kl_from_gold": 0.9665595951901557, + "brier": 0.5427219958129672, + "ece": 0.0883382849075786, + "mean_confidence": 0.5909347225687963, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + } + }, + { + "run": "direct4", + "model": "JevAny-Qwen3.5-4B-Direct-Token", + "family": "Qwen3.5-4B", + "weights": "jevany_sft", + "native_readout": "direct-token", + "zero_shot": { + "choice_t1": { + "n": 724, + "correct": 417, + "accuracy": 0.5759668508287292, + "cross_entropy_from_gold": 0.9595839832215673, + "gold_entropy": 0.0, + "kl_from_gold": 0.9595839832215673, + "brier": 0.5411152452677374, + "ece": 0.10266762314870827, + "mean_confidence": 0.6575552343312288, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "native_shipped": { + "n": 724, + "correct": 423, + "accuracy": 0.5842541436464088, + "cross_entropy_from_gold": 0.9150453634780736, + "gold_entropy": 0.0, + "kl_from_gold": 0.9150453634780736, + "brier": 0.5160204686928853, + "ece": 0.09170853327092285, + "mean_confidence": 0.6676634582809498, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 724, + "correct": 427, + "accuracy": 0.5897790055248618, + "cross_entropy_from_gold": 0.9182029376845215, + "gold_entropy": 0.0, + "kl_from_gold": 0.9182029376845215, + "brier": 0.5196712705778425, + "ece": 0.07987836608665412, + "mean_confidence": 0.6592116906583616, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 724, + "correct": 417, + "accuracy": 0.5759668508287292, + "cross_entropy_from_gold": 0.9368946832422624, + "gold_entropy": 0.0, + "kl_from_gold": 0.9368946832422624, + "brier": 0.5297156601921297, + "ece": 0.06832163591814601, + "mean_confidence": 0.6163866223639445, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 724, + "correct": 429, + "accuracy": 0.5925414364640884, + "cross_entropy_from_gold": 0.905162432888216, + "gold_entropy": 0.0, + "kl_from_gold": 0.905162432888216, + "brier": 0.5125121544257433, + "ece": 0.06810038720073028, + "mean_confidence": 0.6374560089027138, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + } + }, + { + "run": "base27", + "model": "Frozen-Qwen3.8-27B", + "family": "Qwen3.8-27B", + "weights": "frozen_base", + "native_readout": null, + "zero_shot": { + "choice_t1": { + "n": 724, + "correct": 447, + "accuracy": 0.6174033149171271, + "cross_entropy_from_gold": 1.0435471379379775, + "gold_entropy": 0.0, + "kl_from_gold": 1.0435471379379775, + "brier": 0.5477779993257101, + "ece": 0.18642668688209224, + "mean_confidence": 0.7965262996294791, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 724, + "correct": 447, + "accuracy": 0.6174033149171271, + "cross_entropy_from_gold": 0.8776519496796663, + "gold_entropy": 0.0, + "kl_from_gold": 0.8776519496796663, + "brier": 0.4981210685244041, + "ece": 0.08864165712545208, + "mean_confidence": 0.6911587312881367, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + } + }, + { + "run": "pointer27", + "model": "JevAny-Qwen3.8-27B-Pointer", + "family": "Qwen3.8-27B", + "weights": "jevany_sft", + "native_readout": "pointer", + "zero_shot": { + "choice_t1": { + "n": 724, + "correct": 419, + "accuracy": 0.5787292817679558, + "cross_entropy_from_gold": 0.9221648550274847, + "gold_entropy": 0.0, + "kl_from_gold": 0.9221648550274847, + "brier": 0.5384293459629866, + "ece": 0.12300846658221797, + "mean_confidence": 0.654141167166119, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "native_shipped": { + "n": 724, + "correct": 481, + "accuracy": 0.664364640883978, + "cross_entropy_from_gold": 0.8240838957036632, + "gold_entropy": 0.0, + "kl_from_gold": 0.8240838957036632, + "brier": 0.4761621807919303, + "ece": 0.10150582984409334, + "mean_confidence": 0.7093057903641973, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "fixed_blend_0_5": { + "n": 724, + "correct": 465, + "accuracy": 0.6422651933701657, + "cross_entropy_from_gold": 0.8340868983096242, + "gold_entropy": 0.0, + "kl_from_gold": 0.8340868983096242, + "brier": 0.48649946917918646, + "ece": 0.09293531151742315, + "mean_confidence": 0.6762654332459023, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + }, + "transfer_dev_tuned": { + "transfer_dev_calibrated_choice": { + "n": 724, + "correct": 419, + "accuracy": 0.5787292817679558, + "cross_entropy_from_gold": 1.0389937594613554, + "gold_entropy": 0.0, + "kl_from_gold": 1.0389937594613554, + "brier": 0.5859846798375742, + "ece": 0.18095024427280532, + "mean_confidence": 0.7592297383784663, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + }, + "transfer_dev_tuned_blend": { + "n": 724, + "correct": 466, + "accuracy": 0.643646408839779, + "cross_entropy_from_gold": 0.8472248615476166, + "gold_entropy": 0.0, + "kl_from_gold": 0.8472248615476166, + "brier": 0.49231968730390446, + "ece": 0.10423989831022704, + "mean_confidence": 0.7135438249208921, + "cohort": "clean and knowable", + "excluded_non_headline_rows": 0, + "ece_bins": 10 + } + } + } + ], + "external_baselines": [ + { + "model": "Jev 1.13 (OpenRouter)", + "canonical_model": "TypeSafe Jev 1.13.0", + "kind": "external_api", + "result_source": "api_rerun", + "status": "OpenRouter full-context rerun", + "accuracy": 0.6505524861878453, + "nll": 0.836, + "brier": 0.461, + "ece": 0.076, + "answered": 724, + "requested": 724 + }, + { + "model": "Kev-27B", + "kind": "open_kev", + "status": "local full-context 4,096-token chunked-KV rerun", + "accuracy": 0.6422651933701657, + "nll": 0.8774529517226382, + "brier": 0.48251125757984015, + "ece": 0.07166661519467314, + "answered": 724, + "requested": 724, + "repository": "jaredpalmer/kev-27b", + "revision": "01b81998019be550f0ae858727df49bac9511195" + } + ] + }, + "jevjudge_full": { + "label": "JevJudge full", + "records": 3220, + "status": "unsupported", + "accuracy": null, + "reason": "Training-free choice-token readout is text-only; image/video records are not stripped or relabeled as a full-suite result.", + "native_checkpoint_results": "results/external-zero-shot-v1.json#jevjudge_full" + } + }, + "legacy_results": { + "status": "retained unchanged", + "artifact": "results/letter-readout-v1.json" + } +} diff --git a/site/files/reports/JevAny_Tech_Report.pdf b/site/files/reports/JevAny_Tech_Report.pdf index de0ad588a7bc9b185964e4d24d4e35e03437c9f8..5e0b4c473188a421332f0f479802cd82f7c515c2 100644 GIT binary patch delta 24235 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 delta 11006 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 From 2dd0df1f8c426da3d26d8f5e1dacec8903b66a90 Mon Sep 17 00:00:00 2001 From: Tianxin Wei Date: Fri, 2 Oct 2026 01:39:38 +0000 Subject: [PATCH 09/10] Clarify choice readout terminology --- docs/EVALUATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/EVALUATION.md b/docs/EVALUATION.md index f858b77..a7db080 100644 --- a/docs/EVALUATION.md +++ b/docs/EVALUATION.md @@ -150,7 +150,7 @@ not accuracy. A native + choice stack can change both. The stack weight and additional temperature below are selected only on 1,046 clean/knowable Transfer-v9 development decisions and then frozen. Transfer test, Typed Decisions, and JevJudge text are held out. JevBench is a public diagnostic. -All values are accuracy. +All values are accuracy percentages. | Weights | Readout | JevBench public | Transfer test | Typed test | JevJudge text | |---|---|---:|---:|---:|---:| From 5b808fcb97ca7e8e2fb66c49249a67c86757ad61 Mon Sep 17 00:00:00 2001 From: Tianxin Wei Date: Fri, 2 Oct 2026 01:57:46 +0000 Subject: [PATCH 10/10] Finalize choice readout report and provenance --- docs/CHOICE_READOUT.md | 18 + docs/EXTERNAL_EVALUATION.md | 19 +- reports/JevAny_Tech_Report.pdf | Bin 303110 -> 371281 bytes results/choice-readout-v2.json | 114 ++++++ scripts/build_choice_readout_results.py | 137 ++++++- scripts/build_external_report_appendix.py | 384 +++++++++++++++++-- site/files/reports/JevAny_Tech_Report.pdf | Bin 303110 -> 371281 bytes tests/test_build_choice_readout_results.py | 66 +++- tests/test_build_external_report_appendix.py | 51 ++- 9 files changed, 737 insertions(+), 52 deletions(-) diff --git a/docs/CHOICE_READOUT.md b/docs/CHOICE_READOUT.md index ba70c79..7444be9 100644 --- a/docs/CHOICE_READOUT.md +++ b/docs/CHOICE_READOUT.md @@ -98,6 +98,24 @@ held-out panel runs: | JevAny 4B Direct-Token | 818/1,046 · 78.20% | 187/231 · 80.95% | | JevAny 27B Pointer | 900/1,046 · 86.04% | 208/231 · 90.04% | +### Evaluated model sources + +| Run | Canonical public repository | Evaluated revision | +|---|---|---| +| Frozen Qwen3.5-4B | `Qwen/Qwen3.5-4B` | `851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a` | +| JevAny 4B Pointer | `SimpleJev/JevAny-Qwen3.5-4B-LoRA` | `1c7aa9bab14ac347aeb917c0bcd757838a8a78ce` | +| JevAny 4B Direct-Token | `SimpleJev/JevAny-Qwen3.5-4B-Direct-Token-LoRA` | Not verified for the evaluated local release | +| Frozen Qwen3.8-27B | `Qwen/Qwen3.8-27B` | `1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0` | +| JevAny 27B Pointer | `SimpleJev/JevAny-Qwen3.8-27B-LoRA` | `09c9e9102d5b8cc7d56558d25da1202a761b6c0d` | + +The Direct-Token run is still byte-identifiable: its evaluated adapter is +`b768d54b9e5e20204b2db9e24c18ae6b10fa310b241d1931bac6b8882f127c53` +and its native head is +`d3909cffc156d077061114627c8aa22f60c7a8f1bdcfd20c489bb3bb009fc4a6` +(SHA-256). The evaluation manifest and release metadata do not prove which +immutable public-repository commit contains those exact files, so the result +artifact records its public revision as `null` rather than guessing. + ## Results ![Training-free choice-token, checkpoint-native, Transfer-dev-tuned blend, and external baseline accuracy on Typed Decisions and JevJudge text](choice-readout-results.svg) diff --git a/docs/EXTERNAL_EVALUATION.md b/docs/EXTERNAL_EVALUATION.md index 0366469..004551c 100644 --- a/docs/EXTERNAL_EVALUATION.md +++ b/docs/EXTERNAL_EVALUATION.md @@ -201,15 +201,16 @@ build/report-repro-env/bin/python scripts/build_external_report_appendix.py \ cp reports/JevAny_Tech_Report.pdf site/files/reports/JevAny_Tech_Report.pdf ``` -`--base-pages 19` always takes the original report and agent-harness pages and -discards any previously appended evaluation pages before rebuilding Appendices -L and M. The result is exactly 23 pages: 19 preserved pages plus four generated -pages. The base and merged paths may therefore be identical without growing the -report on repeated runs. Both outputs are written to temporary files in their -destination directories and installed with `os.replace`; an interrupted build -cannot leave a partial tracked PDF. `reports/` contains only one PDF—the merged -report; the `site/files/` copy is its byte-identical deployment mirror. Appendix -and merged PDF metadata use the fixed creation and modification timestamp +`--base-pages 19` retains the report and agent-harness pages, synchronizes the +superseded 27B release headline fields to step 44,319, and discards any +previously appended evaluation pages before rebuilding Appendices L and M. The +result is exactly 23 pages: 19 retained pages plus four generated pages. The +base and merged paths may therefore be identical without growing the report on +repeated runs. Both outputs are written to temporary files in their destination +directories and installed with `os.replace`; an interrupted build cannot leave +a partial tracked PDF. `reports/` contains only one PDF—the merged report; the +`site/files/` copy is its byte-identical deployment mirror. Appendix and merged +PDF metadata use the fixed creation and modification timestamp `D:20261002000000Z`. With the pinned inputs and dependencies, repeated commands produce byte-identical appendix and merged files. diff --git a/reports/JevAny_Tech_Report.pdf b/reports/JevAny_Tech_Report.pdf index 5e0b4c473188a421332f0f479802cd82f7c515c2..cedc9fbbb46d310a861c6ee8670378c9763fc2fc 100644 GIT binary patch delta 163578 zcmc$H34mNxm3FEsB54v5_OMA25E>+%s#m)KWM9IPod5>0lXMnHxt2vo$a(|I63m$xxJD*ug;CzrD9-K3@vB% zOgnnb(CXx=_vQ}Scxd6UDI~aO+HtjFnKkg(C`Kd|kF8>*oE-77+)>xxTUc`9#`j-V z-gxaP2j-f|8w$BI^X2l!BQAfE4h^n5 zZpF%hs8}Xr8xK2mx8&Y|!d@G1e)l1{(#D_PveWf{ocZ=juDo&OJ2oX>y|~bO{RiK< zD7W#PcaBfSPcDq#`0>leZT!p0J8V4Qvgx2z>08 z-}t9@?E_*C*?7gxdvE-~JI`dqQ%}!optHFZ=djE?;gXxygmyEPXp}9Cwq@SKheoO*?NKzG+f&cp*36 zQW;f*%3_+zh^h@xM^{ZQ>}tvV@qT5|Ftp%Fjc5^J{iHm$(3|Y{s@%>SZ@6X8jpy7n z{`%kDa@-NQ%Jutgen)QOg}3zH@Uz_d+{Qa^NsBUBpU)jBv?jY0a=rPePL1ZGjc>So zpMBFrR;aWPX1O`i{uGCi&;?x!8IpcpkvqWX$a z=HZ8DD9&oM5;=-@O8$C8?&*E4)gOK0F>?pj6)RaaR4T*U9+msq{>hZb^1aEXrMXM; zmD=zn%W``ba+M@^Pri5fyXWNIo{#BmVl#&x+B5Csfx%_#mKV$QD2hZ}YuBwA=wH=y zUXSRpR4f-8QL#~O^|j>xii=nEl#0vn-|_Z8_pJY(*HbC=MU_^oLPkr);OEq+)K`vc z05rAM*Ql4ukwBbw;;K^dn4zB8J*B=<9bER6OORS$RB9II08v}vnq@sl&4cF3aR0tm zREwI$c}siB&Aw8#S*|PWc}sfUSnOR`DwXQ;=VbXaYhjGPbLDZS{5e7XEG*S({>K>y z70)Wpo6&RZJms;k(h$0;LRPh5$;y;Dsw%~1U#niPH0nYX1kqP2H4({LeYH{rEX7Jz z!y*R(LPV8hf|!c@F58#Y^vkOKU27avoLVV241**eEeVOuBWYGU2+A`xP@QZ|jjMgt zdad3pPA!-ETD4}Ys01x6MV5xB5`uSxDfkrWj{X!CzzzDE20H-Tmr+dew4hS%B=FH1 z0t~gU(W*pH$^|{NFPc|W>1(05SzOgqZ8rPLD2$3zSsF|6%AUDmZ?t3BZkvg9v%9Tt zVJ$U^`dZ~`3EmXLD&Pn8aUMQcamzAP@+Z-pD@$J;T2sam0B-Cqt<=5jguI3Fn9f zEfX=ah3cE-atx!dm0|zloz1>lxr$IawT#C`xs1>hDxzAY5nCrC5k@$k@nhvlrKbE) z+|K-u_uL%E_|rvt>Ms-oQ7yFUHtHccRw*Y6Pzrv2SUk%YD^`M|wicG+vWXlP3Xx=_ zO)hNp4z;Nf!=IH5iDU5@f}QMvsHhZ}BVU6!#9ys$Fm=)^`VY})t${v>-WV1@pLn;0 z8s~-apjkl-Z#65$T3l-ORm$>ME~BtjEwzek26~pV|Jf4Em+k-PsOk}1b>7mrXKK08 zSB9~bVRp?1EU;0pWz0^bNYkPgC?z#A+9AsjMLR@KhMXEY9ETFUP+zrDDOChv+zI0r zBMvOA+GrVd62I|yp_3{%<|#j&O0_aDXJeK~))@0*U0Y*tHLnPz0u$7WX?&gH=v@r=Io$7fhZde=>Jdt+`1D^ek z>%h zd;`=rVKIfPVjz>`oG%u{LJvYYNU^O{8zh(wR2x);tI9r&6_httrR-C*5UME_jI1S0 z#wPsGl)O=vC=LE0bYs(PM3O7kA|aHJ1YgfeCXkv6ssMS4GyEAJp4uq6eq1V1X(=HQ ztLqH-gAyQySs@90N4}{u@eFG$B^pUA-Ct-WtmefN_%G7)ULi&+kaP+`ksJPP)j;>8 z$re-nY&@U-N2+NogyX!IKS&APlC_XjK)e=?>Whd7Hc-D1Yvfd7w^hXugd8B-X~;{d zi~K_#k#0-p?m;U+K9OhA!=HQ#6xY_0%#b|NLn@5rTGD-Q9haz_l-QInx)>QJUc%u| z1`;O+#vE24-tIxnaB787r!e9Y6_{bm89bwkF_f&GkbOoZwl$^>K~ylSLn*V^IDS~H zDM1kbB!pHlb;O2hRusEBtw_*=r(ne#d^1`^m7OjI7h?=+nt6W`JsaFI# ziv_UqPfC5H_n=hX^cObdXw1n?^+RD~nLi9Q4_brf1Hx&?mZ6hXS&PW|L}*TC4kKU32A)vOhRQG7@DEAj2+J`@ znka5o=t!b~OtG56OSG^p_&@2_z+oIIG!hc3%&@B38l}RY>;tk~l0ep>CG&}#8lq`` zfnd8*&FVx0NsQ1hkug;^69&w;W$c225xK^}oCsMw2|iM-+M((^A&2}@2W*u!g@9ri zeobv)IfK|5^2R%K)e)gUMAxx#9hF~zD5OM9T1|o?@tl;b(bEAV92%3RO4HK28^=!)pX) zZ1h1y^q(;a2@clx+us-o(M$qEC;J%{jHH&9R}18i#hQ8zlk#;806K7Aj*fh#Lbft4 zH?UsQYRO`U7jjzvX03&irN~w86xQuO7nw6AMr517?XmQ6-t|&CLbJ zsx<1vE5-~$)Cvj2rQBN~Z*`lFizson6!v+Zc%zvu3|CPqQLPV3syEWhC|dk-mQmuO za|o>8Rr@N{dKudUU=x9_*0Dy_C}Djhbk#@(KV?YQ2pZbRtRv8iRrFLUg`JYAjDG_2 za!WwjfBP^kjFm{)RnQmNb;z&;OyG4@UdwnC^20(mJs?|1`C|E}*=%4D>H^IFR{E-N z04zvVF&AC0v?yRLML}P^S*?}bGSw((;hb-(Uv2bNY7r)S0wk2CU;oiQfJv~6g@LC} z#oDS7HYKQiAM40qCc@?sn*A~151KUn!#I(+1RG0PT%wP$KSJC=?2%R3&Q9aOGwP|w zb)4`BDPZs;a#Aj{4GtFi-{4m;_e%=dMm<+@aM!S;#k4BoV7I_0=dN16BUi!rvs1=U zE!E%>rG{)02#w#^x(qoZ>}{aPHI5i{cqtPvX_--rn@-zvg z&}jCcVhn`g2oRkb6DaK^sZLcf$N7$J76_BhpP;NE*xNsPrVh zV43#}PUUg@$0D5{pc%MnL^kxJC_nv2Y=lfrWz?kx>cDiP?hj3!OWxD@+yO` znu!VIOC?0nGly_g9YgY(p<2D-Mms`!yQs)IMMcG^;Rs%= zzlE6er6wYj4atMcGJte81=2(SY(Zrk)_@^$(^Xo5@8VMr!uB^48(jQ{@ynPra(tn0 zZwPA?XHA~$bbaLJ+lWwoj@F<;BTDv9jtE1W;D}w38wN>QXHY~;C_sz|+0&o32EGfs zJqM6tCu-770rHPylM0O~1r;6Zl75q3D`LK(Lv3M-Eu@x$Aqw_gPF_S!A;d&&j7HcN z-}Ah(YjpgO6*eRnY)z5Vw1`ONC4FNuL88?X0-ab>!dBEy<7_3!s1|^$F6a`b(CJHr zPX9ywO(~`>h};REKL82wH?z9r&}eJ5E~3_WW*xBM#M%%EdI$6rQC>u-=0Kew^>OnL zrcTI*;_v8+f=YDiv1oru2~bmP>E?y7+oxAr(X3shk^EN*!Ajl!TQ>iic_Akaysz2v z$oy;Oh4g(VA2l`2ypXG@e2H9RGtNl+geF z|D688{BJ?S-k}FESQ)>u3v8}? zqGd3usm_=vxC-bx=o@rB0^=X1Us}h_R2s2E{w6zh6;j^W-8p5N)A1nAOF=kYdYE?B zZv>b!|Kogy9v=d2^Z3A@eLWS1gd<-JxOSLFxtUoJ-_aXELClChlsF~DKa?I*KqrzQ zHd${svJ=WflwFKLBQHtS-U#hW@rWsn)@9mOvh>sY*%#2zXcO!eXb#xb%V?ps=-wnF zL&0eW#U`W5_&l~e_hEM^_hzfHgI=4Gl&!km9KHM3#pvvNS6-pnV3v^t3PpDRiyQVZ z8FZ^m>P5;5%hTSlh5SR)qZK<5Xje)R&>m>de4+`MUc;JE zHs;sC6=qXq`@C{R0tnCSe<2t`HTGkzjAo@e2>K*(Ez(Ndgv6i5@7)+5vu5Qgx~@+7 z6-khp7ps4@P#7757|oTG@eflww?IBxsn|FaxvhfA4Dk!g;rdtLZNi~%9r}}M&!sffs<@sbfkChK{D`tBerC-F*|1E>7EcEJ>t;mKG8*NohjfjZWV>R2gM&r%^v ze}gDwm?cg0cr_mqbTY@^6gOspHJ4V`<2z1KQcQ$2E39hbzEDRx#fk*)p{2!3D>!no z2>Ess|21%g19fV3{D)^Lt5pui{F~3;Rn%$?1W939n=+|%)dq0Qn#mXKLpn8~hJ>4; zN=UA{2b-Ks`BOB?pB0S*Lj2ZoB%aWIC?QINI#QCI09Z?loxNFe^Q02L%2%nL8&XXI z`Hp``ASJ>lr5ue$n4ln~J%Fdd zl+{VL(nGSzg*6N&VK~iXAXP*j#K6YP`y6MfMH-Shhx#u|8zirF^RGF#WG$}Z*rEss zm|~?mRC%O|RrrFPxS}=rB5SjvX)F?m9e6{n5J348kw|Ssi=u2P8ifrLsR@`7BxW*x z8W!TO+eTpq&`u1@TO0>!pNdDdukPSvmRhJ$`p6l1_m+kE&(QX$Yl_HOI%{^(C#{A- zNQqG#X`8i^>{`*_jjho$AP@~}q>Pp%(Bj;zg_)=F9-~b>U#3^1h4L+5kTIQSmVPq! zK(rHLHB!)SON66wmsL4fajH_EH;u&<_|(~$!u4m0GhjB3<*6oEs5jR-qMU|UkF)IgpZz^@g%k8c3a=@|_2PtT)^I};>bE~?OoMR&0o{%1N+ z*G2;jIUp}AGfa9KQ{pXS3z_Wgkt5MgndRETiime=)UXMRU15vmtSI#h(qwcEJw&R9 zRuM^Nqtb-*<)9``@3!hKoYabNYPO6n&%rNM7FJ62&h*F$Tlhnfu++f*rw(M5W%1FD(o=)`Dkk9MSg8uu57T_6 zk4_$>+Mvf^3(Mxbg~=D(teO#fT+(<(rLrSXMbo1Ys4X=qMNchb(g4S3Z=8fX2?)4K!1-z| zx$_ITDGD6-W$bfBZpx@3&prc}GW_g{+~+3bt91jqA)xo%hH-R`-Y&8|x0=q8>lc)j zgD)9@6}eFa9w66R$UP&xTqGvg>@^ClX0814u2C5E5p?!x}G*N#B1mQQ{{%F z2J&0Fnk6EC>=7?Zavk(fSlB5yK&auHujOr)Vs*ce2a+gQe+A z9MfW2Ee+5ZD%&xShW=@tUE*m27c5~1b!tt{VM%u8%-UvA&rbgI>)4*JcL_n2l?Cw^RvA)=vlPh~lYLn4|xevQ+ z>Fz}Bs_9Nsd1kUU1V~e0G}KA)*<8k>=kPU=a^=C1!O)}=hOA2KAuYtrpC<@O#@(!8 zu&SBx4aq=XA4y%}JDvEAt4bk3@eNm591)r})6-c&DKRlW#6f`CAE=~YDyGk{snMCl zPa=t$h*JEUsY-9_y3!KjGZYI0L`rz1O7bwTQ56=2N`&Sbmz3PJ!y+(NBSU<0%+ka< z@9iT1^2`>RNZ7m!=VFW}A$C<_yR1#PN+D6((FpOqh*J9cIH} zF3kO>HZ8!O>w;G7+WuPyQDo{J?BO0>+_Z?xs9LQCFQf9sX0oHhLJTG~%1zlj zj!9jtN3^gQqMfzKrqeM>s=3@mHy_s|y9tsW>EP`gH2I~_sCI9YWz5hT*4@Mi-?O3Z z#!^)xfvEOES0ilfjlwY^H#eo_bOs7jE){5pg}~*`i8ckc#7|y2NPQ>+9aSd{G#P^b z8M7r$s(G%eP1IGz%ssAg4ko2Q5iWwOH!+);)|M|Gjkc;d)ZWDSFVL3j8%YKuEB|!e z2WtsM_S}a|6^n+U9w8)mqX5;T(Lq_A@3v85+6WhXVooE3fOHo?Xe)KhJd|X>o__EA z&u=PZrq*zQJ8)1pEmFo%9)n*G$!?=SO=~`GKcd^2>Hd{tt0V<;Wpb_)(|E{1`7+Vr_>?n@*iTp>?2}DAk&6LhWkE?ilaOkb1_Fo&MbY5?r8b_kum(*k)maJfLG zvK@_R!~83Z%cGR7Oh48$)8v7=5p~ldb@`GH$MAnld{pU+evA*(b5862ZIi9DW*3%` zCbz*nXkW8g!yyl_!c4Y7JUfo!wJG_l`4 zib?%y6AR9M^u>TecVeGlhFrKtx>rDC-OP^n7-LvHH72BoF~eg8lc)$7KfJWl&C|L0c>c`OuYFt-<7TOQ!HTPY9b4w-t$Y&6L3m_P-qW$Y{rF zD>Lq*NbDF-v`nQ*WN4BUqyQ_u-e+z6C0NqPH~h(1{v?yUfdBa zRN8B-h>&@{d&%k~hEyA1p(LnUC+N0a^Ah?TC<1`xu@B758O8R~4&cr^jW7nEh|>SM ze$Mp-?!7D5oLbtcK;gD@OJn-kF|$3!O4U(xRGo>=wl2x1_vo;V6gtd~BFBLrjo5JD z-`5OBu1}L~l`{Uccqn2b)5#!~sUlMZsF6`kMjwKKPvnSHQi$aEU_w| z$TgqP`FhUW1wf;`)F;-DkaV8+!M8s8us}x@)J|pTXCrzT?NlitQstLvmLs3_PY|h` z!Tf`dGFvPXAw)8$M$ZQP1Jk1~kxue7DT1)%BFWWL|~rf-F9FLXO1Id^MsWeiEi?U<7os@vOz><~BB9T^fKy zIUj$l);ootm&v<}Q1B4M|4z=8^_{8Gca|_Vl`XohsGK@((h%k;Ee)RpKvh#9=sgh_ zLmMPk^BEx%It2*Z8wt4G_O=aWV**SF#RMj6S*)Y6j9M2-3Stgoc%gqMB03q7rljh67tmI>?8|mPFE?^ZqRb?XMU(MDRc=6&Hy(!dDr zGPllFM7m6`BvOiL>uyegKqxr=QHfB8bR$=l8YDhaONdH?&c~QW;iU_6glq>UyvB^> zwx)(E0wpCx$)BBs>maHZFx#g=SmTy!ntO$7j8E-xGCTi6*>Blejj=jR1f6jS>W^Y! zf9I-dExzL)+C|LH7$-eQS9ei{GNTB{CdEwJSq78h;(!CkLc-)9$}baUVl(Ou=e5=F z*~-|d|0bakCDSu8FwEIzu5v)UM4!=q_|h$`5UBA9s3QD~7z?Rs{!yF5mmVn|@88(qeT6@~e(8wXD^h|gWWw^;Lw$nU>npK*Jlyq_w z;9#kZ#>PkqG31hP=sOM($)brWctdK~m?Z?SG$BG`e}@sF^@J+!>!C>Frm#$Gq?JNv zdtxE!YSSz;29c2fW42?DKqx9DRjrg|tOEXGZ%OTj7=%gvG#4eHWRX+@Oc=2_fIsj; zA}AB8kF}Lg2t$q$hC47=BV|~+mD|y*#3**PE|yXF;%Wk&Udm#nWG<8@pBpptALNuT z`G*ox`6*3g9#f-rX^ly@UQaxAq^_YVlQ87JYc@~nQxfC_D3JfCj)2$Rkksg7wLr z0Z8uc_$GGLbYX;a@Erv}05o&Lzc^Y2~O;&IYjZrI0qGuGm9nRz9~SIIFq zt>sO4|+m{N#vJ3qcyf#Yhy{u__WZYAlk@v>Ht9e)i7-kPtqUSB*K>&5wD zY<HT{r``2%QCM|%&6*2#5J^*xp9im7O2J_!s;=$P;n+CVHa5J-R$JQ26 zIFzm<77UaMuI9t4SX64l{{A@|4M(dU?0*?iKVBV9YVKbQ^vFs#^*Cv}e{DN2604rL ztF*n)EQE@jnHo_&*7n>6MAKjd3lLh&{9E1Ho|^zu{rt1lBl-U*wteEHiXAY)E!@R3 z>@eH6fNsgiF3+ZR;e?F2KHZXl}`OGXtRj`13qj}$N3!e zM+j)<%Bf7DJ6Vxhldj;rBjki@G^f_kJ7Y#YeJw&;v~YIoWF@{&`xipGU%e0{4b3L@ zySFgp@+;^P?qGkJ5rA79Kv-pj(!6t)SSkOomu9xFLD)qnN2%$m_bBuV{4dT|q(ol$!O{6|rg{fuNJo0f6oeo9CoMQxP0zQa=FciN9mMz}^!<5%ZU zRcZ8L=_jqW5h=1VbF%ZhRs)LsUZ2=}t_?@cD$NKt6jX3^N?;x%;&D1~{jLiJz|&>; zRmXX*s9D818<)Sv2t9((m3K{G{Z?p5q7R3Cn!%M_h@;u9&^WQwE2hJt6^AnX!&o~% zc!BsUE8u3S$+qEuhz%Kg*~W}L#Zyy|3CgS>*~W7PxQWm5M6CQdTYhr5O3-$aO7L^g zhi(}1n<{rbkZ|jI?&2NyubESJG;|-|l-*_WZdKClveL08hv3+MVeoYxyljzOGm7;B zC6nYQHHZZmJISQo7e!&lznoQ}q|&0Wt0heVPskKE(;g~?WjbR(h8&`il*nLemZ#$E z=jk)@alJ7KBT}71qTXExtP4=As*{eKE_1cCGdB}JlK=RD6d}(J@(P?u!-+IlJNBF) zM`@V^CtK*EMtU?{!UYW2CQyZQRWORiUVJ34vsYFvAF%f*E~hrj*B+>8m6WwhS`9WUv7O|I64ZmTZo zJ39Hu7jom1H*L!AHT>EebK7j6jQe74!uWtKdt)C)^0;k}H}?4p0l{0ND{mbx=sOw% zRORL5fx%_#mgBo)Q8oI1b3Y$0C~DQ~_B*uyAMWRCfd9_+jC}+D5ANqXjvG`Mi5Ts* zt6;QA?QKLN?G{@ZU)U9E^jHu?R=-O-dqHtq0Ei_lKmwA{{3Gt?bCJYnU_Nm99gwMe z`pg8~NSm!wao=vXPJu8TJaTt;kpV}Dv^z)UyPb|zcDEyI1~I877`@I#lAv+N{8=J61*;tXuo9|h1dwe z^nW1)RwNjcrSP4*AVj+&Wr4KPz{C#Ilu#Uwy>^6dZ;}7Z1NIns0NVSS4O0Bgi5@j+ z<&VHk&!%gpEqr0xZYd{69o1kRqVq-_st^+t=3I+gzh_!Uh$KKeb0%M9t4>`{Hk?B` z759$J5P~K{omSMW%`ri@Nk{i|SHY7k>W{Kg6`6!fk`1F>c`~XDsUu-5whY+`a+Z^H z{$VCa*!sc^Bb=xRh1S6*=Q*mlAVn5p_(H=Ke=aZ!BzA+VU3vEh^|s(;AzxOTW@K+J zRIs|*g23qiF7fJYDV9>OK%aJ-oK!Mf5?T6^8j650MQ-LhqrL{MLc@R(Pi(X{$vge! zFSIPH84rYY)kdjI`m5U`q*^;c5@Zjpqx-Rf9kmf?JG>0RG<=2(C#&vmW$;_@nKGA#P5~f&d}hT#5mb=^M$CS`g)F`Um=-gaUkO-PC1Q#g}ASBcPKhfl(^HYZT@+ zWQc)c@!5d$h8}ct3@kQYN&I8{=pbxQ6G>;S6sh9pzRn0d0!6AFxg$*Tov~EUPvYo? ztU=ROe^GYK#vdy*Emms?x$MqltQ>Sm5L4|#sd^?LHI`H@qDz&SG}NvZE0DCY zgcl^ZS=5YytBFyVY)`%n4?f=*<&o_4iR`$Bd*Rbp@!>=?ZbidViTW|b<@PJMB`R)( z*FACKq+au+n2Mw^Q?DvJO3HNq+@R?;!O{2FihLS@3QRtE%kd}I36Tg`pZyi9DtfiP zAUzR~P?V8dJmUJcUzI82fEvC}h_fU-sv`C3*{V|WohO^Y zS0kHGuqNA(Zo?3KLiVKf3Qo0zS@M&JR30RPDUSolRLM0yMVb)0`OYKtLmbAeO!)J} z5E|(d7?Xby0sD1Fq03I}F zDJ)c*A_GLgK<#<=>o2M^D!}-c#(%>VnKjtL>kqmtc;Y*{*uB=mU6!kQDh({jwCcRq z(ieAK?{qsyWP?l%Hz!A!1n%HlWRK*I+P$p;a0g+BU8yci5v2TygMkYb!#Bns86mQJ zQ*jwmRdy%_`~A9Y4V6Q!^#-OJ0~BNCUJReaLCT*AJDPV{NV z+qTKb?SsI^LDQFMS2p@xD={bPQ=7-I9*Z?u`P6Y2id>j-set?KC^WNhCR}ZavUqE@ zwc#Um*a#<)q8nuu7rM;9E@^RlMLeZ$^nC1y{eK15W#aN37|bZ~Irx5Cm(`J9`oz6E zxGk?*CJVCJIZoeiS{J$*g0!dNlXQ=MAv@azhP?v2qV_J3dceiLSZBvYuo@_&|3k+^ zylQJ+vy31pmt9`v8g3%&wE}jGmF;g^(KAlJK|}6?4I5@Ogah(cRSuFRC8`Hz{R%c= zRpc}St_u_$V)K(cBH#7P((wPbEl-X_$De)v4PSZ z3u6Dv*u&_2nh1k`7_nWJ++FoXc=m>XaI8TiB4FAN&4Oj_%!4t8IAsmS7+kgyS=~C# z*mCo^(om;PP_Cl=?Yz!=VVf0I>;Va9Z_XH;A8AC%+Mm;rLur4z1`5FH49ak}pW2bG z7o=qrBfS5E^#ZTtX-CSH`ZX6+@X`2t=*cXeEp+e+Gx*({IHfknx|gX{V`m@c25Bcm z=fuw7trPRAmX!Brr0><{8ajjMpm0O0!&ssHOPsK_=4`B?zSbr8jBt9UQIpRUr!oR9 z?wS$!Q628sjB)2p6T4|NB9H=?%i|cX9g?X(L58J=Glv9@^0TAp|_*`j6H4_#W zJdYv&oO@PpV0UK@|nYCn6{5nV!qdfnyU{uha>e0EMTP+A`m{ zpC#3(1ck>|sih<}5w&fVF^SMBoR)BlP^_W0>C2($*0mTXB@K>a$H;a2!0v)*{(88~0j>Fyhi%L~Ji8)!9+Pp;%?s#cu~ziI5V~LX_!h3=}>G$AhR}}gU(WlOI^g&F`SG3C% z7C&z@af(weQZmz!N2MA>LXkJ7Znl&$J|))7zp2WNVT;sKH>;RpL8U^ZfZ0;tk|Y~S z{ADm@X(tO_I8$r_f-24AKU*=iqd-!t0!lA=qZN@XIvbxzudyO4GYNRuAZwksA*H5Q zl_#>oF4XcYIUo+hy z(#ZIa`k?ok*dRDv=%m;v7S>`UaqAS$7&NJ6d&VYLM;DEZJ_y^6Qv|lVAUiDAAfWOk z_6+Y12L@Ht9x>EJG&0Q>LS#enS;|!YkS~x6{%Iq|YMEe*lsetJV^<-=K^FN{(UWi^ zC&3}+6S>bZJ0fW%&RWd=C(@ld=%~#UxY4V?4fI6}mZDDP#i}Y#F&R%0MG*T$(wBoA z)hLaUkoiP-+}h)gK{9+&4kn?qqE!w-@CXdX<|X?dhFpRoLefX9nuFNx#cw5at9GP< zwnkixIc{c8#@ygrttIeOlYg)w(PiP2UjjFxv!Ygqpls;~;5hQ;MJf!~5p%>k^Ut8R zK*$hY!L`J_Qa+j<&$KsZZQRmkT?)J40DdDKy}*bTN{U(xrP2W)&j6nFP1SP`wKq_f5-a0+0WP|V2320#sg}WdbAH=2P-2|4K5%Z&H zYf^5YS{9S?Xo#3-_=ka7g8|D-#t_`|Wd=~OyJiUHUXvw5`l?ovZGI%WU?uRPU~>r#Zt>*NAAc|I|XHk|HMfmJB=&4QzTk& zFlmV7h_nzI%a{qeTI7>d`C{@RaGjT=l^Jg6joB5&JTtP+f9d*ELL&|Lb%Leok*MM>h994ve3Zs|+ z_tR^Y7Cr`t&y9ui?G1sWIY)%`A9q#&n@Orw{I^uj3b@P1=>h6ad?Fj9j~SNRG@yP> z8Peh@C(4yA#kxIcI%1IjIFw|XvQ=zGK0-V3N|?b!qfw*cRrwM&kq06SkvqO%|7_Jk z_nuIK*qZR*NmKVzGJ;VA2*L$nHi(ksKr>-6!;FmKJY>cZ7E?$PlsqIv1*%#4im;qM zPd-#5v5S%;bXF8ODWj;2=l~dNIYt9%(tR=8v5ARTVH4*MkiWMi-_R+?rd%3F_(Tq9 z5H?$^y2Hq1iwH>(!>6N>SX2wnzJNaqh1dy&Na(8BI;4SyM@*!PO!5gap+VVz_`?~EGCq3QE3G_2JLRbKFswLuYWp+;@;8FGn3vYIgTe&~f{ZOx=!8PPq<@hL z{6U`6!z8aUo#7)a3J%Ubf>GHFC|v)jR1D{4n+?9VX(iF28&&ZV6)9%t>ps3r!H{Al zh+MFPb+xh|XmU8&hWb z2{(ALDh-%)(v=hjk@5v8X1e0=ktIT9Ei{7tu`VS@%+B)M;ROa6GQo<*aY8P;iaH?Q zPV%&yiTHmA%$fzP&gI1}mie16I?SR#`VFxim4dNADa%OGO{`FeX8w!3cONq#FS1EM zS58C0`PwWbShmx~q>>6aEpD7R8X^AUVyOjxYQ` zA{F7}qmhch*-91afMuM5w(8!c_TLg5g+T+Nn@B~*pEOCOPV*2dmbQVMPbj-j9Esd0 zEee*f$d!>lHdFO_>miS>cd*F;>+*%k>1=(~C_RHFmyuJ=_K>Pt?RtqMPN~k_&cQ+w zobh$ypclbGA|cv!q?SRSSQ2_IDUOkv&SC(&Hc6ltT;=G=v(P=XlzU_sAP!Q;rD? zv1oRxx z;N}Q?R%DnUw=@s7qqwCC)}K=Lm_1a{Y0lA^u@2Z3x*9w2(D7KGFZh%CHa=qZKil3- z?Kah4WXNYN@zlBXAM*F~glq9|iC};O1Pkk%5(`4B{+qe57zMb1XdEFJcU8l&UD06z``*mdjB##{~962f`9EZl;ID#=jd2~09Y=z|A zxBrgE5<8yFM;YT$T!+J5edS{FpK&)yyd{fB-M`b>AYRGcBynn1P`X@~abd<7cmA5l zt}aV=p#RXJ=~(nZlS0|G>TZ(IW^myQwsG(tl6Lz_XFEC3W z%$$tsaO((s51p9wY(CJl|B&7rvTZ*fbu2p^<{p$H;}9jwfX&g8n}X9UkV%`yDFmkM zJY45ZayXHJ=^b1~*Qnq=P{fI1Ic8DUy>YsGwH)DIz6#cmGF#&`$Dl1>;+C|OarJl| z_t&PsALnMA*@#hFzDQ*nRBwg)7uKE=D;fv(WoeD=g%YPrRMo$>d*y% z3c%!Z(nzhC$~0djB7doF-tf+NWb*+UfDvR^tR|rfAb->hu_?+F3esf0NTmWHC4?kC zZ1sdia@?wbJMHCr+_FtSy-Qz(!Q@1NDc4F{#?52r=guHH*aP)yv@a6WIWJ-Yo%9k4 zf~*px;Kw{t{K0#Xj~q5mhbPAvejT$}UeQ}I{-!7bL7{zhF`h2g7&xjocDE$sSfggh zA0!X{*(S8RO5NIB=cy%9`|`|zjM+Zgn5j;95MsNCRV5s7P{jyBTHHkv-DX%sEWyx) z_=%N*?mnBc1s}u!y&<4UUrnB*uW92~joEfoaZk6N$_?#|cfrdD-+3E)`ijBg-`k$v z5A`DZzHa|TQltTqgkT5~e34vHm^F2v7}*eB4h?CVD`m=GFn1zOm5drzsBVZaBi&2~ z(ZT&vYN5@OaZMIs|K>!$S{~y8{edPbgI*sNbh?URM!iN~q+&eHA?42+qWRABKYY}X zV@z>N;?F?R{*!>{7Gx@*!MYyfkqOY0e~Lviow8G9KhXFn`zCk+?avS)+9zQiiM^3j zsWnRqp*3Xs6c9T?U*+&Y@$f)D$KyL6l+#bBWW)jJpQM#dWj!yNYHT|NBQklT3m+-v zGBi@?2qg4RT8Qf@I>b|Nf2_!+Ostt`P%56^k-8bmSOYUfMk9AASU1XeBdx1Md}{e@ zL)&m)?b1Aw7}N)7^bQQgM5z)2=UW|wzz0Q+C)576xMV|xcaR<+5E-G;rbs>iP{Vv{ z0-46X&_>9=>lqaSB|#_9xIm~W?{+~{9b}|lM=3QiNcca&#lKOyQ(YR-eO30Png&T) zi6)@c`aGDfNn_S#Mb?R>FU5^tenzo@$s2c(B5QQ9$xxhrZ=5vgTQ>)!7=r(qi8u?9Vx@j4 z$E+|%9t(8bq#qJN*EUu|?KA#}T!MtFJ-^Qm_ec~i6ip%CY)myvn6!WL$|r2;?-Vxm zz@dgdk)&nRAyBapFV~h`9qqS>h!tpM3;_%jq}YrDGOiql$+ALXd;Xyf@&y4C1}r`ba`K?=mdaeEf-LyEO_D|f z?RgEZ;BVm;oNzN&S*SGj9U87moG2-_Z2C|0BU!R{4F{gBs;Od@6AD8&pD6c`dr1q` zv4o3GOlS=PB>(~?0nRYtf3Ab;9N*c_^`@keC4@;Jgvl~BKcX;3pw1O$@Hnj5j4GHJ zj&(W-5wan?sDGLzTPtAVAx1|H1;b!$IPC0U_Iuv0)1gR}Qgh*s>Mc81CJt4ZPPzqJ z3AMo(sr5-Oe=5q9Gsg3}ozhWV5fyDo`~MMA8i-KC1)FikfK({`)`{$a3#FJIMq;T` zOl!nz7(u8rn1kLM)n@WpI%Wqp(;R9LE-U~Hx6B0^g5Vo=8P;shg z+9n(EiB-uyf2M|{Cd>%!>KZ18Cb`hR&V~_$Qb1{Jja!;T2A;sApZ^jmsnTgA~m^jly$UntQ zarj694KV)I1yEb15bg+O#6FoU(@dSxARs$94gk9hMLWm1J-6I4HEqL#ESHqM)s)$+ zEz}sj+hS_U95^)Q7#>e1$y8{dOIv}s_)guD4r6-}+jrUlEd{O>2~Hj1c(dWqLgq`C zKy=PVQyC#Ck&e-eS|QUpU7s;R2!j9dV5U#Ql~#gh9HC2E)7gqnvLSR)H0m1!O4(D! zv(5a6%?cDZML||DQ78r5OhP`Kgz3&BwCjM41BC_2ir9B5k*AE6`G>fC0BqPVlGB59hQ@kwx?3#?G#Vmp*rmdqEub?%zk{zVuw*jZ z_`j;TW?zu>th&i<(0AG{)eMS4yI=`xv-j0e6Ddy_w>fZ6;qwm}RFh(vk?kw9ji4J( zeK0UCUeMz|bcDlqQC|&LCGd-Zx?e$y%|?yUe0wa`>Hz%7C_^32Vf;~&ook2z z$oJlR1)hr}=yBX_L`^)}DC$Bjad@^mNQ|V;6M!@a@;6ib-^*0l;!9H4g)PP^141C0 z5V)n&r)1u!T^jLcEGJE)n$1?aG2tsvK$I69ffS2fq$?PChsCN*LQo0BGAH3%BjPoT z$j!eHxxUmyjz&O6MAyDGWkuo;v1$TGJk!4>4fI0Oiicp3|Bwl^sCc0eG-+g))((YU z#99QZQlSBa-A9BwvU^F2{Gmsw+JFN1w+zCXns$CWfFQRS=)}MXyptkDyaKh$6NJ zF(xHHWd;KC|CoVj)%$9d7Czbj4_ZZRVSlf|k?Q|q6vDAF=NvAIQu zosiY4>|>OVLZuWNYx!t2Tf@gyYbEUDQq4M|X`_sa{F54~LEa6~W;UaP{5x;kAv1D8 zE2M}kF+#IY-MM?w-If&huUt@R0N}&l7#_9*plJypmw#lh^={%~;RY_?QpC6s1RFIi zVAl7Mtxllk+DB1!)|BX;zT@;bY|ioR@)>sW*6Y zhHeG#)_&y*UJ7R_PS^LQomaNAcsN14LQx^J+%qnn<)sae)PKu!}*f0%!%Hz?@T`jcNbcd~PubM5OEQLE8cFV)bs zb;0B6XW&R;9mrlpFBjhbvt@5`33dkzlk`;Exybwnj0IZ6`ev{AD0YJ3OTT<%Z=RYr zG-B@Jm;N#wYjSQUoGbUAX2%zZonMCQzo5ej&MB%^@52Sn@)6=x#PNIYoPwN@JL3k0 z$UJa1EZxq#plw&F`u!g!Jo(UJyrZjgKxUc2aOebP4REF)fWM|`us&ImIF5`YX0r(rp<3JZF5j#hgg9}8}MHL>R6RC4f*RGo? zjC13{A?xMdo4rokoaL4H3djjngzq@@+AHCM$6Fb&!j9Kz z)j61p@m_aZidR*;|Ho5m?aO(t(~Scho-FHf;-Xe=;7C;o8=6vih872D2EH>OAAGjG z=u|yyQo~LAxGNR+A1J#djsBy_9aFN?#25L(O~BDRN0%WZ{>_&5+=S%jFcwG&{f$p1 z@w3*L%@jbXe0VT3$wRKdj`D|oL6??pliGZo{7smY^M=3c8ba6+dRWfD9s z4g{~+OPrbzs?~5SdaEQ^O>myD^F$*2DXQAR9itq_!LEcdn=8cg&wMXYH_)_!QTiX& zrKCC1bRcx9hN@&O4oZ!}L;P&aMBKm%-_bHyOd>QY^M%=e=TMH$W&)G(65yDRCPOR? z_U*LLK*>3Woj_|YoTCXEg9}Ng=tw4kl5Znw5vQ1}$4|;B9irwFSv5pReYW)Lv$iGV zPXt<*NCTfDmbOtU@tMSdy+h~WFj*pVW2cR zGJ^FlQpeoT)eQtEpxQ0mG?90+!Uq5pQgtgFJ}6A|!9A<4ps z;W2G)TaZi5v|;8Y`L9X69HH$@XG|uXd>}#U2IPvAsx7ceO0{9E1pj6}S_2^YBP1Uz zy#)~!A-Vu!JA*L38BfXtZ|7hs@5A~lO@W0e8kLBQX1Su3_3w&Elw^@_-k#{>N&6%_ zgh}^u+6Vp=U6g$)(vb@SRokJc_=hO@M2M7*>}vJ$(s}xn@v3uR{w=KUCX>ieQ@A2b z!!PY1SSjjuiNYw3%=`5CzmcP1o9d@OfVuAcP2acuAKv;UL3WKz<&D>q@{(L;ml1_svwiOihLNsgS8 zKW%?bN(c~qgACu^b(b(SWGF1f^KySE;%f{|`e%4Qq&viZem0eGy&>U!)9#SCh{hlC*itSFH77 zFFH=}!9<3Jc`>}H&(}V*(QE)K=)m+uRKhb9j(_18^~LzF47zG{I0tG7wYB~wa2Qes z4fI$0>orFkv67yI*nSrh@J$a z@nY}1zL#GGvwil*9^of2m04S-JLX7!+dWbR1s+J+6xC4FE@N( z@yfZAeE!tRd(F_`ii-x80Ajs(;=1D6b?cX`K!?y@T)SFK1ot>CF0Q?BE$nt}@x1i| z@!EC95ToMa{=p*tck;=nz<}5FuNW+@>tA~=p8MAV=HN2CTvA*-Bn~ZYtceGfR%;?e zsu7UhA<_zMdwJ5y;Z+w54y;+bJYKQ7c){`&D+ge~E1h*T(Z!}h!ZHg5Gc#5z4od$z zq0D*;n64HV^{-vAnEoIxwzjx#C~%-^MU_wb;`8BZn0rS2aGYQUx35-&dHPsjMJr>Q z2us{08-cC)BPj7w&W1y!rsZNd7n>KVf*cmvWVDkq&@_DZ_7!@|TXwqe}J^G_B~YzWA;hA%%MfAzTJ`GfM~C-@xVjm0L&UzA_?vNMRJ5#=W&Q%^4J zJ-l#k{>JT-yQgOmR9+c^Tr>H=#6s`zti}1I+wU$3jZIUd5)mN*L^q&Zc{@3^O?|D_S>_1`YXWX9ObsKDwV(R_v++KTmE5r^pDeakd$h`zC-=?C@0OhXll-{kTi?u2n2t0YUlV5a z5p7se$||0KU(a?#qt-cojM zt}^`bpXP7L?QDseH#8q9-q4Z(AgLyoe?Px>GJbNQXXBer9iRMu-$GCFr91NfDHq&% zw>=f4*>TB{c(jxqg+~r(dZrzXM+wwm^%y)l$>5P@#@o3S_!lOku8;=&<96VZdb1|f`=;qWkhG%UuWrD5^V%ArC0v~-~JUj_KceA#>n z9xW{pKCbm*>41TT6C38|dFzpW%LS+A_fpifL@ix$zO=K%EXB|jzbp~U@Mwuxjz>$t z@(Wil9{@qP28+N~;L%cW4jwH9=i<>~Ux`OS97C0>@N6*)2#Xe(4TwNuPJlyrv}lJ| zZV|68V*iP$vbulGz@U*M^DjVVk(DBcWKuSOfw*`Yc8!*b6HLj9A;_#&P44?ee%Bbg z|A=$-%JpkeYUzOtczYQdNU0@e?ZElMH@B3?n-v?dDbflfnLQXs>OHES0St&z%>j`Q(og>nHNEggc^isVB0o2B3) zJX$qeG_VFp8p(~f=l4nOC>8chdVZB3OqTs5zkPD;BK*Anz{14jE5FLGLjRDvC*M1K z+<)gQxqOKoQ}6J<-<2Pi!>bQ0Ec7PVtSU@O9{zRy;pBl|<_}6vE}?1u?)HLOVa`lrayz#I3`;xB~3eDsfe}gddPXU?GoXmbE|Hfp-|K@wI>H)(KJp=jvFMf%F zCM92gHor94_*eWq>bd-^Nr2PtPU0KLeoqya1qIz|-)7FTx0ti=Rayw6JsX z^Oy1uCc8g}pZESfzdqTBr{Rk?A%jSseO{>9od0IB`GtJ%l~)(Oom}}+zIXW2Twyqu z-0*kcT39H|OJ;9EJ!=1n$MV}GPZkPCCLh?0vNOgO)?B%!aB%Xsv4#Ii=H<|%-v6q? z`MIQ0C{Xe%lDE}SxWl%El{w}c$?vH8eDhU>gNJ|7 zQ@AvjEO<4H^n&q)XOc%>jXGCNC_Ir|G7eAIZC{w3{92wMFHDkx)l$<&qKhK_2xF{K%04TW~3SUazwLPBTFB6kTb|}0#*(^T?cPwm1mp8am;eve9 zm{jN;p0I1-e{;!iCKV<~N4Fr~s3#Y%E$o#{nOxXCIr*pg*^-x|i!3M8))mGj2ku@t z-~j8LL0@S*_n_ki9CW<9XvcZ&?uE~t+K?dNa)jzgHcNEeItee}& z!h#C=c@rK&6vnS_+M{rO^3X02z#I20{N<|2h4CA&n7P;R+PwXoAQa715y!2b_FB z3_c!@mMWwQ9KfO!sRB+#5CT$#78Ydrd=j25I;0II$?KVR3Z|DVx|w*i=w|VvMK+s_ zM;qhk;L)O+i$@#a=i$+!n~z7!+yXpWY^TR~w%nar#I%*6tsjpTT|aU}m)$JFqs15I z6>>*3=}F}kav?NJ7o(LW!sHc^%X*Mk#7Hb6m-;Nhqa_Aus7rlJViCFYXMl-C+l30FW%)}yciO&kWvlN_Dj9kKVE*`BoR^rhr zdleonX5`2&&tYC6cZ?J55MEfcl2^!nSeCEGBUNyW%_5js;EUk)wd5Mc-y)auxWpoI znGXsiuZXIV9QaU7F-Inc3>6KY03K{ zmkOEe1CzXlD#<<~mkzC6*}oQ&bg9tV)v;tFkxPSMHB5kfHWFcqhOA3AB8LR{V?7?N ze1c>oa+%Qicx^590z6teK!wYJF2tkN%>EbQ*{X)wh@3MLBw+|(>R~d7?;n6r`_a7- zZ4j$hBtM@%_8{rVFENAJ-sFzim}t4Ow=g-!c^7oNJ0y=BRM=#?iGAiGzVG$A!fGzOJw?`KCM}J6WBa)mu1dxHPqZKI{tzq5DCOGBJ5;U!gzwpM&ueUDavH&tHe9 zf1OrHl2wP`34Q3g%z6@b_f#G*EnarEpuaf2n{p zqgvP}m;9GJUQjEH%Ox|)K>eTf!bQ2{NjysGFezEs1j3yw0RFpX;g;mpRlFeZkNpl) zLcebT;i)y0U2!gY`3qiOSVfk{PcNL8?B4|F&rC0zmP@Xe$Ndf~d^(?8-NNIOZz$}N zOOALwNIT@n!m{MS*8>F8ncI&<$9KteP=Z7XY0nFX;?1Q;7gi=ecmtl;IZinSbqYt~ zCq;Y1F%a#Cjx6*JBd>W)E_v-yc-;5+!sm0z{G$tvB!6PzP03?N7bN%CG5P3;=#eiu z2924~ET2(8M)S2}@%s}e72cXWcO3fa87CLk=aSn_K;a)wDSSUU8v|;SB|LI!;rL|Q z4E!Xu12YR}CL2xy8%UIPNS>Qn_^>D$vim!-*B{O*EKR<93f`i3pOh?}S=c4HXm(+0 z@}*M?y~D7QUZiz1@fs%agXET(V0!y`g{8UV{j&h#n)%S#A+zxW6PcL&;Fu z-J2H_cFZNu%z;imc6#9($s=@f4UfsY z`U@M9ozKG40gDRfCUf6}#@||0Sb>xRj{3&OamG9t~dwky;FW7*_o8QcuwJ*WbHD5 zhmBp5+_)SsqLs*fzPG4hMJ%@{vjB8AmI8x(CK71_xXAHWJdQC()*dXw$Xhpfh(hbJg~Qu68Z z3#TM|t}TpD4jYS%@V8Bv$iWvBzAV`ka?A~d>x^LU+5k&`WIf7Gzp(Ila_;$sgN6q$ zDon~He>uM}uJ}gGbRgrXBj0IZ5Lie5C;#C-YD{!v_B*-bfz^WNKf=)MK_uA1}u((CW|NaMjh zFB-gdk8`KoIeh=Tf8Al5#p}O&_tYcz{cCfNlMdegp3iMK`r&zVA02nfL+4+!->FyZ zz1I%;KU_THXXP)h-ten0{cykBm>2e_ZnOCK<|Wtu_D^$f+z{V7E?zbExKEG0;^=dp z{oV7);8CA=*CpqyyY1?4Uc2L}FF$wsVfks_{PtT;Y+Z5vlRIDbv-h1a_M`)j9RK6} zkNm>shljrSmfzeqxBP|IUNY`ypMP=u>1Qq5ywd^kZ$5d?_g=c~!tX!*{m)N-{+a_f zJ@d2gJ^k7Gz&Ydgeb2#vI^>+MZNK=u-+uFg!>|9^<}t@?ig(A9Rr2s95c_o-3KL2v zmZ3^(0VhV7EVDV(i4{DXzR4@F2Q`yDE-jQMR%`X1X(yP}rcx;-XIxrX6HB3(t6dI{ zTFpvT3hdJVhL1gd?**6a{KH!tfBDGNU*CA@&D+hNH|vwn{(izYAN>8}zd7&P-<PkfZmn-~@^0HsD$Y9k=4an@>aU0XbW?oDkD{AD_HWz& z^x2J5o_OY&rO!U|md5ny(+fKsH75REY5dc#S@h}c-r7I;{F`sR^n`j6aPq*LzmF=>USFJiS^t|Zd&e%{EB)m5)n8dO`LYXl-~P1m-z!b~-YaPo zzkF2h-Q$n>H~)H%qyCV;KzGdS_+Uw&xL zzHfMR#)czTf9rRXcHHBJU);FoyZaw~=R4N_g=Mx|M`bR(fxv#9) z>A-i6eg5nt|MK`fAAj-VjW=97{nG#0=PiGmeB!kaK77O9eme8-o8I+_F;DNg`GuR$ z{LD@N^V3_ua*|-|-@Wh4pZGtItp3^|zrOdkGbfe4`{(0l$4fRWpLEA|yKUI_s1tTR zcH42=#$P;Z#>D)XZQnlmh5HX0v+mhv8sGT(Z}$1J8geg`G+x64`Rd{)AK3fF?XNpCTF{r=z1PpKn?7;U*z>;k!Pc>_z4E`;pYq^N zi)Os;oTqzceCpr->oup}c))k>8#`wC;Gcf?*S|k;+ef$A{ggYdz4nGRPwjQ(oVQK- z{1;a|x8cEO>o+d`@k_hEef1~5a&1(8{OtIm&)&D_(kC~@r(8Y!#Ph%0Zs-R;IOdpl zUH+B5F1Y_M?>XzmwJ%Qn#=GzQ#2ZhW`kIeVD(t)S*atTB-~G(JtyR|?mRtJE>%Oqt zvQHmy++~-4@r?Q#-m-B2cYb%#4Xa*Sdi?AYcbWE=ZEhZXvPJDB1)tq}i z`tZ}=c;dFZpL_g$ciundh8e9lU;pHfTW8+)n-^|-`a6&R@$q=`-Cy4Hi+g^5!=k7D z{KU1J9^LfRMXQd!ee>bZA2IXn&C@=9=_kMR)z?m`e(tlk{Ac{#^WXMgKkB{hj?X{b9pMBeYLz`dx)`{Yy`Md}za=E9V_^!rXJG-+tasAAIok*Zt}Dr*HoL*Wy=IlD-c=_4@;_{`I9V z{q3eRHa_^nWAAEw&?%9@%G0reE#v7E6+dv^b?B5e(-Onth)UDyBznlZ`!0Us z`CrexYQdM^R6qMOA4(SdH~xos(udHp&6qKJ+;GlM$GrQB_igy&m%lT(>dA}lk2h_2?)3iks~&vYeg}U3oIMvzz2?QM zKYrhRLtp*wS-0^~|Q+Zx1~CEfe>B>G92nemMTv8U@wXXE8h_f+QWlshUv z?a+0PUUB=bKfCCWZ|(N%K8L?);hDSaz58AF{QNVg9keVy^`|Gi`-W=!#&Cp|Lr>}P-c*_&HG96bB_zy9c{PmPJcbNAfqx7qDSGxiy}^H0Azc>d~7 z#K+D*>-YsTS6#Z@nBt=+Y??ap(EX+?+5Ic~!q)#UCvP1U)f;~c9}r|jVo(qfq`pWC zNOwsiAl)be(jnakk&^s53ys{H>Ece%n47Ef;Ifj7$(bJajrAu{lTHV{ zX6ujikdUSPJJ27JjK&5UJ|@=B)%9hpXd&?uE#-7>iGm?4oD>X*txC2)vnM0*=qPcj z$@@4{AyAl>bulSw8s-^AhX31;Nray8_riUXmjA|K`8>ileF`i(MxI=STCa1YBtZds z&Y6g=P^&2SH~?(FljY8(@y}S>+hwGQ6Epx?ILG17SmB>`Y{`c{GKPAb{;nq+C9M_gnOlpQk29H>U4T^O&Gno|Rp0ISj?sQKGuuaN=Lj|aUR6-A|O zpr>nOcr!&fwjp}v*_N&Qd`7Ws1QYT+XRW8+t*0K!KN5^MWR*a2OJg7q^BSb~p>&x( z>vLe{CTR|5FfvcXIc;tcHl^`ARcDJcT+8@f3iIoHRY&+&TQ2$A|Ja&pWC+gwk`T5c z2agQonY(SGHJOP)i=W!2tIpBLzw1N(w6EGJDYmWts5iS~jzU@g>1Ih0cw%y%d*@Ll z^ND-k%qzWi4jWC|R_y8TU-PcgqZ;k(Hbxt6LM9e;Yux`X78A$P6{uwFwq`sB+ch8k z@7l?XtKg{MK&||+dJ$Hu-CU@J!0uty3FgbsSnoA5w>GBnY16{bE|u! zn|*Mip5ftq%+z?g(vx=CnKVPyA+mRpH2e%tT}O>WKM8gj>oGBS05>%#Mgn9cNMPf|CUJ~MJMHhcHjvh39PBxq<~O{o*2&Az3N z)l3{y&{5;z6MRZbRuDf8lih|Oc=M0wzm{1KqHuN$Zqej2DwL>oEpbBPiq`E(Gh6c> zM#R#UdPlQ{d_h>Zb!-U|6bjXtsRIf-_u#Xwo|&`VA}~_e5Ph5MO;3)Sv}};e(P3dc zt`RcN$vZh*t0*u!VNhE>I@TU5Q)Jf7)q z%RdwhENxfmNC)5vMrh^GAQ&4^Rj{9#E?iMqHnZ+rgZanzL%~G<-HUS^RxNVKk4`R6 zEojzkUobc;PAwz}0$-TmL6!tu1Wo$^n8S#yklxp! zv|gT1wd$r!xj*3vR!x|=)f(6l$|LD>j7ol1*&CC-2TMM$5&n8K+mxi8hCJQ3Z9iyb+t3jY~yu5x<{j7 zIP-1gwXeE0gJg(tjR`ghPUM4$P33@>nQe(a)#aaa&o^7jrzn?ArLV9{{eQR}{rKTV z=<{pNIZ2!Ed^k`&X+m9d%Jlq`&t|GS^TiX@?dw%PLg0*}+P4)l{x=to<^s89Z(rJlTRQ2m&e;P?g5o^VpHabh#S!+xl34VR(4%Q}%x2_dZGTrk(HerN z5eW}A#;J?15y5LM_owDyWtf`hIULLWre}n-BA?*Tu(w%Ttr*2sv}Ov$gre#C#`^}Q zhxqEL$d`q7@i9#Z0ywxk!ja!}9tY;}eE2%lJDJSEM$0Ssy|ktwuc0n0&txcjO|Yc) zOELVTsGTqa|BCl!!1tU`K z2k;tvQ~YtO73NIQ`|5ydTOUn#l2jh( zi)rbblMp(lDqv!!0b;PJg#(tC?;-|DBgj5*cjf49q0 zcCv%6(O@*ywcKknb73J>-df!*E4DS_;8GuNKF^QjQ8ee}c6&)IkCf7mYFl%JQzHZ= zkn*u19}%TrEOGHU7fKr+HuDTW3p1|q3C7KT`$WAUSk_oai>3erawOX_){#Imrubvg zA%3=U5X(yyW*-~8peS1Ao zp{JG(si4lSne%m%SzB8Cuhq*qO^WtZrJ&Tr9xbLt zk*iw`SHroZ9b|E8nqBG#mEF>P}Hq!IOBM* zuUkaggSfj-D)qH!M9sj6^ga|x?WOgiafqBLUCJJ2k)1hI6S(a40oG(z|GCVh@_o(9;xgN(5y zxXGL;6BqcW-?&)tSiwxzQgrvCKQpP(=g?qy81Qh`%qVH5^v9a9|Fw(Fbv?g&RL+|-hGcIzjJ;pYQzox$v>jy_KxCcZB&p{BK^do00 zXs+=&wjG}}@4AQ4tn_<2L}<2LEVVrC(`U;TzGr?zM3nca8%P79{Q^RwCE3slVcLv~ zEg1Ag-DFhv?J5Owm$8%aGH;9vW}h4Xkw83VVJx$@O6nWTGGQn%dqVX`2)vAv{J!V6 zZ`-L<1bhBSN_aC0Iqp?1PM;GuPpfp>Rzqi&PmIFZ<5Qaxz5IqM3kk+`ki9E~scwd! z)C*Gwc{3j~TQ2Cen&+FhR+o*ffp2p}S$}2HA!6vr^#z%&Qbh2qdDreBEJJY*`7r$& zr`|adx|O4=8c^&MOenIP=PEU3+jQNg-%>HuzO`M6@#P*>!(Q2-== z&$DEF(|ojEM7A)#=TrPWq6g(ZGku+8@*jpgQnC*PD~Fa59OP4!`8Ac+*Wq|_$jS&? zs{~1z0bAIxA;$Q9^s|A5G|+bp<1=cDEks3kua<W;O%H*>3=ia9Y0t|NpDM=Zm*Zl>x(=3{6?<@d_QRs z(8MoGJj_2*L`t#|ZVU`P3W%O)=fl8*C%*oZMu>=ZkGulFHP|!LSWxrs6|VsoYtMyy zDZvQ%|Np(_v|Ik(Br@23N{f9*;cD8J7&k$Ts&?vI}8&vFBvB|dgk8N z?7#~P=T4SgEMe~oCREz&(jhyUakYQb30gK)L$YZ?We;*Ru{z(7a4V5H>xI(pA>2n@%JNE_22Sb(GOC0O08#p)#(Ye{Kkr;a`X?hLIg!o>n?iX+ zTi?~?&uvqumE1IUjh(q9Ek0u(-M$^0DBxIs+O*^S=)t{J zhgzf40eC>BfdGouez`xCXvm7D@w+~*du2-X$J%m-wWxSwbK7U5O-%Ucf>hEUlRuIW zZ}+0Lj548*^!%#1d_(-D-&v`?cB0nc<#t)wWUbY+*75^$rA|@j%O3W|qcb6Pi$P{c zTvdL_{FUv$3$I!a%JL$kRFoh$ze%K7puQO}Xms~P}Mf0Nw3 zo@tt{U&vq5z5n|bBzk3K?WKJZTy|BGQ*^T34kyK=xUcHWg?EybY^C$oIXS*v=YuDX zujgZgM9<%Zbc% ztPdZTcBKSeW9VB-clTp+3DtTMP(BTzB0U0w?1BBHEpG@&ND!n-Ob(|^_}{uB!L7;c z{H(@2F6dO^cSdBlmcb(6x?Y(Tk`Mn#=PO?k_k0MGEF(%gYoT|3} zTJEead(2HmHBDEFxxMk^lvahHfR|e1ry0}NBf~x!56QRec@+W)aXE@KyIMU0X<9I1 z=2{@X(Difhn6Nj16=Jb4SA!dZ8>ccJKPC$@Cv5wT8Sqk8yXXDneYffQv~0%2g_*0x z4bV_A;e#(PWcaFFosSM5v$N(f?w_cL^^e$>d?}=A{29LXnnZf>xSWzOXT*}KcH?(5 z)HF6wG3^e22sSP-&9|HI={4;IOlQqy{IM4yM!+Dm+27ZCq+Y>(^xWB+bX@3=jd3yt#F9*G(n+U^v+~nQHkYX(0upUnEt+aI+)83L0ArFKXsct=Nn`9*Ygu2i6+_} zy5et%dDGG}3$uujNmhHo+hAqgCA>4~wfhOEfGas=KQ5ym-zSq%9h4<+I^ZG$%QmQt z&g47Wf1$MEawS@#zBjfrTOD>bc3i8KxXGJv&=aSVB?|pY8%hZzzLIUzS=%l33i{1% zf%CDEiT>DyTbEY|%=2WtN0kaWS|vKd8#DIahZ_t#N#kua0d7;@vI~0lPm*RzzLYi@ zcTUQabtYn@N|bfWDGgdy^0;&-7s`Xh$j4hX#LLaH=olzz$Gv_ih%X%@f#HiK?|TGq z{Y^an8?^jGUz{bo!4xmCfmKI$@K7ihOt^B8Hdd#`G*{X8T2W*mNTU zKpEuDU(TavTSf1(;{aQ`qmxB zq&|WI#T+e~fHXEj`FqTHn37;>kvIw{se{7Gl6!)JgnE4Z0v@{7fO5@;$La|kBDghg z=!;w|v9zfhzqD!q5;dd{dMRJ#$6I7JRC_b;Tl}fa%eU<*pVBAy1Quf*Wn_#3&4EJ9 z?TEJ5NA91-gO+<|#o-S|UTs&=f`VQ@w*MxJTK+r@cxPS~u!5avmY~qaxcN^RtP>4i z2KZhH12<6k3DY*S_C1Qln+_4a%pKiOS|$aL1p8+%s3C6oema4EY_bF7?izU*#=|)! z>X5#=i{Eb|T*40=3=CEHq|bI;6cg`DscLliR1=a(HqRYf!11AdZVFCh6l^jj@ezZT zy3tx?+D5iQOm?`<^&d#3B#?`w^-DJKx|af|H&Rj>NnV3~2+RyVr(M@i{fi z?y9ZMtY;3D*sUBk&OiU()i4{@W88-eQW+aIsBAeZJJJPrBjOkpzWcZQDA;}%#$Gu0 zz)&#SM7v8Cl|1_JVQK$E82~?UaW+0J{5W}heL8|0iw(K!^evlS;o~CShlua^$(AK{ zngNJ3w@jvWIM4}U`TKbmhwHKz$RUvB{5r9If;{-pYk|Hu=HL>42=U&CB}Y|FLEsML z;DMDCd@_Kh!OBK>-WH}$Ps^u<;O6y3B<5{=d}!8v(A?w_-!aw?@!pp_CpyOJ2{yD_ z?%&;+&YXepET(K-n;$b2ZV#g`!4q%3`^8Oe?|h3@iAawG8+y(E?zP+=_#Vg3duf!@ z-H5m86ynq&U(EgX$XPQ!+=5nviP>ilAD!ETt0@ZPkTOZnD|QC?5M=qNE}6822C=x`+%F6U^4BlL5lBptTa@W~`TSltVc?CQZ7dRF zV?ehE2qZs?ja=_ka57`(sYOE&>3{q^>cXjU_oYk+37Dp%E;19)i0;BG_`0>VjjP_G zzw5Ib0pXyC^No-I9VYzYm?BsxvTtcRride)59_X6ax)__A^u6>d)1$)N#w;Pq--0u9mJBgq%ooMtz!Rs&o4EQ zn2DqlTQ4*|OEUe3gr*)usp8+l!1ZsVB#6%W5E-*H^<%b|FNGF{FJMbk; zf`P&Q`G=8Pe3L1y;W?x%M!iZ-@2n0VyT- z?)s7kLA<0mL{ubSXzz4{eJN>wY@BEvPX~SO>H1>E*g^LJ3A)OO2r5Xex@+)VLzt-Y$n)om@5)M;5G#R}GHwg(%Z!N`NjkClLc>pziKJ)#*_YRT zF}I;=vX-~zCYTA_(sCDK9dcwmfO#!1%27*UG2X#cxDe}q#df7}s7DroiJfeUgq8=V z)iVxuAzIS=?v1Ut0#BK46B1h9+=6O-J>5saPXL~Xupjc#XWEbof+!3bxAWbHknp`W zYxtzH<^{hFyNT30%g%!c8DFN28vj0Y@Nw|Lv;gU$gQR(;(CCf^%JsNziF>Xf?iNI0 z*VywrAX&x7cHC=9CgMd=HU@O%I^4Z0mTy%i)SLUVOO8pC1`Fdh_P?>kl(lNvbR(%uJAYQ^b*jWVt+3o|RXoP)F{|4)9U(vAZK^3`m)9#@bA>TPz=? zEUie=jDfS^a8RI@AZy!D3gt*XVZeZE38S!p*h{3?!D4G0mJg){Y^6y)N5 z?ry{9AAMO7N=ME9`Pe8aQ<+T@y0F1Rhe^=q#iJhkY`<=zQcs}2Q=uWuH)Abi_-`a@ zkF4}ikRC-by7lH0s$p9^M-m0O>I*}DP!1+E`!;|rjA8K71cge7SN-o6G@6z+)ESt9 zqdsIdj2`;ecxy9$SOSleDNp0-t2@H^@^E)m%;e@~aTh6h8UG=;efC;xVHeEDfDA$5 zmtJLFt8>Oq@1FZQ2UAeq3a2Iz5uV%=6Uo1LIt*bH*)n&+3zs9Lh0+2NJyM-S;LfNp)`13=WRNUxkGDM0nn*$%GzK_uL%| z-d`uz->I?9Q!8X-D-leA`%MSSbh!0P1P^|&SQ7iEj|BrM9pI9Jw|Oy1t~=@;E?1?S z1~uUsq5>w8j+X)zBs7El(`2uYifXc8b|6z9;p4KL=>&nhSMt_6!TJUJj#`nZ+2D^zEpBd4$5afiPOO6WC~)@ z#PNMG6ltui*}A4tX<&q6?ny=YCdHyC;j82mt?%F600aTmTl(L!3~ka>ER1WL>*+#H z{n-KoU^WFG07#8d)KHnTkqLWSRevBk!G&P9s+HA`Z&Zml$n^nRTixCQ-hv~g5=0gN zjDNR{ET18=6!MBuwiMnpm>m|q07iK|d;on`Eqc(MJUlP~?^?8s?a+qI>r>gp_Z|Ke zP_H0dUZP~Tv$A9_DHd@q?tSYf*WyEcA|C%>`*?w11Akx~(j&HRR9$+vp zzS$%vY~`+UrRWgpn;u&*9!)ua8}QP^N#Fja*?PyfaVdCn8}RF*tKL5U{dG?X;?xPj zF6NC;1(r4%1|_xUL$f2xX@v?gA6}&%6rkj@;xmsb_Y7oE=b6`9nL?UtXt|+Au60zy z2(4ycVJ)+zprLlyE?^GL$IsO?t5YCS{<0vlJGs zD^pRHDk=_TsJmzuGW(QjJYmZ~_0PTgt!*Re`185j)Wds=VqR9uzYgOWr~nzt9s?!g zwK1q-?OI5y;7NN=`m>LZt}kldm1n4FPJF*Uu&=GXm^NtY+jamv6HZ?^1qH#%aUgpm zj7@bMoK07IasN|NU1R!aRk=TiIY+~T-<>|?Ge`5j9}^6glxngwn|+0l=LIXvgYL+P z$m(}XKXVvD-QKSFHki5jn4pEIe1>`lkN*b0ay>b0s`1S+Yd+p?e?^*BJo=e#!-+`M&egNZ$(>VQ3}sS{r|CSL6>z zD$VSO>T0F??4JI%cPpoWfB(Ln>U%lf@z)CD;fo;_ON??O8B|6=={mu)SFR{d?OsfW zMhy}+9?gjh1Jr=aP}Wn(KSVD$A9Yj=W_*_!7Ut)H{kU4#Y9_kY>Vl*SI=vdjbRaAA zbiLK4TR85P{1losa2YyyOvy`1YSmb;A##z?mmwN9utY$J?~ml>t|TE&V%O(TPw~0~ zHQ$vqlIR>P9<=U_06QO39tn@3h)+T#ej)~o{5_curPal#nKu!U2obNjmME*$|B#Y! zT%Qf+cURb1k#N7hcW8j&N=aN&N)9F9NFf_bCmqW|+GFH3@C}#+I``L)|7L`yoMWW= zG`~IJBD2%2v;c)_FenhzpbXSFO=dGieRC_d$!-=>nV`V$Zmx=ENNiQMBdr$)oCTC1Kw^`mbS znq_pFJtL*epMPX1T47l1IHlJaYD$%eJRjFAX&o&ccd?z&Y-QuptYf8|pWH&3=b_^6 zUUf#5&i^b7MXpv=nA5RHu7U;Mnao5duQkM5dSN>7r!X1uSrjd9rgTg4OID#$iP|}w zjF*&7kVFVN?dvn5td{p&1*UB2V(NlNz9$PxO!DuzeNbezaI@Y>Z-4*v*z}k!S@~mO zJ!)DF^_js5zL!RZJsg{0F4Z?bM*vUK;k|UNhBtjSYb&ZnnxIN^af;pnq_q*0F6)!y z+$B0hq$5K-V{94lKD`>ZWj7#HuJcR01 ztk-in+d_ef!+B_xU~GX36}uWYxG2(`RMCUjo@xr6Pj)%wp{TQuGvK>MC7d1yRwTkI zC<56!tB$-ptJ&G>DtBtEYwcK+*f9qGE0pVD9fr912{dfY6UV7W9iWvuHDe>}T>SZa zcA_zV>eL-*m*daAbVZ1Rd*b44WAI|JoxDl=Uwv6AGtG{djM?fDGin|1ZTfr$(j?8i0=h%p zVuIkXY&oK=)sWuAAIiZ4PeR|A*_w=>R*$ZB^aF(^Uki?t$V_{$7|avbMjN9@*h3lyWF7 z(oCAdoZVBXTUSv(I$WY=uPX9fu+qHnP&@ilq`6{^AueXAEpnl~#jHNG@f7k&8pCvG z(GXDquLnb~r7hy)K05W&f?)XT^b`;jMBuaC$IYpRQtU%rPn;~Quw+t$kY5tGp+7a} zkb&!6@e@CWL3ni}=-kpwN?^3qiWjdWJmkI1Qq&g87nM1v$)1S1;!QLTeBFdz(+&J9 zr@0+lrgb&CIWfKR$OfZ4)1}hN;x5!0)*@C|#Qaxzh^0|4kgdj$XL0=;N{c~}!u-N) zS>{91de9#+OAO>KrLGF~%7&hjXrlhPmi>8)()*D|k2W5fRypr@@9>Ggrm!<&`Y%u7 z)})wUaC13QH`*_jJyN_2`TfjC&h5z!GI{DbU=xL z(cOyJ+$fJ)9upH1E)Idzy5u0(j}bF?Ww46~Vv7=2e&WH5eE2BnLDZ6TBx#SXFdvIJl)LfN z+~za7b%2W?`=7PKYr)DPE`l$px+c)sm}pf5R^olVfMrqh;Y8+JY)0C2+}If;b_`mG2j0 zpdTRamNILXB1s*~u!Hr`f4pDGr8`54wqruUMeOgR0mE(-<|g*1prK7>$6=8SnMXb) zj~Gxgq76VkmHqyw${hKSzPgU&f49d@=(JNXpuKh7Z8T0aYaRl&A0m@mky1qsJxq*4 zTV&d09G0gU$h6F*VHq1s?(;jr&ATP=Cs4k0q9|V3nzUg+hdWDfY|j6Tp#L8SL+dK$ z)N`B*ZM>e*-6R~GjlkXd-o7Cd&h;M3YEO^{9T*xg=OyE|8p`vICMYN>=%IaTUn#(< zrT@}Tw=nvZ))^SepscEHa@*{#xmhlK zWJgOByOE*Xjo(pL!F9a$OW<_Ce!s*Y0O|#JtpryKMLVvgH7iN>@aYptabjsgAXzrQ z6P64y6NnT$!s&qz`t8?RaSk}|#`o&OP5oJ)-_Y@?{q-?G zG&e_LJEA9-)~xLjlWe~@>{FgSxq>Mh(4R+mxho~0n`^kg7OIdhQf=5>_sk##7%Yo# z#QwXE?fuyM*O&A9{nc6{l%-#d5cguxOUoWv)y3;M>_5a2T>Y`%`eTheHx^*wlfmoO zb{5onrpNrWZ5^TV3zca%j-9zWdth8VZ--}6G^&=#s;Rd=A?l1Q{=JQf(_n8Q(*|S(0pSb;+C0+o`IZ@clmj$U2+zvU9By znX#GJaI8D}u7zy-)-_I^_%|ZrYVA9xGglOR@Lc8!E5w zV)+d%cXW#EO&$Au-0S0UQF^+S4kYx+t>ZLCW~bVz%7|_>>ECQ7&zr1;wZc1f-<+b- zU`96PlJKyP6v8ZfCtUOlkr(KunT%&o)QeOxAi+AX2_Y}{j51pbSN^H7;7CH9t*jOd ztGxXZ!p6-i9WJ$d|7s;EwQnu)kw0$*tJI&YUJN*UH1MA$)w;K7Rr$PuHQrP_-qhAP z=gy^XclqY=sVIt*Am9_mRz2i;l;L|h4cy)Pffd6(wKa7koL*MLkv_rQ^b36eQ)4V| z)e(mJGbVKc1y_=00h@+NlwO#770A}{X%ERM7R9O+{Hg5>8pZR}N_D(pD>|E4YTd-g zoc$}$6C%gPhqoNf-~*Q_ePWfg9LRpm6dSs2K`0#?O+!d97p z+i`S6z`V%0ZpQ61(1qP}HURtXb){UG19MYAybL4yfn_3)o;L|i7TnCCbn3BhfCI#8s8xh~;|u_kzvv~$rsL^w2>#Sn^1cF|LnucOm2@bKZQo7{dQ$VLSv`rm!+0pWfolSb=Z$ z+6!(MxMM&siKSHwj)gJiW*_BJYIFOn8^#w<;XoZ0dNMbr!pw-Q`2vyeK?FK^kM{NO zT1%gS^GaG(TZV)KNppIV2&Oy9MmdL5sq;ulX32cbx%Z_#7;aTR|M!>*`NpUJ zXutNX7p)v80cO8V-^ex$VHEl7nWE{FK;ZxAa8xEPxCxl}$I&0BR6 z+&LpXgS(5TYv|7p6%SoN+O_BMr1oI z{*o_1i+G*?B7Jr7Ilok1Pv#N1KCy}!U;Dedla-*`IIN#wk+Y>LvFq(%AHFgJekLxn zlj|Hl*5o>iZSFMGC&IDh^ojmy%L<`8h$TnQz4|pL^B0fG$3Xl+Hcd2kr1;a#YD1N> zf=cNZJEYV*)4Se3HlFG{VMU9aM2&nSTzcHe;=4^{e|U_4d6eg-H&J(ew2tEvQIf!e z4;rB&Tzw4qmp9t)sN-WCd_AIXyJZNUucXQzs4SDDd0Suo?ivpw1r*;AXu2zbJ~#)Q zYWIN;;%_RCNAp`iY^Tc4g15W~8u$Me?D$>|0jTKR+ z?OB5gFQ__;9ZZJZ;;n1S+!NpaSR0hs2`4jsXe#9-n;P!B>d~H_Nl0fw(1PJ|UX_#Z zFq;DLD~R7EIUkr^W?D@2B0L@FNFx_+_IIkB998hfEWY%8=~utUkYBJl4NK){tyMnq z_mlacTrf2Iy3IReYMV~pv9o^7-sjXz+C>DK1$BPRE2RN?B?yw6Ar8$YSb<5lr{Tq~ z7xsCwucG`nxO8k?WmPRT-RzsX=bXJh0b>Oa^5WQ=cDATKafw*BPd~?!0OkLqWBkRX zBuoes^YTGYs&TJ15TQYCh$KPF=lbXv6wfU!tPojhY$gwH(=&j&$)3Rox&`164kwxW&{ijGO)x7N!?Zvp(pt&~UK77-T7ul4fh*R+WR zA}r&j`LUhG!-6)af~$)NLSU-+2Un?%DbTZRMl%AcZRk&nr4 zQ&Y>|lSI%gx*PYr&*3ENewP-WzeTWOe?6lp23wR&v{p}LT+Z)D&q9_2cMx*}Q8Nq9 zwS2yHt=kSV0fFL>dq&kbMgLmU_ZVVWdg_N~Cz-yH{>Bb1QT2$p59z83;-WS!_}>-65VeOe>D&^(yHnV`D-pI8KUUNeC!N}c~e~% z4fj<MjQyC;(dlSHW+oK;X)^ovvMpI3jP-yP;|tc&;9nJ=!~|rDk;S z?_j7u_zmV>?Nr8QYkS=N-_KYldIx#!hlw#km!?Gi&46I__0wt~oxx&pVCCsZbQUv_ zb=#(cm8k&d35)Qk=4mW0E(5LluK!CdU53y%dXC;N{X8sifg}YqWR+CzIVn|LFLU4v z$dbUY?7Y+ey3E}E^tzQHjjc*A511kd?%oMN9v|xN<0>q7r+NP#Eq4EGZS&n@A<>KU zR}|HG2ib^k&-DeKw!w#o`4~Q>MXzmlHpj{>FW}tp_+)ji5SFVuMa7oxz~sbL@V2<& z-56Qs1IaIic|OfnDvFU!^G8wq6vd+!POjxfT>Ls^L>Vh&iR#RYjpXU1Ad^aNTiW!x z;d)%BY`E7$y;Sj0@?{HCg*5lN=NAJ(BP5ZesE8J@qZ$j;gdeZ*IuN2eEzj2{_HwTN zz|)L>8U3w`O$gXS{HbT(m&!hn>{xfWI2Rw`EtnX=sin*}Svwb=04V};iT|Hm?6-}x zeLh!Z*WXvib2dhYb3`22s#1uTx%S$3bt03}H(Uv+9Qy$LnQtkqxK(*>t(B|#vr0^R z6StcGcw>H{8m3}##qXu_7zp_IKkptyDrw;``o>{>TN6R^P%wJ0(C#x*a)u(uC;m2z zW)gRXCRd%o2p8Onb=>$I(}Rq==4mZ`<$&D;FIf za4F7qVAo^9kIkoa;p<5c|3&yzl~F4vbWqKmQrQQ`GDEYnIgKPFO*gq z`Ii#QPmPI?fP=6>6S#u-RzCH8M!!%LGC?xp`mUztRDyQgtNEsRPm}q4a>Klz$k?+CU1Zu_AB5Q- z!2Z;T?1@$fiYVkh2Oc{DK@$~Bgq2lMcQT!Mb3M(Fv9-ST9O3aWalxIib@#GRn@NR_IRM_X5=E%2Y8&?LD{kO zE+*#e>c{zSC_z=Jeu6y?JfODMZ1tI68%*@FZjmjA$2tk49Zz|YHr%i{!geaX!q)9C z(8n3_8SZ1nB=BB?%Fzg4B``tpR_i!f7rc-uGZM5v&pnJ`+0-P<}<~IR@l=-8+6FIwfrEeF^M>@KyWGB}_f3@>?*rJr=|&p3^Z?9azudD-4&UC~_E>2xyFRaDSm{Z*?5fLbxSqZpXmAZm zNH@nYrT2EG{pEHNq0jes==qv~bUQcY?K@b_s5~c=oc}UBw%foy7OGgZUGH%gd&afh zuoVQ%Rp}Q5C;G)|szeY)yKGRfY5o%slu{@VKxDxnV+O2fSSeRhs^W+}VKH;h#7!(i zRZWIaTKRhrRMf4iIl)i!#3;o{npiqCce~LguXSx4KK%vyYx;avF_g}X^Bg3l%ybeL zs=HFoXT)yCHw$&KpO8dNdnro0Tz$;`1|*&On@AoP{O$`vDp_c<++A7HW>6hI9a)6` z434hurfReO9MZMn=GWDlnlKk|r)w%kT_3^Mk<_!bQBB*c>yI-z>>QEY;@XQ%E zJ@Y2u;8`+b-&vC091iO|0rAMHt*%ZvrDAJ~Gf(vP;bOp3p%XCk*$|SSn`VwhaLivQ zXNofUOk_|bE&;a_=AV2XH0h=Q?4VJiVKvybRp$gCuY5>Etjq*?`8ZkP5g$2CK+F5( z?P=eN&fdxKuNTdIjo0tN3JXJ{2ASIc7AGV)9I@sBA@*cYShC$n6SGz@b??}|B}*h} zrs-l|IwOaaa`@b&0TKjudNtAcUsf{UD?pSL36J%QcYJEkehewX7AcPWQ~uPrAey+5 zahd(Ccec3&-^RkuYi{Vo)^%m}KZ4)`VA$2uoN(SiNx!lm+j8{sy%>BBbO3;1kB{;H zCsi>aSgXyNVaB=YR6Rd0H3Wv;1rkXmAXSU^v(*&p+InzOgwt@8O-Pa&^%+S1~kEG<8QnpMNyg zG=3$4fDrMLc@d(CrL(Iwo;zSKxi58C#IT(JkJ|zN>D5*ElO>*a&tgEUKECS%8dJ<1 z%`0G>2WTHO8F=n!fBN4+FomWG@Hj2Uzeu5#k3HmCCvi+}oqMChW^AxX1z(Mds0e$3 zZ>(2>`hrU3bJ`DWt61Vre`R2#rYbbbk(rV5N&Drv`PXHV#M~$Cadhuap0aqs=d3rr z3(wJhPya>e<&-I^u7{c|c-C8#w2Bf4r#(#MqMH;7Pe-h&sEIz(N+O7M+h0kLDUsoO z))>%-_#g7KBOpmk$xu2_22;u8exJ`6O9xsrvcX~aspma%`n#jC?ehbc9q(Glz0J?! zH%FQ9#MqQ|r2u99hD`9l5e%ZX|ALGlq@AQO$X)3?>2|@KC;4vgC{i?sPe(WMKlwKx zPM@m9Cam=mHDZ~F{RfXDHSureyW-ELEyYYV?P2*!X}gnUl+O`02HT)Y8nXNPgHeGR z)0^y{pubWQ^0kTp=2kn!gu-u6&177)fmEe%n)c}6&KJF`zWbt%UPQiCf9d<-~Th{vRq(4mae4WlWqV0NuLJ zrN(PBK0P*t{x+F&GRc~U>ilvu#ZI-)VPo#LgRi)9`}{&$Ho&*ij1=t%wmxr%W9F3X zAfwHG*h1s$vlw5Q2AL8`#FPs7N;TI~0f1;oItGF=NU6a;{Hb%z7()R}_ui%|oAJ892c%KxyL~WqC`fRF zl=kJd&@f$_Y*;oUf>ueLqUV0}XGS$oSwE1Kfy6UbEK}}b5n)Kh>mN8Rg~^cg5iSi~C_<9swo2T)@;4)YKH(3mjuXG;G}sOIG@#&!9A)giI{)oY%-<*)(Ix*Eg-; zb|6I)Pjh@u9nASbB}Q$#wYwdGQ8!Sc$>JlZXRBPLl~)Zr{6LxQvxx50-~O!?K|(Hy zlq@mWp5Q_tM_TbPLu3Wpq}43B<9H=lPPWLff;v~Pp0og+N%j8=1$lucgbbut%v6cG z*S+u07>fJKj$Q`hH=xAN*e-$zulm5I5dSK8Xva+#)`X=dm6G z?X&51pX?#Af>RlKHMm2zl&ae(Sr#ob;1g94<>34z_grjU3t-| z;I;m>PWHziQoj#JFW3m!QmGnu z+s(88dk%v;7!X0A76vvl%g_{&r;5ln!?9p+oIJht1Js}6%!VvG_rPfYfNU@pHlY51 zmV=w61O;L#y-;B*D`hGA0=BS!heDL*0Si!EvLB-plz7DxKmVgnZsrE-UX#w;PjYu5%j;j{xVMbEky-knnh>m$!k2 zyOr7|a3%m_mQbB1KyfqRbKCb7udnxOe;k*RzPc$d$?=+ENS(eq-T0k&+*E(++SGQw zUYALlptU{fwF=@kW|cx9u3yw}mAE`iK-lVgJ~YfX0>AU@B71jbKJ!Z^kfAN}QP5+I z=v3`^A4YaiO`ID-J%fYraRQ>zt)Qrspt#&va~<|OpO>&Tzkm+hV5ez++nlxi8EY64 zLfTCKs6zdi>+`ZFt1QHID9->HLI$*Tt=C1Wo?!UxbfY4GzAhKhJ4IIDcuI}&;fR_W z1^1#!T*|HSbk^$z!B`25w`f1x>tZI*edj%mcku6oN5HsAzt@L1z7rJOg^(na`+dKk zR^mSeWFI{6(D$%f_UjGkQ+ap>Ob5a20cr|)`B+}dy#nPlB;_Mwid8Wt5nxyYg(|Vh zT|+1RV6{Svoz(|Km{9d(M$TEAR$ar_nk_({74;dgsDXJM#2tW;DOBsl{;l#MZ$C90 z0NfzutlGNj_S~!fnBD3X{XnJzkDt#!oIf4TyxExn*oxPF>Yz&~bGrIXi(JwPt%G{A zE)o-BB;K##q6ZvZsq2NY?q5(FthAZ#xjEUX$bWnLG}U!e=n|wr*z`HLKn-N$?=4^< z140a4ZYjfCv4u&pIMK=7Eg0Ok=+mb2EA)gGJDafX-uVT&vtEQG+g$=~l@}_0qF+mZt3p2n{U2*@66m;b7##OjREXjiM8xqq5 ziFBf9-_i9#1zm;2#ic;9fpTF^CIuB8!|(ZLu))dMtpleOmRKff0-qp&rHI&O6JXQ= zNaF7H<_?4kG~Ga}-Zjo5u-_Zu)FG5LATlUXk>uar86Zo=!~Xr-l=>b-SbvM<3P~@fbO~xT&xvRlIPtvGY2iDfZij6ny}keK{}BnYJk)HiYt^_#mDHaLq&v~nN?nXz;!9vBNk6($^T4=A>r}?SYW@tO?FwR>HjWa3R*u(}h^q0egZ#f<$~G3>N8oz@3GE!bhgv{4u>0|QdbhRK%9O zHk*ui>2Hn#jh{lkji>%bg1?!!=@zqK|NC)vj+#NhQ{@8_5|qVx;EC7Zh>4&ie=i7! zan8Rk!1u*?tFA{fN|(FkNZM&%I|IyRm5n*8Eoc;w&t|-8vg<8-U8@3sb0Ns%qUa9z z=$I<^>#fWs0(4@K$Cbs{<7ZVqkE?4c2q3r+yp&%6>J27U7xz}_5v6K7{cz!G$0Tj z^Eq$$L0SUqO4+eXhZK~u@*p&R{PQMLEDq4iSScbzC z8Ix`B-70g4ye1^v&X^90H4gyQ3-mz12zE&AUA+XSvf=tVs~!; z3&U1Ph@i?t+91CXw4@Y~=sj;?_)rDdZRum_==dH^=JWobdmOfS;_OIzpW(NoQ@PDg z4p%xb$2TSyh9QjKzn^bILlOz2(D2U#+q{V8Xg0I4u$!EjPgOUo%e~eNS;%Z}Q;*9d z0}b*5Lq0g%G6I+x+^*h|wia)p3wJK-APaGxytUojYc?VPQ5hZ)RjCVvTWycMK8;UI zSrUM5*y5B`Q#bsRqa_5y#8R@)b%dI~-mddlg2fGJX3!DDlEHmGou=0R*y!ReSlbNH zP*uqb>2bM2O(O>y)n270VRRykdNSx<>X|#$9+5EtBKyoQ5Oo=8R0xjC%FzJvu=ns) z+z5;LaJ^Gjeya|`AXWC^RX!%-d_08zzr5FV;Lhm~`DQs+dlu?y`S|gn7{H0gevN~o ztu&!;cW|qmO$vDHVV-QrCPLOCnmXb(E!F0AwplXLT9w*VJ=Pr4Ij^AS!VeXBL_$Ga z13MisO062=S1#zc0T-f4g^BiWs_-Ggif(W{WLG%nQpSbhAfQ2chb0c=I!Q-=#C%^9 z=!?AQ;EL|@8tJ!51vYZ(xN)57R0W~EyIz21HZWRW_*}L}0jsgtUV}>ZXt}-JI$reM z_SflS)gXDM1E(0MsB+r{1kF9{1s;Sq!`~V}W)|L}T}|+6^!16OOs+!rul*3f@v#vc z8$?z-so_O%m>gVh+=F{qKI)9F+l^{>H|=5)ne|2UmkWuen1VpLruaIM$5eF;EPZVo z(QY16NJ{X-$Y1Pdg*Ur?+i|s2z;n6WjRI6H>Ew{Gmiw7)gS3XCg5UdcaE^kym$+jV zU}(+??){2-XzA{|H_ z95k`bS83GFI3xDes0741@PUIflPSE*P*U7->)-8mo;hYq_RBi8)Q~!YEYMQ)K*45t zeFp+R=h)-&(5iz@$d!Y$Kggwqyn_d!HjHc-;yX+N3$n-IEkM(O(VRqs@9P5(5V5(F z$cO1Ew%h1@QtAxI&*2_~4}h(3+R}z|LBj&9*S=Ymxl_JCx;D@2d)fJ%U zL{*nY1nQ`HDLdD!cDtc|j3>;WaqL%|-t?3LAgac_59{z4ccrs9Wb!jB0Qiz+@BZpJ z0_^GIn$*4A0^o$a1FraEATo$clTEh{NMRzn^uH6~U7UMYovESHgc0N6-hzMgXzT*1 zy^&*)Iu#n)<9qxWbdi8^qtKW@Yi@RV&K;zh8bS&&(d>{G@6IL1bMU`J{2iSgpEi1c zX~J&etr^YCxEZAo(C2|_3^%Efr0NUlX3ifq9N-Eg{kNArZj9k#-ctQ5-clI{3y18x zu1r%V^qbD!RgM<7!95E27Cif;WivFhe>`~>%j&)aQq_=!@aizbmKfDxDY<_0qIEZvKT7VTbv4r!xB6K?UO8tzX{3N4%#HBQ&OT~`Kx zdrQq{fjbvhsdu&t=F0#A&Ic>rp5f)4=m3(+`RnRG=YUjqQmlU zufcs3n6knsEj4SHnSaZ~xrxYWYCsHk8Q^GP;mt^|!xIM0UanlH2;`I2^3Ln}2Dp#k z#G@~#wPZTbaQG1QnP1~`06`R}w%d-70PN<;W2wo1eg=?gA57p=wr*cR52F;lFh>EK z{YNiMtmoupKA0Z$2Uoz7aPauea0ZpPjCerv2n2)OTcfd%iQTNMUJdiT-6t;MW>@m3 z@rMa|aaM#u&8`2o@sf^`zb;8}`-{<{o2ou@4F%li4uIvs5Lod7w^iVZw+E2LSUFQZdXdG6> znw6kM31w_iK!}qwO!QtIzApIi9)5oPy1G_-?cy#P#8Aov@_JoZG&-+PgO95S+n#3^6=Bn*$=O<8oK;xj) z)I!k_fac#baF$Qw@A{PZgt>R{(a+W7<8C#*iEnQhKz7Bv8A}ZMC9tdkv^`y(X70|e zjO~)ghxxY|NciBiNsC!8x-+B!6`^Q7L>7X40Emby!>c(US*RJIwwSgZ+_hqiee10= z5xzRg=N=`#(S7 z;LOWsFL|S)hy~o`xUrs#V*!N#usCrSpbB&H>!=$kS$^{5V&l}#K;zj4wW1Syg+u-A ztuLtLmvqSiccp_5hO=Ha$h>7|t4d5k>#x*06-0$kQlKNL)2 zZbhqoWgQRen$b1rd(e}+rFhaTsW@isih!jYwGg!_ZXlEfN~3x8I}u5JWdo^qfXrF! zdJ^4J27;I;V!n?B7nXL{=1!pojB=FnJRm}gcyf_^dv>68cios4Ch<+(VQ)&o!LMgP zKji~fq65fWiO84NB3)UOQ@>N=ECuxkpb=LwKCZpwhEyjeXL)ETUQxX6TGn}v6pP&s z>_lA#r4QoyXnynZ^Hb6Ci{i9_C}jGTLy&41&_)+M1O!B%@*+_aIk?_%*t^YW^St&U zV>NdJT_8xQaCT0~ZQe7Ib~!j0a4dd(?w(>LAVwR^u`5hrj%?SAk8dRYhe;CX zRVBef-|^nJL_ca-a2CRRzTOiLGMA8nP$4gt=Tg-R7PeCHufoRtcQf}|^-QnQv<;^w z{)U%2R}OtZ5!59jdz@OgX253ObEv}2Yw$Z0G$m`_$u}X=;(H-$4|zJRERI+$4*$bP zd3Xu4coXCFpYa`J8G2OApHjkAgW||iI$B{coF_GQ`xHL+QM;O%AY;7f;nT66vAX!J`Z)6R7m{5+}zyA`(*(#&S-y>&_&|8FqASxD}4$B zDl$2U_5tjrFP~d}h=Gb18I@7y)OLTZ@0uXg^rt$;qv!XJB^xiYr4A9%Z+?Nz6huT8 z0^59c`(JdvgxtBCDgie#)N}-cY4;soV2^}9fGNh`oSZdj<2wL#_q|loR4iqH?I7?a z=t-31$AM~UbOdCtGx_U+>Q+R--RTQ7c*b)k)B7Q+#{izXS(_C-cR69`cweUzzlH%2 zcNh5Ps-`LWQirDn24|=DbYd!l8I3cUhJH@v&2d=M!$piBNR0j9z@Y@+8miz0eqknw7iy}a{FH;c9_ z&{(Mk`RmkBlQu4|LRA7ozZ4qWUkHG!r+vyQ9UpVrex>#2{WnLu6CXe1O2<>)+lBk) zTO>0QoJGJU0-#ZFv?V`n^U`O-)#i}H$&~Mm?oa*-KIUCL<)>sRiaAIRd+9Q5%oK2o zO<(c7O08`)7{68b)dBhH(mE8xVAjT1@$%6N!9%Y+KX>dMxx4t@p*L&rg^*)+R_p zRRAVdl)Y+OBb5|&m$8K0_41r*6~=Wdl4;o?U=hDc_<_* z0Z6Gx{#iy1u}OVq6UkBN9tqDSuaFZL{3TD5aUbid`N?KrC$Dms7qkIW=T7mhIu%?l zSFd3R@Ry9{cfHMlnHL$(lZ!@gXVCQBOK85ohw0epZU^;1>qS@kpY?2Afl8%w=k@lb z{3mtmf6AdkMKyb;_gaW~v-wA`YUkCK+IB_QAW-XpW_IAMd6V&~@UWvxq%#`W04V&^ zMmFR`eR$o@q|_X?i!4nvm`(i^)BOW}kdcM5@JnyTg2-NWz^(mZ;5hb_^Bp+8}M2z$|&*yyel zJgNU39ZgzB>ODLs!ha)tIMXq-$G|1=669DtM+Q0dA|Uu1q=q#bUxDPY>zlyI*#-c& zaj;P`gQFV0$G%B-V_#CDYp2aQolLq)4vT2Yg*3!xItB+xSC zKNiOk!SGEyu0Dp0-cJxLi$A+rpZu6~v{#nQy_~#qq4gn*->q6NMy9pd#ktVi(8-^C zIqlt}?4eT;;E#j3HLM0Nqr{Pe9XuKk^(77lcP0y2o^Sl_6O8r+$0@dI3H}kjMM(&^ zyj#=zuPSrszZvr37HiqN$k|w)J7mNZ3-Dd0@8rTP^EDYB(iXm-yTxW-y^l)^Tc)5T zov)&J3gU5Ei)U`r+s;uRY$yDs6LFKIc{-No^Z(T6MK3yzkbNjGmYH~ke>IfFFQa%% zPY;b^<6#TQM%XpcwwAJqKw*^58x44w)5-)<>9i(R9B*ZD_|UyEQ)vCOa+>Qj9^ql% z2To!Ne^W;tsLjJ|WqdCD&q@=BS^}igH5F38H{sPtvv0DZxL?Rve)mWg*lIyVjK`7;4=O_QP1je%tqN?L# z6*5nt?G3{uvj4Fq2JUiqEfoV2&jlbqdK-@p^8`5nP}4!`P49d68Vr_p)8JtJ}PKt^7KArof zFg@7I44Ij@l!^we6I?$B*Uq41s$A6`q?m(w2sHkxWp1})vs{3jMxmw;#*6axLk*?; zfjQNkQr!Wx_*?vQ*VG( z@chz`cwhsvpL#ya7h_om?QE`loG5=vsmVxN3yzPzIT#lp!o$^cDit=FjSGvtteU@B z0uhfumPE7>7EI2m+~A4l)4$tT+aBc+zz$G`JXurrO){uVHHVGhL=HQ9a$xZ9a4?8& z+wmf!{38n5q7rl>LSid0#{#%z09_bW*&hW}_7{f;!UToY?IiVMCI`Uy5Ou~n%biW* zaH>e1Kd34unX8*c)K% zplI-8)_Uc?RLS?~t=VlVXemI`w%$0y)OeBlD}#lF*GZ;-2ypnI+r)^6po!WoLGW@y z8MkS?;*kpd?YcfNM!$}GTTojWnnfEfB!d}}MGNzk6m9+d@QD`~sN%fSfRTw?0R#eX z84|aQSU{$nKsbV%g`w#e17#<);Y9$2ylCGu^38sAj zdF;Xd?Ax~YzK$oy*1V)Ed-qAyGpOE$*;VuN1C&8GkT<5yy%Jgjuz@!~*#e;LG0T!} zeKx6)s0XH>JP8ngr;V&@_#WJPPzr$<@^qo`aeJ zk&7*YgYzZpgWM)AFc)n&xWZsUUk&5Z0C`L1bL%1Mt;uuzz2l*V9$tzf?aMmb8oRbT zs0DD__iC5izZ`68=E27ie@2cJHIRin7wsOvKySi~w^I+eUht4ndeA~B`~ZnB`Kt&Y z3jCbx9vF>3&cU|v4)X*z z$_P#|iDm?!m+$Qxy9?V!s|k$8Gn7P)&a$di2#LsJQH;@1+-fWynEnrlnQEz-9H|GA zlaiwF-MNZ>1Fq!W#J6DQ41(py-#mq?DKM%^G+*Bv%;5+j9QVs>!n$CDkD0@x9ve6U z_^q@np1sV2BcfJ8Iqr7s@*LQ=QGfc;7UcKDg|JPWVIu5~|8#*YP~c#lkAxC=j9kW6 zdgV7J`2K^Z2YLq}2xV$0N^9NV2H!bl4ri6i2ra=B?sy^rK1m##$!nF*z$kOv2ykbN zK68-da??j;Tms^WlyAiW>BLUor&~eNPfz}s9E=wE8OGI)@L_mZDbTRL24wLIMlAJb zT~sxa9Ixo@yRIPuOyS^VARaNdRyh$!a+Sy?S)4{HBC2k8Api-!V~JNHvY<{j-Hcaf ztOCO4dUF{Y;qB065xPi_!buGKZG8>R-7xO(*@hdS z5>F@uD#rNP|ESMFpsu-@F&=WWp<^k-x>fBQq~0W3OZya2&a@t*F8b*53PTz;@^|>Qvuv@0*JXfWLg`j~vxu{CgWX*6#!@7f{NGk4<@ zKB@n&44wC6V%E~VS~e#{ZKpr;$gnpqiq>Dax$6mlNuBI{vo-*8fZD);;E<@*%gxWV zn96tzjOdJXIpyfk1R$d@k z4V%BSvjup0-)>0%4Z?>duHA@j?wj{XX(BLkDVskq{A_FL7nlWxN$piTPo}1Sqw;3t zWrM?hAMr|k#sp&aOyOOd8hh0f5Mm2pIna{|!66W31&(phdsxm3esqlv%-U0{tIZw{ z(^BU!RW(d*oBBKa0D%+w0fr@f{^w!UJ0y#1!;VnwwvTIlAb_J@a0JK4lMKGG^%c_- zR&MXWMa<}f3Hwz9rIgeJQ9cH9H~uaoR5}XdeAfV&7}U1|Ady((A7}F=StT`6n8=VO zCSlw(z`%*6@mfnc>VWSa8<{`g1gRdINe$4Spf-QQcL8XKpmoA`+-#g6&Wo5&R$XHp zG~vxFy?-;#V*op)$_u%irY@HZJvR)0^V_Uj@?))an^*CIpnuvIpp`~KfY!N4HqTJ1 z$HaIGNE{aQQ3j5{hpfQ|xr0RtJlQS*K!)EEjtc_9Kn!X2C{C0-Gs9<<5oS}9F`pHm z!(11bj->nqP;uGB<`<-2P3{QCk=17yAw*mdvxV*NC*#gpTaT4;KKxxGHGApAV z0C%9DLYBSW^)ri}W(Dk;G(cc7fp8^}F^sZwxMMmIDr^bs1}AV_8ep1NSXAVlzHc6X z5CLXsFr27CZ+_8h=@1;nf8P5M1=XUIWdR)(aTyVYU)VDITT`xQC5o&=I4- zvKfsE)p?18t54fAtBO>R$SC(kq?ww5B8bkf^#o|S?qO9|)v=2xf+;l^Y|yCWWsg9O z{w?}pcDB;6$Td9;)H)b&6-{^X)UN+e{$HIt_BQYfU|`#%t5Ch@T|NV0)3h4V z83xyZm#b(-Uz69S22l!8cZI&`q`!DN_7MdUp|OUamCkyl&lZ_PjOKPBDS|2T95EH` z%0mVNE~ufuR>b5F3|pD=aqG~lX|;P7QDiZZ0n6Sg42M)mERtW|wd#Cqt7m`T3&u!F^CpBqdi;$V?v?fd=1Jqv%QVQg zqt-fx@Nv@gM9%mihx5F{8b{=EOtKc7I?IpYna^7Ae*R+R5W;*fgkuoR5qOgcP&r>R z9}_~=pKH-&7S^e(`eO9U8AYWrj;->|SGLp@<&Nt~G~X~8 zV_CcA2NtmJ$(S!f&i3x~f?p02#3eG_uIAF~$ug)rGO0ve4X{Bqflm(k|9fvedWiCw zTKWSGO%B1vl6qxtMD?^qOmqUK!pPv%;BbyCy=9$_g`R14>&DBCG!44vsV&2*=|UYg z^{uNGCOft6qr8!FLlaZ%!yU_$iT@V%$BDQ8txG zp;7yB)vC7c3iauFq({S!U{hOrae{On2SK^RJXac-&(QPc_)2IMC+hEeUz90gMuBk6 zf{2Ik2KFv0izoQ+K6rT|A2a8-Zpd9u)l=P97G-CjghVCOJtqf802atY21jbOVcTK# zvv&XN3a-xRTE-U^hKcP1FfnIpyKn(E@&Rv0j&{Y)Gg)VJX=AeHn+D&DxHNT(pR*fa zunL7n`Ny^cNZ4F&`ZfJ}xx;aUBFC?YC-Jjw=hkpU+@Z8y)c|qm#3%)v{1w7wYnQ@{ zhA}3VYq+h(3{1qavpW-yCP{1c4(Qq9Fz-2F-6%I%y??&GSPmTy2_6;Mfg|A$DUF=ujC^B7C6A9WJ`>OV6TJrBtg;7X*Apk7T<(-+qQ;UF%Xm=+M;?gXldOKzAvFPFWP;k zgmM&VVE_SM<<#VksxS5U&#_HRnmYf728{my%ooM!`EO>KUfpRc%Nj*LV10;<#L4_$ z#Hqt&twwG*;|*{3Z4C{tOh#SbVPSnB`!RLC)WTv!w4so04I z8+_vY0H%syf(g>EpuoO-s|Rr>A$YJ9-2Vd)g8t{figpY|{k!c;^lu6Zk?)7}S>z;& zv|stBA-~g+$Z0dfLN18yjW;_e)1j% z$#`tk6J z6+W#tGz_f$j}f`Kz6L5$xGhXq5V_}O4GaNRUOgsksckj3IXd1 zZ`#K#r}&vU>zzDKdJl6VhLak)i3v<79o4U}U$(yzd`7 zbGw#@`_`7_ZvA>Cd(Vpj14;L_Zy2sm|MENCxvmc+4O!J**lOJ+gG;mWzGGvJqioWy zIjd@iTbl!n&*T9SNyB496hDLwL{to2rjEAeosQQbHvEjZS4VM>8c*X{nZRFmHuv?s zXK_+{`OO=L*B5(C=v*2;AwiZqE_z!@UNiYgjm>w?+Xur-E%Ay>>>q~v8~jMGMn=%q z73Yd|#s7Nb!IVTDZ}9LYz~wutoTU~2(R6*QSj=;F1PW+Om~n15ER!)sbgVkNxQzEeJQZmiSA%|gc^E|pk;4v;oTefmUlQUH%1tg> zKDs|e;l{#SzkG-z!Z&Icg;}cYU~o~c`*k?7=LIpZDxPy?NsfqOgjfBxLeC2-YAy>6 zwWBJi5$YzO_OUxVJ~@77BMsaU4R`yGMPwf~f1<%DH03O_vKZ}r@%qJ4ON~P4H67FI zT91wE(CoRKP}6@2g2kTv|M-~5k-t7c%8ba$q$;ZM_%W%^M))eAt$x>c)$eEG%>LN4 z5l^k#?eE-GG;o76c{pe2q~-=Eu$w*=6W>g#rFWO547y~ z8Cx&u@RsTo4+l@Y(ry;OF^obWbg@LHJYw-y(E%;3qpED zk^pROJ@uCsEaqJSx3opOfLRlu=DO=d%a%&i4aoq@4y2H2R%wr222FdV77uBY`K;(0 z=x1eD+S|<+>keJ*)vDkiLu5|J@AZtWo1vL>q7S0vk*%^~J@s7KEb_E_vuw|(| z=MsI7lPQI9bGNM>zs5uF$;c7+uiB9NyU!jLW5Xi0==Z;vL4$3*ZG7(Vk=Lq$h^tRL5u z_GWCW%d!VdIEvIhm(?1k4v{?<21gLsCNyT<1CP|k$2hk+gyHKmTPN1S$U@A77!ku2 z<4goLwUs;E3&qp(j?j9LxC_{6oVjAt zXp~>C-9PD1fg9b{oVBntuWz=79vf-`*fbT&l3s%IwmqBmfUSRPZn?F6aCyC&*+zOd z=<5IEh;f2M9EYC1dsr@fL@tctX9Ujb-2zdv>+Y&lZtfgZ@1jD6ui9jMbv#Tilp>mL z?O;$NfGH_ij4ga61z*bzRF_4$1qNte4I(*_4ha^3I^aL{7MKo4y-7H zmLJVzvJwj54``(`8Brg8XCbLz!q;oMo^NV`PTQZ#V!r6htF@iY?ex=M(Mq=tENk*2 zi;-*(CH+Zk>9oHn=ACM#DlweRe>sO-XRePC`iv4mIsE92mz)G`(4P>R#-kj=K)$Bi zKEPRKW>Q^Ue+Anns5<;eFTbR<@*fHdf1p%h{3d6@M#$)4rB5G58T|5>E=yw>Qy3*A z&g}xhj{i1l2R0mEf(bH7;7zt28NK4(R@%=5n9b>-Z^1?mZNQp>)a&kSJdHmEB+e$FVnsnu+;A8wrS&eoie>FH z2s#LSBhp~VgD*v=N2ixyt0GIjxBZVqN#VqQUDM~w@a&1;OD%1Xec=HOnsF2ssD%70 z7I_KQ;=Q(31pJU{E z8isN9q(na3%~hFis9vd``$!^30z_r;IS6FKRaLTAo`^$;VE6sHbM6CFXc%e`KUNP1sJf?zWeD8P<|eoX=KeFDo3~ zo16;KZ0>s7_uwfKpv#$x74KMJ)ja zJFU*5(7L7pC@UQDfNZ$Nu^>-evUrD2p7cqV zj{l_WP~-01YLPvim%F)8+_QQhLUm~8?ACD8n)->;=BqOq{w4|j>BU3os<*?Og!ncX zPwPgHFw(v^>r@Hg`>RuS;}hA`5-ca!7BOMMebH>o@?T)D&#XT?#Xxth_sE|92*E>f zGc5->7*`Gg2EubiOht60}Iv@-sj-ikKJg!X5GLpr~ZXr)v^oTd6?`2J>JIyVR=rJ|?D zhsP4bwmBURdym7AiU#Hd7IaE|WLUxMsg< z)9_FjtXRj73d3a;sQlB0$YLc$!@KYaq7iVfo@Hzi1T}q74g*fu!>=I-A#$JN<7eO! zJR>%bQDZN-J)XQ3qF$t=;B&rc$(5RZ$`GRgto=p5dM((`o^hX%nk#cdO~VNmls;Dd z@%;{p5if#qthV)!+``{r!V4f<84E>|yA*nQzHe{F-u|mM)xgNq@0e3Nco@H0(5yrYca`B}g z`n5eCn?p0#qFKXO#W2#RJ^tM<1`0T5XWncdN+*UL_4cS8+Oilz(oBa=*E<^Sno!Cw zvN}WAlbp3+bJt+7^-@wz*pv=)*Iten#>|gRx|SDioK}3K&;dDeiAhoVA@98@K^I|Q zHnB=V><}6K;`|{ZuTB3AQGubYAfY8Bm{`2%GYfRiW{tc(z z_CkrMP)eqVvk!#Xf3Xo2T-iT&F*+ij&qM?BlUgzEhX1 zC|m>=Tm&659C>tVCHiCZ@s8|O_Dr!ndM2_KI+|$(4_srD*1f?%$kMLJK~mDaS;sIBw?mEZf#JOm5KX zsOTh+@C~uqst(Oo{gqdWz!Z+_cQ`$Mwz`_JuAlXicWh#mm6LPZWgd(YSFO74{E-!E zX{;jq)@&BqJ9;Eq8JQTX%3nby@HV|6&$4raOLy_|ejE*;{%fuy2*yR+BqWm0^ULd1)x7wfpV`s=Iu#5|M4a9;-!PV12qv|UxD zGLwyjC9m!Nqz6SzR^^2`Y5AWx1@axVJ0rKj5x)iv#LZ2zhD0Y0&5T7xCfO8MT3;ol zw@N=l{0Y@2v8(M^-FsX}a(`0r(o?L?sVS*9>5qZ-q3a^&7yh!?W?Y;qw`GG)Ni&&s zfBbaq{2X)*KCwyj7XgzvPkQ`weOq+eW1KUX7G{*$(`a%j{W;=a4e8YBT=0euk^M-^ zg;>&suwEfp9^jF%XB+N$vc73He3>WH9IB0rY{Ei-g>8nENfj<6JNi(J$Yd+7S2Vrr z3;f$^alSGGl|)XTZdQbEYiSqFr1@GzhR^87^aTi5G^qfmZA31bnJMXT2q(mt)^)o(?k^QJD($K9^ z>A%zMisyM&QGTY}Msjw9h0p;t3KcCB(gkv{NGBNVqhz3NHxMiDKvIyL6%{ zWuqd&?R@NXr6zGj%6M>k!+ zoH9Ua#ywr4)6-a=aNN$ibK_ljD>PnKWY*UuX}Ys*zPIT(0SX84_$m@Ggi>2abF6cH z=1Yja$|O`Sg!!#J;?GwhB^n0k%I#}0gXNru)oiZFkEd6+6d0)9E6;{|NLtd-U!dSRm!U$gWm4DrX;URqo}i2CnL7P31>5>p0R- zy1IYutmjEM8VgRU-?p=sr#1ye_~e%v*J_XUa@vTbfV$GK>jp9DT`qa3YW))lBBPo) zKBsr*1jQ>=kU1hYiTFv6nj`6CxjCfh;@@V*tHoTQ+MDiTRg+H>s`vKiTS0iNtYZ24 zr`e=;Czg8x7AyqYiaqTGS7*ez0<*)hp;7A4<~BIjuYPjWmtFb1r((-M+99>`)mI7o zv+)^CbH3DaJ~aGMFozY?pGPkN4DO1m5j-yP7Mjv7^S`Y!G6b3b4*uoNE@gYw^@e$c z(7`!891DN>9ixomC68ZnicBq$02bUgPOYxtR?x*nPRK%(hEkx)W9w>9nSJHByxd-d zKUw5r@hd|8@*Gq0jI5^qUa$N?7E#ceIzNqzyLeh>ug?eef6!6; z=hpHyUMB=1vU%{sY|DmSuKXS^{&?!T9P1$beNw6 z?qAo&7)2ByTb2=WKdyu$47Jm~IF-2YSnkrRgrlU})W!!!tH8|zRuN%KV5 zL)v##cXO9ydz;{cL^&SbijP4}PUdIDh^3YUQM$@j8;taig#P~Kl6Rz9+@ll$*iRAzMcMq44ZUwEZCK96) zU-q#==7F|F%_|Td(RjENH=y|j5eUsR(Tsh2ujaOU?lWgPTjBE%!8P{kJ(1qb zauY|Dcd{}N%{r;;7TGomevqZ!Y}>2tLDQ&kO!LNWS>DroM{d-ur}6H%5f)IF1kN>2n)Ta7WUm`3$v*4&q>q=+-_h4t3_q+c3gx>b7 zF(!|r?AB!mBh(cXPmhc1uSy&ikp`=yebc~@`(C#Cs*>GQNmEHdcJ1V%b!Fi^)f|=#FKgY!`rrUmBDt)AeL``(vSh!$9r&!n`fuwzOyo zvTRCGw#NQ4_k&*Y{PZWZesBqlouJZ_VR}u7PY9i&ApPf)Q;J_78to2z_;?RczHL12 zqWxB5A0+iT;OG&m-`)8i0auqtoa>=HLy5ezhwDvER<$)dJX#laLKb6?<_Yf5`1Qh* z&+~VG_N;GE{!oWgHdx&meH>;H6>vgH9z5X5^gqgh)n?`KJX-)(r}ANqE8!Ho1c;IS z!64Vy&6_JVtsA_+BR!HGIvs0Ve7fbYpC&BcwAF@uIYJeF5iT28_Q0r7dqO{i(mL=< zN<@^AVLV3rKc&pj#zQaL-Dkc9hUtaDlkM?oEXe{fG1@`)h@rK^OY;{;G06$yy*p^i zlA_*hh`sluT9&vlb&UUeokIUpCSf*+v<9Zfi;1qWNiHt|z%{JpZ6qO2Wq|7Xv@C)$ z)kpX+X23tK*iF{nMprl@59zy%%K%!sP#kNSq=8wl5p5>NmXnnE6Byp0tLFE#n8aAZ zE#S_@Ja$C#bhz(^q5marL&9;~`w{N9K)!0TVL64h=vFx=;kl3{lk-*`x?ypFdSxu+ zaQ*N0xZ}RyYFOX)1q~!`?)?Clpp9!;9h2&!V@)^k%~v^=$vvrx-25feJj);d`Ctfp zZqMG4(@$3e{+pe3<*2>Z$5d3y`2prL&UODZ5Kj9ljqLqPoxsmupG1gSpmuZpX8vXcMX-El#`U~NrXeIL3KT2?cfZ|pbNNrRv;Ba;ta<;p*zW7sjNeyU)(Yz0 zT280aNEkWJyYDreoDvoNv|r>DK*EIVrL)x(|H^orCk;S{@49{jPhkVpdE)HH)h=*K zy4qePg|;bg#Bl(7!$DeuF}nP6#si^WD+*|&$#-P+-~$MJk}QH)|( zRM_#+Jm1C(R9ZCPIB^d=xOaJC%_R~=q~o?QKI;d;M?ZZXC8<2V!`ZI(wZ*rA9XZO!!F8> z2dl|AUq1NGA<^CAFT(*Pl_>W@mu*!avsQ8fGqb6HTfuGTh%{M4p`Yt+=I-W`tPbP4 z*}?TD0P~bzyTtw_1Rsw1Mot%34_@#!4ZH=Qja&+=6hCLmU5r-**gvj`)K&Mi9KipR zC_A?26@J|JQQ|S3HOE3Ac6$xkzqEJsarnzfnfj%Bj!+cEo3U5wtjBUyR6&pXYo8IQS$RkU`1x`6UK z7?a#Jnf~vSyde&5ct+isZau-PAsUHcebNE`*o6Y5|DI*L+|<)c5U>WZJ(@-I;QYZ| z2E%OAb%Y&aw1SHBCBirt`zbzpo#$*RW=&#)(*gY-Jrn1mTDeex8v%CSEh79hpMIKTmsu|rpOCrR<`bf-$Y@+94L0gh{PlV; z!8~Zk^|j&1W}FDXSt0CQZVIrJ3Zv3~4K&opVzOk{)|0&rz{G+hLx3ssI64=o+@29A zjp^9BDzdyeHgi2oow~fc9kP74Xw^&CDA$SRY5LoYb%oTfj zH$ICoEuWrG@wnz0Mkq+qb2*h4R#`GDj%tE?=l3WJKE^zB(d{j8-EOqQ(en(tSZ95` zGthKts3{V?a=A}McaD-MfH7aq8QLd5E=mikti9-N`@9B+2ovaj*^jh6p@@Wk_2p!EIEUS zBtejzL1Igmoax#A?!7bfpSd;fzBl!%s8XQ2&tadn*IwaU-$yMt=UdZm&E<>kCCJNX zEYod?;r3sbx3ly{T6(jZ$!m}^H5Jr5bj`JcM#_%wX5C*DZoriy z)=x#xEeQ7ooPl;uT2=Bv5z!tldR@Mol0RheX3@#ipcV!j-IU#!hD)@?`Fs6S1h#}p zqvJL=;nTTi;DZ(GPEVLoQUz;TBGeC7ml~7q#Hp27F5MJuXygg|?t5iy8t@cYR$A{y ztxjJ`^vmmQdf5MK7x`cmn(x689Gm5E{rg%Hv&Zl_hx=;9xS@PTH0FHdj5DXPaa9pB z-P^5ih0(m-Wdg~$=C7ZfoUe}ze zul7d5yEc>t(llZdQ~h_g?3huF$JCeKJs}nKnDdje7CRnt9NP4KD-ecn%29;l?t^hH z|I7BWRkUSC!yiut&#p&UL{Y`e_rnWlIIVV@Y*mPX^vu;kExO1`9{1hKU@(V)=;e#3 zz{FnCBLaK~REtv*eMmqAp;ctL+a&C0=(T*(qa=FKT$zG<_5WxlX*d5CTFhd)9_Xo=SPk4`kr=t>sJb1=5jBpN&u=GhfCx!tY-nXV1h%OwZ zm2t9h6hG!<2o$**2?r%LgL?b6L83?m<}zMM^XgY;qFknr*FL8|W_cv%f*d2}ej=G& zw!4`UO>nRiOX1l3^2+@Y!ll36WO8GlNa7)WBYjvb&PkIP#QZp)kqKn&!&{*<8Bq7u z?%jG38mP2}3s<*i{931nDUWx)R*8ILhc1T(*8~Iv2Mj3(q%^HoT(Aj^}U!lnbw$iV{N_%^x}2a zKDk=%yLLS&sK@N8fdkX!x;w>DIUWdCv|PC#Q629rZgiQE?h0{5oSrO7`u2D6XQOu_ z%4+XmmHsP?0%?Snfzh@&$jup(g+?ZiQO*C!i7wCCy0_muwZXuwc*-{|0$NT8 ztCMfj4Y7A=lm?n=ZBFP0jeV8I5Alh^p6IogJrP43Y6o8XqSb_G386(neomfYXR4#x@LDyjPYr##;1 z)pFNBbA^|S=NlSMtj?r3urq2~Pnyg88|t`@-)YUO5?dTprn@FjZycQz-`~$Q^7-v! zba`+w$(w0}k>(-`(`Zw9y+ruB9LOxR)SHzJT3g%OHrpJkio@}#O6+6VM_ zGDww)t8*KbG$vC9RNt&x=B3JaGHYSOvq08ck@FF7SPePHO&=285Bg&LynO(HK>Yt2W0W|DZZ(d$9njW0^L);9toLrq zHAc~9v=sBhx$@d0p8Z{RJ>|rN6!=OZ)?)nJ{#WCeLe@MUkI++p76=rUR5);G9M?oS z=~eoEAj92B08IT%_}Vk!|Hx7Pk15Lk;YG-ueIuv-zrY3;2{9xK>PZ;MwQro@zO*&* zsSyml-%|$eKmp34x%6$8kuDloyDxUU#BsREICMTTe{jAA7X_)zTk-dLIv#6lZ8$^M zX>7p~$FAOxxb}>|$sgo!|JMMm12G8mF6YzMtY6{xPG8LrPx#q>nDa>2v%Wt0c-$02 zvf}0{!~BqHUq^*lEshdITE9UWm7>sTfm_5;`MSqCM3)=OSrObdOLdAudFgd5m^j3Q zWgYxqM~FfHIgV`2?+Ul`6%Cnr`tW|Vlf-Y~0k!Z+`Y>71*Vf<;K<<}u9`)|)zvX^z z;u>-7RR81IS}Jl{+HZnqi_+|C!G^PgReV9bUomWEfQ<$JC+@-jA9yzC{{hgpw(ac% z(R}Pi78ixECUQeu@&eI+Q`_C!8d4R~o%Q(%5eJE+>HCq>1P2Fq|EA67 z$Kq$ZbV5GO)<*{xxU5}}FgPf@#9=Dt3=gfgaLKg7gpPh$LUq@Fx4wS$O)%L)M?+^B zv+!BOyZ>f?ePQ9k=f0&Kn%zCO@fNI*>(S%KOI=sqz&$6Dpp-86tMe+idO~ri&wu&E z#Xk&;0fW@7VwZ6P)Srr(8v*_xj=r~;O*aFjYG4$vWlATe^`P>~bG7tZ%Q8|7hw5WmnZ@pSKDnp^@FAvv}fzSmV?vr#! zuWgh9U(rfALTqdI%v7&rj)s$_rK-mBF1{dJuN)3-D1PIt?bhbD98z9Z_OsM|0YhjV zIz%3fcTkI3s-2W7;?kjK58EiD$BD^odT54A*~bWudZ zX}5Y%Kr!b%ZOzqJRBCPO=(jhi_4jt!pMomw29Ox8 z@hTsePWeCbvzoO$>n60SOu8pA)AIU<>AiQ(_RA0O;s!sIaY`K+>C0Khx7qS_jwYAn z@briNu#EyfERV~wIt5yCGT#1w5di#ia!V*_-k-&RNVgdS~_ zrHR&C(W?_5R@WZgCpFhfB8_fZ>m}8&K;_Gi@n^XD7uGNR*;NBb(!&bDIA+Irz3FL$ zzJ`jvVDqc%Th)e9GLQgT&x4YKrLTaM)87m&o>;D}^`wP4y90Z2LzA;Yx9=Q({QO4t zmQPfvwmv&@@y%4{3*VtISO2E$$+aVVq1oK|1Afnkrt!xL)MPFW(|+csIqUB#^W}10 z<&<}z2}%vc0F$_PelNww`W$xi^1H$cljPbe<}!0+6apS+0Wpk`L~^PSgI@eSqD?U0 zE4ZmxJMLJyYK{V3U5`V&Zw(E*Uj=ul-Hr%^@|D9t}JyE<)&{HJ_gdf!G_^C&#cEH z3eZ;2_snGh^z_lgY8`e_zvlp0(()_@_?uRAe65NUnHgx6>^6}R6#U4O8ocD zMl2+!7j0NTyQ9O#CfLsW6nAgE;G|?*3gq_xTvLu}wmMy0mnCXnIwZhv9H{cJ_^2@D z^T$~t4A%ufMZB7*j9$As*6M(k62KNf3b)<@);sq%OANfCf-HlX#+BM7mQos^jip#! z)i?tfxU6g|9*vDQ<9dhk_j|p>`~fXl?oNQ{W9|Nt@+{+-77f`Q%$8A_MkijJx}k2} zD@Q4Z%hX3qicAwF{H5B)mG7C3x3ffM*9ihw)ss4V(n<^0r*A8!3r;5{SQ|llyh`xE zAJYW*_>EQx3aJRY_gtBt>UA{-aioBt&Fdo>a?Sj*k-SaNDxFBNyIyY(Cl*BL35uUx z?X_t36o>aQOwNDuWE`i2D!`yie?8aWk**AgFNF9_9!OxpUH=tw^}j`ni2tuaToHz# zq?7HdmA*CW z=}RFshxn6faA?}o>?s^YDcrHv9E-en{h9<~on+m$HJ4KBRoz?RQl~F%PrpAG^nURs zX09<_ASy$E!+9r;T~5=W#KX!TV&-mpBNvU|2v7 z{AS}dEUcc_AWXb@2eggg%wUzCeE>Ng)|-I;pMD7a*RB+Y-FR)t>B?Y}yHni0=NGK{ zDgf&R3nw$mBELu>V>Wgn=<8s;)*l~v7WT4Y8(#N!6bDOPQKr}ZgB4=x%xR z3{)h~h%&n8r14_SHxRxz291jxEaR3npAMch;YRZ1ObB;_6)}LK5Io4xBr-D|mfc`6Iv-505bo2Rc!!;X7qipJI^(lqN z=9H%-eey2H8xtiYH6<~vLyFWhdx5 z9D!-!X2rDFq`rL-T1luxm4(;^SwIt3;;`zWmakgSEvvv2xORs34k(LHZkB8@B4bA_ zcgls;kto#yngrbj2SBR}sGJzSb#_Tr;yt>z=u*!j1=zYck_jdB9SLY9QR;hQMve7F z)wAY6ZK_;YJ80B))Xy+n6$!K9cdxEWB8Q;&gA$_?!e`#5oN~x49!aW1$&q6_#{>DhQgzQ`DRtU`C{en zBtzcH0#xqo%;EB*C*6T}hP{Z0zFaXo{*j{k>WbRLOXCOTVtpVz zSq65wSPN;et^l-9`+8!ym_hAI>c6?IY$vSoBknM=p2A~tC$AQE~o>pC)5tIShva5@H z&zAG49hnt#aM?~zF~GZDBx9a8_O!;g*#gY!aU`QRel&uq=Q}d!iat-vJHBGs1*qoi z`q^Hp&`1K0US%b!p9k8N+VsRCgekq?lemcXCIqL*0(c7Q=>J*00W1O~)`z<}b@!if}SjB6%FB4J2r+|hsIEoZag6+kb{ zy3Nu760l=_#7vzZwM4gRHQ)gO!cxk#$t5lR+?}uHvlnj!%k?A1%`7&Tcd>tzY#BLD zY&$jJWK-1c6xl)ct^QreXg8g;o>rf; z1tXD@-1Oonu`!)VHDWJ2`dF5R)#AW%05QhxDmQ9&LDc9cz)wweV$V&+_t9HSf@Qr* zn?+)aG9y}apH5nv9=InL!?|{6L7lZ+nMjD4l|vXHi}vK3lhNoO=?_T={daX#UFK#o zXSeroBvK4F5Ys>;84!8K(*krwgb8q-p=Jxk7p9d64z^fr4!xH!6xzhTS4LNtXEXD^dULA5DFN4 z01Yeg9!WUbe|L0jXb({8d0{B|t)JRPlLJxRfwP3K9%TZg89we&lPX=9o*Eq+HX5k zX!PF1chx%n=#2e{N9}xf_y(lPFxvEmtfN!XGy+g@VGcG2Mp+poLf~J$yxtme#+QS6 z2NqV2bNksO#c+)hO?JH}6lNn#9ArTzWYWZ;&_@G<>520KyC;9e(x%(1Yf^I_wd+mG zI})HVaf(G_wj&<-McD=JBi-_@R<(%7a0%N)o*B5MXPv+Iv4X25B>xqr5-38&!NFMg z@|^H1j1Y+V`McPa`icunl5DQ_*T&t z=Z%|)YlNY)EFEOgeTtYr^PKmP+!BCGoFiFEDhRl%F}^22CF`E*_(T9aEGo=cC+}E6 zKv?-?Vc}~vt`*dLyry-IPyNzyizd*p(J4tayY6e&!x)}|Rcs0Cqbi)2T_rX){9A&r z?7=sH@qXZqS>O;@KJL!S{K)9YsKM6t#k9` z=s!oz!9KTX>L=Gy*H}nK-CM7gHij7^MYYxGVAp#qAXxTE-S1tqeUDA{vtGoUsEVP6 zcOlSlJa3gN8|J9D?1bjdO?ykPJVY(VX|ziEA$(sF~_Mo z2P0_V_ssOmrZGf=IBaVUQZG?mwX}L{z{Tq+DZNvqj0CEGu8Za?<9f%+$mXV(^Vy~- z)pGlV$K({ha|j}wZB;MQ*NDdKCk7q10CP>csDsGk#XAad?^{2}65~j8BOCzf)SrAS z$S@}Da=U9|$M2vv`f9JkFE~1%f$0QFgn+6fx<7?J8E6xC>`A$*BQa>&!{~PMASu|I z`_%c4J_#j>igVu8&+?&-vW&9PLXtaz(i8AJ%pvC7*+Uv>VAdPN?qjKt1}7&kRqp2Q zyi#}>yyhoU5r!{5!iC1;2oKSM*EaJX*0xuL3ry`BtOkGgoW2CbZ>)kZe| zdGoytRo&AW_|fRv0L5pbA#mf@q-LsVSq5>yG$TU_pf z=^Q1RM3goo8*XHeBDhgiy`~PfTgGIJY@$;68sT)@o;uq;o6uz8`=E`aKEE~3XkNS} zqcr2{NpP}Q@YK|_q*_eIJ{lr%sfS{v8Vh)5$nHBnQ6J|Eq3h|F9o(p*>fiA2kvF(s zqU*3#K7aYA44jCU(3QzzoiCCld&IQdL$^2~ryKU%yiSE9+2Ga6S2U6ClssSKTh;f8 zv8F#am{IuoP*J=6u2Z^rS#;DB+b6aJ=#o+!ngmN!{%`jO60#Z!Z-e(cyQI6iq*Vfi z8XJWB?u)~}Mw$S2719=M(s{RDRn4Z1`tbJ*r-nX|2Qzn{VYKh-9 zqQOOW4=lmi-|^P7?Lmn$W9sg^;C)qz1SEDlm)22!zq4U1TmzArO^Q+{#@X2?lt{Sq z4qDAYCu3n<~ePUro&FKXc*8_6ApD)(WU|2Mt{?}BmM_);5o}JZ;7SrUn z!+k?65<1NE;xaOY>z|nIaT09oN7Gc{9i%06lFssVow45rKIQmyyfHfS(}B}mvy{a~ z?ZXM^fPUco=cIR1=Aj3cSO0eDWlBK35ny@R*-KGI1Sd-F+1n*{%9U@k{_ZJk}EGUnBpWf|U z#bg;`DGbmoIaWbRsVw`6Aq!+fl8>L(xB=ShelIT<4(yA8)fK-htpjQ>uq3KE2u5)L@IVRW@z$LX&CghCdD@( zCY{Xn$`dExB;M5KbIsbry5X7^K|g_+CncYq)T!VsMtz`G&IU$dh>`^$-h_khEnZ64>YqF^_ncW ziUVUB5hH=a(CA&9@>nNLIziR%xUFY-N1sAIb2Z-wR{B)WensOh8|lEGJwSRM_O6o2 ze$tPac6p%*NMZ3LMTUn0rIK;>_kc)ocb>Cm)toVy;=m{TyGy0oF%?G|7b6qzQ*n4t zYS8`u-ZDe(V|9z!mPbrz1qv(AhL^fx!6qpL68zGP4%ri+vqUHb7&N;+j5p|KTHxl{s3XaHw42&wUgLVimSYppwh-&I>;u2qUX0I9Nt4%{p$cuDHS_ za%4TIg`C^tQl;N!s_MF|M?evayRQhPZ4=|*Kn52N(23&>GjvKlt$q(SK2X-)Yk6eB zZzwD*UxrGc3sX!O@586?$!kGzA*LZr7bh%d_yajnwWoyAemfpK5!`RNEkDb5Hi#HzDQ#9w zJXG{->vjuj1NM{Gza3a7`%b@;E5#&b&SHdY$&2@no^S)7J;E)og=C?!@@-(cs1eRE z5EaP~FS8(HocTbVj93JX#&`BkQEiM zzXyE(r&2p-fmx2)uHJKVo-ym6EF><8s}Pp}ZU2TSA%%S#?O!SrYDlp;SQC;y+ss- zwrSYnU^1qmxU08NNb z0T1~bxBdfJ+08p8TifXxdZ5mtSW^c zwYTrdSSllxG{$s%(rtR2$ys)xLxnAOg{{~H6YNL4k}AcYY#s=+;?j;-R3~SY-W|4q zp_cY4NnFS!LR58ihDSzhIUaX+l4TVZl1jxTJ2&cusaHm~ko z%HZ=)07OvM^$FG_mPXV0NPRbB6fv9&$M2@1d1VZiuZ z@#ln>-mW*_=Wv-g_CKU6kwSbV?QK0NGb^B)%^tMs<1ReC~nSt9_iDt_@rdwp)q4OC+IB#J=1L>s?CxsZhcY zPYR=@i_QM|Y_|;hiU(dn0u<*IOoN2!3aBd*{&pVp0qW}YGjT)EmB0pF8yGR@(9kIV zr$4+`1ij(VVDJ^#J?3>g3zw+q^}jK_QdCPJ#pBh6(=zX1V0G-$=GgsQpzT?H_w3vA$fu0Kq7!$o8UIG|N$Zc^_4ykj zW1YQmQT7XBLi4ezM(lpU9|NIs%q=9#Q(@5Giz}Dcy*bAEsY5~60cOVdG@bjCPc(4j zWi7k3IR~IRtyz$8V1)dAE`*qjSUYWaKLHo+#nwARj z{FU*KH#u>aQVapi`1Tb4T*lU!wqHiq-qePz+V$J2xMOD3bO%w%x7D@guN7u5Roo4R z@Aj68|DM6le{;j@+G|PU2(rG_u1BbdCpwqWpX;3kkjQOGChsvr}-%S(w z-$5ll)AAYdU8H#berv^Q-}62WI`YSp2Ip3VwC%Cjd|NcF$aSv5r~Pg&X&Om4W3Ak~ zvTvz~j0g{ndiX1?>6sxG&0R9Hhj|hvg)gb0Ar_t4+GNDU2&_2iJh~65)Pqza|HJp? z;=e!e7puO3ZSuO3RpO|YgY0(fY?#V-F50S83Hk)`UnHT=XTfvi=U{60D{p?w%6Qo5clIvB`KJR zVDI$Aabu%KNkmBSX8_@U5dR4eyx+LPiKIiO>-OT000O7!x~@g z%xJR`RbAfhSd)-}8bnffy@9#VIUP!6%T7?`OW?Qh6V`{NEA9-n8RbTsq#2+l}CqTvg z2JghuZaZClL@t&>fS(0BZ1*OLYAUSz~yLIk*NzzG*g-3m>*t-9hIjj=dHUqf@5aR!zV}!;sdw(M~@<634^H4Ey^t8`usiKxKjC{ z#k8So&p7+Ocg-KZbWorA&`g&ejfDmMn}11gTjk`+jgpNmY7dx=YJ1L-6B0Dyct?U6 zzX1QZtQU{X%KWXazXH!b=spv4z0(vGGlb&|2C9mR1&6Bz>x#Bqxs&tqOk|xkb6J|d zz1Y4<+D;b$1lyG*O{I;=$a$Z2JZ897c|A!n`8jj7xGh+GJwdiyKR!2ea}Mfr<@ zx04^kW8@8CfyA{VX%CN#`ER}!ceh(uHs)W@qdl0!#`?|F69JmIFj_D+nzj~xsAu3f zP+YK-ulS%6@@S6=<2}TCt5v2CPQ_8k_Bl&Qse^u_b1-vE$9_I(R6k`*r3jf377YVc z|9KjsWp(9LqqppZd;K^yi^D~-*~(K z^H$2d()j!Vc&xhmxI)^Kqc_4@?CHFtTMIWK@emll>FOp;4$wExYZ2U&JZ*X2rS&*9 zKAjjr{=n2G3@R^*-S>X5KsD^w zUllhql_d5T-H zvRA43=LDf|pNwl4AE&fpLI2Te2bH1EDLr9fkt6E?voUSYaZoUBd6+{UTH7w5NzV9@k28!;oVbTxwZP$7H<@%NSN6DtCM$4f(EsYstgjXy7s&rSqDFwHk>^w7`U3XJ$dMH;*L6v=$g-F^nrE%`e_%5wg7x8FyWUJTiA$P9J{U2$i z8btx4y5PYEK%pinAHPiujKB2St55D#)RGCJ=)50s;d?p8>mCV8{5!LvZcW8s&1>wZ z{eBa0w(6+OKP;A$1)<1IHS@sTL((j--CBee^Kw?%{M64GLyh4-xq6!NtdA$kYeD}- zF*)t{R67Vrm_#EEa=V?Dk|}Dii>kqy`;5!o!#x>F$k1e6rp)UX9A@mcc#NtlEw8TZ zEgUWsrP|4Dy;uV6V#cW6=%|Q^mSd;J0@u@=$u`>(^P_wTKXxv>z=9D9suQmWvEpOG z$_`DB!iwpscsQx3vwn*{hp>T$XG4Q#t|lmpG0K-sS8P1n4v7Ft=I_r|iSk)N(!fM| z9|9utAN1(}B;5FqehR5avP_9claWW+DLD!8JF!P(zngjQqs*gf@5}1G$mGPuW!SP60X*1BjA814iAD#Sn}Tc&GlcrN&n_BMVaIupHS=*(H9PXWd+n z+T~^4b##NAV{TyGBKw~XUF)lWAldR7!$3OZB|rNe5@MCGAgknExVGrv--2fJuk#iD ztm8m+QPBRo)9mg0{P(uVjkQ=ETt@o*+l?fb45>|U92N(=fJ5llvm8gy#(>$tYc#1~ z5d_v;b^R7}JhGbU{iyKn|22_k@W9sA(^%BBG+`#fbqKf;{&~#Mwp+t&x?;Z9+2A>- zyGqEyzX9zdYBu)h(6FY%AO`8TVs)|C#$~Wz*JERH7UNIrCxMW*kpCugiL~E1I~!Hq ze&-mzs~#l7)SiCX4wU~+mx^8*@!EZF%VnB-g$e&vL;iPX5R)wcp{$(6iuubGqsAi= zZag9qlg*mL^a8sEhf&qZ`jv(Bakh{E>0C>lnq~8M1EkA7gdoI%-@Nng;++v1d2S2ujpQB!Ig-GiSw`oGX2tVg%|)PL0IbQDLJu@4 zzTEO*q5_Xf{DUeB(W2=Cy(z~A$K6evC7mSN1d*eqX|#OSZ_c8pF9DQJf+HE~eH0P2 zS`#&YvDgJ#RN}2-bg7q{%FzU}Q}bH0e}9etku7p=tM`KI+C{-Do5|Iw&8Um4)O@(9=~O=m@qbSp_YGa zsU7jWuy^P>g6v<=P)Lb=Qc$0G{^=n#T|ykZs7r>K;dw6A{2%5l{=d$Ws_l3=NXbV) z>7OVNl`s_Z{(?v<)+7K5Pn9$~*_YFLaV8C!YlWKcNzgB=%itE@zWw&gs`K4<-2HHR zV2m^&9R3ePp4g!c?gDqCuWKfE-a2;*T68QVdyOxffeMXZy@?M9&i14#trRHdeYEpC z&Q8xts_*_Fm}?Yb6J-dw+k%w%Jwx`I&9Zt@PD!IgTLcY;j3dKfU1ZVS^6X`wpaMI4 zK@1mpFL)e)D$9Ot8%I;Ej1|~Dhr&M{)Yi*|_rUh%0T3t=Dd;@{dzZ7b9T?*D9QYCw z5h+o7;agv@zV{$~_FoQM?GPdC>}{r-_Mc^y-II8zm`oFb7UdJkmjlCabvKbV4u1Pl z%~HZg_ni%a5Mt=@X%v(pu;o_2L7@j)`0^P^K2)ba*4A|!+jaDHL9sNRiSR4Sxl+#k4NOw>>kKit)Nj^T6x9l}2(lU-{Y;bVqR^czxC#In6@$w2DzMN2U z1cAYV8yzv_W)J~a&!$zdH=tuQm+J8K+Q~&udWtEu)=JMo9oQ}}NRBFRaN*sZeBkvK z2bezAo4CG{0E-lr^3c$rKgRs#f8azE5P_4{Q8s&ekc#uC@&WdifjXVf**p!L96>y7zdz&%@=HRofeHb)qic<1+k zBSJc0jad4US*owmJ0A6ainK0RfknzvWgqC z5Ezq*)N>gXs9#)05_GMizjYZdNT0*s%iS=p6Ee^!8vVrYFmGfqEz3OX^Xb({cONV_ z`xpxV+OpCdV8b%ffk3K~&7T$+Fnx5}1XG9{Y2OS~w`|>0P|`@?-Rv7&UsRaahHw>U z+2ZnS#z1T1wI#aOy{UM-cL`82z^JJDr_DSyN1r?t(w%bH^4cE2 zghk=)R7}idvbA=@_Q4t$s`_pk6eVXBeyTsN+1Qt{Bw4mpL#o9SF-G?CyX(SeiL=So z?y#T~O1A1B@c_Y>#O4_Dqe>sq7EZ$%oDGN*OSU^o_v}}dSh5tS%e6xZE#DXadA!CvOhiI#ouUa`$?VkH%g=GN`RUh&SAO{M-(?H6V3F?E>ZV{QjMvUS+Q@rplwS-~Q(&}Z z$X+f^@`~<>J+K)KdIFi9ocV+R$X7H zZ}L9vEZHf4cVt997qrj+}f5?z0hB3~N*a%u!6Qxxp--Ui9-4RUyb= z8}KEtrwhA(oluz_qXH^l4%N3Co0ISEfj0ta(e|%Z>?L`6TI`@s*o!Ip+Uy5GZrV4OChu@{Z>4Kk+OIu|-`IMJ2 zYc>nXGW!?rXTAQ78*{nIrjj5Z>kR(9Tba73srsF6#N5@#nrxlU??Qb^wFv!2la0(d zIRx-msFTpmi!KGw#!{^%L8k8QQCQv%TVwi8a*~(Dd`q=q_w#YVK^J}y@DL0B_@4{>Y^N0We2;Q5X8|Klv%~J5y0HOJ;e``w{Y4 zPZ+`A0htT1tc0Ob$4_0iZ@th})&C5oaS^{@1mP|nhKB*8ixO+k+F>u}*XP7}4ua?y z&el0iuRL*-I;gSnywh)4O{0_~-f4ABL_7c_3mr@Z4_Q=LNQ#m;Ln5OdCjDE$-wm7$ z*l#bj{S^WSzi>J`AtaTMTZRc<}@n`(ArSb5lj+)xo#yT$D_77BVDqRbXegA`HM`s+z*k zejEH-EKIvmd`M;)vOClg>rrOP1_R-{q2i2v)c>rK2~S_t-kzw z5uYJ-;}Krxi`_w5jp%4qB(nud&AtT#XoaC5Y0;K$C%?~spM&KU-II;oP`bfqqAfJ! z=@W5d7w^nFNt=Vd1`quD^G|vI1J!&CKRvJw8k~Q%4I~T)#7dHQe*-2CGiMq!@MrCT z-Opm>4Nq=5&yKyNQi6?wS$@^PAW8RzHVGK{L|P)n0rZ~Y{y@x&<@NTTOsQc0*+XyZ zGu1==`&l%y79FqYWr1msJ)5VXYfh}V=s3QJ{!-s}O2hfAJ3GN~((B98iWWzzU8V4t z2*whD`NBFd$RuOm~X#@0vu5#iE-+N2^ zo~AB_2CdcQR+Y3060UB5;2^TMtln#du5s7`HjG_c{TvW7!PuJyYTb{sJNEM`@s?Ij zd8-vfJrY> z@-R6*5xnxU&5DBgP{~j^@MpiDRAs&rL^curv>mI7lBP;hn#?SRPItOb)FW64ey3~M zJ*7d2U9(yjFs`(;{uYq246x{gk=(!!R; zZ&qKw0V%$g_1MyIA@Sz3@g<7yb`7A+F#0qBI=aCIOe!0`QBf866KH7sRQgx5fsio5 z7ci21B1yV1NqEOUQ4n~2_|Yq4&JVA9LE!rbXK{-CwupWRu$~YcRPUR>pK_X?be`Br z*qZhkSHpaRLByxT55IQw0 zPlM;Uv>!j6EQjtDrj_Z1>`r<`nlXI`IM7Pd-I=(GJNJ;u%eNmcW5@6XnMk7}tsOT$ z3uk29-)MWpGJ+1vRYg9-dBv4ja^W_%#3Wc?c8zF=QA1Y*)LK4=Q#6MF`$Ou-`l!7 zC=F`(jx8RV9^R&(^|+1y`k0FQ|1qZo%A##t0#8}^K8{@v4^%~3+%T}WW!}dDFv>ou zaW#P97q=t${Tu|#wA#_p$2fq5amf{`6Hvf9JP8`Uu3+=SNY>JK&`#j8^d{ZNKb=HX zLJ2d2|1Avu#C%T$FS`R1AbV4VI1v8V8cly4-qyK)Xa6oA=pib>#~mkkb_0K&K_GAZ zy+}$LOdk7FPG3P(1{@?!_DQrI`k zi5Mmso^vJhOMmswGsD*lZ&kfF0Y_${)pmndM?n~GBmTq0^3A)wt4#lmvA2MYBMQ=W zV`gTEIp&ySW@ct)X680CGc!}n?3kIEnJMO&?d#pQPpiG}>FP?Oo}<=Gw_1IArmD_Y z|38pjP4UrtjHkISW7%|;qos!1=iF;kjo$l!!r=YIJKA(0rKl~)OFnR3COk9m85RC{ z^7OhQIjYEb@nEI^bXtt*XsNrI;;{77kVWb}jz8L%DX5ZQgA_C+`@R@PH$ryS8hzs{ zM#r|dafJ2lm#Yk0D>>v$YMuZ1Mp=!`*Q@b?eIh%h*_C<7ed<2^s}xQ{Rhf5OLYL$I z^mm)@-QL&sXsds?xZcT9-2WMlLAq?Q;!EiCWoU_@187)Zn_t-wMPW++8KjuG>F}h! z+KH2hh@MQD9PzbeK-*?&TDwm3W%Q+=CN3r0`kHdSL16op_5~=AQdgc!j#nBQk=SL` zBO6sMmYFcAmM$**YNpj<_fjO-VMpntAfitGXKMc6(eD3m^IU?WZqPzDz%DPoRBMX8(bD{AKdevn zt}A8wDQ?FlbSyWreK>PUA3%rX@gIfrFGlkVJ^JsJaX;exmIFTQu_A>}LG;1)Ay=g1 z$tm%#(vV_0emiIY5erMEC;n8>N?B_fbdY@JsNva#tW*pV+ewJkFja9jrdG>jd}6vZ z@b#0#{N+nJ(J0)d%0t^BP1VG;=@NZu!0F;C$z5Fbr)~4_sD2)8k@`^nZ#~Np){j!l zm=x9KjCR&bTHLK%L0PQj4cnv6KVMj0`p;%Wr^li1q2KAI*JQkSve;ItJ2ctpyM6R- za{7y*^V>`QWkX3;@l3g7KE8r8uE=yy16g#_$9h-ITv;V_G}CYtH0NI$9GeDzKTKbD ziGAliU=ARN8w*MtKBD?O=HaS&zC31DK30i>q98XnEcQ!crX#~y9J3z3(2g&4@2b(l zeuNi9C%0NHHx*>T$nlCVDn1F8L$GX5dcX0e&{XbwKf{Wp6L^PoVx4pJ&Dzh7=uI;U9l!bE09=SaZovMX>*k7JmxkUeQw&4C#TZ^m4 zm5cM{3p+Lq?|`qrLu|!}DaXv9q(v^3LLq6qtt%ioPa{^~qyZmV|M zy?~(|$3}ROjg&zTZyHvJ%S5?Y(!s#AlKX;5lrr1O2bDGEjag16be2}i>{B!6%aVI- zH&sv!X3UBPCbPKL4v)SVnMO;MmdP50%1+%}PjfeoFVyKQbG_w?3!pPfN5PSpCx<`g zT6Ib5*nTq?vQ*5HEKg%()GM8@T5U}=!H(8!GTdcp(*}L^ZF}qXXYFv>k11HM$=zPu zyjxrXV@jvxoNINT5;kSulI^|RU9#DdqkMP{`Z5!)=hDe^BaE-Bk4k#dP!dHt)U&hr zDg!Kht^CD5QA~v10(fh3yaM*@8|Vr_wYsG#Qq=#Wak0`IR|`z2Hb8}Mb=8{*C@`K4|8O{g~xk@;Is+*e8G z|A>wJR~R7le?gf1cf|0&vncNX&V#4Hf7yvKrSfxu8^baaF%a1qTEcR3r^5SyVZ(p* z@|xHh|HmFC_SASoFiL=liH-4pwD?YHjN59np=ST;4!{Ge4*sp~?PM_=Gn64PgapFO z9~VqI8Ua5_zgmB1^3ugYyS46<=~8VA)qqVLumOMWa;982!t3qm>*eA3aeMOBS(VJW z;mz5lP_(`GD2Vrt*{%2Ml=MxPGEi=rI$>~|2KdN~80?;SdPFMTDvQnFMxYvst8&rk zKy7O9xMn;;4tOZ4mJ(XXoqFhg;PQEbuCUAd{OQ{*c&BhYDTe8`ScW4~8GNDfaN5M{ zE&4L*^)A>ov!WG}U*ykS+V&GM@U;Pw+bArn2H)Gv5`~{}H0@ZTUd$u3lPArz`&G2Ix zz>-TUkPAW@gw%t_(7(AAw_CTS98!lok9RC`taW|yE?%HHc*EwTTq$AN5voNu{H4Z6 zw0TK!Du=bbUEb{47u)4h5xY}1i-fRe4g4)ZrlBldKD!C$d`oB9S)#B@Yo;)he6LXZ zjY!1MdZ3-ZBQUsl2x276xT<{C_sgyh=i&iV{&X?4{l{}!Ju`<^Q~R08l*Uk1>iwC! zmZ`<~rGKAffptMRN|A;IX1m>POp~x!+t=NOvsU0TmOTUY2=)A-Ci3MW@|==e0iYtu z={Q3isK0lOflE@Rs-xAt6zjP!K~Xw$S8z>$lL>#UW`!!i;t0b1`C}|yMT}-R+WV8^ zHA^i3ii#9R>;6nH3&~7M|4KdDO(-8bf^jq$0{LIBCQOyvXjocqLqc1_oDWU`|0_)8 z=#Z9amQK$nUuT>k1F?K@wLRj~Cr~0AvpA3T0NO^vLqx&itHGCSq3Ebx28~<&2B~}9xq;`7wLl5Ql(PswRTz5!wU%uDe$G0!T@;mB_e*yAdbKZmLZ)=19X_aHDhyBny|6`>Z9 zZ#B|TQaoxyfinxtOxFe$wfXU%TwIGwvgDsRoIjq81abue(^u?VP$;lC;fnKxof*nx za$r$J*KGvdAypLTQe@^*%haf zxUmu7el98}(mzT<<3W?f20S{xD0y*%rNM;G$uZ9lQxx@FsV7J+h1SO$oaw}K7JQs+ zejUyXf8JXp&C_uPSAHVc+a_9YluC>RuAMQ$`X9Tp;fS`#VjI3UBJdN$ar!mn%73GEl`%Ni6!;1jsFp!khs=E@8LwFZB(n`JoTu%Ho56`B zKUFUV2WssG>_?hI+Io@iNw14xpaJ< z?eFEtSyH<1W=OUMA+SRFZPWxLO1dzNUtY2?wPUOH+E}2xAPT04)j&IiNe=EiM-X!B z8}i1(A7QPCJJ;V4Dg2kSjH!9^9Tf*97!T6o`i~yI6XAhFCB&<%jEMt`P7|VV@kpz? zW-KURIC9>16)LLJ&PJ21`wDm>PP8zjk1PpKelS?M=Y6?dVSp(!$DyJ8%zT&W-51E{ z_WoSNih&y!ax{{$$k-u|ZyQ?qJ3C^T8w6d;P}ci?u{nr?H&N%Y-9x=gO03!v_Q=j# z6%~rSk6W^CUn#R^AG+lFv;wN;aaZD1nvU%xZF!w_Od5=8o)`*@oQDc36S_{nW~ zM5$(hyYHd;_}||Z#*oQ2%B&-QH@gg4d8MQix@f(Zh)Zx`X%dcVg^zEMuF2dt#lE8N z{jLr<_AQUDoE!Y1_8iLcHW>38n+LerrGo~^jLb8NuNeEkPokuK%>`{1Cko_QqY^~GRS7fVyS5RZ+5WQb z=r&P-24I}co@7G0&k4-AatS%~X&O2D1bQyhZ%8kzXI*hXRu)Y_`8R26^eEbZ-FbAX z8x#m1^^Ooorq2oSe(wy=6ODqKf~!OYc{1?-sm)LlgdMa>;0G}So@z`hWpSluY;iFl zytGZwc@&8NF4*;+h$U(F9GCZ!W~F#ZYonVl51jSu`uL&o-*qx!#n;geuK(7iy%x1G zt3`-ycq03mLZZq=g8BE6nxM&S9|7`GBSDc>xvE^|L?iY*r)jHtWDg|>ZIILiT$bzg znh(+04-vb!PQ;Zk?t_g3b5>lH{#wfQJcg>?{o}*|eNyw%a;czD{J>*qjS{2jc3IQ^ z8qf(K13SX<3%cns?q8$?dK44qq0jSq_QUU{)S7sXxKix?Kx)iZKO zF9@S?i%=gi0%g;3*Q8EMvUc1$8Uk%&Ue5-*wA_qw!3;WSB6xTfWdrAEK6kc2|_%G{#AeB6fnQS zb@?*|F-b~CWKYAuWh=sq_A+PoWR15JNnp}M`e~P;2ZC|XFb;=VFnyO3(iEa(zX zJ*tm?6P8w|{GGn9Zm0YwM9?pAh5rnf;MrOKD^AiP(n+o30>?;I9|y&PL*I>QA{4g6wc`cFI7{}x5jIZycB;^4YR#&<+@6g$?TvPJwCn=H8^4E1NE zDXnCIk(x19q?>}aZX>$OXyna9z&M8)wX%AXHi7+&IZHdAGaIgxt@qo( z>v3$3_s!_!O(A_K-f=EL*Y|@bnKuvCh?~r(JD{I(SZ;5+L0~|R9*RQc%_jqR&D-l9 zIC@;}zjl6dT`;(HdRvcHtsG5Lz~R#PmBWMc$^8d+(Va{2mHgw4A;Q9S43DNS>*IS= z(iRRq6eEO*%$FG-hwsg3t8s1APGk0c6^YbRr9`<-|0#nvhc|CM2TI~}C-~RX!<#f4 z4CJCQ$5R;`Yoz;3ti<_BxJ^yg97?V@7@!*TPBnjxX77v#0lim)L@Oa}XC`D0_Al3>1Wt41%!8n9n__kYkwr{dAcgDkBa?D9vUfl3{(PEz- z_%Iwlk131DLJF6M%zeW5Tqa=&7sZF zR7XdjZ{*#~->ewx!g0XZVNZx{@kI&`i60DIP5k5Y$$GiC{1?w&rrK-zXQ)u`GG`kF zPT@$@j%$EGVPpx<$h0yc&j04P5#-1-qey{|@u(@O!M=|JC)Yi5S<$OHu#ueSwtyC3 z0m=ih=%R(nvgZ8rcRz3dowViQe!Q1@o@(6sS?@VSa-0SN4w-zs1}j?>p@WGy z9ce6hD?b*|&9Heotc64E_wS4hwfWF7kVQ>4Ed`e<4+Y`HaPLj2rIZBr;it3wn#!sQ zAD9U|H#Z8U#9Z-KZBros4_n%h73^6DqiU6#?6bLYw>(Q8uQO620W|C90C zuM}+QLqMn;h{vq~xdbDV&qJ>-hod0wo;Yc(} z@d{n{nnk@7U279-ZT(8lu8wnp{S}EM&(=ASK^7f@n#?&0?3t7(w#@V(&Q`J{Q z!!wsp*9-OSDc=h4U1if1X)r&q_avh(8tmuj)!!` zVfwSR^Gl%7_Y@ZF2lK|p@EYQA=g@ApqLg`yv^a{C_prk|NJt*E%s^>?bbV8`H$EY? z==UX>Pi!Uu+K{o-Ddkm+VNwv)6TAZOD-Y2RF6gpwA-{&pKgR8`x0*o<`lhVDz5#oGK##tIT))6i zu3bjwd8?rrm=i5?OypUzk^I5upTncQJMD~6fM#X~X)hvOh(#Tf4f~TgO5a&bD=O$yL8pn z1VzMw>v%6U{c%ztm6z`eeR#`*n`-+oST+*d9-0N?z$FDCp3JWEuFv03tn-GdW#Lz2 zpn+_^CCd>;6dxT)s(Z_(Yrpll;M#}RTmED^8LQQeque2Z(h-1$&xLQsHs8rRpH>IT zepyh7R=I(s(EV6Ce=@erDS8FGwZiRa;o9xh$F!5k!D}40o(#%W%UStf;BHo8@jh$~ zsAwV2(+rEk#E>$u!Esxa=mS~7Oh3_j-M--NR!|c79w!s1zoSP2KB^0%v7HGL8jep)_+%7DTi7!fq%tT|)l=9!P(Pw%$dieg zlM%cqNHO_;e<8{h&dz3q3@&soKZ0@@L&jXyV(*=uzEO5Y#EyCl#LL1p(rQA^ISO)h zO;D@c%n?b2d#AoCZ}5i3`(FU(7lf2JQD`?`4pIa&yzIot<2Z+yBF`y~uXGKL^7ah< zEmDU^DW+FGj;#|37sw96>6WiZ)tu((XWvK#Me2RM7;LiY8x+?jqeB#-FRT7FWC&3O z$<{Z`&gKR?a?)#n*5w_y91qVHPSJr7r zAF>i0l%b%jh%tYORq4cRE?zrM-pn+wPQVGnI1XW=PSN56>jcwLkAyPGtr!VF_J^+0 zt9f%2#j$g_m_Jd3#kT}-MCMbRSOj-Tj}FZWKBzb>e(u$vMT@XPg0`CZR{<2(bS4iOX(v^09pNo>|Zlm zslDYmlA^Fh;he>$zcKQ2?35T;baT**9qthy9!7XRfTOm_pga@!8F^{6(w}1A-U<)D zpCw=LdKNnNr||7SrRm&##U;WEy@XCmdaoWhi&MjBTCplFq%iLhV?)a~%j-%o*GKS^Z*76hW?0ciy}_f0g+J?BCSchaHUCkNOTwKV~xHxDdheT&*RbGkwi0FgMi^D3nWgiSNShfFzFPq8C<1fl$Sg1l) zC2V0|XWs!#N%5%m*Gx$_H_g8OZ&jw9xKzoL0<`Lcw+5$%C9c;*M4%}tzgwLB=~16jJhZNH#L;yIDdS^V zUs4=+q}tdAUmw=^hXuR4%QhHVfyjy5P{P@thFTk03!}__9;+VA5Z)!PS}VORzuS}- z9s}MI_)RdwuX7yV)|Ha9k0ntuHe*$h){&GIkD-EP)k|Cpn6cwH*bctPb$ z71M~LaXm|xLGW%Iq`LWn3ZxT1gqunpXeSRa)iEk4>(ihSt^|cDz`gE`NjeTy_uSPsO^PbAZuL)--iAK^*HgS^cT~S`#=47tl_OWOPZ)Z7tm%3w-tV zvx{R#e{+2c@kN$~g4^iq>;&@;eXMUC_f$# zBAE@JXxWL$?&4%o+(D3v<0DS3=r;f(9jDQwK&Je_#kyM4=nYgO!O*F#fZU`rvo`is zin*CF-dV*aUJXQ+{`UQuJQxJI{uhyQE_d9|sGN{o*S1}mY7v!(sTtTZ2tAkBqIf%; z_U9Dc{&%Tcw-PIFk^*gKjKzGk_*il3?@H%n!|x^WkQ5qMSLTp}vcd$cG2{R=3j5VJ z5{3!6gI}RtN`7iRB2+1y_6gsN_QHo_$M@k+9OihY+#$667&uFn&GPty>qr?gcwp(gT~?9)GRyI;CoF!?iVV5z-WD*0t5%I*b|!5m z;Ljmrg4hI~?;A87FusPaK_%tshh}mxmsLbyE)a3EjeB(WjO z@3G5|41n!r1RFyb-T^fC3Z3;S^&yRe+58+%ysW0e^v$QjnU5e9VqYA1=+Mltz|p*+ zY@wqT#@g=D)0{|>Xp?m#%ELckzbD^Ynf2qhR3=;Ljv4D8hI5?5u2af&)8|xPc3Z){ zX{-0E_JZP+0JsZ=1l}v%%lS~pZwMr~vKg%op3*iUV7GCkK|sY%dmjTGjvunL4g~<) z{gYYS_<~-5th8GjoMaZ7LoMilhByb?R)H+UMno|Z2U*{jW@ly1@Hx*ek5IIl1WFh9 zWN^z5X5(q4@fufVCXs7hl%ZGKS3B@tCxWSudFYkXDE7auq?e)wO}VaC5@&mMAVKrU zL;+&oJHbb1xB$SEOg!x-`Zlr7$cld$nNt8k`ts~kQ(XQnOW`i`WQa1e50KSU6hC+>iAo~ckWzQ?b>>ni z=gQarHjm?h;CJ5fyw>J$^Evn@3|BJwW!njiZXDnrgs$CoFEqC6HZDTOFWTghpwXSH;&-M4PuLK5(PI)-3iC=ny8NzP@HWq9rTpaptb@+Uk?{AX}cfgont%zrH{yca{ z04Yv!H^eUaepu!^#%gNX9Xt43EwHZ8(2rdkOAj)Fn4R-OA2Tw9da@+9625A^yP5^Q zi*LJ%P*h*Zzw7skUq4tHr+Ze-KYooL>Vu3`oJ&>T-E3W25;+Dk4Qr6-9-3kKzla21 z>$H9u@2*-=VYdU^&u1aFS4;;{)qrSh_zK~`}%{5 zK8w5Oa6zk&G>ZTCZ}%2hW7VGkRYw|XM*GsNr1wvGBe^ ze%O26CR}Zgd>BWEa@x>!#s&0+$*ZmayVfqKjc|Q(dQUqWbtMKlo)&{clNFV-3%pS* zG>GG~)ZWFl8+vTB^m5n+240AtyoY2xr@Xg&?%K)U^b}kWo&u3wTw?Aruy9yt;tTMK zvgBI4@BfM%G))F(G$WR_r^@P!M$SJ?hkiof(&CH%KVr| zI0#q2wjO-9H#ARx6EA~#7cM;3^=IT0a1QYnefasw4L=RV>d{}Nlpw_=*pF+H?kYPF zGBd{fchViksfS^i>GK`* z!zW)dBYut`22$YOIcu>uzo zfBvMRFoF%=5WnATTAAqAOETU2IVUdNQlAZ;%!dtW>))=ZVz`TMuA)*NjFtF17w$Lt zm2$pLrpWbeo}?}z*&nzjS|yySF}>Du+?^CT&%eFVq5jluYqSxRNEOs)f!1Zxba-`% zUFO1$C;g@Xp}e4sYHJ0`55LowwuBvzv1Aa-2J<5BGTd^c1n6K*%vyL>FX1E8e=w#L zzaHbH<>pizn>2Jt`O>lrxkvuSkD4Hz8q?z{jLFVLRG`?b6m^y1f<()f_|`!n+KCv2kOV6B*gu;$#@E}JVcSD`IkK}2Ewh#0Sz0h54mbU*g^=Okw0z@UH=DSNdw67QA)ZY zbVGlN)d(d3 zhPa0ydSoXF&B!(dX90};)RY>Qe_;{Cl6h2TP2zfvTG~ueB#&De;}3(6seAP^i@0^{NlU0Za=7ZKgdDYqMzrWnbCB7GD zFDPiHe+p7dsfARWQBEkkP!183pM#wO@5TogsBP)lBrO$M3$DhEabt!=Z4I36e2hYY zhwh{E-Ldm6K|28 zZBeH5PuV4$k(4?bmIPmnX_)0NixH&307#8BpNED^UGs@4 z(Puo7xRODgSxGe!)71r!8xxk()m0R*C%bec0rYlYRK;HF?}v-~iUhB>C1v;Q_nP~G){UWNOqI$Q12Khm+2;Ula_}SC;}WH`SA{opiB3z(E#{&JGmAB*;Gi#-cF0pB4sU1E0rxyb0$=a0dcdxPObE{H&|*j}NsG8#1EzzvDKG*8n5eqsidtPvF>B z!0&UaX^cD{CX1TQq9VXjtc4H1V??8Ue4;f;nR182X`HlF!9`Uy4}~JUrZK`F^1lvC&4;8J2%f-5*wjmj7f?EHrD#s<|ZLb11ST;9A&LV8wIch&Nw=L za3^8Zjv&q1gpM2=z*9BKZe^iexXM>4kc>(MM1@r9MtQ0G2pVJ3E$;GP)O}I*oXKN?zd-5t#fG$TD$mQ0VjFFWr-ch$;mKI zZNh6ja=AnFA&8aD;=;8-nYS^F?`3-da0&r)4Vmr}SYD|b`?QHQf`EAx$I3J-uji%> z_?zcXW#__cDelCo6@=9`{T&}Ex>`T-v&N+OsBmYI{=&#wGq;xOa`>hY2O3ILTHb0B zK@wk}`7X$c!WgT}aaQ$pwhT6}!u-^=#8=B6VM*fszT5u#2;t}1oX4c+8{?7#)b4x> zS)`Gy8jwgrAKIs+hFwFu39q{QGk9iOSDJ=+$#3*4Lwd7_z_s47pT{be5%8S)&l+!A zT+>?Kg`+vNe>kt*tq1qsrIjukgLd>$H+M-Cr>yU!Lhf9C>Bbv`!)?JPb`D zZM4aFSWWM;troy@P3*3Ot4)#vp>}=$O4Cq=nMMsvI?4T_`?BQRfg}1+9CY(Q|Du8v z2{KxgR{Mzs!vv6Z9E`_5^;rDCRT5KwpH$g6YI0+vyl!}Hgb{{ETD-J5#DHa8v&Xp% zwGsAE(6^jOzW(-Eso#wUQpTu?*yCycri_2{_4owu*@?>Ll-ojF#2x+tVw=!Q)6s@# zhT&GkAsm^1M^C|tL+2-1PK9&h>j@bq3Q%N0oiWl8z2&Q@kZk zir!lVDx^K4POC6N5pMHYqy-j-i z_1pGOE1r7abpuj4zSJ!7+I|Y6AWQi#ASNxFE&1PoG|u7v%nF$S7%uV35i$EK_O1QY z*YGE~;Oo81O10m|d;~95LlGcxdOr0se}=yaVi)~22>K3#SO>F3uiy7g=2K14D9{2O zBIh`xBx%Zt#(FE;&S4i64Ap}_@4!dfNFvP{URoENm9j>DCt|5kMv zD7Qf4%^Q3C&>podbStJB?=fMVV?WvkIAf*whVSWpKPNxUD4sG#L%g{@%P?FqyaI^`=JfnC3v#VPDlqppf!ri>nu z>3#ee!q$10)fHFb*4JD2ILIfnydTq8395a#gEMNgSXFxy*zV0m;2F}}_{JpA-2K$r zo8K{#ml7#xMn801;_}L>wELVuBgpF3{4ufdL-vq&RPk>y3SGgVK?Jg3=Zcw&rNt=R zMdK3_JVZl00V#RUW!SezkfgyD04|HHw%*7Ar)74@A%rZ09(8F7X-<_yZRoZKR_0gg zQWs_SrshfF*f)_k_hy`xrQK_Ca8pNOE`{Cu9 z%Uqp24T2_>0|_&AEc5y>C*h{CKgfAJ=**Hk#};cu*HCxZ8y?Xa)Hd$%qgP*A6Qul$ znk{Y~PoyPE-5Dae`1};(7AgQ?Y(u%D%=L-^wfGf6;o+MA5{>qwz4>L7`lf+v} zZqDK?NWQy$&bKxM(f`{b<1Kj@DqLR^?T1#B>Hs^R7LSn@mcxhuOGWj_r-a`%%;O z6(Zhz9Rb=F-m6)L5(Ae|7=gv}RNNUBb~|n*O^?+ZOijqmJbY(4hPppn+lc0Q&}%*v zh;I#;K{Iz0+rv%3p(1`pBQ93HQ`jNrk=Dx#j>jJme%nzP#c0_o=KgHKuyWo z_4Z34$%USY6Oa^_Yt@j{C7o*wrz{qPEC{ERDH@%@@;QrUx(Q-#aCb{0&pxP8J_X%j z!1^=j;abv<6e~tUj<^QdZ7l^w80gp!fIeNU(2*-aPQqcTma_2RDtHRd8hb_LIopjb z6J+u{Aab8L%ZPNwn6>aahICwF`!HTkvA8<{UU1*(SN11Ngk%S3{yoo`(g~Y3d}n^+7!5uNk5M69Ngvy?pt^_eYD{X_;e~6h)7f4F>Vilt zdj*~?SUL?MQ)b5@wZ5@*+OO1E>M~JFR$VpQu5g<*jCVs`=)u)>pUF$h&am0e{N`9d z9f&UbCjli^R(l|CJZ-+6o>RT*k0^XE1)5RC;#Q>u9gmbhMMhrg&VFWr#I~-iEyTL} zuPE~ry2DQ#ZZ*`N>#mHB%L~iqOLyFhOR6Ao*9Ep@ z3I}o_(unV-u<$1dLwS0{wHdoaXOAARSx~5ha6_@{R{WyF5EyQT=Da8)vtC5GN)nqZ zPe{57WAReo6h6{PM8Y%|1MOglhj$ZK{wGVesNq^t%Eg~-f5!w7!>a0d3N&H=Rd(&x zvVGcD<&>E_jQvgYskbQ5T*-L?QQ%r(vS(x0jBjQp+i|{eqZ;?KnCQN`xPIM~sjU{F zw&|tlMkX#e!`xz*-P_sU;{tBNO}&P$NDA-Q;P>*)!<2@Ls@1Kp9w;31@;?GCXD37L z3W>18RNCxJ_${wNFin_OIskiQCvqw;ci5w*y&p`sHtd!<=Z9^|!}_`r8LTgxhRdIH zwT$vEk?4IeNuyxt7~q2c(9JjLXT5gSB7iA%)!pvAgY7O2D60JfQ~W&o1{! z-(O?br&yEsEz8XSkieuXlbtv}~tUTbVO=3$G@UZ%&THcl6r%kSf5cXuwD z=k$92a&zuEJDOloeL3_)WBQ!?-VV-W2$m})g(TZA(us3xa6Wan2RJp=#@c_?cs8@b zf8N<_2RA#yrrz6@UjqVsPfFImYo6=vc+WX>{6a8(M4jt~V0$!Kjz<1@-gReR;Sg8H zu#?MaAY&^Z)FFbAfI?cvT8{kSzMBA54c7ia#g&!e(ibXe#hDFl1ztlWLD><;&_wg2 z8UZegJr@rHQHA;!h3}d;_J|gZ)OQWa@>au*1336rc(&iYk`h4bV#eMQBjoM+dLTR4 zcT9B>e}OHa{ZQCB#Gi*+$_$b?wk|F`2<}Fyw>&w;oL0tF6zW%~_c7V_0R$EDweFcr zHKfu4jksML%mFX-YoYq$M@hIJXT{%9 zQH3pt>z2m*|4{=8&x(52x|bp=mDypDCrOg5K4q%t`Dg}1750aWJ>TVECOCh z7t!=Pg-i0sW(7LKoPFs4YBJ5nVTOEfsFTPD@ zjkB5mt)S6L@UUMX#VhjMi4aiyEf6#4CEj5vsaB6|k=*n9>ki_Ilgy$upF@e@MR><4($55us+X&y_t7BV6T z(X;>wwdx+|h=NL*3!YN(z-~j7?oE4oVI-gfY>626?{^2J1G|x}D-^aBW39Xe1;S_qQwOVTX6`sx!5!)LdRjHAS7$QSP6ZgQ zzzPnGcOi@n6%zMn`>OwX<2Q2U5tU=O<6%o(L3wGBTi^Z_ehp<+^8B~9&FIyL{JEgc zTMoEWBJYY=9`X@wylzLr>Kqlu<`W7iB!l20y3JdzWZoVcFz64ixf~mpN%=S+xp&c} zwBo2N)wR)<>2p+E=He}yM>Ng9&II&N`Js(=i$YR%H#S!V{`PU}t=y&!LfC<$-H^9Ya}1H%oKe~0qr_Z*<#ZtC}&d;+Jw>$6e={D4{?2&qJ! zE|Z*2G&@yg651FdD>^osW~4oign!BIKH^U#ImWbaLvrmr1CHvtoPWn-%gU~~S|FD^ z4E+b1pJHc-p1o1oVeWR1y__cNRG54Zf3hx6(Kc}+8B8PPrtub=GB%YGefLa_!NzMKLj?q^(AD^^ct#-bqFjD9%y=J}dfb!K>D zL$8u_up0L}~_4#S{-Rm7|+tuw@E|s2Z>r))axv%<+|LFYd z0pxygp90uEo?AF{Cs&nEEe`=7gG1f9uA4}kd|p&gMxMm?`XaxRaih*L9s_^9PwszA zzqd=ib?u6JesJZJh&Eha4%;C0nA zbL3vlH51F?46FrNVs)l=j(aO()Fp&?3DGUD2O_)^t<436&Se6buPeRn3+!74C&FL} z=@X;6PvykN6ki1xB-W1=bHtperVAUA`qIo4X@}qm$5$Fy*J2AX8DMY`C|+j z4yeVY9bf37)jfu~43G{6`RjecN91W^3l?mS zQeh*#h)d=8bKK5aX)L=85oWl&NSm1C#HT2yObEH5wlIl#$azBYS&WlHEx{62u)0j4 zEYbM>VPf1=r24!>_DSFDr5N0M$ejg;AA1=k5$6th_1WqJ`$LjUf^1F#ahHt^0Iu(k z2o~bv3Z~+BXqz7t1Y34`9j_FJ8&%!?=7A{f24F2kH>uMdURax(+jmF>J;uO%V;>)-^Wb zB}pV5Ic$`6=gbt`cuJ#InWeHY40_Zhk4J@zFQ*P1^Z51;d$y+@^ARy{eh6pY@9%X| zxTA-+K`VrG4y?-TWi@VS0$8xuc{4@GQP=|3(Acnj?G33nIHML}uqYy(@5H?OKJwJX zJ~DUqg9l$|TLnJDWJWQ*h@4f{W|d}EgeRy7plE>3JH6${-disP z2b}p6Bs{RtJnreGM>pKcf=bY$TV`(Um~%0y{HbFc zv&=}iB6F3>_ip-p^NYK4MQW(0>Gibstz7qim31ZHRBdgT6q!pDu1bb7guVAUdpISx z6h#P`DkO9ABT3^8aW%i`H}h%zqYk#{NLT1^<7lF zKGqI)=p2<*SSoTSTaTT4&2v(j>}o){|HrK;<5{NDsK_~@^O8|1TpV^c^!;9AqtKyZ z0^O~o9T6;9RY-|Flu6+_#j-MNU6Zj;epTcf*Y&2^ui^{1O)?ipCD~jnXbslo;;!@0 z?0I*@L^PTEVT$h#6P0336IXW?i9^Lr!Y!*agjp|ncm}?h2Z3*5Pb8c=ytPKvQk5z_ z!aCSxd*6EUme)tEaI5@*_?&-g|Lz#v!>^~BU&=D_jl6zOS=#h72fvwybz#ER;Y7j& zFV0k1J5n7`E;A@9df#VwtS+Odeba5R`s}|{E-LInT09L7B$T!nvWDh`8Bw^|kon|tOzi7pn*JYOzyP(DP->a0?F zV7nf-n1%vl(rIH>_S3ITs9!?&)==QX4o#G-~XqLZ03k8|2$~P*O>(HmLZ#qs(?s z?T(m&!#3~Q=#ROkL$traB7xaHmKO*-etiz5s||_(Ks5d z_0@0RJf|*`>i*9{1#G_9i!ZI-SDxG$q0i+oY+$`@-pFaejJ4)N`|qB&GN-;>y{B3IDOo!c$HmCu3eS7>Udm$Mpu8VZpzWReec7ZCTy$`PoABKNOgQm zukoj*`Yyh!8mVQ+!Qa3zFG}P}%&PvJ{(LU+yf~rzVwvXymToN$_VNL~bOe;v7j_*^ z%F}E#e%AR#yC3{9ExUlP+_CfT?VUAm z^K7C?u=ZT_ZP~_%;b5+)s)J8ls=x_VC)M>JM>?aYJt#$R@vD^_w~{mkRNl_H_F=t& zw)agJyTE=;l#469u)oZ({iPE_>)W&aZ#kblURhX?7`|(%&BDbeq&3g3N?0|N_4Tfr zxr4!n{~7Mee(_$n=UxqRfSveX`k8%+%Kx>l+2*x6^T4Rg#Wgz3_eShXeLRb|FP?B~ zdYt;cPQRCr{i-lyrNNpy!3{g!MBNAf6Q{$ZgM(MHT<|~Rj?YI-o2EYbR(LKJ(cA6Q zyN64zqF?&>9)Ek!O+~Ez+ALat*gDt{I>A@UeQGgxj^8F59A;Z}x_Gn9Y^fv^{q($UW+Y0rp=J$7_HU)I&_u+nne5j7O^3lE=ezWe?|XW52%NeIT>I;q-(s@9V*7-R7Y!a9|EH!O-a>QO(K4_;86 zp{(cJ<;_!UeTdt6a+PXCpw`$X8ILd3`KRr4H-s(_oBSp(WZ|i?eI(%y$IzCvady;4 z`=YjEMQM($@&~vYmuAUSyp&k3=`zL?5|`a=wX4^H^B;zBJQ^QS^}x^m)@HHm2X-l4 z7FJ0s-z@xI;@jNJICoL&%3JH-rJX-lHQtij+?!kHSrFMXPhiD;+*+>Nk_xbo+v0vk zbvRmfSo?%uXKGv8u9sxE3hR8j`uKwpwx^iI$25PrH1V(m^-1rDzJJ8Os&D%{=4-5@ z?}-(CpVhiklEM;i$JP({$z`533V6?{`>Elf?y~c|{~Zf^6x5IPg@lhe zl6i5lzAp-@HkCK2+(;R?neP#zd4~7E84kbafzk6f*Sh-5%}MZ2jOw-Y*RsAg5HjnI zf=&C1-t{7>9u|+vq+S#y8U|Gb^;-lL1&Xp8bS-Wc2`HPp^{6QbU}{!H4k{z(D>JWz zi%H-)9$yoau!hhsX(y^h=30btQ5b)amaD$`e5q@*61n+(xP9j?pOh>3==$D+m)FNESrC~PzA-A&e`0mvAst1IH*Jk`XYrOidUbu|<8g6E%=JWaNs)iOsr`#Z z7<)rhk#5bCF=36oWlPipO~aB8?;O~4MX7CQdG1<=Wrm?Vk7JepE6qxjEz#bW>T@l9 zR>`s+M#&?I_%zW533tV)J4q9fsWnj=$B&Y|vKd)J={=#Jj)aa@O%!Z7I5@Nq@vc_f zCwcV6$^G5QABS%zHa`@3_3+qZ{-t{+#doaSJ;Hun`Xu(6Nd4FHRx2)nv3dFmX(Eno z$u%vfYi8@2%XjMv>`Z-XSy*0(miHV=mJ2_#%_v%WS+Y*KJU#(dkqe;oT9S#Gk>Faj=P3qvAq+X@FW}S$3TOe>wAr zS^M)HwJRbwd@IgeAV1q{R`t{FtKm1%HhuZrQ>$M`7jMaM>#coOwynKNLcy%KA8kAN zIN8MDS#D)uSg(stRoMpx?t0lQ(ID=>`S!=5Rx<3W*}E0YMI#<5WTnVl)4X7H_TdH=$(c(Mfd&Zxot7u0q)-_c)C3*Quw47jdxy6lV3tNKEr(7C5&eFy7@{b?v zSKOwHv1j*LxrOtY@UGo0!a7of;^URXN0r8r^Cbp&Hx7 zC#E|Q8GJ6~_@H&LP)AzE3Vi{jG+gufv%6<{&v~2p@TdN-H~|gr47)u4({nG|b6YQ8 zY+pGcBy_z@qQauOcb}B{D6e{uhpO?y%o97R4w(nC%l_*0@DJS3`Oke5j{Of~lT4(& z=SD4SAR;gNh*ErN+<}(&qQh}(ba|;m3x4^;PIKbQb>|o(*TF1f@M2+Y-KVj3ZdjG& z*ZREkmAWT-YcT7Vi<$2m!c)tt8m}vxD=J2&a>soM@$?p)`rN!q%4DsPQMU13mnzd) z<=)TO*=OEScsTprl8szWVTayUbl-Zgmt*5)k1vtRTDOnwC|+Rv@xwR2j^jHI_NX@& zERT&|p0+k4#W>P7*XrpD-(3X>0wG&^OVynQlr=uDtg4%}Xszwq_w6^{*~w`;te5}vNx|f` zlEWOAMO^Ql3`2KvncKX1yRIv&HQ`Q0(5c93!>+$v7TT3tAS$C?PgMLT%L{85@}UXy z3b2hdooR;3y-0@0v26C7h8jO3EJ`71QMWrk zt5ej*%0`M}$vF>N+b@5Ws3slWW%G$?nb-ZiYtyKT$3nfiw{F?nAK$vRJyTaw+2FF| za{M}{p|rG_Yu>v62b2Hp%a#{8JzGU&V#%$HlGmtAqVUx@O-EF60&dT0|2NXe^?{At zUpdbM_U@9Ae#s8DnWHN3VGg&;jRW#W$|ME5g#6kv1x;UnLUlNDW{>js&T(G1)oemQ zn8%!_y~bRz&pag8FC+=So|{=>CHQYtV4|cs0)(j@dFI<*y8f)dLcOZICgJy5amoF?xxmS$%TOQYQ-5eOk7# zgrOg_EVZewkPV?Mq5eRu=`utL^keg>;Z{!w#RPaV| zy@jQ)n7&53G;chotagOq+l#jAcRmU})0%VpT$Qi<31;kFRBKpUXr4 z+F9cw2DJsUy`rUz>@wv~&g=)TxZnDR>x~GczTL@Rd5!O_N3&qCR5^|w;^UmK)Z7^HCjc7v~IYN(e-s!mQZhXb~t&&yH5&43E!i<#c!&|l= zpu{EcXU{_g$Ag_MQ-xJZ(iN`^H05Rn%TlLz3yyk~6*L$6xO9ft#u==)Ti7VBTrQh= z*!3~9Tr1i6Zf@XZ&Xt;+%L3{e6Ww=L3vTEOtR zNMkG_+V;e3Z8jo}OnphaN%MXG2Z9h3c#{PrqnIQtLlK06WGIWEFi3_W2m@Y+Aq?hE z9qiwVF@(v0H!~OnZ8L*GvA~yl(7*O%3{EwVPPaTGfxP@ zVld$lq6jEBNHLJn>H|d)l!9~s83U!Hg=bieqS1TL&R)PqnFIx;8bz5D3IzkmSUBVm zic(A#R9y~A~fGFS-Qr9g&Zuo5Ol5Ktgd&?^Xi9gF$L zVfJ?@071Y>*b^o}B5=e(u;5gq1QXajGu1!_(n^!jngylU2n9C*N&&q2W8n`2DHMf^ z8>Mhs=K>i)O9@3Xaj4Nyio#h_(VB|?FI+-d2nn|{s92Qd38>HSn`i6=7zBr(MUYH5 zGAtH`PD{!6{7(TVTwI{fm;@AlfSbUanPJ5c01wk1I8{#=LV@;!)}aW&pv#njFYrPp z$)qjB5hgQw-6ha4P;+4*Y6RpYhG7g0UWZ`>Xu>J;eg_PLp9U8)1XCpiPIVR)L2&kg;G~0#5@~2rD7M z(51;(6iqfYBc;DL?4Jr|QW#88Feb%>nF5eeOj@*9D2#m=XmENISRmHF3}pWxZ(tz_ z4`(1_p>RHN1fg{VNHGmsaD)b&7!HbmX7wW&c+DB;r(f3mGzLfU-!$VV%K)1xoMsF_ zHMk?dP8c{b93yF+gJTp1@MFfn1PfWTw!^`&rzvJIXrO`v+6dPI&H%^>I|s(rpLKtI zpGO$Z1e^|D7(M?Y07x9zh8?8XcwBGsY9t#|S z;8+9VM++f~q>(o;2B$z}CJ2xSeYBDYMng^#oXcT~Mj~KVL&i)ICAh^%boyn&PY@sh z1A-9*qy&e7fdpg@_KpPY4MzbS6=|%91n;PXb3(EK|D-*T0VV_N2`K+*z4N`SNx<%C zMgwT2DFKHexFvvj-w$vzK!5}XecA(Yz^Gwk0DGc65GQFvg2Y)gTqX&0I=}b<0wjP7 zS`@%_0xb%lGHEFxFp;2s9f=adWvE+C`en;kI70nhvsEicbqyImbT!Ed$T<5SzZ+t=ay-FHL$?B#+} POGy@=yu8*HZNC2l3%*J* delta 95133 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 T-zEkF6fqb97nh=z62boiU;czM diff --git a/results/choice-readout-v2.json b/results/choice-readout-v2.json index 8c1213f..05d9a87 100644 --- a/results/choice-readout-v2.json +++ b/results/choice-readout-v2.json @@ -98,6 +98,20 @@ "base": "Qwen/Qwen3.5-4B", "checkpoint": null }, + "public_source": { + "repository": "Qwen/Qwen3.5-4B", + "url": "https://huggingface.co/Qwen/Qwen3.5-4B", + "revision": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a", + "revision_status": "verified", + "repository_evidence": "manifest.base_loading.canonical_base", + "revision_evidence": "manifest.base_loading.canonical_revision", + "evaluated_artifact_sha256": {}, + "base_model": { + "repository": "Qwen/Qwen3.5-4B", + "revision": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a" + }, + "limitation": null + }, "checkpoint_artifacts": null, "base_loading": { "canonical_base": "Qwen/Qwen3.5-4B", @@ -229,6 +243,33 @@ "base": null, "checkpoint": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.5-4B-LoRA/snapshots/1c7aa9bab14ac347aeb917c0bcd757838a8a78ce" }, + "public_source": { + "repository": "SimpleJev/JevAny-Qwen3.5-4B-LoRA", + "url": "https://huggingface.co/SimpleJev/JevAny-Qwen3.5-4B-LoRA", + "revision": "1c7aa9bab14ac347aeb917c0bcd757838a8a78ce", + "revision_status": "verified", + "repository_evidence": "manifest.source.checkpoint", + "revision_evidence": "manifest.source.checkpoint", + "evaluated_artifact_sha256": { + "adapter_model.safetensors": { + "sha256": "5acbaec8d554517250bf9c626b3f4b9e2ec31fc67957f3486aa72e2c91ee907f", + "bytes": 64993432 + }, + "head.pt": { + "sha256": "e5ea982efd06ecaf71b493045ebccdbfdca39f6fe128de164bf636d73152cd44", + "bytes": 5513315 + }, + "adapter_config.json": { + "sha256": "8a138b2c091d2ba463a8107e9e19182202395188750742047f4a12e8b59e1eda", + "bytes": 1265 + } + }, + "base_model": { + "repository": "Qwen/Qwen3.5-4B", + "revision": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a" + }, + "limitation": null + }, "checkpoint_artifacts": { "requested": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.5-4B-LoRA/snapshots/1c7aa9bab14ac347aeb917c0bcd757838a8a78ce", "resolved": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.5-4B-LoRA/snapshots/1c7aa9bab14ac347aeb917c0bcd757838a8a78ce", @@ -377,6 +418,33 @@ "base": null, "checkpoint": "/lustre-storage/fsx/tianxinwei/JevAny/releases/model-family-v2-20260929/JevAny-Qwen3.5-4B-Direct-Token-LoRA" }, + "public_source": { + "repository": "SimpleJev/JevAny-Qwen3.5-4B-Direct-Token-LoRA", + "url": "https://huggingface.co/SimpleJev/JevAny-Qwen3.5-4B-Direct-Token-LoRA", + "revision": null, + "revision_status": "not_verified", + "repository_evidence": "results/model-family-v2.json#released_models", + "revision_evidence": null, + "evaluated_artifact_sha256": { + "adapter_model.safetensors": { + "sha256": "b768d54b9e5e20204b2db9e24c18ae6b10fa310b241d1931bac6b8882f127c53", + "bytes": 64993432 + }, + "head.pt": { + "sha256": "d3909cffc156d077061114627c8aa22f60c7a8f1bdcfd20c489bb3bb009fc4a6", + "bytes": 4699 + }, + "adapter_config.json": { + "sha256": "f84527483265b514d0a5f68d9c44e3d7952825c61d3659a858af8342e6ce9ff2", + "bytes": 1265 + } + }, + "base_model": { + "repository": "Qwen/Qwen3.5-4B", + "revision": "851bf6e806efd8d0a36b00ddf55e13ccb7b8cd0a" + }, + "limitation": "The evaluated checkpoint came from a local release. Its exact files are pinned below by SHA-256, but the run manifest and release metadata do not prove an immutable public-repository revision for those bytes." + }, "checkpoint_artifacts": { "requested": "/lustre-storage/fsx/tianxinwei/JevAny/releases/model-family-v2-20260929/JevAny-Qwen3.5-4B-Direct-Token-LoRA", "resolved": "/lustre-storage/fsx/tianxinwei/JevAny/releases/model-family-v2-20260929/JevAny-Qwen3.5-4B-Direct-Token-LoRA", @@ -525,6 +593,20 @@ "base": "Qwen/Qwen3.8-27B", "checkpoint": null }, + "public_source": { + "repository": "Qwen/Qwen3.8-27B", + "url": "https://huggingface.co/Qwen/Qwen3.8-27B", + "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0", + "revision_status": "verified", + "repository_evidence": "manifest.base_loading.canonical_base", + "revision_evidence": "manifest.base_loading.canonical_revision", + "evaluated_artifact_sha256": {}, + "base_model": { + "repository": "Qwen/Qwen3.8-27B", + "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0" + }, + "limitation": null + }, "checkpoint_artifacts": null, "base_loading": { "canonical_base": "Qwen/Qwen3.8-27B", @@ -656,6 +738,33 @@ "base": null, "checkpoint": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.8-27B-LoRA/snapshots/09c9e9102d5b8cc7d56558d25da1202a761b6c0d" }, + "public_source": { + "repository": "SimpleJev/JevAny-Qwen3.8-27B-LoRA", + "url": "https://huggingface.co/SimpleJev/JevAny-Qwen3.8-27B-LoRA", + "revision": "09c9e9102d5b8cc7d56558d25da1202a761b6c0d", + "revision_status": "verified", + "repository_evidence": "manifest.source.checkpoint", + "revision_evidence": "manifest.source.checkpoint", + "evaluated_artifact_sha256": { + "adapter_model.safetensors": { + "sha256": "f4b835f4f7be78a819067dac226feaad2fc7b9eb0b968c5e6e665ad5328e6731", + "bytes": 233584648 + }, + "head.pt": { + "sha256": "f78fa283f7e979ce418c5b137d24459350fecaea73e5766894532c1cd99684c7", + "bytes": 10756195 + }, + "adapter_config.json": { + "sha256": "31d8e262a21a45b80fd409f08d47930effd6cd9bc12156c59ad61e627f50a275", + "bytes": 1266 + } + }, + "base_model": { + "repository": "Qwen/Qwen3.8-27B", + "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0" + }, + "limitation": null + }, "checkpoint_artifacts": { "requested": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.8-27B-LoRA/snapshots/09c9e9102d5b8cc7d56558d25da1202a761b6c0d", "resolved": "/storage/home/tianxinwei/.cache/huggingface/hub/models--SimpleJev--JevAny-Qwen3.8-27B-LoRA/snapshots/09c9e9102d5b8cc7d56558d25da1202a761b6c0d", @@ -847,6 +956,11 @@ } } }, + "repository_catalog": { + "path": "results/model-family-v2.json", + "sha256": "a49f03bd9371f1846d78a09682f000cba9f64004d8d5a222b3868f11ccb48d8c", + "release": "model-family-v2" + }, "datasets": { "transfer_calibration": { "label": "Transfer-v9 development", diff --git a/scripts/build_choice_readout_results.py b/scripts/build_choice_readout_results.py index 8eb6fcc..0a37b3c 100644 --- a/scripts/build_choice_readout_results.py +++ b/scripts/build_choice_readout_results.py @@ -18,12 +18,14 @@ import hashlib import json import math +import re from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] DEFAULT_EXTERNAL = ROOT / "results/external-zero-shot-v1.json" +DEFAULT_MODEL_CATALOG = ROOT / "results/model-family-v2.json" DEFAULT_JSON_OUT = ROOT / "results/choice-readout-v2.json" DEFAULT_SVG_OUT = ROOT / "docs/choice-readout-results.svg" @@ -40,6 +42,7 @@ { "key": "base4", "family": "Qwen3.5-4B", + "public_repository": "Qwen/Qwen3.5-4B", "weights": "frozen_base", "native_readout": None, "native_decision_mode": None, @@ -47,6 +50,7 @@ { "key": "pointer4", "family": "Qwen3.5-4B", + "public_repository": "SimpleJev/JevAny-Qwen3.5-4B-LoRA", "weights": "jevany_sft", "native_readout": "pointer", "native_decision_mode": "pointer", @@ -54,6 +58,7 @@ { "key": "direct4", "family": "Qwen3.5-4B", + "public_repository": "SimpleJev/JevAny-Qwen3.5-4B-Direct-Token-LoRA", "weights": "jevany_sft", "native_readout": "direct-token", "native_decision_mode": "lm_token", @@ -61,6 +66,7 @@ { "key": "base27", "family": "Qwen3.8-27B", + "public_repository": "Qwen/Qwen3.8-27B", "weights": "frozen_base", "native_readout": None, "native_decision_mode": None, @@ -68,6 +74,7 @@ { "key": "pointer27", "family": "Qwen3.8-27B", + "public_repository": "SimpleJev/JevAny-Qwen3.8-27B-LoRA", "weights": "jevany_sft", "native_readout": "pointer", "native_decision_mode": "pointer", @@ -244,6 +251,98 @@ def _validate_source(spec: dict, manifest: dict, context: str) -> None: ) +def _immutable_revision(value: Any) -> str | None: + if isinstance(value, str) and re.fullmatch(r"[0-9a-f]{40}", value): + return value + return None + + +def _snapshot_revision(path: Any, repository: str) -> str | None: + """Return a Hub snapshot revision only when the path names this repository.""" + + if not isinstance(path, str): + return None + encoded_repository = repository.replace("/", "--") + match = re.search( + rf"(?:^|/)models--{re.escape(encoded_repository)}/snapshots/([0-9a-f]{{40}})(?:/|$)", + path, + ) + return match.group(1) if match else None + + +def _public_source(spec: dict, manifest: dict) -> dict: + """Expose reproducible public identity separately from local load paths. + + A local release directory is not silently treated as a public Hub revision. + In that case the evaluated files remain identified by their SHA-256 values + and the missing public revision is explicit. + """ + + repository = spec["public_repository"] + base_loading = manifest.get("base_loading") + base_loading = base_loading if isinstance(base_loading, dict) else {} + base_repository = base_loading.get("canonical_base") + base_revision = _immutable_revision(base_loading.get("canonical_revision")) + if not isinstance(base_repository, str) or not base_repository: + raise ValueError(f"{spec['key']}: canonical base repository is missing") + if base_revision is None: + raise ValueError(f"{spec['key']}: immutable canonical base revision is missing") + + artifacts = manifest.get("checkpoint_artifacts") + artifact_hashes = {} + if isinstance(artifacts, dict) and isinstance(artifacts.get("files"), dict): + for filename, metadata in artifacts["files"].items(): + if not isinstance(metadata, dict) or not re.fullmatch( + r"[0-9a-f]{64}", str(metadata.get("sha256", "")) + ): + raise ValueError( + f"{spec['key']}: checkpoint artifact {filename!r} lacks SHA-256" + ) + artifact_hashes[filename] = { + "sha256": metadata["sha256"], + "bytes": metadata.get("bytes"), + } + + if spec["weights"] == "frozen_base": + if repository != base_repository: + raise ValueError( + f"{spec['key']}: public base repository does not match canonical base" + ) + revision = base_revision + revision_evidence = "manifest.base_loading.canonical_revision" + repository_evidence = "manifest.base_loading.canonical_base" + limitation = None + else: + source = manifest["source"]["checkpoint"] + revision = _snapshot_revision(source, repository) + revision_evidence = "manifest.source.checkpoint" if revision else None + repository_evidence = ( + "manifest.source.checkpoint" + if revision + else "results/model-family-v2.json#released_models" + ) + limitation = None if revision else ( + "The evaluated checkpoint came from a local release. Its exact files " + "are pinned below by SHA-256, but the run manifest and release metadata " + "do not prove an immutable public-repository revision for those bytes." + ) + + return { + "repository": repository, + "url": f"https://huggingface.co/{repository}", + "revision": revision, + "revision_status": "verified" if revision else "not_verified", + "repository_evidence": repository_evidence, + "revision_evidence": revision_evidence, + "evaluated_artifact_sha256": artifact_hashes, + "base_model": { + "repository": base_repository, + "revision": base_revision, + }, + "limitation": limitation, + } + + def _load_run(run_root: Path, spec: dict) -> dict: directory = run_root / spec["key"] manifest_path = directory / "manifest.json" @@ -385,6 +484,7 @@ def _load_run(run_root: Path, spec: dict) -> dict: }, }, "source": copy.deepcopy(manifest.get("source")), + "public_source": _public_source(spec, manifest), "checkpoint_artifacts": copy.deepcopy(manifest.get("checkpoint_artifacts")), "base_loading": copy.deepcopy(manifest.get("base_loading")), "predictor": copy.deepcopy(manifest.get("predictor")), @@ -467,10 +567,41 @@ def _load_external(path: Path) -> dict: } -def build_results(run_root: Path, external_path: Path = DEFAULT_EXTERNAL) -> dict: +def _load_repository_catalog(path: Path) -> dict: + catalog = _read_object(path) + released = catalog.get("released_models") + repositories = { + row.get("repository") + for row in released + if isinstance(row, dict) and isinstance(row.get("repository"), str) + } if isinstance(released, list) else set() + required = { + spec["public_repository"] + for spec in RUN_SPECS + if spec["weights"] != "frozen_base" + } + missing = required - repositories + if missing: + raise ValueError( + "model release catalog is missing canonical repositories: " + + ", ".join(sorted(missing)) + ) + return { + "path": _display_path(path), + "sha256": _sha256(path), + "release": catalog.get("release"), + } + + +def build_results( + run_root: Path, + external_path: Path = DEFAULT_EXTERNAL, + model_catalog_path: Path = DEFAULT_MODEL_CATALOG, +) -> dict: """Build a validated, JSON-serializable result artifact without writing it.""" run_root = Path(run_root) + repository_catalog = _load_repository_catalog(Path(model_catalog_path)) runs = [_load_run(run_root, spec) for spec in RUN_SPECS] if len({run["model"] for run in runs}) != len(runs): raise ValueError("the five run manifests must have unique model labels") @@ -568,6 +699,7 @@ def build_results(run_root: Path, external_path: Path = DEFAULT_EXTERNAL) -> dic "code_revision": next(iter(revisions)), "source_runs": public_runs, "external_source": external["artifact"], + "repository_catalog": repository_catalog, "datasets": result_datasets, "legacy_results": { "status": "retained unchanged", @@ -838,11 +970,12 @@ def main(argv: list[str] | None = None) -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--run-root", type=Path, required=True) parser.add_argument("--external", type=Path, default=DEFAULT_EXTERNAL) + parser.add_argument("--model-catalog", type=Path, default=DEFAULT_MODEL_CATALOG) parser.add_argument("--json-out", type=Path, default=DEFAULT_JSON_OUT) parser.add_argument("--svg-out", type=Path, default=DEFAULT_SVG_OUT) args = parser.parse_args(argv) - artifact = build_results(args.run_root, args.external) + artifact = build_results(args.run_root, args.external, args.model_catalog) write_results(args.json_out, artifact) render_svg(artifact, args.svg_out) print(f"Wrote {_display_path(args.json_out)} and {_display_path(args.svg_out)}") diff --git a/scripts/build_external_report_appendix.py b/scripts/build_external_report_appendix.py index a5dddd6..341bad4 100644 --- a/scripts/build_external_report_appendix.py +++ b/scripts/build_external_report_appendix.py @@ -19,7 +19,14 @@ from matplotlib.backends.backend_pdf import PdfPages from matplotlib.patches import FancyBboxPatch from pypdf import PdfReader, PdfWriter -from pypdf.generic import IndirectObject +from pypdf._cmap import get_encoding +from pypdf.generic import ( + ArrayObject, + ContentStream, + IndirectObject, + NumberObject, + TextStringObject, +) ROOT = Path(__file__).resolve().parents[1] @@ -57,6 +64,49 @@ "/ModDate": PDF_DATE_LITERAL, } +# The source report predates the final 27B release checkpoint. Synchronize the +# handful of release-headline fields while merging so the one published PDF +# does not call two different checkpoints "current". The original layout, +# resources, links, and every unrelated result remain untouched. +CURRENT_RELEASE_TEXT = { + 1: (("85.76", "86.04", 2), ("90.48", "90.04", 2)), + 4: ( + ("22,160", "44,319", 1), + ("85.76", "86.04", 1), + ("90.48", "90.04", 1), + ("0.392", "0.388", 1), + ("0.200", "0.195", 1), + ("0.030", "0.026", 1), + ), + 7: (("85.76", "86.04", 1),), + 8: ( + ("22,160", "44,319", 1), + ("18.83", "39.43", 1), + ("602.7", "1,261.7", 1), + ("1,423", "2,082", 1), + ), +} + +CURRENT_COMPUTE_PROVENANCE = ( + ( + "onds;itsvalueusestherecordedrunstartandcheckpointtimestamp." + "Direct-token,Qwen27B,and", + "onds; its value uses the recorded run start and checkpoint timestamp. " + "Direct-token and Muse use", + ), + ( + "Museusecheckpoint-nativeelapsedtime;Qwen4Bpointerusesterminaltrainertelemetrybecause", + "checkpoint-native elapsed time; Qwen 4B pointer uses terminal trainer telemetry. " + "Qwen 27B uses", + ), + ( + "thereleasedcheckpointisthecompletedstep13,850run." + "Peakwithin-runparallelismwas40GPUs.", + "estimated cumulative seconds-per-record timing. " + "Peak within-run parallelism was 40 GPUs.", + ), +) + @contextmanager def atomic_destination(destination: Path): @@ -112,26 +162,48 @@ def overview_page(pdf: PdfPages, data: dict, chart: Path) -> None: fig = plt.figure(figsize=(8.5, 11), facecolor="white") header(fig, "External decision-suite evaluation", "Accuracy on Typed Decisions · JevJudge-Public v0.3 full multimodal suite · text-only subset", 1) - ax = fig.add_axes([0.055, 0.42, 0.89, 0.36]) - ax.imshow(mpimg.imread(chart)) - ax.axis("off") + chart_image = mpimg.imread(chart) + # The source asset is a wide, three-panel README graphic. Rendering it as + # one image makes its labels roughly four points on a portrait report page. + # Crop the panels (including their titles and axes) and use a 1 + 2 layout + # so every label is materially larger while the source pixels stay intact. + height, width = chart_image.shape[:2] + if width >= 3 and height >= 3: + y_start, y_stop = int(height * 0.15), int(height * 0.91) + panels = ( + (chart_image[y_start:y_stop, int(width * 0.02):int(width * 0.43)], + [0.065, 0.500, 0.870, 0.300]), + (chart_image[y_start:y_stop, int(width * 0.42):int(width * 0.71)], + [0.065, 0.115, 0.420, 0.335]), + (chart_image[y_start:y_stop, int(width * 0.70):int(width * 0.995)], + [0.515, 0.115, 0.420, 0.335]), + ) + for panel, bounds in panels: + ax = fig.add_axes(bounds) + ax.imshow(panel) + ax.axis("off") + else: + # Keep tiny fixture images usable in report-generation tests. + ax = fig.add_axes([0.065, 0.115, 0.870, 0.685]) + ax.imshow(chart_image) + ax.axis("off") full = {row["model"]: row for row in data["jevjudge_full"]["models"]} text = {row["model"]: row for row in data["jevjudge_text"]["models"]} typed = {row["model"]: row for row in data["typed_decisions"]["models"]} - qwen = full["JevAny-Qwen3.8-27B"] - jeff = full["Jeff-Qwen3.5-2B"] - card(fig, 0.065, "Full-suite accuracy", - f"Qwen3.8-27B: {qwen['accuracy'] * 100:.2f}%\n" - f"Jeff-2B: {jeff['accuracy'] * 100:.2f}%\n" - f"Gap: {(qwen['accuracy'] - jeff['accuracy']) * 100:.2f} points") - card(fig, 0.365, "Typed accuracy", - f"JevAny Qwen27: {typed['JevAny-Qwen3.8-27B']['accuracy'] * 100:.2f}%\n" - f"Jev 1.13: {typed['Jev 1.13 (OpenRouter)']['accuracy'] * 100:.2f}%\n" - "Typed result published · gap 0.10 pt") - card(fig, 0.665, "Text-only accuracy", - f"JevAny Qwen27: {text['JevAny-Qwen3.8-27B']['accuracy'] * 100:.2f}%\n" - f"Jev 1.13: {text['Jev 1.13 (OpenRouter)']['accuracy'] * 100:.2f}%\n" - f"Kev-27B: {text['Kev-27B']['accuracy'] * 100:.2f}%") + qwen_full = full["JevAny-Qwen3.8-27B"] + qwen_typed = typed["JevAny-Qwen3.8-27B"] + qwen_text = text["JevAny-Qwen3.8-27B"] + fig.text( + 0.065, 0.818, + "Same 13-model order · — unsupported/no result · teal JevAny · gold Jev API · purple Kev · gray other open · hatch published", + color=MUTED, fontsize=7.8, + ) + fig.text( + 0.065, 0.795, + f"Qwen3.8-27B topline · Typed {qwen_typed['accuracy'] * 100:.2f}% · " + f"JevJudge full {qwen_full['accuracy'] * 100:.2f}% · text-only {qwen_text['accuracy'] * 100:.2f}%", + color=TEAL, fontsize=8.2, weight="bold", + ) pdf.savefig(fig) plt.close(fig) @@ -407,31 +479,40 @@ def choice_method_page(pdf: PdfPages, data: dict) -> None: ("JevAny SFT", "Pointer", "Choice-token", f"{sources['pointer4']['family']}, {sources['pointer27']['family']}"), ("JevAny SFT", "Direct-Token", "Choice-token", sources["direct4"]["family"]), ) - fig.text(0.065, 0.405, "Five-run comparison", color=INK, fontsize=12, weight="bold") + fig.text(0.065, 0.425, "Five-run comparison", color=INK, fontsize=12, weight="bold") columns = (0.065, 0.245, 0.420, 0.610) for x, label in zip(columns, ("Weights", "Native reference", "Training-free path", "Backbone family")): - fig.text(x, 0.370, label, color=MUTED, fontsize=8.0, weight="bold") - fig.lines.append(plt.Line2D([0.065, 0.935], [0.357, 0.357], transform=fig.transFigure, + fig.text(x, 0.390, label, color=MUTED, fontsize=8.0, weight="bold") + fig.lines.append(plt.Line2D([0.065, 0.935], [0.377, 0.377], transform=fig.transFigure, color=RULE, linewidth=1)) for index, values in enumerate(table): - y = 0.326 - index * 0.055 + y = 0.346 - index * 0.052 for x, value in zip(columns, values): fig.text(x, y, value, color=INK, fontsize=8.4) fig.lines.append(plt.Line2D([0.065, 0.935], [y - 0.019, y - 0.019], transform=fig.transFigure, color="#EEF2F6", linewidth=0.7)) - _rounded_box(fig, 0.065, 0.095, 0.870, 0.105, facecolor="#F7FAFC") - fig.text(0.083, 0.169, "Scope", color=INK, fontsize=9.5, weight="bold") + _rounded_box( + fig, 0.065, 0.123, 0.870, 0.075, + facecolor="#FFF8E8", edgecolor="#E7C46A", + ) + fig.text(0.083, 0.176, "Release sync and protocol boundary", color="#8A5A00", fontsize=9.5, weight="bold") fig.text( - 0.083, 0.142, - "Text only · up to 52 option IDs · JevJudge text 724 is supported.\n" - "The full 3,220-record multimodal suite is unsupported; media is never stripped into a score.", - color=MUTED, fontsize=8.2, linespacing=1.35, + 0.083, 0.151, + "The merged report and README identify the current 27B release checkpoint: step 44,319.", + color=INK, fontsize=8.0, ) fig.text( - 0.083, 0.112, - "The earlier agent-harness appendix is unchanged; these readout experiments do not re-score agent tasks.", - color=MUTED, fontsize=8.2, + 0.083, 0.132, + "Appendix M native rows are matched prompt-v2/runtime reruns; Appendix L's earlier external-study row can differ by one decision.", + color=INK, fontsize=8.0, + ) + _rounded_box(fig, 0.065, 0.061, 0.870, 0.047, facecolor="#F7FAFC") + fig.text(0.083, 0.091, "Scope", color=INK, fontsize=8.7, weight="bold") + fig.text( + 0.137, 0.091, + "Text only · up to 52 IDs · JevJudge text 724 supported · full multimodal unsupported · agent tasks unchanged", + color=MUTED, fontsize=7.6, ) pdf.savefig(fig) plt.close(fig) @@ -486,7 +567,7 @@ def choice_results_page(pdf: PdfPages, data: dict) -> None: header( fig, "Choice-token accuracy matrix", - "Matched runs · Transfer-dev settings frozen before every displayed evaluation panel", + "Matched runs · current 27B release step 44,319 · Transfer-dev settings frozen before evaluation", 2, appendix="M", section="Choice-token readout", @@ -655,6 +736,203 @@ def page_invariants(page) -> tuple: ) +def _font_text_maps(page) -> dict[str, tuple[dict[str, str], dict[str, str]]]: + maps = {} + fonts = page["/Resources"]["/Font"] + for name, reference in fonts.items(): + _encoding, character_map = get_encoding(reference.get_object()) + reverse = { + unicode_text: glyph + for glyph, unicode_text in character_map.items() + if isinstance(glyph, str) + and isinstance(unicode_text, str) + and len(unicode_text) == 1 + } + maps[str(name)] = (character_map, reverse) + return maps + + +def _decode_text_object(value, character_map: dict[str, str]) -> str: + return "".join(character_map.get(glyph, glyph) for glyph in str(value)) + + +def _encode_text_object( + text: str, + reverse_map: dict[str, str], + prototype, +) -> TextStringObject: + try: + glyphs = "".join(reverse_map[character] for character in text) + except KeyError as error: + raise RuntimeError(f"release-sync font cannot encode {error.args[0]!r}") from error + original_length = len(prototype.original_bytes) + glyph_count = len(str(prototype)) + if glyph_count == 0 or original_length % glyph_count: + raise RuntimeError("release-sync text object has an unsupported encoding width") + byte_width = original_length // glyph_count + if byte_width not in {1, 2}: + raise RuntimeError(f"release-sync text uses unsupported {byte_width}-byte glyphs") + raw = b"".join(ord(glyph).to_bytes(byte_width, "big") for glyph in glyphs) + result = TextStringObject(glyphs) + result._original_bytes = raw + return result + + +def _word_array(text: str, reverse_map: dict[str, str], prototype) -> ArrayObject: + words = text.split() + result = ArrayObject() + for index, word in enumerate(words): + if index: + result.append(NumberObject(-300)) + result.append(_encode_text_object(word, reverse_map, prototype)) + return result + + +def synchronize_current_release(page, page_number: int) -> bool: + """Replace the superseded 27B headline fields in the retained report core.""" + + replacements = CURRENT_RELEASE_TEXT.get(page_number) + if replacements is None: + return False + extracted = page.extract_text() or "" + legacy_counts = {old: extracted.count(old) for old, _new, _count in replacements} + if not any(legacy_counts.values()): + return False + for old, _new, expected_count in replacements: + if legacy_counts[old] != expected_count: + raise RuntimeError( + f"base report page {page_number}: expected {expected_count} occurrences " + f"of legacy value {old!r}, found {legacy_counts[old]}" + ) + + text_maps = _font_text_maps(page) + content = ContentStream(page.get_contents(), page.indirect_reference.pdf) + active_font = None + replaced_counts = {old: 0 for old, _new, _count in replacements} + provenance_replaced = 0 + for operands, operator in content.operations: + if operator == b"Tf": + active_font = str(operands[0]) + continue + if operator not in {b"Tj", b"TJ"} or active_font is None: + continue + character_map, reverse_map = text_maps[active_font] + values = operands[0] if operator == b"TJ" else ArrayObject([operands[0]]) + decoded_line = "".join( + _decode_text_object(value, character_map) + for value in values + if hasattr(value, "original_bytes") + ) + + if page_number == 8: + provenance = next( + (replacement for legacy, replacement in CURRENT_COMPUTE_PROVENANCE + if decoded_line == legacy), + None, + ) + if provenance is not None: + prototype = next( + value for value in values if hasattr(value, "original_bytes") + ) + operands[0] = _word_array(provenance, reverse_map, prototype) + provenance_replaced += 1 + continue + + for index, value in enumerate(values): + if not hasattr(value, "original_bytes"): + continue + decoded = _decode_text_object(value, character_map) + updated = decoded + for old, new, _expected_count in replacements: + count = updated.count(old) + if count: + updated = updated.replace(old, new) + replaced_counts[old] += count + if updated != decoded: + values[index] = _encode_text_object(updated, reverse_map, value) + if operator == b"Tj": + operands[0] = values[0] + + for old, _new, expected_count in replacements: + if replaced_counts[old] != expected_count: + raise RuntimeError( + f"base report page {page_number}: replaced {replaced_counts[old]} of " + f"{expected_count} expected {old!r} values" + ) + if page_number == 8 and provenance_replaced != len(CURRENT_COMPUTE_PROVENANCE): + raise RuntimeError( + "base report page 8: could not synchronize the 27B compute provenance" + ) + # In-place list edits do not invalidate ContentStream's raw-byte cache. + content.operations = content.operations + synchronized_parts = [] + active_font = None + for operands, operator in content.operations: + if operator == b"Tf": + active_font = str(operands[0]) + continue + if operator not in {b"Tj", b"TJ"} or active_font is None: + continue + character_map, _reverse_map = text_maps[active_font] + values = operands[0] if operator == b"TJ" else [operands[0]] + synchronized_parts.extend( + _decode_text_object(value, character_map) + for value in values + if hasattr(value, "original_bytes") + ) + synchronized = "".join(synchronized_parts) + for old, new, expected_count in replacements: + if old in synchronized or synchronized.count(new) < expected_count: + raise RuntimeError( + f"base report page {page_number}: failed to synchronize {old!r} to {new!r}; " + f"old={synchronized.count(old)}, new={synchronized.count(new)}" + ) + page.replace_contents(content) + return True + + +def synchronize_report_boundary(page, page_number: int) -> bool: + """Update the old appendix claim that the merged core is byte-unchanged.""" + + if page_number != 10: + return False + extracted = page.extract_text() or "" + if "The original PDF is pre-" not in extracted: + return False + replacements = { + "original ": "merged ", + "pre-": "release-", + "serv": "synch", + "ed unchanged.": "ronized.", + } + counts = {old: 0 for old in replacements} + content = ContentStream(page.get_contents(), page.indirect_reference.pdf) + for operands, operator in content.operations: + if operator not in {b"Tj", b"TJ"}: + continue + values = operands[0] if operator == b"TJ" else ArrayObject([operands[0]]) + for index, value in enumerate(values): + if not isinstance(value, TextStringObject): + continue + updated = str(value) + for old, new in replacements.items(): + count = updated.count(old) + if count: + updated = updated.replace(old, new) + counts[old] += count + if updated != str(value): + values[index] = TextStringObject(updated) + if operator == b"Tj": + operands[0] = values[0] + if any(count != 1 for count in counts.values()): + raise RuntimeError( + f"base report page 10: could not synchronize appendix boundary: {counts}" + ) + content.operations = content.operations + page.replace_contents(content) + return True + + def merge_report(base_report: Path, appendix_report: Path, merged_output: Path, base_pages: int) -> None: if base_pages != REPORT_BASE_PAGES: raise ValueError(f"--base-pages must be exactly {REPORT_BASE_PAGES}") @@ -671,8 +949,15 @@ def merge_report(base_report: Path, appendix_report: Path, merged_output: Path, writer = PdfWriter() writer.pdf_header = "%PDF-1.7" + synchronized_pages = set() + expected_base_invariants = {} for index in range(base_pages): writer.add_page(base.pages[index]) + changed = synchronize_current_release(writer.pages[-1], index + 1) + changed = synchronize_report_boundary(writer.pages[-1], index + 1) or changed + if changed: + synchronized_pages.add(index) + expected_base_invariants[index] = page_invariants(writer.pages[-1]) for page in appendix.pages: writer.add_page(page) writer.add_metadata(MERGED_METADATA) @@ -685,8 +970,41 @@ def merge_report(base_report: Path, appendix_report: Path, merged_output: Path, if len(merged.pages) != expected_pages: raise RuntimeError(f"expected {expected_pages} merged pages, got {len(merged.pages)}") for index in range(base_pages): - if page_invariants(base.pages[index]) != page_invariants(merged.pages[index]): + expected = ( + expected_base_invariants[index] + if index in synchronized_pages + else page_invariants(base.pages[index]) + ) + actual = page_invariants(merged.pages[index]) + if index in synchronized_pages: + # A writer-attached page temporarily extracts its glyph IDs, + # while the serialized page resolves them through ToUnicode. + # Compare every invariant except that transient text view. + expected = expected[:4] + expected[5:] + actual = actual[:4] + actual[5:] + if expected != actual: raise RuntimeError(f"base page {index + 1} changed during merge") + for page_number, replacements in CURRENT_RELEASE_TEXT.items(): + page_text = merged.pages[page_number - 1].extract_text() or "" + is_release_page = (page_number - 1) in synchronized_pages or any( + page_text.count(new) >= expected_count + for _old, new, expected_count in replacements + ) + if not is_release_page: + continue + for old, new, expected_count in replacements: + if old in page_text or page_text.count(new) < expected_count: + raise RuntimeError( + f"serialized base page {page_number} does not contain the current " + f"release value {new!r}" + ) + boundary_text = merged.pages[9].extract_text() or "" + boundary_text = boundary_text.replace("-\n", "-") + if "The original PDF is pre-" in boundary_text or ( + "The merged PDF is release-synchronized." not in boundary_text + and 9 in synchronized_pages + ): + raise RuntimeError("serialized base page 10 has a stale appendix boundary") def main(argv: list[str] | None = None) -> None: diff --git a/site/files/reports/JevAny_Tech_Report.pdf b/site/files/reports/JevAny_Tech_Report.pdf index 5e0b4c473188a421332f0f479802cd82f7c515c2..cedc9fbbb46d310a861c6ee8670378c9763fc2fc 100644 GIT binary patch delta 163578 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 POGy@=yu8*HZNC2l3%*J* delta 95133 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 T-zEkF6fqb97nh=z62boiU;czM diff --git a/tests/test_build_choice_readout_results.py b/tests/test_build_choice_readout_results.py index 7cf760c..eb8863d 100644 --- a/tests/test_build_choice_readout_results.py +++ b/tests/test_build_choice_readout_results.py @@ -90,6 +90,14 @@ def _external_fixture(path): def _run_fixture(root, spec, ordinal): is_base = spec["weights"] == "frozen_base" + canonical_base = f"Qwen/{spec['family']}" + snapshot_revision = f"{ordinal + 1}" * 40 + encoded_repository = spec["public_repository"].replace("/", "--") + checkpoint = ( + f"/fixture/models--{encoded_repository}/snapshots/{snapshot_revision}" + if not is_base and spec["key"] != "direct4" + else f"/fixture/local-release/{spec['key']}" + ) components = ["choice"] if is_base else ["choice", "native"] model = f"fixture-{spec['key']}" reports = {} @@ -148,8 +156,8 @@ def _run_fixture(root, spec, ordinal): manifest = { "model": model, "source": { - "base": f"fixture/{spec['family']}" if is_base else None, - "checkpoint": None if is_base else f"fixture/{spec['key']}", + "base": spec["public_repository"] if is_base else None, + "checkpoint": None if is_base else checkpoint, }, "checkpoint_artifacts": None if is_base else { "requested": f"fixture/{spec['key']}", @@ -161,12 +169,17 @@ def _run_fixture(root, spec, ordinal): } }, }, - "base_loading": {"requested": spec["family"], "resolved": "/fixture/base"}, + "base_loading": { + "requested": spec["family"], + "resolved": "/fixture/base", + "canonical_base": canonical_base, + "canonical_revision": "a" * 40, + }, "predictor": { "method": "training-free exact choice-token projection", "adapter_applied": not is_base, "native_decision_mode": spec["native_decision_mode"], - "canonical_base": spec["family"], + "canonical_base": canonical_base, "canonical_revision": "a" * 40, }, "runtime": { @@ -250,6 +263,40 @@ def test_build_results_preserves_all_five_run_types_and_protocol_groups(matrix): assert len(artifact["source_runs"][0]["artifacts"]["manifest"]["sha256"]) == 64 assert artifact["source_runs"][0]["runtime"]["torch"] == "2.fixture" + sources = {run["key"]: run["public_source"] for run in artifact["source_runs"]} + assert sources["base4"] == { + "repository": "Qwen/Qwen3.5-4B", + "url": "https://huggingface.co/Qwen/Qwen3.5-4B", + "revision": "a" * 40, + "revision_status": "verified", + "repository_evidence": "manifest.base_loading.canonical_base", + "revision_evidence": "manifest.base_loading.canonical_revision", + "evaluated_artifact_sha256": {}, + "base_model": { + "repository": "Qwen/Qwen3.5-4B", + "revision": "a" * 40, + }, + "limitation": None, + } + assert sources["pointer4"]["revision"] == "2" * 40 + assert sources["pointer4"]["revision_status"] == "verified" + assert sources["pointer27"]["revision"] == "5" * 40 + assert sources["direct4"]["repository"] == ( + "SimpleJev/JevAny-Qwen3.5-4B-Direct-Token-LoRA" + ) + assert sources["direct4"]["revision"] is None + assert sources["direct4"]["revision_status"] == "not_verified" + assert sources["direct4"]["revision_evidence"] is None + assert "do not prove an immutable public-repository revision" in ( + sources["direct4"]["limitation"] + ) + assert sources["direct4"]["evaluated_artifact_sha256"] == { + "adapter_model.safetensors": { + "sha256": "3" * 64, + "bytes": 123, + } + } + def test_build_results_rejects_component_count_drift(matrix): root, external = matrix @@ -286,6 +333,17 @@ def test_build_results_rejects_cross_run_dataset_hash_drift(matrix): build_results(root, external) +def test_build_results_rejects_release_catalog_without_canonical_repositories( + matrix, tmp_path +): + root, external = matrix + catalog = tmp_path / "model-catalog.json" + _write_json(catalog, {"release": "fixture", "released_models": []}) + + with pytest.raises(ValueError, match="missing canonical repositories"): + build_results(root, external, catalog) + + def test_synthetic_artifact_and_svg_are_written_only_to_requested_paths(matrix, tmp_path): pytest.importorskip("matplotlib") root, external = matrix diff --git a/tests/test_build_external_report_appendix.py b/tests/test_build_external_report_appendix.py index 43fffe1..ef2281f 100644 --- a/tests/test_build_external_report_appendix.py +++ b/tests/test_build_external_report_appendix.py @@ -2,6 +2,7 @@ import os import shutil from datetime import datetime, timezone +from pathlib import Path import pytest @@ -198,6 +199,13 @@ def _producer_metric(n, correct, excluded): def _producer_run(root, spec, ordinal): is_base = spec["weights"] == "frozen_base" + canonical_base = f"Qwen/{spec['family']}" + encoded_repository = spec["public_repository"].replace("/", "--") + checkpoint = ( + f"/fixture/models--{encoded_repository}/snapshots/{str(ordinal + 1) * 40}" + if spec["key"] != "direct4" + else f"/fixture/local-release/{spec['key']}" + ) components = ["choice"] if is_base else ["choice", "native"] model = f"producer-{spec['key']}" reports = {} @@ -251,8 +259,8 @@ def _producer_run(root, spec, ordinal): manifest = { "model": model, "source": { - "base": f"fixture/{spec['family']}" if is_base else None, - "checkpoint": None if is_base else f"fixture/{spec['key']}", + "base": spec["public_repository"] if is_base else None, + "checkpoint": None if is_base else checkpoint, }, "checkpoint_artifacts": None if is_base else { "requested": f"fixture/{spec['key']}", @@ -264,12 +272,17 @@ def _producer_run(root, spec, ordinal): }, }, }, - "base_loading": {"requested": spec["family"], "resolved": "/fixture/base"}, + "base_loading": { + "requested": spec["family"], + "resolved": "/fixture/base", + "canonical_base": canonical_base, + "canonical_revision": "a" * 40, + }, "predictor": { "method": "training-free exact choice-token projection", "adapter_applied": not is_base, "native_decision_mode": spec["native_decision_mode"], - "canonical_base": spec["family"], + "canonical_base": canonical_base, "canonical_revision": "a" * 40, }, "runtime": { @@ -511,3 +524,33 @@ def test_choice_artifact_requires_full_3220_to_be_unsupported(tmp_path): with pytest.raises(ValueError, match="must be explicitly unsupported"): load_choice_artifact(choice) + + +def test_published_report_uses_one_current_27b_release(): + report = Path(__file__).resolve().parents[1] / "reports" / "JevAny_Tech_Report.pdf" + pdf = PdfReader(report) + assert len(pdf.pages) == 23 + + expected = { + 1: ("86.04", "90.04"), + 4: ("44,319", "86.04", "90.04", "0.388", "0.195", "0.026"), + 7: ("86.04",), + 8: ("44,319", "39.43", "1,261.7", "2,082"), + } + stale = ( + "85.76", "90.48", "22,160", "18.83", "602.7", "1,423", + "0.392", "0.200", "0.030", + ) + for page_number, values in expected.items(): + text = pdf.pages[page_number - 1].extract_text() or "" + assert all(value in text for value in values) + assert all(value not in text for value in stale) + compute_text = pdf.pages[7].extract_text() or "" + assert "estimated cumulative seconds-per-record timing" in compute_text + boundary_text = (pdf.pages[9].extract_text() or "").replace("-\n", "-") + assert "The merged PDF is release-synchronized." in boundary_text + assert "The original PDF is pre-" not in boundary_text + + method_text = pdf.pages[21].extract_text() or "" + assert "current 27B release checkpoint: step 44,319" in method_text + assert "matched prompt-v2/runtime reruns" in method_text