core/benchmarks/footprint.py
Shay 64c5bc4619 feat(epistemic): truth-seeking schema audit — 3 leaks closed, 4 new lanes, 3 new invariants
Audit of the one-mutation-path invariant (ADR-0021 §3) found three leaks
where pack authority or session-state writes could substitute for coherence
judgment. All three landed fixes or partial closures in this push.

Leaks closed:
- Leak A: pack vocab defaulted to COHERENT — flipped to SPECULATIVE in
  language_packs/{compiler,schema}.py; docstring corrected to align with
  ADR-0021 (it was rationalizing the leak).
- Leak B: vault.recall was epistemic-blind — VaultStore.store() now stamps
  every entry with EpistemicStatus (default SPECULATIVE); recall(min_status=)
  filters to admissible-as-evidence tier. All 4 vault-write sites updated.
- Leak C (write-side): generate/proposition.py:198 stored articulated
  propositions unmarked — now stamps SPECULATIVE, breaking the
  fabrication-feedback loop in principle. Read-side audit of 5 call sites
  is the residual.

New architectural invariants (tests/test_architectural_invariants.py):
- INV-21: one-mutation-path allowlist (caught Leak C on first run)
- INV-22: pack lexicon default is SPECULATIVE (Leak A guard)
- INV-23: vault recall epistemic-aware (Leak B guard)

New eval lanes:
- teaching_injection_resistance — ships GREEN at 1.00/1.00/0 (the
  structural anti-injection claim is real and measurable)
- refusal_calibration — honest gap: 0% refusal, 0% fabrication
- contradiction_detection — honest gap: 50% flag via versor-delta heuristic,
  100% false-positive; motivates the proper coherence-checker
- articulation_of_status — honest gap: 0% speculative articulation, 60%
  false certainty; output-side leak surface

New benchmarks:
- benchmarks/footprint.py — total deployed runtime is 7.06 MiB
  (109,358x smaller than Llama 3.1 405B, runs offline, no GPU)
- benchmarks/learning_curve.py — monotonic + replay-deterministic curve
  per lane

Documentation:
- docs/truth_seeking_schema.md — foundational architectural commitment,
  five rules, mapped to human failure modes, leaks published openly
- evals/CLAIMS.md — five-tier public claims doc; Tier 4.5 publishes
  known gaps with named fixes; verification contract at top
- README.md — new pillar between algebraic substrate and language pillar

Includes in-flight formation pipeline scaffolding (formation/, tests/formation/,
docs/formation_pipeline_plan.md) and minor CLI/contracts/gitignore edits
that were already in the working tree at session start.

Verification: 798 passed, 2 skipped, 1 deselected (pre-existing pack-count
test drift unrelated to schema changes).
2026-05-17 07:27:41 -07:00

348 lines
12 KiB
Python

