Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/deploy-frontend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Deploy Frontend to GitHub Pages

on:
push:
branches: [Front_end]
workflow_dispatch:

# Allow one concurrent deployment
concurrency:
group: pages
cancel-in-progress: true

jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: write # needed for gh-pages branch push

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
working-directory: frontend
run: npm ci

- name: Build production bundle
working-directory: frontend
env:
# Backend URL — set this secret in GitHub repo Settings → Secrets → Actions
# If backend is not yet deployed, the app falls back to demo mock data.
VITE_API_BASE_URL: ${{ secrets.VITE_API_BASE_URL }}
run: npm run build

- name: Deploy to GitHub Pages (gh-pages branch)
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./frontend/dist
# Ensures SPA routing works — redirects 404 to index.html
enable_jekyll: false
71 changes: 71 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Dependencies and build output
frontend/node_modules/
frontend/dist/
backend/__pycache__/
backend/.venv/
backend/venv/
*.pyc
*.pyo

# Environment files (may contain secrets)
frontend/.env.local
frontend/.env.*.local

# Editor
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Logs
*.log
npm-debug.log*

# Python
__pycache__/
*.py[cod]
*.pyo
*.pyd
.Python
*.egg-info/
dist/
build/
*.egg

# Virtual environments — all common naming conventions
.venv*/
venv*/
env*/
ENV*/
.env/

# PyTorch checkpoints (large binaries — committed explicitly when final)
# Uncomment the line below if you do NOT want to commit model weights:
# backend/model_weights/*.pt

# pytest / coverage
.pytest_cache/
.coverage
htmlcov/
*.lcov

# IDEs
.idea/
.vscode/
*.suo
*.user

# OS
.DS_Store
Thumbs.db

# Hugging Face cache
.cache/

# Node (if frontend is added)
node_modules/
.next/
64 changes: 64 additions & 0 deletions docs/one_page_summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Memory Under Pressure — One-Page Concept Summary

**Project:** Interactive KV Caching Explainer · DataForge 2026 · Pathway × Rime Track
**Audience:** ML practitioners, students, and technically curious non-experts who have heard of "attention" but have not thought carefully about inference-time memory costs.

---

## The Claim

When a Transformer generates text, it avoids recomputing past attention by caching the key and value tensors for every token it has processed. This cache grows by one row per token per attention head — linearly, without bound, as long as the model keeps generating. That single fact explains why running a large language model on a long document costs far more memory than running it on a short one.

The research community has developed two broad strategies to contain this cost:

**Eviction and compression** keep only a subset of past tokens — either the most recent (Sliding Window / StreamingLLM), the most attended-to (Heavy Hitter / H2O), or a compressed approximation (KVzip). Each strategy trades exactness for a bounded budget: the cache stops growing, but the model can no longer see everything it has processed. A fact buried in an evicted token is simply gone.

**Architectural replacement** avoids the trade-off by eliminating the growing cache altogether. The BDH (Beyond Dense Hop) architecture, proposed in arXiv:2509.26507, replaces the KV cache with a fixed-size synaptic weight matrix **W**. When a new key–value pair *(k, v)* arrives, it is written into **W** by a local Hebbian update: `W ← W + k·vᵀ`. Reading is a matrix lookup: `v̂ = W·q`. The state never grows — memory is O(1) regardless of sequence length. The failure mode changes too: instead of eviction, BDH forgets via interference — new associations partially overwrite old ones.

The one-sentence claim this project teaches:

> *"A Transformer's KV cache grows linearly with every token it has ever seen because it stores an exact copy of the past; eviction and compression trade that exactness for a bounded budget, and architectures like BDH remove the growth altogether by replacing the cache with a fixed-size associative state that overwrites itself instead of appending."*

---

## What We Built

An interactive explainer running a toy decoder-only Transformer in the browser. The learner controls four cache policies — Full Cache, Sliding Window (StreamingLLM-style), Heavy Hitter (H2O-style), and BDH Recurrent — and observes three real-time readouts:

1. **Cache heatmap** — which tokens are alive in the cache, color-coded by policy; needle positions marked in red.
2. **Memory footprint chart** — live policy cache size vs. the full-cache baseline, both plotted as the sequence grows.
3. **Needle retrieval accuracy** — whether the model can still answer questions whose facts were embedded at specific positions in the sequence.

