import React, { useState, useMemo, useCallback, useEffect } from 'react'; import { Zap, RotateCcw, Info, Loader2, Play } from 'lucide-react'; import circuitData from '../data/circuit_heads.json'; const BACKEND_URL = process.env.REACT_APP_BACKEND_URL || ''; export const CircuitExplorer = () => { const [selectedGroup, setSelectedGroup] = useState(null); const [hoveredHead, setHoveredHead] = useState(null); // Ablation Knockout States const [knockedOutHeads, setKnockedOutHeads] = useState([]); // List of "L.H" strings, e.g. "5.1", "6.9" const [prompt, setPrompt] = useState('When Mary and John went to the store, John gave a bottle of milk to'); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [knockoutResult, setKnockoutResult] = useState(null); const [ablationMode, setAblationMode] = useState('zero'); // 'zero' or 'mean' const [modelStatus, setModelStatus] = useState(() => { return sessionStorage.getItem('cs_model_status') || 'demo'; }); useEffect(() => { const handleStatusChange = (e) => { setModelStatus(e.detail); }; window.addEventListener('cs_model_status_change', handleStatusChange); return () => { window.removeEventListener('cs_model_status_change', handleStatusChange); }; }, []); const allHeads = useMemo(() => { return circuitData.groups.flatMap(g => g.heads.map(h => ({ ...h, groupId: g.id, groupName: g.name, groupColor: g.color, groupDesc: g.description }))); }, []); const filteredHeads = selectedGroup ? allHeads.filter(h => h.groupId === selectedGroup) : allHeads; // Build layer grid (0-11) const layerGrid = useMemo(() => { const grid = Array.from({ length: 12 }, () => Array(12).fill(null)); allHeads.forEach(h => { if (h.layer < 12 && h.head < 12) { grid[h.layer][h.head] = h; } }); return grid; }, [allHeads]); // Toggle head ablation const toggleAblation = (layer, head) => { const key = `${layer}.${head}`; setKnockedOutHeads(prev => prev.includes(key) ? prev.filter(k => k !== key) : [...prev, key] ); setKnoutResultNull(); }; const setKnoutResultNull = () => { setKnockoutResult(null); setError(null); }; const clearAllAblations = () => { setKnockedOutHeads([]); setKnockoutResult(null); setError(null); }; const handleAblationModeChange = (mode) => { setAblationMode(mode); setKnockoutResult(null); setError(null); }; // Run Knockout Request const runKnockout = useCallback(async () => { if (knockedOutHeads.length === 0) return; setLoading(true); setError(null); try { const headsList = knockedOutHeads.map(k => { const [l, h] = k.split('.').map(Number); return [l, h]; }); const res = await fetch(`${BACKEND_URL}/api/knockout`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: prompt.trim(), heads_to_knockout: headsList, mode: ablationMode }) }); if (!res.ok) { const errData = await res.json().catch(() => ({})); throw new Error(errData.detail || `Knockout failed (${res.status})`); } const data = await res.json(); setKnockoutResult(data); } catch (e) { setError(e.message || 'Failed to complete attention head knockout'); } finally { setLoading(false); } }, [knockedOutHeads, prompt, ablationMode]); return (
{/* Header */}
Circuit Analysis

The IOI Circuit in GPT-2 Small

Reproduced from Wang et al. (2022). 26 attention heads across 7 functional groups — the largest end-to-end circuit reverse-engineered in a language model at time of publication.

{/* Task Explainer */}

The Indirect Object Identification Task

Given: "When Mary and John went to the store, John gave a bottle of milk to"
GPT-2 Small correctly predicts " Mary" (not " John"). A circuit of 26 attention heads collaborate: some track which name is the subject, some detect the duplicate, some suppress the subject, and Name Mover heads move the indirect object to the output.

{/* Group Filters */}
{circuitData.groups.map(g => ( ))}
{/* Circuit Diagram - Layer Grid */}
Layer × Head Grid — click cells to ablate heads {knockedOutHeads.length > 0 && ( )}
{/* Header Row */}
{Array.from({ length: 12 }, (_, i) => (
H{i}
))} {/* Data Rows */} {layerGrid.map((row, layer) => (
L{layer}
{row.map((head, headIdx) => { const isAblated = knockedOutHeads.includes(`${layer}.${headIdx}`); const isCircuitMember = !!head; let bg = '#0A0D18'; if (isAblated) { bg = '#FF5063'; } else if (isCircuitMember) { bg = (selectedGroup && head.groupId !== selectedGroup ? `${head.groupColor}22` : `${head.groupColor}55`); } return (
isCircuitMember && setHoveredHead(head)} onMouseLeave={() => setHoveredHead(null)} onClick={() => toggleAblation(layer, headIdx)} style={{ width: '100%', aspectRatio: '1', background: bg, border: isAblated ? '2px solid #FF5063' : (hoveredHead === head && head ? `2px solid ${head.groupColor}` : '1px solid #1E2B4533'), borderRadius: 3, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 8, color: isAblated ? '#060810' : (isCircuitMember ? head.groupColor : 'transparent'), fontWeight: isAblated ? '700' : 'normal', fontFamily: "'JetBrains Mono', monospace", transition: 'border-color 200ms, background 200ms, color 200ms', }} > {isAblated ? '✖' : (isCircuitMember ? head.name.replace('L', '').replace('H', '.') : '')}
); })}
))}
{/* Hovered Head Detail */} {hoveredHead && (
{hoveredHead.name} {hoveredHead.groupName}
Attends to: {hoveredHead.attends_to}
Score: {hoveredHead.score}
Logit contribution: 0 ? '#00E676' : '#FF5063' }}>{hoveredHead.logit_contribution > 0 ? '+' : ''}{hoveredHead.logit_contribution}
Composition: {hoveredHead.composition}
)}
{/* Group Descriptions */}
{(selectedGroup ? circuitData.groups.filter(g => g.id === selectedGroup) : circuitData.groups).map(g => (
{g.name} L{g.layers}

{g.description}

))}
{/* Live Knockout Intervention Deck */}