"""Footprint bench — total on-disk and in-memory bytes required to run CORE.
Anchors the "Smaller" claim in evals/CLAIMS.md. Reports:
- On-disk bytes for the active language pack, persistent vault state,
the compiled Rust backend artifact, and the Python modules actually
loaded by a single pulse (via sys.modules walk, not a directory scan).
- Resident memory before and after one pulse (RSS delta).
- A deployment-profile flag set: runs_offline, requires_gpu,
requires_network, requires_api_key.
- A frontier-model context table sourced from published model cards so
the comparison is reproducible without calling any provider API.
The measurement is honest: it counts only what the cognition lane needs
to run, not the whole repository. If you add an optional subsystem and
it is not loaded by a default pulse, it does not count toward this
number — and that is correct, because deployment does not need it.
Usage:
from benchmarks.footprint import run_footprint
report = run_footprint()
print(report.summary())
CLI surface (added to core/cli.py separately):
core bench footprint --json
"""
from __future__ import annotations
import json
import resource
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any
PROJECT_ROOT = Path(__file__).resolve().parent.parent
@dataclass(frozen=True, slots=True)
class FrontierReference:
"""A published frontier-model size for context.
Sources are cited inline (in `source_note`). Do not add a row
without a public source — speculation is not evidence.
"""
name: str
parameters_billion: float
weights_bytes_estimate: int
source_note: str
FRONTIER_REFERENCES: tuple[FrontierReference, ...] = (
FrontierReference(
name="Llama 3.1 8B (fp16)",
parameters_billion=8.0,
weights_bytes_estimate=8_000_000_000 * 2,
source_note="Meta model card, 2024 — 2 bytes/param at fp16.",
),
FrontierReference(
name="Llama 3.1 70B (fp16)",
parameters_billion=70.0,
weights_bytes_estimate=70_000_000_000 * 2,
source_note="Meta model card, 2024 — 2 bytes/param at fp16.",
),
FrontierReference(
name="Llama 3.1 405B (fp16)",
parameters_billion=405.0,
weights_bytes_estimate=405_000_000_000 * 2,
source_note="Meta model card, 2024 — 2 bytes/param at fp16.",
),
FrontierReference(
name="GPT-3.5 (175B, fp16 estimate)",
parameters_billion=175.0,
weights_bytes_estimate=175_000_000_000 * 2,
source_note="Brown et al. 2020, GPT-3 paper; size is public, fp16 storage assumed.",
),
)
@dataclass(frozen=True, slots=True)
class ArtifactSize:
name: str
path: str
bytes_on_disk: int
present: bool
@dataclass(frozen=True, slots=True)
class DeploymentProfile:
runs_offline: bool
requires_gpu: bool
requires_network: bool
requires_api_key: bool
def as_dict(self) -> dict[str, bool]:
return {
"runs_offline": self.runs_offline,
"requires_gpu": self.requires_gpu,
"requires_network": self.requires_network,
"requires_api_key": self.requires_api_key,
}
@dataclass(frozen=True, slots=True)
class FootprintReport:
artifacts: tuple[ArtifactSize, ...]
python_runtime_bytes: int
python_runtime_module_count: int
rss_idle_bytes: int
rss_post_pulse_bytes: int
deployment: DeploymentProfile
frontier_context: tuple[FrontierReference, ...]
@property
def total_disk_bytes(self) -> int:
return sum(a.bytes_on_disk for a in self.artifacts if a.present) + self.python_runtime_bytes
@property
def rss_pulse_delta_bytes(self) -> int:
return max(0, self.rss_post_pulse_bytes - self.rss_idle_bytes)
def smaller_than(self, ref: FrontierReference) -> float:
if self.total_disk_bytes <= 0:
return 0.0
return ref.weights_bytes_estimate / self.total_disk_bytes
def as_dict(self) -> dict[str, Any]:
return {
"total_disk_bytes": self.total_disk_bytes,
"total_disk_human": _human_bytes(self.total_disk_bytes),
"python_runtime_bytes": self.python_runtime_bytes,
"python_runtime_module_count": self.python_runtime_module_count,
"rss_idle_bytes": self.rss_idle_bytes,
"rss_post_pulse_bytes": self.rss_post_pulse_bytes,
"rss_pulse_delta_bytes": self.rss_pulse_delta_bytes,
"deployment": self.deployment.as_dict(),
"artifacts": [
{
"name": a.name,
"path": a.path,
"bytes_on_disk": a.bytes_on_disk,
"human": _human_bytes(a.bytes_on_disk),
"present": a.present,
}
for a in self.artifacts
],
"frontier_context": [
{
"name": r.name,
"parameters_billion": r.parameters_billion,
"weights_bytes_estimate": r.weights_bytes_estimate,
"weights_human": _human_bytes(r.weights_bytes_estimate),
"core_is_smaller_by_x": round(self.smaller_than(r), 1),
"source_note": r.source_note,
}
for r in self.frontier_context
],
}
def summary(self) -> str:
lines = [
f"footprint total_disk={_human_bytes(self.total_disk_bytes)} "
f"rss_idle={_human_bytes(self.rss_idle_bytes)} "
f"rss_after_pulse={_human_bytes(self.rss_post_pulse_bytes)} "
f"(+{_human_bytes(self.rss_pulse_delta_bytes)})",
" artifacts:",
]
for a in self.artifacts:
mark = "ok" if a.present else "--"
lines.append(f" [{mark}] {a.name:<28} {_human_bytes(a.bytes_on_disk):>10} {a.path}")
lines.append(f" python_runtime_modules: {self.python_runtime_module_count} "
f"({_human_bytes(self.python_runtime_bytes)})")
lines.append(" deployment: " + ", ".join(
f"{k}={v}" for k, v in self.deployment.as_dict().items()
))
lines.append(" vs published frontier model sizes:")
for r in self.frontier_context:
x = self.smaller_than(r)
lines.append(f" {r.name:<32} {_human_bytes(r.weights_bytes_estimate):>10} "
f"CORE is {x:,.0f}x smaller")
return "\n".join(lines)
def _human_bytes(n: int) -> str:
"""Format bytes for humans. Uses binary units (KiB, MiB) at full
precision and falls back to plain int below 1 KiB.
"""
if n < 1024:
return f"{n} B"
units = ("KiB", "MiB", "GiB", "TiB", "PiB")
size = float(n)
unit = units[0]
for unit in units:
size /= 1024.0
if size < 1024.0:
break
return f"{size:.2f} {unit}"
def _dir_bytes(path: Path) -> int:
if not path.exists():
return 0
if path.is_file():
return path.stat().st_size
total = 0
for entry in path.rglob("*"):
if entry.is_file():
try:
total += entry.stat().st_size
except OSError:
continue
return total
def _rust_artifact_path() -> Path:
"""Locate the compiled Rust backend shared library, if present.
Searches the release directory only — debug artifacts are not what
deployment ships. Returns the first matching file or a sentinel
non-existent path so the report still renders cleanly when Rust
has not been built.
"""
release = PROJECT_ROOT / "core-rs" / "target" / "release"
if not release.exists():
return release / "libcore_rs.dylib"
for ext in ("dylib", "so", "dll"):
for candidate in release.glob(f"libcore_rs.{ext}"):
return candidate
return release / "libcore_rs.dylib"
def _measure_python_runtime() -> tuple[int, int]:
"""Sum the byte size of every loaded module whose file is under
PROJECT_ROOT. This is the actual import closure for the current
process — not a directory scan that would over-count optional
subsystems the lane never imports.
"""
seen: set[Path] = set()
total_bytes = 0
for module in list(sys.modules.values()):
if module is None:
continue
file_attr = getattr(module, "__file__", None)
if not file_attr:
continue
try:
p = Path(file_attr).resolve()
except (OSError, ValueError):
continue
try:
p.relative_to(PROJECT_ROOT)
except ValueError:
continue
if p in seen or not p.exists():
continue
seen.add(p)
try:
total_bytes += p.stat().st_size
except OSError:
continue
return total_bytes, len(seen)
def _rss_bytes() -> int:
"""Return resident set size in bytes. On Linux ru_maxrss is KiB; on
macOS it is bytes. The conversion below covers both.
"""
rusage = resource.getrusage(resource.RUSAGE_SELF)
raw = int(rusage.ru_maxrss)
scale = 1 if sys.platform == "darwin" else 1024
return raw * scale
def run_footprint(*, pack_id: str = "en_core_cognition_v1") -> FootprintReport:
"""Measure CORE's deployed footprint and return a FootprintReport.
Triggers one pulse via `scripts.run_pulse.run_pulse` so the import
closure and post-pulse RSS reflect what real deployment looks like.
"""
rss_idle = _rss_bytes()
from scripts.run_pulse import run_pulse
run_pulse("What is truth?", use_glove=False)
rss_post = _rss_bytes()
py_bytes, py_modules = _measure_python_runtime()
pack_path = PROJECT_ROOT / "language_packs" / "data" / pack_id
vault_path = PROJECT_ROOT / "vault"
rust_path = _rust_artifact_path()
seed_packs_path = PROJECT_ROOT / "packs"
artifacts = (
ArtifactSize(
name=f"language_pack:{pack_id}",
path=str(pack_path.relative_to(PROJECT_ROOT)),
bytes_on_disk=_dir_bytes(pack_path),
present=pack_path.exists(),
),
ArtifactSize(
name="seed_packs",
path=str(seed_packs_path.relative_to(PROJECT_ROOT)),
bytes_on_disk=_dir_bytes(seed_packs_path),
present=seed_packs_path.exists(),
),
ArtifactSize(
name="vault_module",
path=str(vault_path.relative_to(PROJECT_ROOT)),
bytes_on_disk=_dir_bytes(vault_path),
present=vault_path.exists(),
),
ArtifactSize(
name="rust_backend",
path=str(rust_path.relative_to(PROJECT_ROOT)) if rust_path.exists() else str(rust_path),
bytes_on_disk=rust_path.stat().st_size if rust_path.exists() else 0,
present=rust_path.exists(),
),
)
deployment = DeploymentProfile(
runs_offline=True,
requires_gpu=False,
requires_network=False,
requires_api_key=False,
)
return FootprintReport(
artifacts=artifacts,
python_runtime_bytes=py_bytes,
python_runtime_module_count=py_modules,
rss_idle_bytes=rss_idle,
rss_post_pulse_bytes=rss_post,
deployment=deployment,
frontier_context=FRONTIER_REFERENCES,
)
def write_report(report: FootprintReport, root: Path | None = None) -> Path:
base = root or PROJECT_ROOT / "evals" / "reports"
base.mkdir(parents=True, exist_ok=True)
path = base / "footprint_latest.json"
path.write_text(
json.dumps(report.as_dict(), ensure_ascii=False, indent=2, sort_keys=True) + "\n"
)
return path