Every number on screen is labeled: live values come from a `/simulate` API call that runs a real attention forward pass. The accuracy-vs-budget sweep curve is precomputed and labeled as such. BDH paper claims are labeled "Published — not reproduced by our team."

A five-step guided walkthrough takes the learner from the linear-growth observation (Full Cache) through the two eviction strategies (Sliding Window, Heavy Hitter) to the architectural alternative (BDH), ending with a free-play Sandbox where all controls are unlocked.

---

## Learning Objectives

After completing the walkthrough, a learner should be able to:

1. Explain *why* the KV cache grows linearly (one row per token per head, never deleted in Full Cache mode).
2. Describe *what each eviction policy trades away* — early context (Sliding Window) vs. unattended tokens (Heavy Hitter) — and see it directly in the heatmap.
3. Explain *how BDH removes the growth* — fixed-size matrix, Hebbian overwrite, interference instead of eviction.
4. Predict *what happens to needle retrieval accuracy* as the budget shrinks, for each policy.

---

## Honest Scope Statement

This project covers the core BDH memory mechanism. BDH-CQ (a contrastive-query variant from the same paper) is acknowledged but not the focus — its relevance to standard KV cache replacement is limited and is stated as such in the UI. Published BDH accuracy numbers are displayed with a precomputed badge and a disclaimer that they come from a full-scale model, not our toy.

The toy simulator does not implement a full language model. It demonstrates the cache-growth and retrieval properties structurally, not at production scale. This is disclosed in the UI.

---

## Primary Sources

- Vaswani et al., "Attention Is All You Need" (2017) — arXiv:1706.03762
- Xiao et al., "Efficient Streaming Language Models with Attention Sinks" (StreamingLLM, 2023) — arXiv:2309.17453
- Zhang et al., "H2O: Heavy-Hitter Oracle" (2023) — arXiv:2306.14048
- Yıldız et al., "Beyond Dense-Hop" (BDH, 2025) — arXiv:2509.26507

*Word count: ~680 words*
2 changes: 2 additions & 0 deletions frontend/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Development — points to local backend
VITE_API_BASE_URL=http://localhost:8000
3 changes: 3 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# .env.example — copy to .env.local for local development
# Day 3: set this to the real backend URL when wired
VITE_API_BASE_URL=http://localhost:8000
4 changes: 4 additions & 0 deletions frontend/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Production — points to deployed backend
# Update this URL once the backend is deployed (Render/Railway/Fly)
# Example: VITE_API_BASE_URL=https://cachequake-api.onrender.com
VITE_API_BASE_URL=https://cachequake-api.onrender.com
179 changes: 179 additions & 0 deletions frontend/CONTROLS_SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# CONTROLS_SPEC.md — "Memory Under Pressure"
**Frontend/Interaction Engineer · Day 1 Deliverable · DataForge 2026**

> Every control maps to exactly one backend variable. Every readout is specified as a live value beside a baseline/ground-truth value. No decorative controls exist in this spec.

---

## Section 1: Controls

---

### 1.1 Cache Policy Selector

| Field | Value |
|---|---|
| **Variable name** | `cachePolicy` |
| **Type** | `enum` (string) |
| **Allowed values** | `"full"` \| `"sliding_window"` \| `"heavy_hitter"` \| `"bdh_recurrent"` |
| **Default** | `"full"` |
| **UI element** | Segmented button group (4 tabs) |
| **Backend mapping** | Maps directly to the `policy` field in the `/simulate` request body (see `backend/app/api/schemas.py`). The backend routes this to one of: `full_cache.py`, `sliding_window.py`, `heavy_hitter.py`, or `bdh_inspired_state.py` in `cache_policies/`. |
| **PrecomputedBadge?** | No — policy selection triggers a live simulation run. |
| **Notes** | When `cachePolicy === "full"`, the `budgetSize` control must be **disabled** (greyed out). When `cachePolicy === "bdh_recurrent"`, the BDH Module explainer panel should expand inline. |

---

### 1.2 Budget Size