Live Head Knockout Intervention Lab

GPT-2 Active

Select one or more attention heads on the grid above (such as the critical S-Inhibition heads L7.3 or Induction heads L5.1) to ablate their attention patterns. Observe the downstream effect on logit difference necessity.

{/* Ablation Mode Segmented Controller */}
Ablation Mode:
{ablationMode === 'zero' ? "Zeroes attention weights. Can trigger out-of-distribution (OOD) downstream noise." : "Averages attention patterns across tokens. Preserves natural activation bounds." }
setPrompt(e.target.value)} disabled={loading} placeholder="Input custom IOI sequence..." style={{ flex: 1, background: '#080B12', border: '1px solid #1E2B45', borderRadius: 8, padding: '10px 14px', color: '#E8EEF8', fontSize: 13, fontFamily: "'Space Grotesk', sans-serif", outline: 'none', }} />
{knockedOutHeads.length === 0 && (
Grid tip: Click any square on the grid above to load it into the knockout deck.
)} {error && (
Error: {error}
)} {knockoutResult && (

Intervention Verdict: Head necessity is {knockoutResult.verdict.toUpperCase()}

{knockoutResult.interpretation}

)}
Ablation Performance Metrics
Baseline Logit Diff {knockoutResult ? knockoutResult.baseline_logit_diff.toFixed(2) : '3.84'}
Ablated Logit Diff {knockoutResult ? knockoutResult.knocked_logit_diff.toFixed(2) : '--'}
Performance Retained {knockoutResult ? `${knockoutResult.performance_retained_pct}%` : '--'}
{/* Faithfulness Metrics */}
0.87
Faithfulness

The circuit alone recovers 87% of GPT-2 Small's logit difference on the IOI task. Full model F(M) = 3.56, circuit gap = 0.46.

0.91
Completeness

Knocking out the 26 heads degrades performance by 91% of total effect. The circuit is nearly complete.

Verified
Minimality

Each head contributes meaningfully. Removing any single head reduces performance by at least 5% of logit diff.

); };