ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,760 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build checkpoint-free retained training evidence for Experiment 8-3.
|
||||
|
||||
The book already contains the author's historical six-cell evaluation report:
|
||||
original versus QK-Norm + Muon at pretrain, SFT, and DPO. This program does
|
||||
not pretend to rerun the GPU training job. It extracts every saved generation,
|
||||
submits a preregistered stage-balanced subset to an arm-blind external judge,
|
||||
and binds the raw report, judge receipts, immutable future-reproduction source
|
||||
and dataset revisions, environment lock, findings, and limitations into a
|
||||
content-hashed evidence package. Checkpoints are intentionally not published
|
||||
and are not an acceptance artifact for book training experiments.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
EXPERIMENT_DIR = HERE.parent
|
||||
REPO_ROOT = EXPERIMENT_DIR.parents[1]
|
||||
REPORT_PATH = EXPERIMENT_DIR / "README.md"
|
||||
RUNS_DIR = HERE / "runs"
|
||||
LATEST_PATH = HERE / "latest.json"
|
||||
|
||||
DEFAULT_RUN_ID = "exp8-3-training-report-20260731-v1"
|
||||
DEFAULT_ENDPOINT = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
|
||||
DEFAULT_MODEL = "doubao-seed-1-6-250615"
|
||||
BLIND_SEED = 730731
|
||||
|
||||
SOURCE_REVISION = "8bdc5d97d5845a8c1ac2ed56a5b8b4c0d0fb0795"
|
||||
DATASET_REVISION = "84983ed4dec7836d240577760c1d6be5d4cabcf9"
|
||||
SOURCE_FILES = {
|
||||
"model/model_minimind.py": "2d33988711c704be6a22c4c61489b23106a2340a7cb8b97ebe3e40f30819cbb0",
|
||||
"model/tokenizer.json": "e489029175fb3f94b8211a120a72a2ee41a664db65b828d077c7bde989c845a9",
|
||||
"model/tokenizer_config.json": "190cc4738bac3b6f6b563376019c581b320fdb0260a03b9d5ab806296c8c6bb8",
|
||||
"dataset/lm_dataset.py": "213726b1781289548784220b2b2db48fe97d84f3b67ac0cd70186cbbcb7b5d2c",
|
||||
"trainer/muon.py": "00c2c6a225edeb55433df0724c3c74f6ff98ac4b2cc73c4aafcff686824f6267",
|
||||
"trainer/train_pretrain.py": "ddd122645a9f1043bc8dac69a81ac51d2df95df8745d25faed7963d38fedc328",
|
||||
"trainer/train_pretrain_muon.py": "fc83d07754ec3a8c156b6b8bfc0fd4326edecb72efabc5e08ae4ff5e3a7029bc",
|
||||
"trainer/train_full_sft.py": "a57422f1df80bf2867f31f3b4a646a92ac7f66729e98a3b32cd1ec4d6780cb8b",
|
||||
"trainer/train_full_sft_muon.py": "acd0b7db5b1d8b25d3c3103f92d68a7d381f9322a1b33bbec34be3d005930bad",
|
||||
"trainer/train_dpo.py": "97f2c31cc8bc21a777e2efcb5e2fa35a49e4e9e3698db120148f8a0b2f678449",
|
||||
"eval_model.py": "43930a4b55048a4a3ffa17eb78ae67d59582d639aa9365f0bbf41ba149128af8",
|
||||
"requirements.txt": "23f4cea09281765eec7cf03e28231425638e8e42580f418781fed166b75af968",
|
||||
}
|
||||
DATASET_FILES = {
|
||||
"pretrain_hq.jsonl": {
|
||||
"lfs_sha256": "9801b0d2210c61c2e4bc130f6dc4b3c870698a88d04af8f103c23dd5f0ce2440",
|
||||
"bytes": 1_669_750_047,
|
||||
},
|
||||
"sft_512.jsonl": {
|
||||
"lfs_sha256": "053b7d09574e48a86232e929211434ff9e5016c6ed13312e63687dd52edcbebf",
|
||||
"bytes": 7_531_517_862,
|
||||
},
|
||||
"dpo.jsonl": {
|
||||
"lfs_sha256": "ee934a8a455ccc99d1334d63e1254dd1d64f497fd067cfcbb71e3043f5b46768",
|
||||
"bytes": 53_653_322,
|
||||
},
|
||||
}
|
||||
|
||||
ARMS = ("original", "qk_norm_muon")
|
||||
STAGES = ("pretrain", "sft", "dpo")
|
||||
EXPECTED_COUNTS = {
|
||||
("original", "pretrain"): 7,
|
||||
("original", "sft"): 8,
|
||||
("original", "dpo"): 9,
|
||||
("qk_norm_muon", "pretrain"): 7,
|
||||
("qk_norm_muon", "sft"): 9,
|
||||
("qk_norm_muon", "dpo"): 9,
|
||||
}
|
||||
SELECTED_CASES = (
|
||||
{"case_id": 1, "stage": "pretrain", "keyword": "highest mountain", "task": "Continue the prompt by identifying the highest mountain in the world accurately."},
|
||||
{"case_id": 2, "stage": "pretrain", "keyword": "carbon dioxide", "task": "Continue the prompt with an accurate statement about carbon dioxide in air."},
|
||||
{"case_id": 3, "stage": "sft", "keyword": "speed of light", "task": "Explain the physical concept of the speed of light in detail."},
|
||||
{"case_id": 4, "stage": "sft", "keyword": "how to understand chatgpt", "task": "Explain what ChatGPT is and how it works."},
|
||||
{"case_id": 5, "stage": "sft", "keyword": "history of the united states", "task": "Introduce the history of the United States."},
|
||||
{"case_id": 6, "stage": "dpo", "keyword": "speed of light", "task": "Explain the physical concept of the speed of light in detail."},
|
||||
{"case_id": 7, "stage": "dpo", "keyword": "how to understand chatgpt", "task": "Explain what ChatGPT is and how it works."},
|
||||
{"case_id": 8, "stage": "dpo", "keyword": "history of the united states", "task": "Introduce the history of the United States."},
|
||||
)
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def canonical_json_bytes(value: Any) -> bytes:
|
||||
return (json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n").encode()
|
||||
|
||||
|
||||
def write_json(path: Path, value: Any) -> None:
|
||||
path.write_bytes(canonical_json_bytes(value))
|
||||
|
||||
|
||||
def utc_now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def _terminal_transcript(section: str) -> tuple[str, str]:
|
||||
"""Return the terminal header and prompt/output transcript from one cell.
|
||||
|
||||
The historical Markdown has one stray closing fence immediately before the
|
||||
improved-SFT Lu Xun answer. Treat fences as presentation markup rather
|
||||
than semantic delimiters so that the retained answer is not silently lost.
|
||||
"""
|
||||
|
||||
prompt_position = section.find("👶:")
|
||||
if prompt_position < 0:
|
||||
raise ValueError("model section has no user prompt marker")
|
||||
header = section[:prompt_position].replace("```", "").strip()
|
||||
transcript = section[prompt_position:]
|
||||
analysis = re.search(r"(?m)^\*\*[^\n]*Analysis[^\n]*\*\*:?\s*$", transcript)
|
||||
if analysis:
|
||||
transcript = transcript[: analysis.start()]
|
||||
transcript = re.sub(r"(?m)^```\s*$", "", transcript).strip()
|
||||
return header, transcript
|
||||
|
||||
|
||||
def _parse_pairs(transcript: str) -> list[dict[str, str]]:
|
||||
pattern = re.compile(
|
||||
r"^👶:\s*(.*?)\n🤖️:\s*(.*?)(?=\n(?:\s*\n)*👶:|\Z)",
|
||||
flags=re.DOTALL | re.MULTILINE,
|
||||
)
|
||||
pairs = []
|
||||
for match in pattern.finditer(transcript):
|
||||
prompt = match.group(1).strip()
|
||||
output = match.group(2).strip()
|
||||
if not prompt or not output:
|
||||
raise ValueError("empty prompt or output in historical transcript")
|
||||
pairs.append({"prompt": prompt, "output": output})
|
||||
return pairs
|
||||
|
||||
|
||||
def parse_retained_outputs(report_path: Path = REPORT_PATH) -> dict[str, Any]:
|
||||
"""Extract all six historical LLM evaluation cells from the bilingual report."""
|
||||
|
||||
text = report_path.read_text(encoding="utf-8")
|
||||
start = text.index("## Language Model Training Results Analysis")
|
||||
end = text.index("# Analysis of Vision-Language Model Training Results", start)
|
||||
llm_text = text[start:end]
|
||||
arm_markers = {
|
||||
"original": "## Without Muon Optimizer (Original Architecture)",
|
||||
"qk_norm_muon": "## With Muon Optimizer and QK Norm (Improved Architecture)",
|
||||
}
|
||||
cells: list[dict[str, Any]] = []
|
||||
for arm_index, arm in enumerate(ARMS):
|
||||
arm_start = llm_text.index(arm_markers[arm])
|
||||
arm_end = (
|
||||
llm_text.index(arm_markers[ARMS[arm_index + 1]], arm_start)
|
||||
if arm_index + 1 < len(ARMS)
|
||||
else len(llm_text)
|
||||
)
|
||||
arm_text = llm_text[arm_start:arm_end]
|
||||
for stage_index, stage in enumerate(STAGES):
|
||||
heading = {"pretrain": "### Pretrain Model", "sft": "### SFT Model", "dpo": "### DPO Model"}[stage]
|
||||
cell_start = arm_text.index(heading)
|
||||
next_positions = [
|
||||
arm_text.find(next_heading, cell_start + len(heading))
|
||||
for next_heading in ("### Pretrain Model", "### SFT Model", "### DPO Model")
|
||||
]
|
||||
next_positions = [position for position in next_positions if position >= 0]
|
||||
cell_end = min(next_positions) if next_positions else len(arm_text)
|
||||
header, transcript = _terminal_transcript(arm_text[cell_start:cell_end])
|
||||
pairs = _parse_pairs(transcript)
|
||||
expected = EXPECTED_COUNTS[(arm, stage)]
|
||||
if len(pairs) != expected:
|
||||
raise ValueError(f"{arm}/{stage}: expected {expected} pairs, found {len(pairs)}")
|
||||
cells.append(
|
||||
{
|
||||
"arm": arm,
|
||||
"stage": stage,
|
||||
"terminal_header": header,
|
||||
"pair_count": len(pairs),
|
||||
"pairs": pairs,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"schema_version": "exp8-3-retained-outputs-v1",
|
||||
"experiment": "8-3",
|
||||
"source_report": str(report_path.relative_to(REPO_ROOT)),
|
||||
"source_report_sha256": sha256_file(report_path),
|
||||
"arms": list(ARMS),
|
||||
"stages": list(STAGES),
|
||||
"cell_count": len(cells),
|
||||
"output_count": sum(cell["pair_count"] for cell in cells),
|
||||
"cells": cells,
|
||||
}
|
||||
|
||||
|
||||
def _find_pair(retained: dict[str, Any], arm: str, stage: str, keyword: str) -> dict[str, str]:
|
||||
cell = next(cell for cell in retained["cells"] if cell["arm"] == arm and cell["stage"] == stage)
|
||||
matches = [pair for pair in cell["pairs"] if keyword in pair["prompt"].lower()]
|
||||
if len(matches) != 1:
|
||||
raise ValueError(f"{arm}/{stage}/{keyword}: expected one prompt, found {len(matches)}")
|
||||
return matches[0]
|
||||
|
||||
|
||||
def selected_comparisons(retained: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
comparisons = []
|
||||
for case in SELECTED_CASES:
|
||||
rows = {
|
||||
arm: _find_pair(retained, arm, case["stage"], case["keyword"])
|
||||
for arm in ARMS
|
||||
}
|
||||
comparisons.append({**case, "arms": rows})
|
||||
return comparisons
|
||||
|
||||
|
||||
def blind_mapping(case_id: int) -> dict[str, str]:
|
||||
arms = list(ARMS)
|
||||
random.Random(BLIND_SEED + case_id).shuffle(arms)
|
||||
return dict(zip(("A", "B"), arms, strict=True))
|
||||
|
||||
|
||||
def judge_payload(comparison: dict[str, Any], mapping: dict[str, str], model: str) -> dict[str, Any]:
|
||||
candidates = {
|
||||
label: {
|
||||
"historical_prompt": comparison["arms"][arm]["prompt"],
|
||||
"historical_output": comparison["arms"][arm]["output"],
|
||||
}
|
||||
for label, arm in mapping.items()
|
||||
}
|
||||
required = {
|
||||
"case_id": comparison["case_id"],
|
||||
"candidates": {
|
||||
label: {
|
||||
"language_fluency": "number 0-5",
|
||||
"instruction_following": "number 0-5",
|
||||
"factuality": "number 0-5",
|
||||
"factual_errors": ["specific material errors; empty only if none"],
|
||||
"rationale": "brief evidence-based explanation",
|
||||
}
|
||||
for label in ("A", "B")
|
||||
},
|
||||
"winner": "A, B, or tie",
|
||||
}
|
||||
return {
|
||||
"model": model,
|
||||
"temperature": 0,
|
||||
"response_format": {"type": "json_object"},
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": (
|
||||
"You are an independent evaluator of two anonymous small-language-model outputs. "
|
||||
"Do not infer model identity, architecture, optimizer, or training stage. Score only "
|
||||
"the supplied text. Penalize hallucinations, unsafe medical specificity, repetition, "
|
||||
"and non-answers. Return one JSON object with exactly the requested fields."
|
||||
),
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": json.dumps(
|
||||
{
|
||||
"case_id": comparison["case_id"],
|
||||
"task": comparison["task"],
|
||||
"rubric": {
|
||||
"language_fluency": "0 unreadable; 3 understandable with defects; 5 coherent and natural",
|
||||
"instruction_following": "0 non-answer; 3 partial; 5 directly and fully answers",
|
||||
"factuality": "0 dominated by falsehoods; 3 mixed/minor errors; 5 no material error",
|
||||
},
|
||||
"candidates": candidates,
|
||||
"required_json_shape": required,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def extract_json_object(content: str) -> dict[str, Any]:
|
||||
stripped = content.strip()
|
||||
if stripped.startswith("```"):
|
||||
stripped = re.sub(r"^```(?:json)?\s*", "", stripped)
|
||||
stripped = re.sub(r"\s*```$", "", stripped)
|
||||
parsed = json.loads(stripped)
|
||||
if not isinstance(parsed, dict):
|
||||
raise ValueError("judge content must decode to an object")
|
||||
return parsed
|
||||
|
||||
|
||||
def validate_judgment(judgment: dict[str, Any], case_id: int) -> None:
|
||||
if str(judgment.get("case_id")) != str(case_id):
|
||||
raise ValueError(
|
||||
f"judge returned the wrong case_id: expected {case_id}, got {judgment.get('case_id')!r}"
|
||||
)
|
||||
candidates = judgment.get("candidates")
|
||||
if not isinstance(candidates, dict) or set(candidates) != {"A", "B"}:
|
||||
raise ValueError("judge must score A and B exactly")
|
||||
for label in ("A", "B"):
|
||||
row = candidates[label]
|
||||
if not isinstance(row, dict):
|
||||
raise ValueError(f"candidate {label} score must be an object")
|
||||
for metric in ("language_fluency", "instruction_following", "factuality"):
|
||||
score = row.get(metric)
|
||||
if not isinstance(score, (int, float)) or isinstance(score, bool) or not 0 <= score <= 5:
|
||||
raise ValueError(f"candidate {label} has invalid {metric}: {score!r}")
|
||||
if not isinstance(row.get("factual_errors"), list):
|
||||
raise ValueError(f"candidate {label} factual_errors must be a list")
|
||||
if not isinstance(row.get("rationale"), str) or not row["rationale"].strip():
|
||||
raise ValueError(f"candidate {label} rationale is missing")
|
||||
if judgment.get("winner") not in {"A", "B", "tie"}:
|
||||
raise ValueError("judge winner must be A, B, or tie")
|
||||
|
||||
|
||||
def call_judge(
|
||||
comparison: dict[str, Any], *, endpoint: str, model: str, api_key: str, timeout: float
|
||||
) -> dict[str, Any]:
|
||||
mapping = blind_mapping(comparison["case_id"])
|
||||
payload = judge_payload(comparison, mapping, model)
|
||||
request = urllib.request.Request(
|
||||
endpoint,
|
||||
data=json.dumps(payload, ensure_ascii=False).encode(),
|
||||
method="POST",
|
||||
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
||||
)
|
||||
started = time.perf_counter()
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=timeout) as response:
|
||||
raw_body = response.read()
|
||||
http_status = response.status
|
||||
except urllib.error.HTTPError as exc:
|
||||
body = exc.read().decode(errors="replace")
|
||||
raise RuntimeError(f"judge HTTP {exc.code}: {body[:500]}") from exc
|
||||
latency_ms = round((time.perf_counter() - started) * 1000, 3)
|
||||
raw_response = json.loads(raw_body)
|
||||
content = raw_response["choices"][0]["message"]["content"]
|
||||
judgment = extract_json_object(content)
|
||||
validate_judgment(judgment, comparison["case_id"])
|
||||
response_id = raw_response.get("id")
|
||||
usage = raw_response.get("usage")
|
||||
if not isinstance(response_id, str) or not response_id:
|
||||
raise ValueError("judge response has no response ID")
|
||||
if not isinstance(usage, dict) or not isinstance(usage.get("total_tokens"), int):
|
||||
raise ValueError("judge response has no complete usage object")
|
||||
return {
|
||||
"case_id": comparison["case_id"],
|
||||
"stage": comparison["stage"],
|
||||
"keyword": comparison["keyword"],
|
||||
"task": comparison["task"],
|
||||
"provider": "ark",
|
||||
"endpoint": endpoint,
|
||||
"credential_env": "ARK_API_KEY",
|
||||
"credential_headers_retained": False,
|
||||
"blind_seed": BLIND_SEED,
|
||||
"blind_map": mapping,
|
||||
"request": payload,
|
||||
"http_status": http_status,
|
||||
"response": raw_response,
|
||||
"response_id": response_id,
|
||||
"usage": usage,
|
||||
"latency_ms": latency_ms,
|
||||
"judgment": judgment,
|
||||
}
|
||||
|
||||
|
||||
def reproduction_contract() -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": "exp8-3-reproduction-contract-v1",
|
||||
"experiment": "8-3",
|
||||
"historical_evidence_boundary": {
|
||||
"historical_training_executed": True,
|
||||
"six_historical_evaluation_transcripts_retained": True,
|
||||
"historical_source_revision_retained": False,
|
||||
"historical_dataset_hashes_retained": False,
|
||||
"historical_checkpoint_hashes_retained": False,
|
||||
"historical_stepwise_training_logs_retained": False,
|
||||
"claim": (
|
||||
"The author's retained report supports that original and QK-Norm+Muon 104.03M models "
|
||||
"were evaluated after pretrain, SFT, and DPO. It does not establish byte identity of the "
|
||||
"historical checkpoints, datasets, source checkout, or every loss point."
|
||||
),
|
||||
},
|
||||
"future_reproduction": {
|
||||
"source": {
|
||||
"repository": "bojieli/minimind",
|
||||
"revision": SOURCE_REVISION,
|
||||
"selected_at": "2026-07-31",
|
||||
"not_claimed_as_historical_revision": True,
|
||||
"file_sha256": SOURCE_FILES,
|
||||
},
|
||||
"dataset": {
|
||||
"repository": "jingyaogong/minimind_dataset",
|
||||
"revision": DATASET_REVISION,
|
||||
"selected_at": "2026-07-31",
|
||||
"not_claimed_as_historical_revision": True,
|
||||
"files": DATASET_FILES,
|
||||
},
|
||||
"commands": {
|
||||
"original_pretrain": "torchrun --nproc_per_node=8 trainer/train_pretrain.py --epochs 10 --hidden_size 768 --num_hidden_layers 16 --data_path dataset/pretrain_hq.jsonl --use_wandb",
|
||||
"improved_pretrain": "torchrun --nproc_per_node=8 trainer/train_pretrain_muon.py --epochs 10 --hidden_size 768 --num_hidden_layers 16 --data_path dataset/pretrain_hq.jsonl --use_wandb",
|
||||
"original_sft": "torchrun --nproc_per_node=8 trainer/train_full_sft.py --epochs 2 --hidden_size 768 --num_hidden_layers 16 --data_path dataset/sft_512.jsonl --use_wandb",
|
||||
"improved_sft": "torchrun --nproc_per_node=8 trainer/train_full_sft_muon.py --epochs 2 --hidden_size 768 --num_hidden_layers 16 --data_path dataset/sft_512.jsonl --use_wandb",
|
||||
"original_dpo": "torchrun --nproc_per_node=8 trainer/train_dpo.py --epochs 2 --hidden_size 768 --num_hidden_layers 16 --init_from out/full_sft_768.pth --data_path dataset/dpo.jsonl --use_wandb",
|
||||
"improved_dpo": "torchrun --nproc_per_node=8 trainer/train_dpo.py --epochs 2 --hidden_size 768 --num_hidden_layers 16 --init_from out/full_sft_muon_768.pth --data_path dataset/dpo.jsonl --use_wandb",
|
||||
},
|
||||
"environment": {
|
||||
"book_pyproject": "pyproject.toml",
|
||||
"book_lock": "uv.lock",
|
||||
"install": "uv sync --locked --python 3.12 --extra ch7 --extra dev",
|
||||
"boundary": (
|
||||
"The book lock freezes a future software environment. The pinned upstream requirements "
|
||||
"file itself is unversioned, and the future GPU/CUDA stack has not been exercised here."
|
||||
),
|
||||
},
|
||||
},
|
||||
"model_and_training": {
|
||||
"reported_parameter_count_millions": 104.03,
|
||||
"architecture": {"hidden_size": 768, "layers": 16, "sequence_length": 512},
|
||||
"stages": list(STAGES),
|
||||
"arms": list(ARMS),
|
||||
"source_verified_mechanisms": {
|
||||
"qk_norm_before_rope": True,
|
||||
"muon_for_two_dimensional_non_embedding_weights": True,
|
||||
"adamw_for_embeddings_norms_and_lm_head": True,
|
||||
"dpo_uses_adamw_from_arm_specific_sft_checkpoint": True,
|
||||
},
|
||||
"reported_scalars_without_stepwise_logs": {
|
||||
"steps_to_loss_3_original": 36,
|
||||
"steps_to_loss_3_qk_norm_muon": 12,
|
||||
"final_loss_original": 2.0,
|
||||
"final_loss_qk_norm_muon": 1.7,
|
||||
"eight_rtx_4090_pretrain_hours": 6,
|
||||
"eight_rtx_4090_sft_hours": 8,
|
||||
},
|
||||
},
|
||||
"checkpoint_policy": {
|
||||
"distributed_with_book": False,
|
||||
"acceptance_artifact": False,
|
||||
"required_artifact": "reproducible evidence-backed training report",
|
||||
"reason": "Training checkpoints are intentionally not distributed to readers.",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def mean(values: list[float]) -> float:
|
||||
return round(sum(values) / len(values), 4)
|
||||
|
||||
|
||||
def summarize(retained: dict[str, Any], receipts: list[dict[str, Any]], contract: dict[str, Any]) -> dict[str, Any]:
|
||||
metrics = ("language_fluency", "instruction_following", "factuality")
|
||||
rows: dict[int, dict[str, dict[str, Any]]] = {}
|
||||
wins = {"original": 0, "qk_norm_muon": 0, "tie": 0}
|
||||
for receipt in receipts:
|
||||
reverse = receipt["blind_map"]
|
||||
rows[receipt["case_id"]] = {
|
||||
reverse[label]: score for label, score in receipt["judgment"]["candidates"].items()
|
||||
}
|
||||
winner = receipt["judgment"]["winner"]
|
||||
wins["tie" if winner == "tie" else reverse[winner]] += 1
|
||||
|
||||
arm_averages = {}
|
||||
stage_averages = {}
|
||||
for arm in ARMS:
|
||||
arm_scores = [rows[case["case_id"]][arm] for case in SELECTED_CASES]
|
||||
arm_averages[arm] = {
|
||||
metric: mean([float(row[metric]) for row in arm_scores]) for metric in metrics
|
||||
}
|
||||
arm_averages[arm]["overall"] = mean(
|
||||
[float(row[metric]) for row in arm_scores for metric in metrics]
|
||||
)
|
||||
stage_averages[arm] = {}
|
||||
for stage in STAGES:
|
||||
stage_scores = [
|
||||
rows[case["case_id"]][arm] for case in SELECTED_CASES if case["stage"] == stage
|
||||
]
|
||||
stage_averages[arm][stage] = {
|
||||
metric: mean([float(row[metric]) for row in stage_scores]) for metric in metrics
|
||||
}
|
||||
stage_averages[arm][stage]["overall"] = mean(
|
||||
[float(row[metric]) for row in stage_scores for metric in metrics]
|
||||
)
|
||||
|
||||
findings = {
|
||||
"blind_judge_overall_delta_qk_norm_muon_minus_original": round(
|
||||
arm_averages["qk_norm_muon"]["overall"] - arm_averages["original"]["overall"], 4
|
||||
),
|
||||
"blind_judge_prefers_qk_norm_muon_overall": (
|
||||
arm_averages["qk_norm_muon"]["overall"] > arm_averages["original"]["overall"]
|
||||
),
|
||||
"wins": wins,
|
||||
"reported_loss_comparison_retained_but_not_independently_recomputed": True,
|
||||
}
|
||||
acceptance = {
|
||||
"raw_historical_report_hashed": bool(retained["source_report_sha256"]),
|
||||
"all_six_arm_stage_cells_retained": retained["cell_count"] == 6,
|
||||
"all_expected_outputs_retained": retained["output_count"] == sum(EXPECTED_COUNTS.values()),
|
||||
"pretrain_sft_and_dpo_compared": set(retained["stages"]) == set(STAGES),
|
||||
"original_and_qk_norm_muon_compared": set(retained["arms"]) == set(ARMS),
|
||||
"eight_stage_balanced_blind_judgments": len(receipts) == len(SELECTED_CASES),
|
||||
"raw_judge_requests_responses_ids_usage_latency_retained": all(
|
||||
receipt["response_id"]
|
||||
and receipt["usage"].get("total_tokens", 0) > 0
|
||||
and receipt["latency_ms"] > 0
|
||||
for receipt in receipts
|
||||
),
|
||||
"immutable_source_revision_and_file_hashes_frozen": bool(SOURCE_REVISION and SOURCE_FILES),
|
||||
"immutable_dataset_revision_lfs_hashes_and_sizes_frozen": bool(DATASET_REVISION and DATASET_FILES),
|
||||
"future_reproduction_commands_declared": len(contract["future_reproduction"]["commands"]) == 6,
|
||||
"historical_provenance_limitations_explicit": (
|
||||
contract["historical_evidence_boundary"]["historical_checkpoint_hashes_retained"] is False
|
||||
and contract["historical_evidence_boundary"]["historical_stepwise_training_logs_retained"] is False
|
||||
),
|
||||
"reported_loss_claims_qualified": findings[
|
||||
"reported_loss_comparison_retained_but_not_independently_recomputed"
|
||||
],
|
||||
"checkpoints_not_an_acceptance_artifact": (
|
||||
contract["checkpoint_policy"]["acceptance_artifact"] is False
|
||||
),
|
||||
}
|
||||
passed = all(acceptance.values())
|
||||
return {
|
||||
"schema_version": "exp8-3-summary-v1",
|
||||
"experiment": "8-3",
|
||||
"status": "passed" if passed else "failed",
|
||||
"judge": {
|
||||
"provider": "ark",
|
||||
"model": receipts[0]["request"]["model"],
|
||||
"calls": len(receipts),
|
||||
"response_ids": [receipt["response_id"] for receipt in receipts],
|
||||
"total_tokens": sum(receipt["usage"]["total_tokens"] for receipt in receipts),
|
||||
"total_latency_ms": round(sum(receipt["latency_ms"] for receipt in receipts), 3),
|
||||
"blind_seed": BLIND_SEED,
|
||||
},
|
||||
"retained": {
|
||||
"cells": retained["cell_count"],
|
||||
"outputs": retained["output_count"],
|
||||
"selected_comparisons": len(receipts),
|
||||
},
|
||||
"arm_averages": arm_averages,
|
||||
"stage_averages": stage_averages,
|
||||
"per_case_arm_scores": rows,
|
||||
"scientific_findings": findings,
|
||||
"acceptance": {**acceptance, "passed": passed},
|
||||
"limitations": [
|
||||
"Historical checkpoints are intentionally not distributed and were not recreated in this audit.",
|
||||
"The historical source revision, dataset byte identities, RNG state, and stepwise loss logs were not retained.",
|
||||
"Frozen source/data revisions and the book lock define a future reproduction contract, not historical provenance.",
|
||||
"The independent judge covers eight preregistered comparisons; all other retained outputs remain available for inspection.",
|
||||
"The historical outputs are English translations in a bilingual report, so translation may affect the judge scores.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def render_report(summary: dict[str, Any]) -> str:
|
||||
averages = summary["arm_averages"]
|
||||
findings = summary["scientific_findings"]
|
||||
return "\n".join(
|
||||
[
|
||||
"# Experiment 8-3 retained-training-report audit",
|
||||
"",
|
||||
"## Result",
|
||||
"",
|
||||
f"Status: **{summary['status']}**. The historical report retains "
|
||||
f"{summary['retained']['outputs']} outputs across the original and QK-Norm + Muon arms "
|
||||
"after pretrain, SFT, and DPO. Eight preregistered arm-blind comparisons were judged "
|
||||
"from raw retained text by an independent ARK model.",
|
||||
"",
|
||||
"| Arm | Fluency | Instruction | Factuality | Overall |",
|
||||
"| --- | ---: | ---: | ---: | ---: |",
|
||||
f"| Original | {averages['original']['language_fluency']:.4f} | "
|
||||
f"{averages['original']['instruction_following']:.4f} | "
|
||||
f"{averages['original']['factuality']:.4f} | {averages['original']['overall']:.4f} |",
|
||||
f"| QK-Norm + Muon | {averages['qk_norm_muon']['language_fluency']:.4f} | "
|
||||
f"{averages['qk_norm_muon']['instruction_following']:.4f} | "
|
||||
f"{averages['qk_norm_muon']['factuality']:.4f} | {averages['qk_norm_muon']['overall']:.4f} |",
|
||||
"",
|
||||
f"Observed blind-judge overall delta: **{findings['blind_judge_overall_delta_qk_norm_muon_minus_original']:+.4f}**. "
|
||||
f"Pairwise decisions: {findings['wins']}.",
|
||||
"",
|
||||
"The report's loss claims (3.0 reached at 36 versus 12 reported steps; final loss 2.0 "
|
||||
"versus 1.7) are retained as author-reported observations, not independently recomputed "
|
||||
"measurements, because the historical stepwise logs were not preserved.",
|
||||
"",
|
||||
"## Provenance and reproduction boundary",
|
||||
"",
|
||||
"`reproduction_contract.json` freezes the MiniMind source revision, hashes the relevant "
|
||||
"source files, freezes a dataset revision with the three Git-LFS object hashes and sizes, "
|
||||
"and records all six future reproduction commands. These pins were selected for future "
|
||||
"reproduction and are not represented as the exact historical checkout.",
|
||||
"",
|
||||
"Training checkpoints remain local by book policy and are not an acceptance artifact. "
|
||||
"The accepted artifact is this content-hashed training report, its raw retained outputs, "
|
||||
"raw independent-judge receipts, and explicit limitations.",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def input_record(path: Path) -> dict[str, Any]:
|
||||
return {
|
||||
"path": str(path.relative_to(REPO_ROOT)),
|
||||
"sha256": sha256_file(path),
|
||||
"bytes": path.stat().st_size,
|
||||
}
|
||||
|
||||
|
||||
def artifact_record(path: Path, run_dir: Path) -> dict[str, Any]:
|
||||
return {
|
||||
"path": str(path.relative_to(run_dir)),
|
||||
"sha256": sha256_file(path),
|
||||
"bytes": path.stat().st_size,
|
||||
}
|
||||
|
||||
|
||||
def build_manifest(run_id: str, run_dir: Path, summary: dict[str, Any]) -> dict[str, Any]:
|
||||
inputs = [
|
||||
input_record(REPORT_PATH),
|
||||
input_record(REPO_ROOT / "pyproject.toml"),
|
||||
input_record(REPO_ROOT / "uv.lock"),
|
||||
input_record(HERE / "run_training_report_audit.py"),
|
||||
input_record(HERE / "validate_evidence.py"),
|
||||
]
|
||||
artifact_paths = [
|
||||
run_dir / "retained_outputs.json",
|
||||
run_dir / "reproduction_contract.json",
|
||||
run_dir / "judge_receipts.json",
|
||||
run_dir / "summary.json",
|
||||
run_dir / "report.md",
|
||||
]
|
||||
return {
|
||||
"schema_version": "exp8-3-manifest-v1",
|
||||
"experiment": "8-3",
|
||||
"run_id": run_id,
|
||||
"created_at": utc_now(),
|
||||
"status": summary["status"],
|
||||
"run_dir": str(run_dir.relative_to(EXPERIMENT_DIR)),
|
||||
"inputs": inputs,
|
||||
"artifacts": [artifact_record(path, run_dir) for path in artifact_paths],
|
||||
"acceptance": summary["acceptance"],
|
||||
"checkpoint_policy": "not distributed; not an acceptance artifact",
|
||||
}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--run-id", default=DEFAULT_RUN_ID)
|
||||
parser.add_argument("--endpoint", default=os.getenv("ARK_BASE_URL", DEFAULT_ENDPOINT))
|
||||
parser.add_argument("--model", default=os.getenv("ARK_MODEL", DEFAULT_MODEL))
|
||||
parser.add_argument("--api-key-env", default="ARK_API_KEY")
|
||||
parser.add_argument("--timeout", type=float, default=180.0)
|
||||
parser.add_argument("--concurrency", type=int, default=8)
|
||||
parser.add_argument(
|
||||
"--refresh-manifest",
|
||||
action="store_true",
|
||||
help="Rehash an existing run after source-only corrections; makes no provider call.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
if not re.fullmatch(r"[A-Za-z0-9._-]+", args.run_id):
|
||||
raise SystemExit("run ID may contain only letters, digits, dot, underscore, and hyphen")
|
||||
run_dir = RUNS_DIR / args.run_id
|
||||
if args.refresh_manifest:
|
||||
if not run_dir.is_dir():
|
||||
raise SystemExit(f"cannot refresh missing run: {run_dir}")
|
||||
stored_retained = json.loads(
|
||||
(run_dir / "retained_outputs.json").read_text(encoding="utf-8")
|
||||
)
|
||||
current_retained = parse_retained_outputs()
|
||||
# Documentation around the raw transcripts may change, but a refresh
|
||||
# must never silently replace the generations that were judged.
|
||||
if stored_retained.get("cells") != current_retained.get("cells"):
|
||||
raise SystemExit(
|
||||
"refusing manifest refresh because retained historical outputs changed"
|
||||
)
|
||||
stored_retained["source_report_sha256"] = current_retained[
|
||||
"source_report_sha256"
|
||||
]
|
||||
write_json(run_dir / "retained_outputs.json", stored_retained)
|
||||
summary = json.loads((run_dir / "summary.json").read_text(encoding="utf-8"))
|
||||
write_json(run_dir / "manifest.json", build_manifest(args.run_id, run_dir, summary))
|
||||
latest = {
|
||||
"schema_version": "exp8-3-latest-v1",
|
||||
"experiment": "8-3",
|
||||
"run_id": args.run_id,
|
||||
"status": summary["status"],
|
||||
"run_dir": str(run_dir.relative_to(EXPERIMENT_DIR)),
|
||||
"manifest_sha256": sha256_file(run_dir / "manifest.json"),
|
||||
}
|
||||
write_json(LATEST_PATH, latest)
|
||||
print(json.dumps(latest, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
if run_dir.exists():
|
||||
raise SystemExit(f"refusing to overwrite existing run: {run_dir}")
|
||||
api_key = os.getenv(args.api_key_env)
|
||||
if not api_key:
|
||||
raise SystemExit(f"missing required credential environment variable: {args.api_key_env}")
|
||||
|
||||
retained = parse_retained_outputs()
|
||||
comparisons = selected_comparisons(retained)
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=args.concurrency) as pool:
|
||||
futures = {
|
||||
comparison["case_id"]: pool.submit(
|
||||
call_judge,
|
||||
comparison,
|
||||
endpoint=args.endpoint,
|
||||
model=args.model,
|
||||
api_key=api_key,
|
||||
timeout=args.timeout,
|
||||
)
|
||||
for comparison in comparisons
|
||||
}
|
||||
receipts = [futures[case_id].result() for case_id in sorted(futures)]
|
||||
|
||||
contract = reproduction_contract()
|
||||
summary = summarize(retained, receipts, contract)
|
||||
run_dir.mkdir(parents=True)
|
||||
write_json(run_dir / "retained_outputs.json", retained)
|
||||
write_json(
|
||||
run_dir / "judge_receipts.json",
|
||||
{
|
||||
"schema_version": "exp8-3-judge-receipts-v1",
|
||||
"experiment": "8-3",
|
||||
"credential_headers_retained": False,
|
||||
"calls": receipts,
|
||||
},
|
||||
)
|
||||
write_json(run_dir / "reproduction_contract.json", contract)
|
||||
write_json(run_dir / "summary.json", summary)
|
||||
(run_dir / "report.md").write_text(render_report(summary), encoding="utf-8")
|
||||
write_json(run_dir / "manifest.json", build_manifest(args.run_id, run_dir, summary))
|
||||
latest = {
|
||||
"schema_version": "exp8-3-latest-v1",
|
||||
"experiment": "8-3",
|
||||
"run_id": args.run_id,
|
||||
"status": summary["status"],
|
||||
"run_dir": str(run_dir.relative_to(EXPERIMENT_DIR)),
|
||||
"manifest_sha256": sha256_file(run_dir / "manifest.json"),
|
||||
}
|
||||
write_json(LATEST_PATH, latest)
|
||||
print(json.dumps(latest, indent=2, sort_keys=True))
|
||||
return 0 if summary["status"] == "passed" else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user