| Field | Value |
|---|---|
| **Variable name** | `budgetSize` |
| **Type** | `integer` |
| **Allowed range** | `8` – `512` (inclusive), step `8` |
| **Default** | `64` |
| **UI element** | Horizontal slider + numeric readout label |
| **Backend mapping** | Maps to `budget` in the `/simulate` request body. Passed as-is to `sliding_window.py` (window size = `budget`), `heavy_hitter.py` (max survivors = `budget`), and `bdh_inspired_state.py` (fixed state size = `budget`). Full cache policy ignores this field entirely. |
| **PrecomputedBadge?** | No — live parameter. **Exception:** the accuracy-vs-budget sweep curve shown beside the live point IS precomputed (see Readout 2.3 below). |
| **Notes** | Disabled (opacity 0.4, pointer-events none) when `cachePolicy === "full"`. Label must show current value in tokens, e.g. `"64 tokens"`. |

---

### 1.3 Sequence Length

| Field | Value |
|---|---|
| **Variable name** | `sequenceLength` |
| **Type** | `integer` |
| **Allowed range** | `32` – `512` (inclusive), step `16` |
| **Default** | `128` |
| **UI element** | Horizontal slider + numeric readout label |
| **Backend mapping** | Maps to `seq_len` in the `/simulate` request body. Passed to `needle_haystack.py` which generates a synthetic sequence of exactly this length containing `needleCount` embedded facts. |
| **PrecomputedBadge?** | No — live parameter. |
| **Notes** | Increasing this visibly grows the memory footprint under `"full"` policy — this is the primary demonstration of the linear-growth claim. |

---

### 1.4 Needle Count

| Field | Value |
|---|---|
| **Variable name** | `needleCount` |
| **Type** | `integer` |
| **Allowed range** | `1` – `5` (inclusive), step `1` |
| **Default** | `2` |
| **UI element** | Stepper buttons (minus / plus) with numeric label |
| **Backend mapping** | Maps to `num_needles` in the `/simulate` request body. Passed to `needle_haystack.py` to embed this many retrievable facts into the generated sequence. |
| **PrecomputedBadge?** | No — live parameter. |
| **Notes** | Kept small (max 5) so all needles can be checked individually in the Accuracy Panel. |

---

## Section 2: Readouts

All readouts show a **live value beside a baseline/ground-truth value**. No single-number readouts exist.

---

### 2.1 Memory Footprint Readout (MemoryChart)

| Field | Value |
|---|---|
| **Live value** | `memoryUsed` — current token count retained in cache (integer, in tokens). Computed per-step by the active policy. Source: `/simulate` response, field `cache_size_tokens`. |
| **Baseline value** | `memoryBaseline` — what Full Cache would use for the same `sequenceLength`: `sequenceLength x num_layers x num_heads x head_dim_bytes`. Source: `/simulate` response, field `full_cache_baseline_tokens`. Always shown as a flat horizontal reference line. |
| **Comparison being made** | Ratio: `memoryUsed / memoryBaseline`. Chart shows both values on the same Y-axis so reduction is immediately visible. |
| **Chart type** | Line chart (X = generation step, Y = tokens in cache). Two lines: live policy (colored) vs. full-cache baseline (dashed grey). |
| **PrecomputedBadge?** | **No** — both values come from the live `/simulate` call. |
| **Label** | "Live — computed this run" shown as a small inline tag. |

---

### 2.2 Needle Retrieval Accuracy Readout (AccuracyPanel)

| Field | Value |
|---|---|
| **Live value** | `modelAnswer` — the toy model's answer to each needle question, extracted from the generation output. Source: `/simulate` response, field `model_answers[]`. |
| **Ground-truth value** | `correctAnswer` — the correct answer embedded in the needle sequence by `needle_haystack.py`. Source: `/simulate` response, field `ground_truth_answers[]`. |
| **Comparison being made** | Exact match per needle: `modelAnswer[i] === correctAnswer[i]`. Displayed as: correct answers shown in green, wrong answers shown in red, side-by-side with the model's actual output string. |
| **PrecomputedBadge?** | **No** — both values come from the live `/simulate` call. |
| **Label** | "Model output" vs. "Correct answer" column headers. |

---

### 2.3 Accuracy-vs-Budget Curve (budget sweep overlay in AccuracyPanel)

