from __future__ import annotations import gradio as gr import pandas as pd from featurelens.config import SETTINGS from featurelens.hf_runtime import gpu from featurelens.runtime import RUNTIME # Restrained, print-inspired palette. The app deliberately avoids saturated dashboard colors. INK_TEAL = "#708B86" INK_UMBER = "#94785D" INK_RED = "#986D68" INK_PLUM = "#82768F" INK_STONE = "#8E8A83" INK_BLUEGREY = "#71808A" CSS = r""" .gradio-container { width: min(96vw, 1600px) !important; max-width: 1600px !important; margin-left: auto !important; margin-right: auto !important; padding: 0 22px 128px !important; font-family: "Times New Roman", Times, "Liberation Serif", Georgia, serif !important; font-size: 16px !important; line-height: 1.52; } .gradio-container input, .gradio-container textarea, .gradio-container button, .gradio-container select, .gradio-container label, .gradio-container table, .gradio-container .prose { font-family: "Times New Roman", Times, "Liberation Serif", Georgia, serif !important; } .gradio-container input, .gradio-container textarea, .gradio-container select { font-size: 15.5px !important; } .gradio-container table { font-size: 15.5px !important; } .gradio-container table thead th, .gradio-container [role="columnheader"] { font-size: 16px !important; font-weight: 700 !important; } .gradio-container table tbody td, .gradio-container [role="gridcell"] { font-size: 15.5px !important; } .gradio-container label, .gradio-container .label-wrap { font-size: 15px !important; } .gradio-container p, .gradio-container li { font-size: 16px; } .gradio-container h2 { font-size: 1.72rem; line-height: 1.25; } .gradio-container h3 { font-size: 1.46rem; line-height: 1.28; } .gradio-container h4 { font-size: 1.24rem; line-height: 1.30; } .hero { padding: 14px 2px 10px; border-bottom: 1px solid var(--border-color-primary); margin-bottom: 14px; } .hero h1 { margin: 0; font-size: 2.18rem; font-weight: 600; letter-spacing: 0; } .hero .subtitle { margin-top: 4px; font-size: 1.08rem; opacity: .82; } .hero .question { margin-top: 6px; font-size: 1rem; opacity: .72; } .section-rule { margin: 20px 0 11px; padding-top: 9px; border-top: 1px solid var(--border-color-primary); font-variant: small-caps; letter-spacing: .045em; font-size: 1.12rem; font-weight: 700; opacity: .88; } .token-wrap { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 2px 14px; line-height: 1.9; } .token { background: var(--background-fill-secondary); border: 1px solid var(--border-color-primary); border-radius: 2px; padding: 3px 7px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important; font-size: 12.5px; } .token.selected { border: 2px solid #708B86; font-weight: 700; } .token sup { opacity: .58; margin-right: 4px; } .small-note { opacity: .74; font-size: 14px; } .instrument-note { border: 1px solid var(--border-color-primary); border-radius: 2px; padding: 10px 12px; background: var(--background-fill-secondary); margin: 6px 0 11px; } .copy-row { margin-top: -4px; } .copy-row button { min-height: 34px !important; } .gradio-container button { border-radius: 3px !important; font-weight: 600 !important; font-size: 15px !important; } .action-btn button, .copy-btn button { background: #5f817b !important; color: #fff !important; border: 1px solid #5f817b !important; } .action-btn button:hover, .copy-btn button:hover { background: #536f6a !important; border-color: #536f6a !important; } .gradio-container .form, .gradio-container .block { border-radius: 3px !important; } .gradio-container textarea, .gradio-container input { border-radius: 2px !important; } .form-note { margin-top: -3px; margin-bottom: 9px; font-size: .92rem; opacity: .72; } .wide-table { width: 100% !important; } .bottom-spacer { height: 110px; width: 100%; } .tabs, .tabitem { padding-bottom: 26px !important; } .context-card { border: 1px solid var(--border-color-primary); border-left: 4px solid #708B86; background: var(--background-fill-secondary); padding: 10px 14px; margin: 8px 0 16px; border-radius: 2px; } .context-card p { margin: 0 !important; } .start-card { border: 1px solid var(--border-color-primary); background: var(--background-fill-secondary); padding: 14px 16px; min-height: 150px; border-radius: 2px; } .start-card h3 { margin-top: 0; } .graph-note { font-size: .95rem; opacity: .72; margin-top: 2px; } .table-heading { margin: 0 0 -34px !important; padding: 4px 58px 0 0 !important; min-height: 34px; position: relative; z-index: 3; pointer-events: none; font-size: 1.20rem !important; font-weight: 700 !important; line-height: 1.22 !important; } .result-table .label-wrap, .result-table .label-wrap span, .result-table label, .result-table label span, .result-table [data-testid="block-label"], .result-table [data-testid="block-label"] span, .result-table .block-label, .result-table .block-title { font-size: 19px !important; font-weight: 700 !important; line-height: 1.30 !important; } .result-table table thead th, .result-table table thead th *, .result-table [role="columnheader"], .result-table [role="columnheader"] * { font-size: 16.75px !important; font-weight: 700 !important; line-height: 1.25 !important; } /* Gradio native fullscreen is intercepted and converted to an in-place focus mode. Nothing is moved to the top of the HF iframe: the original component expands where the user clicked it. */ .fl-plot.featurelens-inline-focus, .result-table.featurelens-inline-focus { position: relative !important; z-index: 5000 !important; background: var(--background-fill-primary) !important; border: 1px solid var(--border-color-primary) !important; box-shadow: 0 12px 36px rgba(0, 0, 0, .42) !important; border-radius: 4px !important; } .fl-plot.featurelens-inline-focus { transform-origin: top left !important; } .result-table.featurelens-inline-focus { overflow: visible !important; } .candidate-help { opacity: .78; font-size: .97rem; margin-top: -2px; } @media (max-width: 900px) { .gradio-container { width: 100% !important; padding-left: 12px !important; padding-right: 12px !important; } } """ THEME = gr.themes.Base( primary_hue="teal", secondary_hue="stone", neutral_hue="stone", radius_size="sm", ) COPY_JS = r""" (text) => { const value = text || ""; const button = document.activeElement && document.activeElement.tagName === "BUTTON" ? document.activeElement : null; const oldLabel = button ? button.innerText : null; const signal = () => { if (!button) return; button.innerText = "✓ Copied with headers"; button.disabled = true; window.setTimeout(() => { button.innerText = oldLabel || "Copy table with headers"; button.disabled = false; }, 1200); }; const fallback = () => { const node = document.createElement("textarea"); node.value = value; node.style.position = "fixed"; node.style.opacity = "0"; document.body.appendChild(node); node.focus(); node.select(); document.execCommand("copy"); document.body.removeChild(node); signal(); }; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(value).then(signal).catch(fallback); } else { fallback(); } return [value]; } """ INSTALL_REFLOW_JS = r""" () => { if (window.__featurelens_reflow_installed) return []; window.__featurelens_reflow_installed = true; let timer = null; const kick = () => { window.clearTimeout(timer); timer = window.setTimeout(() => window.dispatchEvent(new Event("resize")), 80); }; const root = document.querySelector(".gradio-container") || document.body; if (window.ResizeObserver) { const observer = new ResizeObserver(kick); observer.observe(root); window.__featurelens_reflow_observer = observer; } const mutation = new MutationObserver(kick); mutation.observe(root, {subtree: true, childList: true}); window.__featurelens_mutation_observer = mutation; const restoreFocus = (block) => { if (!block || !block.classList.contains("featurelens-inline-focus")) return; const saved = block.__featurelens_saved_style; if (saved == null || saved === "") block.removeAttribute("style"); else block.setAttribute("style", saved); block.classList.remove("featurelens-inline-focus"); block.__featurelens_saved_style = null; window.setTimeout(kick, 30); }; const closeOtherFocus = (except) => { document.querySelectorAll(".featurelens-inline-focus").forEach((node) => { if (node !== except) restoreFocus(node); }); }; const focusPlotInPlace = (block) => { const rect = block.getBoundingClientRect(); if (rect.width <= 0 || rect.height <= 0) return; const viewportWidth = Math.max(320, document.documentElement.clientWidth || window.innerWidth || rect.width); const screenHeight = Math.max(600, (window.screen && window.screen.availHeight) || 900); const maxWidth = Math.min(viewportWidth * 0.90, 1100); const maxHeight = Math.min(screenHeight * 0.68, 700); const scale = Math.max(1, Math.min(maxWidth / rect.width, maxHeight / rect.height, 1.8)); const focusedWidth = rect.width * scale; let dx = (viewportWidth - focusedWidth) / 2 - rect.left; if (rect.left + dx < 12) dx += 12 - (rect.left + dx); if (rect.left + dx + focusedWidth > viewportWidth - 12) { dx -= (rect.left + dx + focusedWidth) - (viewportWidth - 12); } block.style.transformOrigin = "top left"; block.style.transform = `translate(${dx}px, 0px) scale(${scale})`; block.style.marginBottom = `${Math.max(8, rect.height * (scale - 1) + 8)}px`; block.style.zIndex = "5000"; }; const focusTableInPlace = (block) => { const rect = block.getBoundingClientRect(); if (rect.width <= 0) return; const viewportWidth = Math.max(320, document.documentElement.clientWidth || window.innerWidth || rect.width); const targetWidth = Math.max(rect.width, Math.min(viewportWidth * 0.94, 1400)); let dx = (viewportWidth - targetWidth) / 2 - rect.left; if (rect.left + dx < 12) dx += 12 - (rect.left + dx); if (rect.left + dx + targetWidth > viewportWidth - 12) { dx -= (rect.left + dx + targetWidth) - (viewportWidth - 12); } block.style.width = `${targetWidth}px`; block.style.maxWidth = "none"; block.style.transform = `translateX(${dx}px)`; block.style.zIndex = "5000"; }; const toggleInlineFocus = (block) => { if (block.classList.contains("featurelens-inline-focus")) { restoreFocus(block); return; } closeOtherFocus(block); block.__featurelens_saved_style = block.getAttribute("style") || ""; block.classList.add("featurelens-inline-focus"); if (block.classList.contains("fl-plot")) focusPlotInPlace(block); else focusTableInPlace(block); window.setTimeout(kick, 30); }; // Keep the native toolbar icon, but replace Gradio fullscreen with an in-place expansion. // This avoids HF iframe jumps and preserves the chart's exact rendered aspect ratio. document.addEventListener("click", (event) => { const button = event.target && event.target.closest ? event.target.closest("button") : null; if (!button) return; const label = `${button.getAttribute("aria-label") || ""} ${button.getAttribute("title") || ""} ${button.textContent || ""}`.toLowerCase(); if (!label.includes("fullscreen")) return; const block = button.closest(".fl-plot, .result-table"); if (!block) return; event.preventDefault(); event.stopImmediatePropagation(); toggleInlineFocus(block); }, true); document.addEventListener("keydown", (event) => { if (event.key !== "Escape") return; const active = document.querySelector(".featurelens-inline-focus"); if (active) restoreFocus(active); }); // Rename Gradio's generic chart.png export without touching the export implementation. document.addEventListener("click", (event) => { const button = event.target && event.target.closest ? event.target.closest("button") : null; if (button) { const label = `${button.getAttribute("aria-label") || ""} ${button.getAttribute("title") || ""} ${button.textContent || ""}`.toLowerCase(); if (label.includes("export")) { const block = button.closest(".fl-plot"); if (block) { const id = block.id || "plot-featurelens-chart"; const stem = id.replace(/^plot-/, "").replace(/[^a-z0-9_-]+/gi, "-").replace(/-+/g, "-").replace(/^-|-$/g, ""); window.__featurelens_export_name = `featurelens_${stem || "chart"}.png`; } } } const anchor = event.target && event.target.closest ? event.target.closest('a[download="chart.png"]') : null; if (anchor && window.__featurelens_export_name) { anchor.setAttribute("download", window.__featurelens_export_name); window.setTimeout(() => { window.__featurelens_export_name = null; }, 500); } }, true); kick(); return []; } """ def _raise_ui_error(exc: Exception) -> None: raise gr.Error(f"{type(exc).__name__}: {exc}") from exc def _tsv(frame: pd.DataFrame) -> str: if frame is None or frame.empty: return "" return frame.to_csv(sep="\t", index=False, lineterminator="\n") def _copy_button(label: str = "Copy table with headers") -> gr.Button: return gr.Button(label, size="sm", variant="primary", elem_classes=["copy-btn"]) def _table_heading(text: str) -> gr.HTML: return gr.HTML(f'
{text}
') def _copy_ack(_text: str) -> None: gr.Info("Copied table with headers.", duration=1.2) def _bind_copy(button: gr.Button, source: gr.Textbox) -> None: button.click(fn=_copy_ack, inputs=[source], outputs=None, js=COPY_JS, queue=False) def _analysis_metrics_markdown(result) -> str: return ( "#### Analysis metrics\n" f"**Layer {result.layer} · prompt token {result.token_index}** \n" f"Active SAE features: **{int(result.metrics['active_features'])}/{SETTINGS.sae_top_k}** \n" f"Reconstruction cosine: **{result.metrics['cosine']:.4f}** · " f"NMSE: **{result.metrics['nmse']:.4f}** \n" f"Top-5 activation mass: **{result.metrics['top5_mass_fraction']:.1%}**" ) def _intervention_metrics_markdown(result) -> str: drift = f"Execution-context null drift — JS: **{result.execution_drift_js:.2e}**" if result.execution_drift_mean_logprob is not None: drift += f" · mean log p/token: **{result.execution_drift_mean_logprob:+.2e}**" target = ( "No target continuation supplied. The causal comparison therefore uses only " "next-token Jensen-Shannon divergence." ) if result.baseline_sequence_logprob is not None: tokens = " ".join(repr(token) for token in result.target_tokens) target = ( f"**Target continuation:** {result.target_token_count} token(s): {tokens} \n" f"Sequence log p — baseline: **{result.baseline_sequence_logprob:.4f}** · " f"SAE edit: **{result.modified_sequence_logprob:.4f}** · " f"random-ensemble mean: **{result.random_sequence_logprob:.4f}** \n" f"SAE Δ sequence log p: **{result.sequence_logprob_delta:+.4f}** · " f"random signed mean Δ: **{result.random_sequence_logprob_delta:+.4f}** \n" f"SAE Δ mean log p/token: **{result.mean_logprob_delta:+.4f}** · " f"random mean |Δ|: **{result.random_abs_mean_logprob_delta:.4f}** ± **{result.random_mean_logprob_std:.4f}** · " f"magnitude ratio: **{result.target_specificity_ratio:.2f}×** · " f"empirical tail p: **{result.target_empirical_p:.3f}**" ) inactive = "" if abs(result.feature_activation) < 1e-12: inactive = ( " \n**Inactive feature:** ablate/scale has zero coefficient delta here; " "use `inject` only when you intentionally want a decoder-direction steering test." ) return ( f"Original feature activation: **{result.feature_activation:.4f}** · " f"Δ coefficient: **{result.delta_activation:+.4f}** \n" f"Perturbation L2: **{result.perturbation_norm:.4f}** \n" f"Next-token JS: **{result.js_divergence:.6f}** · " f"random ensemble ({result.random_control_count}) mean JS: **{result.random_js_divergence:.6f}** " f"± **{result.random_js_std:.6f}** · ratio: **{result.js_specificity_ratio:.2f}×** · " f"empirical tail p: **{result.js_empirical_p:.3f}** \n" f"{drift} \n\n{target}{inactive} \n\n" "_Greedy text may remain unchanged even when probability-level causal metrics move; " "deterministic generation changes only after an argmax boundary is crossed._" ) def _dose_metrics_markdown(result) -> str: tokens = " ".join(repr(token) for token in result.target_tokens) inactive = "" if abs(result.feature_activation) < 1e-12: inactive = " **The feature is inactive here, so multiplicative scaling is flat by construction.**" return ( f"Feature activation at baseline: **{result.feature_activation:.4f}** · " f"target continuation: {len(result.target_tokens)} token(s): {tokens}.{inactive} \n\n" "This panel is **always a scale intervention**: 0× = ablation, 1× = no edit, " "2× = double the native coefficient. All rows are compared with the **1× row from the same batched " "execution context**, so 1× is the numerical null reference. \n" f"Single-forward → batched-null execution drift: mean log p/token " f"**{result.execution_drift_mean_logprob:+.2e}**, JS **{result.execution_drift_js:.2e}**." ) def _feature_set_metrics_markdown(result) -> str: tokens = " ".join(repr(token) for token in result.target_tokens) inactive_count = sum(abs(float(row[1])) < 1e-12 for row in result.feature_rows) inactive_note = ( f" \n{inactive_count} selected feature(s) were inactive and contributed zero delta." if inactive_count else "" ) return ( f"Selected feature set: **{len(result.feature_ids)} features** · " f"perturbation L2: **{result.perturbation_norm:.4f}** \n" f"Target continuation: {len(result.target_tokens)} token(s): {tokens} \n" f"SAE Δ mean log p/token: **{result.mean_logprob_delta:+.4f}** · " f"random ensemble ({result.random_control_count}) mean |Δ|: " f"**{result.random_abs_mean_logprob_delta:.4f}** ± **{result.random_mean_logprob_std:.4f}** · ratio: **{result.target_specificity_ratio:.2f}×** · " f"empirical tail p: **{result.target_empirical_p:.3f}** \n" f"SAE Δ sequence log p: **{result.sequence_logprob_delta:+.4f}** · " f"random signed mean Δ: **{result.random_sequence_logprob_delta:+.4f}** \n" f"Next-token JS: **{result.js_divergence:.6f}** · random mean JS: " f"**{result.random_js_divergence:.6f}** ± **{result.random_js_std:.6f}** · " f"ratio: **{result.js_specificity_ratio:.2f}×** · empirical tail p: **{result.js_empirical_p:.3f}** \n" f"Execution-context null drift: mean log p/token **{result.execution_drift_mean_logprob:+.2e}**, " f"JS **{result.execution_drift_js:.2e}**{inactive_note}" ) def _interaction_metrics_markdown(result) -> str: tokens = " ".join(repr(token) for token in result.target_tokens) return ( f"Target continuation: {len(result.target_tokens)} token(s): {tokens} \n" f"Additive expectation (sum of individual Δ mean log p/token): " f"**{result.additive_expected_mean_delta:+.4f}** \n" f"Observed joint Δ mean log p/token: **{result.joint_mean_delta:+.4f}** \n" f"Interaction excess (joint − additive): **{result.interaction_excess_mean_delta:+.4f}** · " f"normalized: **{result.normalized_interaction:+.3f}** \n" f"Execution-context null drift: **{result.execution_drift_mean_logprob:+.2e}** mean log p/token. \n\n" "A non-zero excess indicates **non-additive downstream response under these edits**; it is not, by itself, " "proof that the SAE features form a direct mechanistic circuit." ) def _paraphrase_metrics_markdown(result) -> str: return ( "#### Robustness metrics\n" "**Selected-token comparison** \n" f"TopK feature-set Jaccard: **{result.topk_jaccard:.3f}** · " f"sparse activation cosine: **{result.sparse_cosine:.3f}** \n" f"Shared features among displayed top-{result.top_n}: **{result.shared_top_n}/{result.top_n}** \n\n" "**Prompt-wide comparison** — max activation of each feature across all prompt tokens \n" f"Feature-set Jaccard: **{result.promptwide_jaccard:.3f}** · " f"activation cosine: **{result.promptwide_cosine:.3f}** \n\n" "The prompt-wide view reduces sensitivity to choosing semantically different final tokens, while the " "selected-token view remains the stricter local representation test. Neither metric establishes feature semantics." ) def _concept_metrics_markdown(result) -> str: coverage = f"{result.active_prompt_count}/{result.total_prompt_count}" if result.leading_concept is None: leader = ( "The feature was **inactive in every sampled prompt**. No concept leader is reported; " "increase the sample or inspect a different feature." ) elif result.leading_ratio is None: leader = ( f"Highest prompt-wide mean activation: **{result.leading_concept}**; the runner-up mean was zero." ) else: leader = ( f"Highest prompt-wide mean activation: **{result.leading_concept}** " f"(**{result.leading_ratio:.2f}×** the runner-up mean)." ) return ( f"Feature **{result.feature_id}**, layer **{result.layer}** · " f"{result.prompts_per_concept} controlled prompts/concept · active in **{coverage}** sampled prompts. \n" f"{leader} \n\n" "Each prompt is summarized by the feature's **maximum activation across its non-padding tokens**. " "This is an exploratory contrast scan, not an automatic semantic label; held-out AUROC/F1 remains the stronger evidence." ) def _trace_metrics_markdown(result) -> str: if result.max_token_index is None: peak = "Feature is inactive at every prompt token." else: peak = ( f"Peak activation **{result.max_activation:.4f}** at token **{result.max_token_index}** " f"({result.tokens[result.max_token_index]!r})." ) return ( f"Feature **{result.feature_id}**, layer **{result.layer}** · active at " f"**{result.active_token_count}/{result.token_count}** prompt tokens. \n{peak}" ) def _geometry_metrics_markdown(result) -> str: if result.alignment_ratio > 1.05: geometry = "The activation-weighted ablation directions are net **aligned**." elif result.alignment_ratio < 0.95: geometry = "The activation-weighted ablation directions show net **cancellation**." else: geometry = "The joint norm is close to the orthogonal/independent reference." return ( f"Selected features: **{', '.join(str(x) for x in result.feature_ids)}** · layer **{result.layer}** \n" f"Mean |decoder cosine|: **{result.mean_abs_decoder_cosine:.3f}** · " f"max |decoder cosine|: **{result.max_abs_decoder_cosine:.3f}** \n" f"Joint ablation L2: **{result.joint_ablation_norm:.4f}** · independent-direction reference: " f"**{result.independent_norm:.4f}** · geometry ratio: **{result.alignment_ratio:.3f}×** \n" f"{geometry} This diagnoses SAE decoder geometry; it does not by itself establish downstream causal interaction." ) def _contrastive_metrics_markdown(result) -> str: direction = "toward A" if result.delta_log_odds > 0 else ("toward B" if result.delta_log_odds < 0 else "neither way") return ( f"Feature **{result.feature_id}** · activation **{result.feature_activation:.4f}** · perturbation L2 " f"**{result.perturbation_norm:.4f}** \n" f"Exact-sequence log-odds A−B — baseline: **{result.baseline_log_odds:+.4f}** · " f"SAE edit: **{result.modified_log_odds:+.4f}** · causal shift: **{result.delta_log_odds:+.4f}** ({direction}). \n" f"Token-normalized preference shift: **{result.delta_normalized_preference:+.4f}**. \n" f"Random ensemble ({result.random_control_count}) |Δ log-odds| mean: **{result.random_abs_mean_delta:.4f}** " f"± **{result.random_delta_std:.4f}** · SAE/random ratio: **{result.specificity_ratio:.2f}×** · " f"empirical tail p: **{result.empirical_p:.3f}**. \n\n" "This asks whether the intervention changes the model's **relative preference between two specified continuations**, " "which is stricter than showing that one continuation's probability moved in isolation." ) def _discovery_metrics_markdown(result) -> str: if not result.candidate_ids: if result.ranking_mode == "causal_ready": return ( f"No positively selective **current-token-active** candidates were found for **{result.concept}** " f"at layer **{result.layer}** in this live batch. Try another token/layer or use Balanced selectivity " "to discover concept-associated features elsewhere in the prompt." ) return ( f"No positively selective candidate features were found for **{result.concept}** at layer " f"**{result.layer}** in this small live batch. Increase the sample or try another layer." ) ranking = { "balanced_selectivity": "balanced selectivity × coverage × log-magnitude", "raw_mean_difference": "raw target-minus-other mean activation", "causal_ready": "causal-ready evidence at the current Workbench token", }[result.ranking_mode] context = ( f"Current Workbench token **{result.current_token_index}** was checked in the same model batch. " "The default candidate prefers the highest-ranked feature active at that token when one exists." if result.current_context_available else "No Workbench prompt was available, so current-prompt compatibility was not evaluated." ) return ( f"Concept **{result.concept}** · layer **{result.layer}** · " f"{result.prompts_per_concept} prompts/concept. \n" f"Showing **{len(result.candidate_ids)}** candidates ranked by **{ranking}**. \n" f"Displayed candidates active at the selected Workbench token: " f"**{result.displayed_current_active_count}/{len(result.candidate_ids)}**. \n" f"{context} \n\n" "This is **candidate discovery**, not a semantic label. The offline held-out AUROC/F1 pipeline remains " "the standard for a concept-selectivity claim." ) def _candidate_screen_metrics_markdown(result) -> str: tokens = " ".join(repr(token) for token in result.target_tokens) if result.rows: top = result.rows[0] strongest = ( f"Strongest screened target effect: feature **{int(top[1])}** with " f"Δ mean log p/token **{float(top[5]):+.4f}** and next-token JS **{float(top[7]):.6f}**." ) else: strongest = "No candidate rows were produced." return ( f"Screened **{result.candidate_count}** candidate feature(s); **{result.active_feature_count}** were active " f"at the selected Workbench token. Target continuation: {len(result.target_tokens)} token(s): {tokens}. \n" f"{strongest} \n" f"Execution-context null drift: mean log p/token **{result.execution_drift_mean_logprob:+.2e}**, " f"JS **{result.execution_drift_js:.2e}**. \n\n" "This is a **triage screen**: all ablations share one batched zero-edit reference, but no random-control " "ensemble is spent here. Take a promising feature into **Workbench → II. Single-feature causal test** for " "the full 8-direction specificity comparison." ) def _spearman_rank_corr(left: list[float], right: list[float]) -> float | None: """Descriptive Spearman correlation with tie-aware average ranks.""" if len(left) != len(right) or len(left) < 2: return None left_s = pd.Series(left, dtype="float64") right_s = pd.Series(right, dtype="float64") if left_s.nunique(dropna=True) < 2 or right_s.nunique(dropna=True) < 2: return None value = left_s.rank(method="average").corr(right_s.rank(method="average")) return None if pd.isna(value) else float(value) def _candidate_alignment_outputs( discovery_table: pd.DataFrame | None, screen_table: pd.DataFrame | None, ) -> tuple[str, pd.DataFrame, pd.DataFrame]: """Join discovery evidence to causal triage results without another model call.""" empty_columns = [ "Feature id", "Discovery rank", "Target-effect rank", "Distribution-shift rank", "Candidate score", "Selectivity", "Current token activation", "|Δ mean log p/token|", "Next-token JS", "Discovery→target rank shift", ] if discovery_table is None or screen_table is None: return "", pd.DataFrame(columns=empty_columns), pd.DataFrame() discovery = pd.DataFrame(discovery_table).copy() screen = pd.DataFrame(screen_table).copy() if discovery.empty or screen.empty or "Feature id" not in discovery or "Feature id" not in screen: return "", pd.DataFrame(columns=empty_columns), pd.DataFrame() discovery["Feature id"] = pd.to_numeric(discovery["Feature id"], errors="coerce") screen["Feature id"] = pd.to_numeric(screen["Feature id"], errors="coerce") discovery = discovery.dropna(subset=["Feature id"]).copy() screen = screen.dropna(subset=["Feature id"]).copy() discovery["Feature id"] = discovery["Feature id"].astype(int) screen["Feature id"] = screen["Feature id"].astype(int) needed_discovery = {"Rank", "Candidate score", "Selectivity", "Current token activation"} needed_screen = {"Rank", "Δ mean log p/token", "Next-token JS"} if not needed_discovery.issubset(discovery.columns) or not needed_screen.issubset(screen.columns): return "", pd.DataFrame(columns=empty_columns), pd.DataFrame() discovery_lookup = discovery.set_index("Feature id", drop=False) js_ranked = screen.sort_values(["Next-token JS", "Feature id"], ascending=[False, True]).reset_index(drop=True) js_ranks = {int(row["Feature id"]): rank for rank, (_, row) in enumerate(js_ranked.iterrows(), start=1)} rows: list[list[object]] = [] for _, causal_row in screen.iterrows(): feature_id = int(causal_row["Feature id"]) if feature_id not in discovery_lookup.index: continue discovery_row = discovery_lookup.loc[feature_id] # set_index can technically return a DataFrame for duplicate ids; use the first row deterministically. if isinstance(discovery_row, pd.DataFrame): discovery_row = discovery_row.iloc[0] discovery_rank = int(float(discovery_row["Rank"])) target_rank = int(float(causal_row["Rank"])) mean_delta = float(causal_row["Δ mean log p/token"]) rows.append( [ feature_id, discovery_rank, target_rank, int(js_ranks[feature_id]), float(discovery_row["Candidate score"]), float(discovery_row["Selectivity"]), float(discovery_row["Current token activation"]), abs(mean_delta), float(causal_row["Next-token JS"]), discovery_rank - target_rank, ] ) table = pd.DataFrame(rows, columns=empty_columns) if table.empty: return "", table, pd.DataFrame() rho_target = _spearman_rank_corr( table["Candidate score"].astype(float).tolist(), table["|Δ mean log p/token|"].astype(float).tolist(), ) rho_js = _spearman_rank_corr( table["Candidate score"].astype(float).tolist(), table["Next-token JS"].astype(float).tolist(), ) top_discovery = table.sort_values(["Discovery rank", "Feature id"]).iloc[0] top_target = table.sort_values(["Target-effect rank", "Feature id"]).iloc[0] top_js = table.sort_values(["Distribution-shift rank", "Feature id"]).iloc[0] def fmt_rho(value: float | None) -> str: return "undefined" if value is None else f"{value:+.3f}" summary = ( f"Compared **{len(table)}** screened candidates using the discovery evidence and causal triage from the same workflow. \n" f"Top discovery candidate: **{int(top_discovery['Feature id'])}** · strongest target effect: " f"**{int(top_target['Feature id'])}** · strongest next-token distribution shift: **{int(top_js['Feature id'])}**. \n" f"Spearman ρ(candidate score, |target effect|): **{fmt_rho(rho_target)}** · " f"ρ(candidate score, next-token JS): **{fmt_rho(rho_js)}**. \n\n" "This is the project's central association-vs-causality diagnostic: a feature can rank highly by concept evidence yet " "rank weakly by downstream target effect, or vice versa. With only a handful of screened features and no random-control " "ensemble in triage, these correlations are **descriptive**, not significance claims." ) chart = table[["Feature id", "Candidate score", "|Δ mean log p/token|", "Discovery rank", "Target-effect rank", "Next-token JS"]].copy() chart["Feature id"] = chart["Feature id"].astype(str) chart["Series"] = "Screened candidate" return summary, table, chart def _cue_context_metrics_markdown(result) -> str: active = ", ".join( f"{cue}: {count}/{len(result.stems)} contexts" for cue, count in result.cue_active_context_counts.items() ) interpretation = ( "No tested cue activated the feature." if result.dominant_cue is None or result.active_condition_count == 0 else ( f"**Cue-dominant pattern:** `{result.dominant_cue}` activates in every tested context while all " "other tested cues are inactive. Under this matrix, the evidence is much more consistent with a " "lexical/cue-specific response than with a mathematics-specific response." if result.dominant_cue_context_count == len(result.stems) and result.off_dominant_active_count == 0 else ( f"The strongest cue is `{result.dominant_cue}` ({result.dominant_cue_context_count}/{len(result.stems)} " "contexts), but other cues or context dependence remain. Treat the pattern as mixed/context-sensitive." ) ) ) return ( f"Feature **{result.feature_id}**, layer **{result.layer}** · active in " f"**{result.active_condition_count}/{result.condition_count}** stem × cue conditions. \n" f"Cue coverage — {active}. \n\n" f"{interpretation} \n\n" "This is a controlled diagnostic over the tested stems/cues, not a universal semantic label." ) def _cue_metrics_markdown(result) -> str: return ( f"Feature **{result.feature_id}**, layer **{result.layer}** · active for " f"**{result.active_cue_count}/{result.cue_count}** tested completion cues at the final token. \n" "This helps distinguish a concept-associated feature from a lexical/structural completion-cue feature. " "It is a controlled diagnostic, not a feature label." ) def _global_context_markdown(prompt: str, layer: int, result) -> str: token = result.tokens[result.token_index] if result.tokens else "" short = prompt[:120] + ("…" if len(prompt) > 120 else "") return ( "**Current Workbench context** — " f"Prompt: `{short}` · layer **{int(layer)}** · token **{result.token_index}** ({token!r}). \n" "Panels marked **Uses current Workbench context** inherit this prompt/layer/token. " "Feature selectors remain editable inside each experiment." ) @gpu(duration=30) def analyze_prompt(prompt: str, layer: int, token_index: int, top_n: int): try: if not prompt.strip(): raise ValueError("Enter a prompt first.") result = RUNTIME.analyze(prompt, int(layer), int(token_index), int(top_n)) columns = ["Rank", "Feature id", "Activation", "Offline concept hint"] table = pd.DataFrame(result.rows, columns=columns) choices = [str(int(row[1])) for row in result.rows] feature_update = gr.update(choices=choices, value=choices[0] if choices else None) feature_set_update = gr.update(choices=choices, value=choices[: min(3, len(choices))]) contrast_update = gr.update(choices=choices, value=choices[0] if choices else None) chart_df = pd.DataFrame( { "Feature": [str(int(row[1])) for row in result.rows], "Activation": [float(row[2]) for row in result.rows], "Series": ["Activation"] * len(result.rows), } ) location = ( f"Current Workbench location — **layer {int(layer)}**, **prompt token {result.token_index}**; " f"prompt: `{prompt[:90]}{'…' if len(prompt) > 90 else ''}`" ) return ( RUNTIME.token_html(result.tokens, result.token_index), table, chart_df, feature_update, gr.update(choices=choices, value=choices[0] if choices else None), gr.update(choices=choices, value=choices[0] if choices else None), feature_set_update, contrast_update, gr.update(value=int(layer)), _analysis_metrics_markdown(result), location, location, _global_context_markdown(prompt, int(layer), result), _tsv(table), ) except Exception as exc: _raise_ui_error(exc) @gpu(duration=45) def run_intervention( prompt: str, layer: int, token_index: int, feature_id: str, mode: str, coefficient: float, target_text: str, max_new_tokens: int, ): try: if not prompt.strip(): raise ValueError("Enter a prompt first.") if feature_id is None or str(feature_id).strip() == "": raise ValueError("Choose or enter a feature id.") result = RUNTIME.intervene( text=prompt, layer=int(layer), token_index=int(token_index), feature_id=int(float(feature_id)), mode=mode, coefficient=float(coefficient), target_text=target_text, max_new_tokens=int(max_new_tokens), ) token_columns = ["Token", "Baseline p", "SAE-edit p", "Δ probability"] target_columns = [ "Target position", "Target token", "Baseline log p", "SAE-edit log p", "Random-ensemble mean log p", "SAE Δ log p", "Random-ensemble mean Δ log p", ] token_df = pd.DataFrame(result.top_token_rows, columns=token_columns) target_df = pd.DataFrame(result.target_token_rows, columns=target_columns) return ( result.baseline_text, result.modified_text, _intervention_metrics_markdown(result), token_df, target_df, _tsv(token_df), _tsv(target_df), ) except Exception as exc: _raise_ui_error(exc) @gpu(duration=35) def run_dose_response(prompt: str, layer: int, token_index: int, feature_id: str, target_text: str): try: if not prompt.strip(): raise ValueError("Enter a prompt first.") if feature_id is None or str(feature_id).strip() == "": raise ValueError("Choose or enter a feature id.") if not target_text.strip(): raise ValueError("Enter a target continuation before running the scale dose-response.") result = RUNTIME.dose_response( text=prompt, layer=int(layer), token_index=int(token_index), feature_id=int(float(feature_id)), target_text=target_text, ) columns = [ "Multiplier", "Δ feature coefficient", "Perturbation L2", "Batched-null mean log p/token", "Modified mean log p/token", "Δ mean log p/token", "Δ sequence log p", "Next-token JS", ] table = pd.DataFrame(result.rows, columns=columns) plot = table[["Multiplier", "Δ mean log p/token"]].copy() plot["Series"] = "SAE feature" return table, plot, _dose_metrics_markdown(result), _tsv(table) except Exception as exc: _raise_ui_error(exc) @gpu(duration=35) def run_layer_sweep(prompt: str, token_index: int): try: if not prompt.strip(): raise ValueError("Enter a prompt first.") result = RUNTIME.layer_sweep(prompt, int(token_index)) columns = [ "Layer", "Reconstruction cosine", "NMSE", "Active features", "Top activation", "Top-5 mass", "Activation entropy", ] table = pd.DataFrame(result.rows, columns=columns) long = table.melt( id_vars=["Layer"], value_vars=["Reconstruction cosine", "Top-5 mass", "Activation entropy"], var_name="Metric", value_name="Value", ) return RUNTIME.token_html(result.tokens, result.token_index), table, long, _tsv(table) except Exception as exc: _raise_ui_error(exc) @gpu(duration=40) def run_feature_set( prompt: str, layer: int, token_index: int, feature_ids: list[str] | None, mode: str, coefficient: float, target_text: str, ): try: if not prompt.strip(): raise ValueError("Enter and inspect a prompt in the Workbench first.") selected = [int(float(value)) for value in (feature_ids or [])] if not selected: raise ValueError("Select at least one feature in 'Feature set'.") if not target_text.strip(): raise ValueError("Enter a target continuation for the feature-set causal test.") result = RUNTIME.intervene_feature_set( text=prompt, layer=int(layer), token_index=int(token_index), feature_ids=selected, mode=mode, coefficient=float(coefficient), target_text=target_text, ) feature_columns = ["Feature id", "Original activation", "Δ coefficient", "Offline concept hint"] target_columns = [ "Target position", "Target token", "Baseline log p", "SAE-edit log p", "Random-ensemble mean log p", "SAE Δ log p", "Random-ensemble mean Δ log p", ] feature_df = pd.DataFrame(result.feature_rows, columns=feature_columns) target_df = pd.DataFrame(result.target_token_rows, columns=target_columns) return ( feature_df, _feature_set_metrics_markdown(result), target_df, _tsv(feature_df), _tsv(target_df), ) except Exception as exc: _raise_ui_error(exc) @gpu(duration=45) def run_feature_set_sweep(prompt: str, layer: int, token_index: int, target_text: str): try: if not prompt.strip(): raise ValueError("Enter and inspect a prompt in the Workbench first.") if not target_text.strip(): raise ValueError("Enter a target continuation before running the set-size sweep.") result = RUNTIME.feature_set_size_sweep( text=prompt, layer=int(layer), token_index=int(token_index), target_text=target_text, ) columns = [ "Set size k", "Feature ids", "Perturbation L2", "Batched-null mean log p/token", "SAE mean log p/token", "SAE Δ mean log p/token", "Random signed mean Δ", "Random mean |Δ|", "Random |Δ| std", "SAE/random magnitude ratio", "Empirical tail p", "SAE Δ sequence log p", "SAE next-token JS", "Random mean JS", "Random JS std", "JS empirical tail p", ] table = pd.DataFrame(result.rows, columns=columns) plot_rows: list[list[object]] = [] for _, row in table.iterrows(): plot_rows.append([row["Set size k"], "Top-k SAE ablation", row["SAE Δ mean log p/token"]]) plot_rows.append([row["Set size k"], "Random signed mean", row["Random signed mean Δ"]]) plot = pd.DataFrame(plot_rows, columns=["Set size k", "Condition", "Δ mean log p/token"]) tokens = " ".join(repr(token) for token in result.target_tokens) note = ( f"Target continuation: {len(result.target_tokens)} token(s): {tokens}. For each k, FeatureLens " f"ablates the k strongest active features and compares the effect with **{result.random_control_count} " f"norm-matched random directions**. All conditions share one batched zero-edit reference. \n" f"Execution-context null drift: mean log p/token **{result.execution_drift_mean_logprob:+.2e}**, " f"JS **{result.execution_drift_js:.2e}**. The live empirical p-value is intentionally coarse because " f"it uses only {result.random_control_count} controls; the offline experiment should use more." ) return table, plot, note, _tsv(table) except Exception as exc: _raise_ui_error(exc) @gpu(duration=40) def run_feature_interaction( prompt: str, layer: int, token_index: int, feature_ids: list[str] | None, target_text: str, ): try: selected = [int(float(value)) for value in (feature_ids or [])] if not prompt.strip(): raise ValueError("Enter and inspect a prompt in the Workbench first.") if len(selected) < 2: raise ValueError("Select at least two features in 'Feature set'.") if len(selected) > 5: raise ValueError("Select at most five features for the interaction decomposition.") if not target_text.strip(): raise ValueError("Enter a target continuation for the interaction decomposition.") result = RUNTIME.feature_interaction_test( text=prompt, layer=int(layer), token_index=int(token_index), feature_ids=selected, target_text=target_text, ) columns = [ "Condition", "Feature ids", "Activation summary", "Perturbation L2", "Δ mean log p/token", "Δ sequence log p", "Next-token JS", ] table = pd.DataFrame(result.rows, columns=columns) plot = table[["Condition", "Δ mean log p/token"]].copy() plot["Series"] = "Ablation effect" return table, _interaction_metrics_markdown(result), plot, _tsv(table) except Exception as exc: _raise_ui_error(exc) @gpu(duration=30) def run_paraphrase_compare( original_prompt: str, paraphrase_prompt: str, layer: int, token_index_a: int, token_index_b: int, top_n: int, ): try: result = RUNTIME.compare_paraphrases( text_a=original_prompt, text_b=paraphrase_prompt, layer=int(layer), token_index_a=int(token_index_a), token_index_b=int(token_index_b), top_n=int(top_n), ) columns = ["Feature id", "Original activation", "Paraphrase activation", "Status", "Offline concept hint"] table = pd.DataFrame(result.rows, columns=columns) chart = pd.DataFrame(result.chart_rows, columns=["Feature", "Prompt", "Activation"]) return ( RUNTIME.token_html(result.tokens_a, result.token_index_a), RUNTIME.token_html(result.tokens_b, result.token_index_b), _paraphrase_metrics_markdown(result), table, chart, _tsv(table), ) except Exception as exc: _raise_ui_error(exc) @gpu(duration=35) def run_concept_contrast(feature_id: str, layer: int, prompts_per_concept: int): try: if feature_id is None or str(feature_id).strip() == "": raise ValueError("Choose a feature id first. Run Workbench inspection if the selector is empty.") result = RUNTIME.concept_contrast_scan( feature_id=int(float(feature_id)), layer=int(layer), prompts_per_concept=int(prompts_per_concept), ) columns = [ "Concept", "Prompts", "Mean prompt-wide max", "Median prompt-wide max", "Prompt activation rate", "Mean when active", "Max activation", ] table = pd.DataFrame(result.rows, columns=columns) chart = pd.DataFrame(result.chart_rows, columns=["Concept", "Mean prompt-wide max"]) chart["Series"] = "Prompt-wide max" return _concept_metrics_markdown(result), table, chart, _tsv(table) except Exception as exc: _raise_ui_error(exc) @gpu(duration=30) def run_feature_trace(prompt: str, layer: int, feature_id: str): try: if not prompt.strip(): raise ValueError("Enter a prompt first.") if feature_id is None or str(feature_id).strip() == "": raise ValueError("Choose a feature id first.") result = RUNTIME.feature_token_trace( text=prompt, layer=int(layer), feature_id=int(float(feature_id)), ) columns = ["Token position", "Token", "Activation", "Active in TopK"] table = pd.DataFrame(result.rows, columns=columns) chart = pd.DataFrame(result.chart_rows, columns=["Token", "Activation"]) chart["Series"] = "Feature activation" return _trace_metrics_markdown(result), table, chart, _tsv(table) except Exception as exc: _raise_ui_error(exc) @gpu(duration=30) def run_feature_geometry(prompt: str, layer: int, token_index: int, feature_ids: list[str] | None): try: selected = [int(float(value)) for value in (feature_ids or [])] result = RUNTIME.feature_geometry( text=prompt, layer=int(layer), token_index=int(token_index), feature_ids=selected, ) columns = ["Feature A", "Feature B", "Activation A", "Activation B", "Decoder cosine"] table = pd.DataFrame(result.rows, columns=columns) chart = pd.DataFrame(result.chart_rows, columns=["Feature pair", "Decoder cosine"]) chart["Series"] = "Decoder cosine" return _geometry_metrics_markdown(result), table, chart, _tsv(table) except Exception as exc: _raise_ui_error(exc) @gpu(duration=40) def run_contrastive_causal( prompt: str, layer: int, token_index: int, feature_id: str, mode: str, coefficient: float, target_a: str, target_b: str, ): try: if feature_id is None or str(feature_id).strip() == "": raise ValueError("Choose a feature id first.") result = RUNTIME.contrastive_intervention( text=prompt, layer=int(layer), token_index=int(token_index), feature_id=int(float(feature_id)), mode=mode, coefficient=float(coefficient), target_a=target_a, target_b=target_b, ) columns = [ "Continuation", "Text", "Tokens", "Baseline sequence log p", "SAE-edit sequence log p", "Δ sequence log p", "Baseline mean log p/token", "SAE-edit mean log p/token", "Δ mean log p/token", ] table = pd.DataFrame(result.rows, columns=columns) chart = pd.DataFrame( [ ["Baseline", result.baseline_log_odds], ["SAE edit", result.modified_log_odds], ], columns=["Condition", "A−B sequence log-odds"], ) chart["Series"] = "Contrastive preference" return _contrastive_metrics_markdown(result), table, chart, _tsv(table) except Exception as exc: _raise_ui_error(exc) @gpu(duration=35) def run_concept_feature_discovery( concept: str, layer: int, prompts_per_concept: int, top_n: int, ranking_label: str, workbench_prompt: str, workbench_token_index: int, ): try: ranking_mode = { "Balanced selectivity": "balanced_selectivity", "Raw mean difference": "raw_mean_difference", "Causal-ready at current token": "causal_ready", }[ranking_label] result = RUNTIME.concept_feature_discovery( concept=concept, layer=int(layer), prompts_per_concept=int(prompts_per_concept), top_n=int(top_n), ranking_mode=ranking_mode, current_text=workbench_prompt, current_token_index=int(workbench_token_index), ) columns = [ "Rank", "Feature id", "Candidate score", "Target mean max", "Other mean max", "Mean difference", "Selectivity", "Target activation rate", "Other activation rate", "Current prompt max", "Current token activation", "Active at current token", ] table = pd.DataFrame(result.rows, columns=columns) chart = pd.DataFrame(result.chart_rows, columns=["Feature", "Candidate score"]) chart["Series"] = "Candidate score" choices = [str(fid) for fid in result.candidate_ids] default = str(result.default_candidate_id) if result.default_candidate_id is not None else (choices[0] if choices else None) candidate_update = gr.update(choices=choices, value=default) screen_update = gr.update( choices=choices, value=choices[: min(5, len(choices))], ) return ( _discovery_metrics_markdown(result), table, chart, candidate_update, screen_update, _tsv(table), ) except Exception as exc: _raise_ui_error(exc) @gpu(duration=30) def run_candidate_causal_screen( prompt: str, layer: int, token_index: int, feature_ids: list[str] | None, target_text: str, discovery_table: pd.DataFrame | None, ): try: selected = [int(float(value)) for value in (feature_ids or [])] result = RUNTIME.candidate_causal_screen( text=prompt, layer=int(layer), token_index=int(token_index), feature_ids=selected, target_text=target_text, ) columns = [ "Rank", "Feature id", "Native activation", "Active at current token", "Perturbation L2", "Δ mean log p/token", "Δ sequence log p", "Next-token JS", ] table = pd.DataFrame(result.rows, columns=columns) chart = pd.DataFrame( result.chart_rows, columns=["Feature", "Δ mean log p/token"], ) chart["Series"] = "Candidate ablation" choices = [str(feature_id) for feature_id in result.feature_ids] candidate_update = gr.update( choices=choices, value=choices[0] if choices else None, ) alignment_metrics, alignment_table, alignment_chart = _candidate_alignment_outputs( discovery_table, table ) return ( _candidate_screen_metrics_markdown(result), table, chart, candidate_update, _tsv(table), alignment_metrics, alignment_table, alignment_chart, _tsv(alignment_table), ) except Exception as exc: _raise_ui_error(exc) @gpu(duration=25) def run_feature_cue_scan(feature_id: str, layer: int, prompt_stem: str, cue_text: str): try: if feature_id is None or str(feature_id).strip() == "": raise ValueError("Choose a feature id first.") cues = [line for line in str(cue_text).splitlines() if line.strip()] result = RUNTIME.feature_cue_scan( feature_id=int(float(feature_id)), layer=int(layer), prompt_stem=prompt_stem, cues=cues, ) columns = ["Cue", "Full prompt", "Final token", "Activation", "Active in TopK"] table = pd.DataFrame(result.rows, columns=columns) chart = pd.DataFrame(result.chart_rows, columns=["Cue", "Activation"]) chart["Series"] = "Cue response" return _cue_metrics_markdown(result), table, chart, _tsv(table) except Exception as exc: _raise_ui_error(exc) @gpu(duration=30) def run_feature_cue_context_scan(feature_id: str, layer: int, stems_text: str, cue_text: str): try: if feature_id is None or str(feature_id).strip() == "": raise ValueError("Choose a feature id first.") stems = [line for line in str(stems_text).splitlines() if line.strip()] cues = [line for line in str(cue_text).splitlines() if line.strip()] result = RUNTIME.feature_cue_context_scan( feature_id=int(float(feature_id)), layer=int(layer), stems=stems, cues=cues, ) columns = ["Prompt stem", "Cue", "Full prompt", "Final token", "Activation", "Active in TopK"] table = pd.DataFrame(result.rows, columns=columns) chart = pd.DataFrame(result.chart_rows, columns=["Prompt stem", "Cue", "Activation"]) return _cue_context_metrics_markdown(result), table, chart, _tsv(table) except Exception as exc: _raise_ui_error(exc) def select_candidate_row(table: pd.DataFrame, evt: gr.SelectData): if table is None or len(table) == 0: return gr.update() index = evt.index row_index = int(index[0] if isinstance(index, (tuple, list)) else index) if row_index < 0 or row_index >= len(table): return gr.update() value = str(int(float(table.iloc[row_index]["Feature id"]))) return gr.update(value=value) def use_candidate_feature(candidate_id: str): if candidate_id is None or str(candidate_id).strip() == "": raise gr.Error("Run concept-guided discovery and choose a candidate first.") value = str(int(float(candidate_id))) status = ( f"**Feature {value} loaded.** It is now selected in Single-feature causal test, " "Scale dose-response, Contrastive preference, and Feature evidence. You can still change any selector independently." ) return value, value, value, value, status def mode_help(mode: str): if mode == "ablate": return gr.update(value=0.0, interactive=False, label="Coefficient (unused for ablation)") if mode == "scale": return gr.update(value=2.0, interactive=True, label="Feature multiplier") return gr.update(value=5.0, interactive=True, label="Additive feature coefficient") def set_mode_help(mode: str): if mode == "ablate": return gr.update(value=0.0, interactive=False, label="Multiplier (unused for ablation)") return gr.update(value=2.0, interactive=True, label="Shared feature multiplier") with gr.Blocks(title="FeatureLens — Causal Interpretability Workbench", fill_width=True) as demo: gr.HTML( '
' '

FeatureLens v0.10

' '
Causal Interpretability Workbench
' '
Discover sparse features, test robustness, and separate correlation from causal influence.
' '
' ) global_context = gr.Markdown( "**Current Workbench context:** none yet. Go to **Workbench → I. Inspect a prompt location**, " "set the prompt/layer/token, then click **Inspect sparse features**.", elem_classes=["context-card"], ) with gr.Tab("Start here"): gr.Markdown( "## Start here\n" "FeatureLens is organized as a short evidence workflow. You do **not** need to know a feature id before " "starting: inspect a prompt first, or use **Feature evidence → Concept-guided candidate discovery**." ) with gr.Row(equal_height=True): gr.HTML( '

1. Choose a location

' '

In Workbench, enter a prompt, choose a residual layer and token, then click ' 'Inspect sparse features. This establishes the shared Workbench context.

' ) gr.HTML( '

2. Choose evidence

' '

Pick a feature in the experiment you want to run. Feature selectors are populated from the latest ' 'inspection but remain editable. Feature sets use 2–5 features.

' ) gr.HTML( '

3. Ask a causal question

' '

Ablate/scale features, compare against norm-matched random controls, test dose response, ' 'preference shifts, robustness, geometry, or concept association.

' ) gr.Markdown( "### Plain-language glossary\n" "- **Token:** a chunk of text the model processes.\n" "- **Residual layer:** a checkpoint in the model's internal computation.\n" "- **SAE feature:** one sparse direction used by the autoencoder to represent a residual activation.\n" "- **Ablate:** remove that feature's current contribution. **Scale:** multiply it.\n" "- **Random control:** an equally large residual edit in an unrelated direction.\n" "- **Teacher-forced score:** probability assigned to exact continuation text, even if greedy generation does not change.\n\n" "**Tables:** use the top-right focus control or copy-with-headers action. **Plots:** use the top-right focus control for a bounded reading view; PNG exports use descriptive FeatureLens filenames." ) with gr.Tab("Workbench"): gr.HTML('
I. Inspect a prompt location
') with gr.Row(equal_height=False): with gr.Column(scale=5): prompt = gr.Textbox( label="Prompt", lines=5, value="The derivative of x squared is", placeholder="Enter a prompt to inspect…", ) gr.Examples( examples=[ ["The derivative of x squared is"], ["In Python, reverse a list using"], ["Ich möchte einen Tisch für zwei reservieren."], ["I am not fully certain, but the answer may be"], ], inputs=[prompt], label="Controlled examples", ) with gr.Column(scale=3): layer = gr.Dropdown( choices=list(SETTINGS.layers), value=SETTINGS.layers[1] if len(SETTINGS.layers) > 1 else SETTINGS.layers[0], label="Residual layer", ) token_index = gr.Number( value=-1, precision=0, label="Prompt token index", info="-1 = final prompt token.", ) top_n = gr.Slider(5, 20, value=12, step=1, label="Displayed active features") analyze_btn = gr.Button("Inspect sparse features", variant="primary", elem_classes=["action-btn"]) gr.Markdown("#### Prompt tokens\nThe selected prompt token is outlined more strongly.") token_view = gr.HTML( '
Prompt tokens appear here after clicking Inspect sparse features.
' ) analysis_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Strongest active SAE features') feature_table = gr.Dataframe( headers=["Rank", "Feature id", "Activation", "Offline concept hint"], datatype=["number", "number", "number", "str"], interactive=False, label="Strongest active SAE features", show_label=False, wrap=False, max_height=380, buttons=["fullscreen"], elem_classes=["result-table"], ) feature_tsv = gr.Textbox(visible="hidden") feature_copy = _copy_button() with gr.Column(scale=2): feature_plot = gr.BarPlot( x="Feature", y="Activation", color="Series", color_map={"Activation": INK_TEAL}, title="Activation profile", elem_id="plot-activation-profile", x_title="Feature id", y_title="Activation", x_label_angle=-35, buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=330, ) gr.HTML('
II. Single-feature causal test
') gr.Markdown( "The edit is compared with an **8-direction norm-matched random ensemble** in the same batched " "execution context. If a target continuation is supplied, the complete continuation is scored teacher-forced." ) with gr.Row(equal_height=False): with gr.Column(scale=2): feature_id = gr.Dropdown( choices=[], allow_custom_value=True, label="Single feature id", info="Populated from the latest inspection. You can choose another active feature or enter any valid id.", ) mode = gr.Dropdown( choices=["ablate", "scale", "inject"], value="ablate", label="Single-feature intervention", ) coefficient = gr.Number( value=0.0, interactive=False, label="Coefficient (unused for ablation)", ) target_text = gr.Textbox( label="Target continuation (optional)", placeholder="e.g. 2x", info="Exact text appended to the prompt for teacher-forced scoring.", ) max_new = gr.Slider( 4, SETTINGS.max_new_tokens, value=min(12, SETTINGS.max_new_tokens), step=1, label="Greedy generation length", ) intervene_btn = gr.Button("Run single-feature causal test", variant="primary", elem_classes=["action-btn"]) intervention_metrics = gr.Markdown() with gr.Column(scale=3): with gr.Row(): baseline_out = gr.Textbox(label="Baseline greedy generation", lines=6, interactive=False) modified_out = gr.Textbox(label="SAE-edited greedy generation", lines=6, interactive=False) _table_heading('Next-token distribution shift') token_prob_table = gr.Dataframe( interactive=False, label="Next-token distribution shift", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) token_prob_tsv = gr.Textbox(visible="hidden") token_prob_copy = _copy_button() _table_heading('Target continuation token-by-token score') target_token_table = gr.Dataframe( interactive=False, label="Target continuation token-by-token score", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) target_token_tsv = gr.Textbox(visible="hidden") target_token_copy = _copy_button() gr.HTML('
III. Single-feature scale dose-response
') with gr.Group(): gr.Markdown("**Uses current Workbench prompt / layer / token.** Choose the feature for this experiment below.") with gr.Row(equal_height=True): dose_feature_id = gr.Dropdown( choices=[], allow_custom_value=True, label="Dose-response feature id", info="Populated from Workbench inspection, or enter any valid feature id directly.", scale=2, ) dose_target_text = gr.Textbox( label="Dose-response target continuation", value="2x", info="Exact continuation scored across all dose-response conditions.", scale=2, ) gr.Markdown( "0× = ablation, 1× = numerical no-edit reference, and 2× = double the native coefficient. " "The six scale conditions are evaluated together against the 1× row." ) dose_btn = gr.Button("Run scale dose-response", variant="primary", elem_classes=["action-btn"]) dose_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Scale dose-response measurements') dose_table = gr.Dataframe( interactive=False, label="Scale dose-response measurements", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) dose_tsv = gr.Textbox(visible="hidden") dose_copy = _copy_button() with gr.Column(scale=2): dose_plot = gr.LinePlot( x="Multiplier", y="Δ mean log p/token", color="Series", color_map={"SAE feature": INK_TEAL}, title="Scale dose-response", elem_id="plot-scale-dose-response", x_title="Feature multiplier", y_title="Δ mean log p/token", buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=330, ) gr.HTML('
IV. Contrastive continuation preference
') with gr.Group(): gr.Markdown("**Uses current Workbench prompt / layer / token.** Select the feature explicitly below.") contrastive_feature_id = gr.Dropdown( choices=[], allow_custom_value=True, label="Contrastive feature id", info="Populated from the latest Workbench inspection; editable here.", ) gr.Markdown( "Absolute target probability can move because an intervention broadly perturbs the distribution. " "This test asks the stricter question: **does the edit shift preference between two exact continuations?** " "Both continuations are teacher-forced and compared with the same 8-direction norm-matched random ensemble." ) with gr.Row(equal_height=True): contrastive_a = gr.Textbox(label="Continuation A (preferred)", value="2x", scale=2) contrastive_b = gr.Textbox(label="Continuation B (comparison)", value="x", scale=2) with gr.Row(equal_height=True): contrastive_mode = gr.Dropdown( choices=["ablate", "scale", "inject"], value="ablate", label="Contrastive intervention", scale=1, ) contrastive_coefficient = gr.Number( value=0.0, interactive=False, label="Coefficient (unused for ablation)", scale=1, ) contrastive_btn = gr.Button( "Run contrastive preference test", variant="primary", elem_classes=["action-btn"] ) contrastive_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Contrastive continuation scores') contrastive_table = gr.Dataframe( interactive=False, label="Contrastive continuation scores", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=320, ) contrastive_tsv = gr.Textbox(visible="hidden") contrastive_copy = _copy_button() with gr.Column(scale=2): contrastive_plot = gr.BarPlot( x="Condition", y="A−B sequence log-odds", color="Series", color_map={"Contrastive preference": INK_TEAL}, title="Preference between exact continuations", elem_id="plot-contrastive-preference", x_title="Execution condition", y_title="Sequence log-odds A−B", buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=320, ) with gr.Tab("Feature sets"): gr.Markdown( "### Distributed sparse representations\n" "Run **Inspect sparse features** in the Workbench first. **Uses current Workbench context:** prompt, layer, " "and token are inherited from that inspection. The feature set below is always editable." ) feature_set_location = gr.Markdown("No Workbench inspection has been run yet.") feature_set_ids = gr.Dropdown( choices=[], value=[], multiselect=True, allow_custom_value=True, max_choices=12, label="Feature set", ) gr.Markdown("#### Joint feature-set causal test") gr.HTML( '
Ablation ignores the multiplier. Scale applies one shared multiplier ' 'to every selected feature before decoder deltas are summed. Additive injection is deliberately omitted ' 'because one coefficient is not comparable across unrelated directions.
' ) with gr.Row(equal_height=True): set_mode = gr.Dropdown( choices=["ablate", "scale"], value="ablate", label="Intervention", scale=1, ) set_coefficient = gr.Number( value=0.0, interactive=False, label="Multiplier (unused for ablation)", scale=1, ) set_target = gr.Textbox( label="Target continuation", value="2x", lines=1, scale=2, ) set_btn = gr.Button("Run joint feature-set causal test", variant="primary", elem_classes=["action-btn"]) set_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=2): _table_heading('Joint intervention features') set_feature_table = gr.Dataframe( interactive=False, label="Joint intervention features", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) set_feature_tsv = gr.Textbox(visible="hidden") set_feature_copy = _copy_button() with gr.Column(scale=3): _table_heading('Target continuation token-by-token score') set_target_table = gr.Dataframe( interactive=False, label="Target continuation token-by-token score", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) set_target_tsv = gr.Textbox(visible="hidden") set_target_copy = _copy_button() gr.HTML('
Set-size sensitivity
') gr.Markdown( "Ablates the **1, 3, and 5 strongest active features**. Each targeted edit is compared with eight " "norm-matched random directions, all sharing one batched no-edit baseline." ) set_sweep_target = gr.Textbox(label="Target continuation for set-size sweep", value="2x") set_sweep_btn = gr.Button("Run 1/3/5-feature ablation sweep", variant="primary", elem_classes=["action-btn"]) set_sweep_note = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Feature-set size measurements') set_sweep_table = gr.Dataframe( interactive=False, label="Feature-set size measurements", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) set_sweep_tsv = gr.Textbox(visible="hidden") set_sweep_copy = _copy_button() with gr.Column(scale=2): set_sweep_plot = gr.LinePlot( x="Set size k", y="Δ mean log p/token", color="Condition", color_map={ "Top-k SAE ablation": INK_TEAL, "Random signed mean": INK_STONE, }, title="Effect vs feature-set size", elem_id="plot-feature-set-size", x_title="Number of jointly ablated features", y_title="Δ mean log p/token", buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=330, ) gr.HTML('
Non-additivity / interaction decomposition
') gr.Markdown( "For the selected 2–5 features, FeatureLens batches each **individual ablation** plus the **joint " "ablation**. It compares the observed joint effect with the sum of individual effects. This diagnoses " "non-additivity without claiming that the features form a direct circuit." ) interaction_target = gr.Textbox(label="Target continuation for interaction test", value="2x") interaction_btn = gr.Button("Run individual-vs-joint decomposition", variant="primary", elem_classes=["action-btn"]) interaction_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Individual and joint ablation measurements') interaction_table = gr.Dataframe( interactive=False, label="Individual and joint ablation measurements", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) interaction_tsv = gr.Textbox(visible="hidden") interaction_copy = _copy_button() with gr.Column(scale=2): interaction_plot = gr.BarPlot( x="Condition", y="Δ mean log p/token", color="Series", color_map={"Ablation effect": INK_UMBER}, title="Individual vs joint effect", elem_id="plot-individual-vs-joint", x_title="Intervention condition", y_title="Δ mean log p/token", x_label_angle=-25, buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=330, ) gr.HTML('
Decoder geometry
') with gr.Group(): gr.Markdown( "Joint causal effects can reflect both **decoder-direction geometry** and downstream non-linearity. " "This zero-extra-generation diagnostic measures pairwise decoder cosines and compares the actual " "activation-weighted joint-ablation norm with the norm expected for independent directions." ) geometry_btn = gr.Button( "Inspect selected-feature geometry", variant="primary", elem_classes=["action-btn"] ) geometry_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Pairwise decoder geometry') geometry_table = gr.Dataframe( interactive=False, label="Pairwise decoder geometry", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=340, ) geometry_tsv = gr.Textbox(visible="hidden") geometry_copy = _copy_button() with gr.Column(scale=2): geometry_plot = gr.BarPlot( x="Feature pair", y="Decoder cosine", color="Series", color_map={"Decoder cosine": INK_PLUM}, title="Pairwise SAE decoder cosine", elem_id="plot-decoder-geometry", x_title="Feature pair", y_title="Cosine similarity", x_label_angle=-30, buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=320, ) with gr.Tab("Feature evidence"): gr.Markdown( "### Feature activation evidence\n" "Inspect **where a feature fires inside the current prompt**, then test whether it activates preferentially " "across FeatureLens's seven controlled concept groups. The concept scan uses the feature's **maximum " "activation across non-padding tokens in each prompt**, avoiding arbitrary final-token comparisons." ) gr.HTML('
A. Concept-guided candidate discovery
') gr.Markdown( "Start from a concept instead of an anonymous feature id. The default **Balanced selectivity** ranking " "downweights globally high-activation features and favors candidates that are both selective and consistently " "active for the target concept. The table also checks whether each candidate is active in the current Workbench " "prompt and selected token, so you can tell whether it is immediately usable for a causal test." ) with gr.Row(equal_height=True): discovery_concept = gr.Dropdown( choices=[ "code", "mathematics", "positive_sentiment", "negative_sentiment", "german_language", "factual_entities", "uncertainty" ], value="mathematics", label="Target concept", ) discovery_layer = gr.Dropdown(choices=list(SETTINGS.layers), value=SETTINGS.layers[1], label="Residual layer") discovery_n = gr.Slider(2, 6, value=SETTINGS.contrast_prompts_per_concept, step=1, label="Prompts per concept") discovery_top_n = gr.Slider(5, 20, value=12, step=1, label="Candidate features") with gr.Row(equal_height=True): discovery_ranking = gr.Dropdown( choices=["Balanced selectivity", "Causal-ready at current token", "Raw mean difference"], value="Balanced selectivity", label="Candidate ranking", info="Balanced selectivity finds concept-associated candidates; Causal-ready restricts to features active at the selected Workbench token; raw mean difference exposes scale-dominated ranking.", scale=2, ) gr.Markdown( "**Input source:** controlled concept prompts + the current Workbench prompt/token, evaluated in the same GPU batch.", elem_classes=["instrument-note"], ) discovery_btn = gr.Button("Discover concept-associated candidates", variant="primary", elem_classes=["action-btn"]) discovery_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Candidate feature evidence') discovery_table = gr.Dataframe( interactive=False, label="Candidate feature evidence", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=420 ) discovery_tsv = gr.Textbox(visible="hidden") discovery_copy = _copy_button() with gr.Column(scale=2): discovery_plot = gr.BarPlot( x="Feature", y="Candidate score", color="Series", color_map={"Candidate score": INK_TEAL}, title="Candidate evidence score", elem_id="plot-candidate-discovery", x_title="Feature id", y_title="Exploratory ranking score", x_label_angle=-35, buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=330 ) gr.Markdown( "Click a row in **Candidate feature evidence** to place that feature in the selector below. " "The selector defaults to the highest-ranked candidate active at the current Workbench token when available.", elem_classes=["candidate-help"], ) with gr.Row(equal_height=True): discovery_candidate = gr.Dropdown(choices=[], label="Selected candidate feature id", allow_custom_value=True, scale=3) use_candidate_btn = gr.Button( "Use selected candidate across feature tests", variant="primary", elem_classes=["action-btn"], scale=2 ) candidate_use_status = gr.Markdown() gr.HTML('
B. Batched causal candidate triage
') gr.Markdown( "Use discovery candidates as a short list, then ablate several of them in **one batched scoring run**. " "This ranks target effects cheaply before you spend a full 8-random-control causal test on a feature. " "Uses the current Workbench prompt/token and the discovery layer." ) with gr.Row(equal_height=True): candidate_screen_ids = gr.Dropdown( choices=[], value=[], multiselect=True, allow_custom_value=True, max_choices=8, label="Candidate features to screen", info="Populated by concept-guided discovery; up to eight features per batch.", scale=3, ) candidate_screen_target = gr.Textbox( label="Screen target continuation", value="2x", info="Exact continuation used only for this screening run.", scale=2, ) candidate_screen_btn = gr.Button( "Screen candidate ablations", variant="primary", elem_classes=["action-btn"] ) candidate_screen_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Candidate ablation screen') candidate_screen_table = gr.Dataframe( interactive=False, label="Candidate ablation screen", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) candidate_screen_tsv = gr.Textbox(visible="hidden") candidate_screen_copy = _copy_button() with gr.Column(scale=2): candidate_screen_plot = gr.BarPlot( x="Feature", y="Δ mean log p/token", color="Series", color_map={"Candidate ablation": INK_TEAL}, title="Candidate ablation target effect", elem_id="plot-candidate-causal-screen", x_title="Feature id", y_title="Δ mean log p/token", x_label_angle=-35, buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=330, ) gr.Markdown( "Click a row in **Candidate ablation screen** to move that feature into **Selected candidate feature id**, " "then use the existing handoff button above if you want it copied into the deeper feature tests.", elem_classes=["candidate-help"], ) gr.Markdown("#### Association vs causal influence") gr.Markdown( "Computed from the discovery table and the ablation screen above — **no additional GPU call**. " "It compares concept-evidence rank with target-effect and distribution-shift ranks for the same shortlist.", elem_classes=["candidate-help"], ) candidate_alignment_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Discovery–causality alignment') candidate_alignment_table = gr.Dataframe( interactive=False, label="Discovery–causality alignment", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=360, ) candidate_alignment_tsv = gr.Textbox(visible="hidden") candidate_alignment_copy = _copy_button() with gr.Column(scale=2): candidate_alignment_plot = gr.ScatterPlot( x="Candidate score", y="|Δ mean log p/token|", color="Series", color_map={"Screened candidate": INK_TEAL}, title="Association evidence vs target effect", elem_id="plot-association-causality", x_title="Discovery candidate score", y_title="|Δ mean log p/token|", tooltip=[ "Feature id", "Discovery rank", "Target-effect rank", "Next-token JS", ], buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=330, ) gr.HTML('
C. Inspect one feature
') contrast_location = gr.Markdown( "**Activation trace uses the current Workbench prompt.** The controlled concept scan below uses its own balanced prompt set." ) with gr.Row(equal_height=True): contrast_feature_id = gr.Dropdown( choices=[], allow_custom_value=True, label="Feature id", scale=2, ) contrast_layer = gr.Dropdown( choices=list(SETTINGS.layers), value=SETTINGS.layers[1], label="Residual layer", scale=1, ) contrast_n = gr.Slider( 2, 6, value=SETTINGS.contrast_prompts_per_concept, step=1, label="Prompts per concept", scale=2, ) gr.Markdown("### Activation trace across the current Workbench prompt") gr.Markdown( "Uses the prompt currently entered in **Workbench** and shows exactly which prompt tokens include the " "selected feature in the SAE TopK support." ) trace_btn = gr.Button("Trace feature across prompt tokens", variant="primary", elem_classes=["action-btn"]) trace_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Feature activation by prompt token') trace_table = gr.Dataframe( interactive=False, label="Feature activation by prompt token", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=340, ) trace_tsv = gr.Textbox(visible="hidden") trace_copy = _copy_button() with gr.Column(scale=2): trace_plot = gr.BarPlot( x="Token", y="Activation", color="Series", color_map={"Feature activation": INK_TEAL}, title="Feature activation across prompt tokens", elem_id="plot-feature-token-trace", x_title="Prompt token", y_title="Activation", x_label_angle=-35, buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=320, ) gr.HTML('
D. Completion-cue sensitivity
') gr.Markdown( "Feature 22632 in the current math example fires only on the final `is` token. This scan tests whether a " "selected feature responds to **completion cues themselves** rather than the underlying concept. Enter a stem " "and one cue per line; FeatureLens appends each cue and measures the feature at the final token." ) with gr.Row(equal_height=True): cue_stem = gr.Textbox(label="Prompt stem", value="The derivative of x squared", lines=2, scale=3) cue_text = gr.Textbox(label="Completion cues (one per line)", value="is\n=\n:\nequals\ntherefore", lines=5, scale=2) cue_btn = gr.Button("Run completion-cue scan", variant="primary", elem_classes=["action-btn"]) cue_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Feature response by completion cue') cue_table = gr.Dataframe(interactive=False, label="Feature response by completion cue", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=340) cue_tsv = gr.Textbox(visible="hidden") cue_copy = _copy_button() with gr.Column(scale=2): cue_plot = gr.BarPlot( x="Cue", y="Activation", color="Series", color_map={"Cue response": INK_UMBER}, title="Completion-cue feature response", elem_id="plot-completion-cue-response", x_title="Cue", y_title="Final-token activation", buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=320 ) gr.HTML('
E. Cue × context specificity
') gr.Markdown( "The single-stem cue scan can tell you that a feature prefers `is`, but not whether that preference is lexical " "or context-sensitive. This matrix crosses several prompt stems with the same completion cues in one batch." ) with gr.Row(equal_height=True): cue_context_stems = gr.Textbox( label="Prompt stems (one per line)", value="The derivative of x squared\nThe capital of Germany\nThe weather today\nMy name", lines=5, scale=3, ) cue_context_cues = gr.Textbox( label="Completion cues (one per line)", value="is\n=\n:\nequals\ntherefore", lines=5, scale=2, ) cue_context_btn = gr.Button("Run cue × context scan", variant="primary", elem_classes=["action-btn"]) cue_context_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Cue × context feature response') cue_context_table = gr.Dataframe( interactive=False, label="Cue × context feature response", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=420, ) cue_context_tsv = gr.Textbox(visible="hidden") cue_context_copy = _copy_button() with gr.Column(scale=2): cue_context_plot = gr.BarPlot( x="Prompt stem", y="Activation", color="Cue", color_map={ "is": INK_TEAL, "=": INK_UMBER, ":": INK_RED, "equals": INK_PLUM, "therefore": INK_STONE, }, title="Cue response across contexts", elem_id="plot-cue-context-matrix", x_title="Prompt stem", y_title="Final-token activation", x_label_angle=-25, buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=340, ) gr.HTML('
F. Controlled concept contrast for selected feature
') gr.Markdown("### Prompt-wide controlled concept contrast") contrast_btn = gr.Button("Run controlled concept contrast", variant="primary", elem_classes=["action-btn"]) contrast_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Feature activation by controlled concept') contrast_table = gr.Dataframe( interactive=False, label="Feature activation by controlled concept", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) contrast_tsv = gr.Textbox(visible="hidden") contrast_copy = _copy_button() with gr.Column(scale=2): contrast_plot = gr.BarPlot( x="Concept", y="Mean prompt-wide max", color="Series", color_map={"Prompt-wide max": INK_BLUEGREY}, title="Prompt-wide controlled concept contrast", elem_id="plot-controlled-concept-contrast", x_title="Concept", y_title="Mean max activation", x_label_angle=-25, buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=330, ) with gr.Tab("Paraphrase robustness"): gr.Markdown( "### Local and prompt-wide robustness\n" "The selected-token comparison is strict but can be misleading if the two chosen tokens play different " "semantic roles. FeatureLens reports a **prompt-wide max-pooled feature profile**: for every SAE " "feature, take its maximum activation across all prompt tokens, then compare the two sparse profiles." ) with gr.Row(): para_a = gr.Textbox( label="Original prompt", lines=4, value="The derivative of x squared is", ) para_b = gr.Textbox( label="Paraphrase", lines=4, value="Differentiate x squared with respect to x:", ) with gr.Row(): para_layer = gr.Dropdown( choices=list(SETTINGS.layers), value=SETTINGS.layers[1], label="Residual layer", ) para_idx_a = gr.Number(value=-1, precision=0, label="Original prompt token index") para_idx_b = gr.Number(value=-1, precision=0, label="Paraphrase token index") para_top_n = gr.Slider(5, 20, value=12, step=1, label="Displayed active features") para_btn = gr.Button("Compare paraphrase representations", variant="primary", elem_classes=["action-btn"]) with gr.Row(): with gr.Column(): gr.Markdown("#### Original prompt tokens") para_tokens_a = gr.HTML() with gr.Column(): gr.Markdown("#### Paraphrase tokens") para_tokens_b = gr.HTML() para_metrics = gr.Markdown() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Top-feature overlap at selected tokens') para_table = gr.Dataframe( interactive=False, label="Top-feature overlap at selected tokens", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) para_tsv = gr.Textbox(visible="hidden") para_copy = _copy_button() with gr.Column(scale=2): para_plot = gr.BarPlot( x="Feature", y="Activation", color="Prompt", color_map={"Original": INK_TEAL, "Paraphrase": INK_PLUM}, title="Selected-token feature activations", elem_id="plot-paraphrase-selected-token", x_title="Feature id", y_title="Activation", x_label_angle=-35, buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=330, ) with gr.Tab("Layer trajectory"): gr.Markdown( "### Representation structure across early, middle and late residual streams\n" "This is not a cross-layer feature-ID comparison; the SAE dictionaries are layer-specific." ) with gr.Row(): trajectory_prompt = gr.Textbox( label="Prompt", lines=5, value="The derivative of x squared is", scale=4, ) trajectory_token = gr.Number( value=-1, precision=0, label="Prompt token index", info="-1 = final token", scale=1, ) trajectory_btn = gr.Button("Compare layers", variant="primary", elem_classes=["action-btn"]) gr.Markdown("#### Prompt tokens") trajectory_tokens = gr.HTML() with gr.Row(equal_height=False): with gr.Column(scale=3): _table_heading('Layer diagnostics') trajectory_table = gr.Dataframe( interactive=False, label="Layer diagnostics", show_label=False, buttons=["fullscreen"], elem_classes=["result-table"], wrap=False, max_height=380, ) trajectory_tsv = gr.Textbox(visible="hidden") trajectory_copy = _copy_button() with gr.Column(scale=2): trajectory_plot = gr.LinePlot( x="Layer", y="Value", color="Metric", color_map={ "Reconstruction cosine": INK_TEAL, "Top-5 mass": INK_UMBER, "Activation entropy": INK_RED, }, title="Representation trajectory", elem_id="plot-layer-trajectory", x_title="Layer", y_title="Normalized value", buttons=["fullscreen", "export"], elem_classes=["fl-plot"], height=330, ) with gr.Tab("Offline benchmark"): gr.Markdown(RUNTIME.catalog.benchmark_markdown()) gr.Markdown( "The offline pipeline remains the source of held-out feature/concept AUROC + F1, reconstruction " "quality, paraphrase stability, dense residual linear probes, and causal intervention conclusions. " "The live workbench uses batch-context null references and random-control ensembles; " "the saved report should be generated only from real experiment artifacts." ) with gr.Tab("Method"): gr.Markdown( r""" ### Reconstruction-preserving intervention For residual vector $h$, sparse coefficient $z_i$, decoder direction $d_i$, and scale $\alpha$: - **Ablate:** $h' = h - z_i d_i$ - **Scale:** $h' = h + (\alpha - 1)z_i d_i$ - **Inject:** $h' = h + \delta d_i$ For a feature set $S$: $$h' = h + \sum_{i \in S}\Delta z_i d_i.$$ FeatureLens patches the delta into the **original residual**; it never replaces the residual with the complete SAE reconstruction. ### Control discipline Batched experiments include an explicit **zero-edit row**. Causal effects are measured against that row rather than a separately executed baseline, which removes batch-vs-single floating-point drift from the measured effect. Random specificity uses an ensemble of norm-matched residual directions rather than one arbitrary seed. ### Evidence ladder 1. SAE reconstruction quality. 2. Held-out feature/concept prediction. 3. Concept-guided candidate discovery and batched causal candidate triage. 4. Selected-feature concept contrast and token-local activation traces, completion-cue sensitivity, and cue × context specificity. 5. Local and prompt-wide paraphrase robustness. 6. Single-feature causal intervention and dose-response. 7. Contrastive continuation preference under intervention. 8. Joint feature-set intervention and set-size sensitivity. 9. Decoder-direction geometry and individual-vs-joint non-additivity. 10. Specificity relative to norm-matched random controls. Association, robustness, geometry, and intervention evidence remain separate claims. """ ) gr.HTML('') demo.load(fn=None, js=INSTALL_REFLOW_JS, queue=False) # Event wiring. analyze_btn.click( analyze_prompt, inputs=[prompt, layer, token_index, top_n], outputs=[ token_view, feature_table, feature_plot, feature_id, dose_feature_id, contrastive_feature_id, feature_set_ids, contrast_feature_id, contrast_layer, analysis_metrics, feature_set_location, contrast_location, global_context, feature_tsv, ], ) mode.change(mode_help, inputs=[mode], outputs=[coefficient]) intervene_btn.click( run_intervention, inputs=[prompt, layer, token_index, feature_id, mode, coefficient, target_text, max_new], outputs=[ baseline_out, modified_out, intervention_metrics, token_prob_table, target_token_table, token_prob_tsv, target_token_tsv, ], ) dose_btn.click( run_dose_response, inputs=[prompt, layer, token_index, dose_feature_id, dose_target_text], outputs=[dose_table, dose_plot, dose_metrics, dose_tsv], ) contrastive_mode.change(mode_help, inputs=[contrastive_mode], outputs=[contrastive_coefficient]) contrastive_btn.click( run_contrastive_causal, inputs=[prompt, layer, token_index, contrastive_feature_id, contrastive_mode, contrastive_coefficient, contrastive_a, contrastive_b], outputs=[contrastive_metrics, contrastive_table, contrastive_plot, contrastive_tsv], ) set_mode.change(set_mode_help, inputs=[set_mode], outputs=[set_coefficient]) set_btn.click( run_feature_set, inputs=[prompt, layer, token_index, feature_set_ids, set_mode, set_coefficient, set_target], outputs=[set_feature_table, set_metrics, set_target_table, set_feature_tsv, set_target_tsv], ) set_sweep_btn.click( run_feature_set_sweep, inputs=[prompt, layer, token_index, set_sweep_target], outputs=[set_sweep_table, set_sweep_plot, set_sweep_note, set_sweep_tsv], ) interaction_btn.click( run_feature_interaction, inputs=[prompt, layer, token_index, feature_set_ids, interaction_target], outputs=[interaction_table, interaction_metrics, interaction_plot, interaction_tsv], ) geometry_btn.click( run_feature_geometry, inputs=[prompt, layer, token_index, feature_set_ids], outputs=[geometry_metrics, geometry_table, geometry_plot, geometry_tsv], ) trace_btn.click( run_feature_trace, inputs=[prompt, contrast_layer, contrast_feature_id], outputs=[trace_metrics, trace_table, trace_plot, trace_tsv], ) contrast_btn.click( run_concept_contrast, inputs=[contrast_feature_id, contrast_layer, contrast_n], outputs=[contrast_metrics, contrast_table, contrast_plot, contrast_tsv], ) discovery_btn.click( run_concept_feature_discovery, inputs=[ discovery_concept, discovery_layer, discovery_n, discovery_top_n, discovery_ranking, prompt, token_index, ], outputs=[ discovery_metrics, discovery_table, discovery_plot, discovery_candidate, candidate_screen_ids, discovery_tsv, ], ) candidate_screen_btn.click( run_candidate_causal_screen, inputs=[ prompt, discovery_layer, token_index, candidate_screen_ids, candidate_screen_target, discovery_table, ], outputs=[ candidate_screen_metrics, candidate_screen_table, candidate_screen_plot, discovery_candidate, candidate_screen_tsv, candidate_alignment_metrics, candidate_alignment_table, candidate_alignment_plot, candidate_alignment_tsv, ], ) candidate_screen_table.select( select_candidate_row, inputs=[candidate_screen_table], outputs=[discovery_candidate], queue=False, ) discovery_table.select( select_candidate_row, inputs=[discovery_table], outputs=[discovery_candidate], queue=False, ) use_candidate_btn.click( use_candidate_feature, inputs=[discovery_candidate], outputs=[feature_id, dose_feature_id, contrastive_feature_id, contrast_feature_id, candidate_use_status], queue=False, ) cue_btn.click( run_feature_cue_scan, inputs=[contrast_feature_id, contrast_layer, cue_stem, cue_text], outputs=[cue_metrics, cue_table, cue_plot, cue_tsv], ) cue_context_btn.click( run_feature_cue_context_scan, inputs=[contrast_feature_id, contrast_layer, cue_context_stems, cue_context_cues], outputs=[cue_context_metrics, cue_context_table, cue_context_plot, cue_context_tsv], ) para_btn.click( run_paraphrase_compare, inputs=[para_a, para_b, para_layer, para_idx_a, para_idx_b, para_top_n], outputs=[para_tokens_a, para_tokens_b, para_metrics, para_table, para_plot, para_tsv], ) trajectory_btn.click( run_layer_sweep, inputs=[trajectory_prompt, trajectory_token], outputs=[trajectory_tokens, trajectory_table, trajectory_plot, trajectory_tsv], ) for button, source in [ (feature_copy, feature_tsv), (token_prob_copy, token_prob_tsv), (target_token_copy, target_token_tsv), (dose_copy, dose_tsv), (contrastive_copy, contrastive_tsv), (set_feature_copy, set_feature_tsv), (set_target_copy, set_target_tsv), (set_sweep_copy, set_sweep_tsv), (interaction_copy, interaction_tsv), (geometry_copy, geometry_tsv), (trace_copy, trace_tsv), (contrast_copy, contrast_tsv), (discovery_copy, discovery_tsv), (candidate_screen_copy, candidate_screen_tsv), (candidate_alignment_copy, candidate_alignment_tsv), (cue_copy, cue_tsv), (cue_context_copy, cue_context_tsv), (para_copy, para_tsv), (trajectory_copy, trajectory_tsv), ]: _bind_copy(button, source) if __name__ == "__main__": demo.queue(default_concurrency_limit=1, max_size=8).launch( css=CSS, theme=THEME, ssr_mode=False, show_error=True, )