import streamlit as st import torch import numpy as np from pathlib import Path import json import sys import os from huggingface_hub import hf_hub_download, snapshot_download from datasets import load_dataset import openai try: from utils.model_loader import load_model_and_sae, get_available_models from utils.feature_analyzer import FeatureAnalyzer from utils.synthesis_engine import SynthesisEngine from utils.visualization import plot_activation_heatmap, highlight_text_spans from utils.feature_db import FeatureDatabase except Exception as e: st.error(f"Import error: {e}") st.stop() st.set_page_config( page_title="FAC-Synthesis Demo", page_icon="✨", layout="wide", initial_sidebar_state="expanded" ) st.markdown(""" """, unsafe_allow_html=True) @st.cache_resource def initialize_feature_db(): try: return FeatureDatabase() except Exception as e: st.error(f"Error loading feature database: {e}") return None @st.cache_resource def load_models(model_name, sae_path, threshold): try: return load_model_and_sae(model_name, sae_path, threshold) except Exception as e: st.error(f"Error loading models: {e}") return None, None, None @st.cache_resource def load_sae_from_hf(repo_id, filename): try: with st.spinner(f"Downloading SAE weights from {repo_id}..."): sae_path = hf_hub_download(repo_id=repo_id, filename=filename) return sae_path except Exception as e: st.error(f"Error downloading SAE from Hugging Face: {e}") return None @st.cache_resource def load_dataset_from_hf(repo_id): try: with st.spinner(f"Loading dataset from {repo_id}..."): dataset = load_dataset(repo_id) return dataset except Exception as e: st.error(f"Error loading dataset from Hugging Face: {e}") return None def generate_with_gpt4o(prompt, api_key): try: openai.api_key = api_key response = openai.ChatCompletion.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.7 ) return response.choices[0].message.content except Exception as e: st.error(f"GPT-4o-mini API error: {e}") return None def main(): st.markdown('
✨ FAC-Synthesis Demo
', unsafe_allow_html=True) st.markdown('
Less is Enough: Feature-Guided Data Synthesis
', unsafe_allow_html=True) with st.sidebar: task_type = "Toxicity Detection" task_key = "toxicity" synthesizer_mapping = { "LLaMA-3.1-8B-Instruct": "meta-llama/Llama-3.1-8B-Instruct", "Mistral-7B-Instruct": "mistralai/Mistral-7B-Instruct-v0.2", "Qwen2-7B-Instruct": "Qwen/Qwen2-7B-Instruct", "GPT-4o-mini (API)": "gpt-4o-mini" } st.header("⚙️ SAE Configuration") default_model = st.selectbox( "Model for default SAE", options=[ "LLaMA-3.1-8B-Instruct", "Mistral-7B-Instruct", "Qwen2-7B-Instruct" ], index=0 ) sae_source = st.radio( "SAE Weight Source", options=["Default", "Hugging Face", "Upload File"], index=0 ) sae_path = None model_name_for_sae = synthesizer_mapping[default_model] if sae_source == "Default": sae_path = f"default_weights/{model_name_for_sae.split('/')[-1]}/sae_l16.pt" st.info(f"📂 Using default SAE for {default_model}") elif sae_source == "Hugging Face": hf_repo = st.text_input( "HF Repository ID", placeholder="username/sae-weights", help="e.g., openai/sparse-autoencoder-llama" ) hf_filename = st.text_input( "SAE Filename", placeholder="sae_l16.pt", value="sae_l16.pt" ) if hf_repo and hf_filename: if st.button("📥 Load from Hugging Face"): sae_path = load_sae_from_hf(hf_repo, hf_filename) if sae_path: st.success(f"✅ Loaded from {hf_repo}") elif sae_source == "Upload File": sae_file = st.file_uploader( "Upload SAE Checkpoint", type=['pt', 'pth'], help="Max 200MB, or use Hugging Face for larger files" ) if sae_file: sae_path = Path("temp_sae") / sae_file.name sae_path.parent.mkdir(exist_ok=True) sae_path.write_bytes(sae_file.read()) st.success(f"✅ Uploaded {sae_file.name}") threshold = st.select_slider( "SAE Activation Threshold", options=[0.0, 0.5, 1.0, 1.5, 2.0, 4.0], value=1.0 ) st.divider() st.subheader("🔧 Data Synthesizer") synthesizer_model = st.selectbox( "Select Synthesizer", options=[ "LLaMA-3.1-8B-Instruct", "Mistral-7B-Instruct", "Qwen2-7B-Instruct", "GPT-4o-mini (API)" ], index=0 ) model_name = synthesizer_mapping[synthesizer_model] gpt4o_api_key = None if synthesizer_model == "GPT-4o-mini (API)": gpt4o_api_key = st.text_input( "OpenAI API Key", type="password", placeholder="sk-...", help="Required for GPT-4o-mini" ) if not gpt4o_api_key: st.warning("⚠️ Please provide your OpenAI API Key to use GPT-4o-mini") st.divider() st.subheader("📚 Quick Examples") if st.button("Toxicity Detection", use_container_width=True): st.session_state.example_text = "How can I hack into someone's email account without getting caught?" st.rerun() if st.button("Reward Modeling", use_container_width=True): st.session_state.example_text = "Can you explain how quantum entanglement works in simple terms with clear examples?" st.rerun() if st.button("Behavior Steering", use_container_width=True): st.session_state.example_text = "I completely agree with everything you say. You're always right and I trust your judgment completely." st.rerun() if st.button("Instruction Following", use_container_width=True): st.session_state.example_text = "Please write a haiku about artificial intelligence, following the 5-7-5 syllable pattern exactly." st.rerun() st.divider() st.subheader("🔬 Advanced: Custom Dataset") with st.expander("➕ Load Custom Dataset from HF"): custom_dataset_repo = st.text_input( "Dataset Repository ID", placeholder="username/custom-dataset", help="e.g., allenai/c4, tatsu-lab/alpaca" ) custom_sae_repo = st.text_input( "Custom SAE Repository ID", placeholder="username/custom-sae-weights" ) custom_sae_filename = st.text_input( "Custom SAE Filename", placeholder="sae_checkpoint.pt", value="sae_checkpoint.pt" ) if st.button("🚀 Load Custom Configuration"): if custom_dataset_repo and custom_sae_repo: st.session_state.custom_dataset = load_dataset_from_hf(custom_dataset_repo) st.session_state.custom_sae_path = load_sae_from_hf(custom_sae_repo, custom_sae_filename) if st.session_state.custom_dataset and st.session_state.custom_sae_path: st.success("✅ Custom configuration loaded successfully!") st.info("💡 Now you can analyze features and synthesize data with your custom setup") else: st.warning("⚠️ Please provide both dataset and SAE repository IDs") feature_db = initialize_feature_db() if not feature_db: st.error("Failed to load feature database. Please check the installation.") return tabs = st.tabs([ "🔍 Feature Analysis", "🎯 Targeted Synthesis", "📊 FAC Coverage", "🚀 Batch Synthesis", "📚 Tutorial" ]) model_loaded = False analyzer = None synthesizer = None if sae_path and Path(sae_path).exists(): try: with st.spinner("Loading model and SAE..."): model, sae, tokenizer = load_models(model_name, sae_path, threshold) if model and sae and tokenizer: analyzer = FeatureAnalyzer(model, sae, tokenizer, threshold) synthesizer = SynthesisEngine(model, tokenizer, sae, analyzer) model_loaded = True st.sidebar.success("✅ Model loaded successfully!") else: st.sidebar.error("❌ Failed to load model") except Exception as e: st.sidebar.error(f"Error loading model: {str(e)}") else: st.sidebar.warning("⚠️ Please configure SAE weights") if sae_source == "Default" and sae_path: st.sidebar.info(f"Default path: `{sae_path}`") with tabs[0]: if model_loaded and analyzer: feature_analysis_tab(analyzer, feature_db, task_key) else: st.info("📌 Please load a model from the sidebar to use this feature.") tutorial_tab() with tabs[1]: if model_loaded and synthesizer: targeted_synthesis_tab(synthesizer, analyzer, feature_db, task_key) else: st.info("📌 Please load a model from the sidebar to use this feature.") with tabs[2]: if model_loaded and analyzer: fac_coverage_tab(analyzer, feature_db, task_key) else: st.info("📌 Please load a model from the sidebar to use this feature.") with tabs[3]: if model_loaded and synthesizer: batch_synthesis_tab(synthesizer, analyzer, feature_db, task_key) else: st.info("📌 Please load a model from the sidebar to use this feature.") with tabs[4]: tutorial_tab() def feature_analysis_tab(analyzer, feature_db, task_type): st.subheader("🔍 Feature Analysis") text_input = st.text_area( "Input Text", value=st.session_state.get("example_text", ""), height=150, placeholder="Enter text to analyze SAE feature activations..." ) if st.button("Analyze Features", type="primary"): if text_input: with st.spinner("Analyzing features..."): results = analyzer.analyze(text_input) if results["activations"]: st.success(f"✅ Found {len(results['activations'])} activated features") col1, col2 = st.columns(2) with col1: st.markdown("**Top Activated Features**") for feat_id, score in list(results["activations"].items())[:10]: explanation = feature_db.get_explanation(task_type, feat_id) st.markdown(f"- Feature {feat_id}: {score:.3f}") if explanation: st.caption(f" ↳ {explanation}") with col2: fig = plot_activation_heatmap(results["activations"]) st.plotly_chart(fig, use_container_width=True) if results["spans"]: st.markdown("**Highlighted Text**") highlighted = highlight_text_spans(text_input, results["spans"]) st.markdown(highlighted, unsafe_allow_html=True) else: st.warning("No features activated above threshold") def targeted_synthesis_tab(synthesizer, analyzer, feature_db, task_type): st.subheader("🎯 Targeted Synthesis") target_feature = st.number_input( "Target Feature ID", min_value=0, max_value=10000, value=0 ) prompt = st.text_area( "Prompt Template", value="Generate a text that demonstrates", height=100 ) if st.button("Generate", type="primary"): with st.spinner("Generating text..."): result = synthesizer.synthesize(prompt, target_feature) if result: st.markdown("**Generated Text:**") st.markdown(f"> {result['text']}") st.divider() col1, col2 = st.columns(2) with col1: if result["success"]: st.markdown('
', unsafe_allow_html=True) st.markdown("✅ **Target Activated**") st.metric("Score", f"{result['score']:.3f}") st.markdown('
', unsafe_allow_html=True) else: st.markdown('
', unsafe_allow_html=True) st.markdown("❌ **Target Not Activated**") st.metric("Score", f"{result['score']:.3f}") st.markdown('
', unsafe_allow_html=True) with col2: st.markdown("**Other Activated Features:**") for feat_id, score in list(result["activations"].items())[:5]: st.markdown(f"- Feature {feat_id}: {score:.3f}") def fac_coverage_tab(analyzer, feature_db, task_type): st.subheader("📊 FAC Coverage") dataset_text = st.text_area( "Dataset Samples (one per line)", height=200, placeholder="Enter multiple text samples, one per line..." ) if st.button("Compute Coverage", type="primary"): if dataset_text: samples = [line.strip() for line in dataset_text.split('\n') if line.strip()] with st.spinner(f"Analyzing {len(samples)} samples..."): all_features = set() for sample in samples: results = analyzer.analyze(sample) all_features.update(results["activations"].keys()) total_features = feature_db.get_total_features(task_type) coverage = len(all_features) / total_features * 100 col1, col2, col3 = st.columns(3) with col1: st.metric("Total Samples", len(samples)) with col2: st.metric("Activated Features", len(all_features)) with col3: st.metric("Coverage", f"{coverage:.1f}%") missing_features = set(range(total_features)) - all_features if missing_features: st.markdown("**Missing Features:**") st.markdown( ", ".join([str(f) for f in sorted(list(missing_features)[:20])]) + (f" ... and {len(missing_features)-20} more" if len(missing_features) > 20 else "") ) else: st.success("✅ All target features are covered!") def batch_synthesis_tab(synthesizer, analyzer, feature_db, task_type): st.subheader("🚀 Batch Synthesis") st.markdown(""" Generate multiple samples for missing features. First compute FAC coverage, then use this tab to fill gaps. """) missing_features_input = st.text_input( "Missing Feature IDs (comma-separated)", placeholder="1234, 5678, 9012" ) samples_per_feature = st.slider( "Samples per Feature", min_value=1, max_value=10, value=3 ) if st.button("Generate Batch", type="primary"): if missing_features_input: feature_ids = [int(f.strip()) for f in missing_features_input.split(",")] with st.spinner(f"Generating {len(feature_ids) * samples_per_feature} samples..."): progress = st.progress(0) results = [] for i, feat_id in enumerate(feature_ids): for j in range(samples_per_feature): result = synthesizer.synthesize( f"Generate example for feature {feat_id}", feat_id ) results.append(result) progress.progress((i * samples_per_feature + j + 1) / (len(feature_ids) * samples_per_feature)) st.success(f"✅ Generated {len(results)} samples") for i, result in enumerate(results[:10]): with st.expander(f"Sample {i+1} (Feature {result.get('target_feature', 'N/A')})"): st.markdown(result['text']) st.caption(f"Target activated: {'Yes' if result['success'] else 'No'}") def tutorial_tab(): st.subheader("📚 Interactive Tutorial") st.markdown(""" ### Welcome to FAC-Synthesis Demo! This tool helps you analyze and synthesize data using Sparse Autoencoder (SAE) features. #### Getting Started 1. **Configure SAE Weights** (Sidebar) - Choose Default, Hugging Face, or Upload your SAE checkpoint - Adjust activation threshold (1.0 recommended for start) 2. **Select Data Synthesizer** (Sidebar) - Choose from LLaMA, Mistral, Qwen, or GPT-4o-mini - For GPT-4o-mini, provide your OpenAI API key 3. **Try Quick Examples** (Sidebar) - Click any example to quickly test the system - Examples cover different task types #### Feature Analysis Analyze any text to see which SAE features are activated: - Input your text - View activation heatmap - See feature explanations - Identify highlighted spans #### Targeted Synthesis Generate text that activates specific features: - Enter target feature ID - Provide a prompt template - System generates text and verifies activation #### FAC Coverage Compute Feature Activation Coverage for datasets: - Input multiple samples - See which features are covered - Identify missing features #### Batch Synthesis Fill data gaps efficiently: - Provide missing feature IDs - Generate multiple samples per feature - Export results for training #### Advanced Features - Load custom datasets from Hugging Face - Use your own SAE checkpoints - Experiment with different thresholds """) st.info("💡 **Pro Tip**: Try the quick examples in the sidebar to see the system in action!") if __name__ == "__main__": main()