Planner-Cache / assets /generate_assets.py
Dermitio's picture
Upload folder using huggingface_hub
ca3d977 verified
Raw
History Blame Contribute Delete
19.7 kB
"""Generate publication assets from recorded Planner Cache artifacts."""
from __future__ import annotations
import csv
import hashlib
import json
import shutil
import subprocess
import tempfile
from pathlib import Path
import cairosvg
ASSETS = Path(__file__).resolve().parent
PACK_ARTIFACTS = ASSETS.parent / "artifacts"
ROOT = ASSETS.parent if PACK_ARTIFACTS.is_dir() else ASSETS.parents[1]
ARTIFACTS = ROOT / "artifacts"
def load(name: str):
return json.loads((ARTIFACTS / name).read_text())
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def write_csv(name: str, fields: list[str], rows: list[dict[str, object]]) -> None:
with (ASSETS / name).open("w", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=fields)
writer.writeheader()
writer.writerows(rows)
def architecture_svg() -> str:
return """<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="820" viewBox="0 0 1400 820">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="#385170"/></marker>
</defs>
<rect width="1400" height="820" fill="#f8fafc"/>
<text x="700" y="54" text-anchor="middle" font-family="Arial,sans-serif" font-size="34" font-weight="700" fill="#12233f">Planner Cache publication architecture</text>
<text x="700" y="86" text-anchor="middle" font-family="Arial,sans-serif" font-size="18" fill="#50627a">Portable bounded semantic state for frozen language models</text>
<rect x="45" y="125" width="1310" height="220" rx="22" fill="#edf4fb" stroke="#7da2c8" stroke-width="2"/>
<text x="75" y="160" font-family="Arial,sans-serif" font-size="20" font-weight="700" fill="#244d76">Model and runtime owned</text>
<rect x="90" y="195" width="260" height="105" rx="16" fill="#ffffff" stroke="#2c7a7b" stroke-width="3"/>
<text x="220" y="235" text-anchor="middle" font-family="Arial,sans-serif" font-size="24" font-weight="700" fill="#1f5960">Recent KV</text>
<text x="220" y="266" text-anchor="middle" font-family="Arial,sans-serif" font-size="16" fill="#50627a">Exact recent wording</text>
<rect x="570" y="195" width="260" height="105" rx="16" fill="#ffffff" stroke="#315c9b" stroke-width="3"/>
<text x="700" y="235" text-anchor="middle" font-family="Arial,sans-serif" font-size="24" font-weight="700" fill="#244d76">Frozen model</text>
<text x="700" y="266" text-anchor="middle" font-family="Arial,sans-serif" font-size="16" fill="#50627a">Pythia and Gemma proof paths</text>
<rect x="1050" y="195" width="260" height="105" rx="16" fill="#ffffff" stroke="#6856a5" stroke-width="3"/>
<text x="1180" y="229" text-anchor="middle" font-family="Arial,sans-serif" font-size="22" font-weight="700" fill="#55428e">History and tools</text>
<text x="1180" y="259" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#50627a">Archive and retrieval systems</text>
<text x="1180" y="282" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#50627a">Outside Planner Cache core</text>
<rect x="45" y="390" width="1310" height="350" rx="22" fill="#fff7ed" stroke="#cf8a49" stroke-width="2"/>
<text x="75" y="425" font-family="Arial,sans-serif" font-size="20" font-weight="700" fill="#91501e">Planner Cache owned</text>
<rect x="85" y="442" width="245" height="32" rx="12" fill="#fffdf8" stroke="#b36a2e" stroke-width="2" stroke-dasharray="7 5"/>
<text x="207" y="464" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" font-weight="700" fill="#8f4d1e">Hidden post-turn review</text>
<rect x="85" y="485" width="245" height="125" rx="16" fill="#ffffff" stroke="#b36a2e" stroke-width="3"/>
<text x="207" y="527" text-anchor="middle" font-family="Arial,sans-serif" font-size="25" font-weight="700" fill="#8f4d1e">P-cache</text>
<text x="207" y="558" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#50627a">Bounded mutable state</text>
<text x="207" y="582" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#50627a">Canonical P protocol</text>
<rect x="405" y="485" width="245" height="125" rx="16" fill="#ffffff" stroke="#3e7c59" stroke-width="3"/>
<text x="527" y="527" text-anchor="middle" font-family="Arial,sans-serif" font-size="25" font-weight="700" fill="#2d6848">Universal router</text>
<text x="527" y="558" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#50627a">Canonical selection</text>
<text x="527" y="582" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#50627a">Model independent</text>
<rect x="725" y="485" width="245" height="125" rx="16" fill="#ffffff" stroke="#315c9b" stroke-width="3"/>
<text x="847" y="527" text-anchor="middle" font-family="Arial,sans-serif" font-size="25" font-weight="700" fill="#244d76">.ttl / .ltl</text>
<text x="847" y="558" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#50627a">Compatibility boundary</text>
<text x="847" y="582" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#50627a">Semantic / lexical</text>
<rect x="1045" y="485" width="245" height="125" rx="16" fill="#ffffff" stroke="#6856a5" stroke-width="3"/>
<text x="1167" y="527" text-anchor="middle" font-family="Arial,sans-serif" font-size="25" font-weight="700" fill="#55428e">P-package</text>
<text x="1167" y="558" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#50627a">Disk resident personality</text>
<text x="1167" y="582" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#50627a">Portable .ppkg</text>
<path d="M 330 548 L 395 548" stroke="#385170" stroke-width="4" fill="none" marker-end="url(#arrow)"/>
<path d="M 207 474 L 207 482" stroke="#b36a2e" stroke-width="3" fill="none" marker-end="url(#arrow)"/>
<path d="M 700 300 C 650 375 350 385 225 439" stroke="#b36a2e" stroke-width="2" stroke-dasharray="8 6" fill="none" marker-end="url(#arrow)"/>
<path d="M 650 548 L 715 548" stroke="#385170" stroke-width="4" fill="none" marker-end="url(#arrow)"/>
<path d="M 1045 620 C 930 695 585 695 527 620" stroke="#6856a5" stroke-width="3" stroke-dasharray="10 7" fill="none" marker-end="url(#arrow)"/>
<path d="M 847 485 C 820 405 755 335 710 302" stroke="#315c9b" stroke-width="4" fill="none" marker-end="url(#arrow)"/>
<path d="M 350 247 L 558 247" stroke="#2c7a7b" stroke-width="3" fill="none" marker-end="url(#arrow)"/>
<text x="700" y="782" text-anchor="middle" font-family="Arial,sans-serif" font-size="16" fill="#50627a">Canonical files never store base weights, token IDs, conversation text, or model-native hidden vectors</text>
</svg>
"""
def line_plot_svg(title: str, subtitle: str, series: list[tuple[str, str, list[tuple[float, float]]]], x_label: str, y_label: str) -> str:
width = 1100
height = 650
left = 105
right = 55
top = 115
bottom = 90
plot_width = width - left - right
plot_height = height - top - bottom
all_x = [x for _, _, points in series for x, _ in points]
all_y = [y for _, _, points in series for _, y in points]
x_min = min(all_x)
x_max = max(all_x)
y_min = min(0.0, min(all_y))
y_max = max(all_y) * 1.08
def px(value: float) -> float:
return left + (value - x_min) / (x_max - x_min) * plot_width
def py(value: float) -> float:
return top + plot_height - (value - y_min) / (y_max - y_min) * plot_height
parts = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">']
parts.append('<rect width="1100" height="650" fill="#ffffff"/>')
parts.append(f'<text x="550" y="42" text-anchor="middle" font-family="Arial,sans-serif" font-size="28" font-weight="700" fill="#12233f">{title}</text>')
parts.append(f'<text x="550" y="72" text-anchor="middle" font-family="Arial,sans-serif" font-size="16" fill="#50627a">{subtitle}</text>')
for tick in range(6):
value = y_min + (y_max - y_min) * tick / 5
y = py(value)
parts.append(f'<line x1="{left}" y1="{y:.2f}" x2="{width-right}" y2="{y:.2f}" stroke="#dbe4ee" stroke-width="1"/>')
parts.append(f'<text x="{left-12}" y="{y+5:.2f}" text-anchor="end" font-family="Arial,sans-serif" font-size="13" fill="#50627a">{value:.1f}</text>')
x_ticks = sorted(set(all_x))
for value in x_ticks:
x = px(value)
parts.append(f'<line x1="{x:.2f}" y1="{top}" x2="{x:.2f}" y2="{top+plot_height}" stroke="#eef2f7" stroke-width="1"/>')
parts.append(f'<text x="{x:.2f}" y="{top+plot_height+27}" text-anchor="middle" font-family="Arial,sans-serif" font-size="13" fill="#50627a">{int(value)}</text>')
parts.append(f'<line x1="{left}" y1="{top+plot_height}" x2="{width-right}" y2="{top+plot_height}" stroke="#385170" stroke-width="2"/>')
parts.append(f'<line x1="{left}" y1="{top}" x2="{left}" y2="{top+plot_height}" stroke="#385170" stroke-width="2"/>')
for index, (label, color, points) in enumerate(series):
coordinates = " ".join(f"{px(x):.2f},{py(y):.2f}" for x, y in points)
parts.append(f'<polyline points="{coordinates}" fill="none" stroke="{color}" stroke-width="4"/>')
for x, y in points:
parts.append(f'<circle cx="{px(x):.2f}" cy="{py(y):.2f}" r="5" fill="{color}"/>')
legend_x = left + index * 280
parts.append(f'<line x1="{legend_x}" y1="{height-30}" x2="{legend_x+32}" y2="{height-30}" stroke="{color}" stroke-width="4"/>')
parts.append(f'<text x="{legend_x+42}" y="{height-25}" font-family="Arial,sans-serif" font-size="14" fill="#28384e">{label}</text>')
parts.append(f'<text x="{left+plot_width/2}" y="{height-55}" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#28384e">{x_label}</text>')
parts.append(f'<text x="28" y="{top+plot_height/2}" text-anchor="middle" transform="rotate(-90 28 {top+plot_height/2})" font-family="Arial,sans-serif" font-size="15" fill="#28384e">{y_label}</text>')
parts.append('</svg>')
return "\n".join(parts) + "\n"
def main() -> None:
audit = load("active-system-audit.json")
cuda = load("active-system-cuda-attribution.json")
split = load("phase-b-split-translator.json")
personality = load("phase-b-personality-package.json")
gemma = load("gemma4-e4b-q8-causal.json")
vram = load("vram-comparison.json")
router_rows = []
for count, values in sorted(audit["router_scaling"]["measurements"].items(), key=lambda item: int(item[0])):
router_rows.append({
"slots": int(count),
"top1_accuracy": values["top1_accuracy"],
"top4_recall": values["top4_recall"],
"mrr": values["mrr"],
"routing_latency_ms": values["latency_seconds"] * 1000,
})
write_csv("router_scaling.csv", list(router_rows[0]), router_rows)
ppkg_rows = []
for count, values in sorted(audit["ppkg_scaling"].items(), key=lambda item: int(item[0])):
ppkg_rows.append({
"entries": int(count),
"disk_bytes": values["disk_bytes"],
"checksum_ms": values["checksum_seconds"] * 1000,
"db_open_ms": values["db_open_seconds"] * 1000,
"routing_header_ms": values["routing_header_wall_seconds"] * 1000,
"row_hydration_ms": values["row_hydration_wall_seconds"] * 1000,
"canonical_conversion_ms": values["canonical_conversion_wall_seconds"] * 1000,
"candidate_headers": values["candidate_headers"],
"entries_loaded": values["entries_loaded"],
"logical_bytes_read": values["logical_bytes_read"],
"inactive_vram_bytes": values["inactive_vram_bytes"],
})
write_csv("ppkg_scaling.csv", list(ppkg_rows[0]), ppkg_rows)
causal_rows = []
for condition, values in cuda["causal"].items():
causal_rows.append({
"condition": condition,
"selected_state": values["selected_state"] or "",
"router_score": "" if values["router_score"] is None else values["router_score"],
"router_accepted": values["router_accepted"],
"gate": values["gate"],
"alice_logit": values["alice_logit"],
"bob_logit": values["bob_logit"],
"generated": values["generated"].replace("\n", "\\n"),
"kl_from_base": values["kl_from_base"],
"latency_ms": cuda["latency_seconds"][condition] * 1000,
})
write_csv("causal_conditions.csv", list(causal_rows[0]), causal_rows)
gemma_rows = []
for condition, values in gemma["conditions"].items():
gemma_rows.append({
"condition": condition,
"router_accepted": values["router_accepted"],
"gate": values["gate"],
"strength": values["strength"],
"alice_logit": values["alice_logit"],
"bob_logit": values["bob_logit"],
"alice_probability": values["alice_probability"],
"bob_probability": values["bob_probability"],
"generated": values["generated"].replace("\n", "\\n"),
"kl_from_base": values["kl_from_base"],
"max_abs_logit_difference_from_base": values["max_abs_logit_difference_from_base"],
"latency_ms": values["latency_ms"],
})
write_csv("gemma_causal_conditions.csv", list(gemma_rows[0]), gemma_rows)
vram_rows = []
for values in vram["results"]:
vram_rows.append({
"workload_tokens": values["prompt_tokens"],
"condition": values["condition"],
"generated_tokens": values["generated_tokens"],
"p_cache_slots": values["p_cache_slots"],
"p_cache_canonical_bytes": values["p_cache_canonical_bytes"],
"retained_kv_cache_bytes": values["retained_kv_cache_bytes"],
"baseline_allocated_bytes": values["baseline_allocated_bytes"],
"peak_allocated_bytes": values["peak_allocated_bytes"],
"peak_reserved_bytes": values["peak_reserved_bytes"],
"incremental_peak_allocated_bytes": values["incremental_peak_allocated_bytes"],
"incremental_peak_reserved_bytes": values["incremental_peak_reserved_bytes"],
"runtime_seconds": values["runtime_seconds"],
"failure": "" if values["failure"] is None else values["failure"]["type"],
})
write_csv("vram_comparison.csv", list(vram_rows[0]), vram_rows)
architecture = architecture_svg()
(ASSETS / "architecture.svg").write_text(architecture)
qpdf = shutil.which("qpdf")
if qpdf is None:
raise RuntimeError("qpdf is required to normalize publication PDF metadata")
with tempfile.TemporaryDirectory(prefix="planner-cache-publishing-") as temporary:
raw_pdf = Path(temporary) / "architecture.raw.pdf"
cairosvg.svg2pdf(bytestring=architecture.encode(), write_to=str(raw_pdf))
subprocess.run(
[qpdf, "--remove-info", "--remove-metadata", "--deterministic-id", str(raw_pdf), str(ASSETS / "architecture.pdf")],
check=True,
)
(ASSETS / "architecture.mmd").write_text("""flowchart LR
subgraph Runtime[Model and runtime owned]
KV[Recent KV]
LM[Frozen model]
EXT[History and tool systems]
end
subgraph Planner[Planner Cache owned]
P[P-cache]
R[Universal router]
C{Compatibility boundary}
N[Native P]
TTL[.ttl semantic/internal]
LTL[.ltl lexical/output]
PKG[P-package .ppkg]
REVIEW[Hidden post-turn review]
end
KV --> LM
LM -. side-channel after visible reply .-> REVIEW --> P
P --> R --> C
C --> N --> LM
C --> TTL --> LM
C --> LTL --> LM
PKG -. selected canonical state .-> R
EXT -. external evidence .-> LM
""")
router_points = [(float(row["slots"]), float(row["top1_accuracy"]) * 100) for row in router_rows]
legacy = audit["router_scaling"]["immutable_pre_fix_baseline"]
legacy_points = [(float(key), float(value) * 100) for key, value in sorted(legacy.items(), key=lambda item: int(item[0]))]
(ASSETS / "router_scaling.svg").write_text(line_plot_svg(
"Canonical router scaling",
"Post-audit identity-safe storage compared with the immutable pre-fix baseline",
[("Post-audit top-1", "#2c7a7b", router_points), ("Pre-fix top-1", "#b36a2e", legacy_points)],
"Configured P slots",
"Top-1 accuracy percent",
))
ppkg_points = [(float(row["entries"]), float(row["routing_header_ms"])) for row in ppkg_rows]
(ASSETS / "ppkg_lookup.svg").write_text(line_plot_svg(
"P-package indexed lookup",
"Bounded header routing while package contents grow on disk",
[("Routing header latency", "#6856a5", ppkg_points)],
"Package entries",
"Latency ms",
))
condition_labels = (
("p_cache_only", "P-cache only", "#b36a2e"),
("kv_only", "Retained KV only", "#2c7a7b"),
("p_cache_plus_kv", "P-cache plus KV", "#6856a5"),
)
vram_series = []
for condition, label, color in condition_labels:
points = [
(
float(row["workload_tokens"]),
float(row["incremental_peak_allocated_bytes"]) / (1024 * 1024),
)
for row in vram_rows if row["condition"] == condition
]
vram_series.append((label, color, points))
(ASSETS / "vram_comparison.svg").write_text(line_plot_svg(
"Matched P-cache and retained-KV VRAM",
"Frozen Pythia-1.4B, batch 1, float16 base, 8 greedy generated tokens",
vram_series,
"Prompt tokens and configured P slots",
"Incremental peak allocated MiB",
))
manifest = {
"generated_from": {
name: sha256(ARTIFACTS / name)
for name in (
"active-system-audit.json",
"active-system-cuda-attribution.json",
"phase-b-factorized-representation.json",
"phase-b-personality-package.json",
"phase-b-split-translator.json",
"ppkg-100k-profile.json",
"gemma4-e4b-q8-causal.json",
"gemma-native-prompt-equivalence.json",
"post-turn-memory-review-acceptance.json",
"debug-actions-profile.json",
"vram-comparison.json",
)
},
"public_binary_artifacts": {
name: sha256(ARTIFACTS / name)
for name in (
"canonical-p-v1.router",
"pythia-1.4b-final-layer.ttl",
"personality-proof.ppkg",
"gemma4-e4b-q8-llama.ltl",
)
},
"selected_configuration": {
"base_model": cuda["base_model"],
"ttl_parameters": (
audit["ttl_profile"]["cpu"]["parameter_count"]
if "ttl_profile" in audit
else audit["translate_profile"]["cpu"]["parameter_count"]
),
"canonical_protocol": "pcm-canonical-p-v1",
"router_format": "pcm-canonical-router-v1",
"ttl_format": "planner-cache-ttl-v1",
"ppkg_format": personality["format"],
"ppkg_protocol": personality["protocol"],
"selected_attachment": split["selected_variant"],
"gemma_model": gemma["model"]["name"],
"gemma_ltl_format": "planner-cache-ltl-v1",
"gemma_ltl_parameters": 0,
},
}
(ASSETS / "EVIDENCE_MANIFEST.json").write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n")
if __name__ == "__main__":
main()