| Field | Value |
|---|---|
| **Live value** | The single point `(budgetSize, accuracyScore)` from the current live run. Source: `/simulate` response. |
| **Baseline value** | The precomputed accuracy curve `accuracy_vs_budget.json` — a sweep over all budget values at a fixed `sequenceLength`. Source: `data/precomputed/accuracy_vs_budget.json`. |
| **Comparison being made** | "Where does my current budget sit on the trade-off curve?" — live point plotted atop the precomputed curve so the learner sees the full trade-off at a glance. |
| **PrecomputedBadge?** | **YES** — the curve (not the current point) is precomputed. `PrecomputedBadge` must render beside the curve legend with text: "Accuracy curve — precomputed sweep, not this run." |

---

### 2.4 Cache Heat-Map (CacheHeatmap)

| Field | Value |
|---|---|
| **Live value** | `aliveTokenIndices` — which token positions are still present in the KV cache after the last generation step. Source: `/simulate` response, field `alive_token_indices[]`. |
| **Baseline value** | Implicit baseline is "all tokens alive" (Full Cache state). Tokens NOT in `aliveTokenIndices` are rendered as dim/grey; tokens that ARE alive are rendered as bright/colored. The full-cache baseline is shown as a reference row of all-bright cells. |
| **Comparison being made** | Visual: alive tokens (current policy) vs. all-alive (full cache). Makes the "what did you lose?" question immediately perceptible. |
| **PrecomputedBadge?** | **No** — `aliveTokenIndices` is live per-step. |
| **Label** | "Live cache state" below the heat-map grid. |

---

### 2.5 BDH Comparison Panel (BDHModule)

| Field | Value |
|---|---|
| **Live value** | Our toy simulator's accuracy and memory stats for `bdh_recurrent` policy at the current `budgetSize` and `sequenceLength`. Source: `/simulate` response (same fields as 2.1 and 2.2). |
| **Baseline/Published value** | BDH paper's published long-context performance claims. Source: `data/precomputed/bdh_published_claims.json`, sourced from `research/papers/dragon_hatchling_bdh.md` (arXiv:2509.26507). |
| **Comparison being made** | Qualitative side-by-side: "What our toy shows" vs. "What the BDH paper reports (at full scale)." NOT a direct numerical comparison — the two models differ in scale by orders of magnitude. |
| **PrecomputedBadge?** | **YES** — published claims panel must carry `PrecomputedBadge` with text: "Published result (arXiv:2509.26507) — not reproduced by our team." This is mandatory per the honesty rubric. |

---

## Section 3: PrecomputedBadge Inventory

| Location | Badge text | Why |
|---|---|---|
| Accuracy-vs-budget curve overlay (Section 2.3) | "Precomputed sweep — not this run" | Curve is a pre-run sweep over many budgets, not computed live |
| BDH comparison panel (Section 2.5) | "Published result (arXiv:2509.26507) — not reproduced by our team" | Numbers come from the BDH paper, not our simulator |

All other readouts are live. No further precomputed elements exist in Day 1 scope.

---

## Section 4: Controls Pending Backend Sign-off

None at this time. All four controls (`cachePolicy`, `budgetSize`, `sequenceLength`, `needleCount`) have confirmed mappings to `/simulate` request fields per `ARCHITECTURE.md Section 3`.

---

## Section 5: Variable to API Mapping Summary

```
POST /simulate
{
"policy": cachePolicy, // "full" | "sliding_window" | "heavy_hitter" | "bdh_recurrent"
"budget": budgetSize, // integer 8-512 (ignored by backend when policy="full")
"seq_len": sequenceLength, // integer 32-512
"num_needles": needleCount // integer 1-5
}

Response (per step):
{
"cache_size_tokens": number, -> memoryUsed (Readout 2.1 live)
"full_cache_baseline_tokens": number, -> memoryBaseline (Readout 2.1 baseline)
"alive_token_indices": number[], -> aliveTokenIndices (Readout 2.4)
"model_answers": string[], -> modelAnswer[] (Readout 2.2 live)
"ground_truth_answers": string[], -> correctAnswer[] (Readout 2.2 ground truth)
"accuracy_score": number -> live point for Readout 2.3
}
```

---

*Produced by Agent 1 — Spec Agent.*
*Companion documents: `ARCHITECTURE.md`, `PRD_KV_Cache_Explainer.md`, `wireframe_notes.md`*
Loading
Loading