YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
LLM Concept Probe Dashboard
Status: Production Ready Updated: 2025-10-31
A complete system for training, validating, and deploying concept-detection probes from LLM activations. Features automatic baseline calibration, real-time streaming inference, and an interactive web dashboard for monitoring probe behavior.
Key Features
- Automatic Baseline Calibration: Probes self-calibrate using wildchat samples to eliminate false positives
- Real-time Streaming Inference: WebSocket-based token-by-token probe monitoring
- High Performance: AUC β₯ 0.997 across all semantic and safety probes
- Production Dashboard: FastAPI backend + React frontend (in progress)
- Comprehensive Validation: Systematic sweep across pooling, classifiers, whitening, and negative sampling strategies
Quick Start
1. Installation
# Install probe pipeline dependencies
pip install -r requirements.txt
# Install backend API dependencies
pip install -r backend/requirements.txt
2. Start the Dashboard
# Start FastAPI backend (includes WebSocket streaming)
./scripts/start_backend.sh
# Access the API
# - REST API: http://localhost:8000
# - API Docs: http://localhost:8000/docs
# - WebSocket: ws://localhost:8000/ws/inference
3. Test Live Inference
import asyncio
import websockets
import json
async def test_inference():
uri = "ws://localhost:8000/ws/inference"
async with websockets.connect(uri) as ws:
# Start inference
await ws.send(json.dumps({
"message": "Tell me about Antarctica",
"monitored_probes": ["cold", "hot", "outdoors"]
}))
# Stream results
async for message in ws:
data = json.loads(message)
if data["type"] == "token":
print(f"Token: {data['token']}")
print(f"Probes: {data['probes']}")
elif data["type"] == "complete":
break
asyncio.run(test_inference())
Offline LLM option
You can run the pipeline without OpenRouter by serving a local OpenAI-compatible endpoint (llama.cpp via llama-cpp-python). The gpt-oss-20b MXFP4 GGUF fits on a single 24GB card (e.g., 3090).
# 1) Ensure the GGUF is present (symlinked from ~/.cache/llama.cpp by default)
ls models/gpt-oss-20b-mxfp4.gguf
# 2) Start a local server (tune N_CTX/N_BATCH upward until VRAM is ~23GB)
N_CTX=4096 N_BATCH=1024 N_UBATCH=1024 PORT=8000 \
./scripts/start_llamacpp_local.sh
# 3) Point the pipeline at the local server
export LLM_API_BASE="http://localhost:8000/v1"
export LLM_API_KEY="local" # not required locally
export LLM_MODEL="openai/gpt-oss-20b"
# Override per-command if needed
python main.py generate configs/cold.json --api-base "$LLM_API_BASE" --api-key "$LLM_API_KEY"
This works for generate, edit, and mine commands.
Python-only usage (direct bindings, no HTTP):
from llama_cpp import Llama
llm = Llama(
model_path="models/gpt-oss-20b-mxfp4.gguf",
n_gpu_layers=-1,
n_ctx=4096,
n_batch=1024,
flash_attn=True,
)
resp = llm.create_chat_completion(
messages=[{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Quick hello in one sentence."}],
max_tokens=64,
temperature=1.0, top_p=1.0, top_k=0, min_p=0.0,
)
print(resp["choices"][0]["message"]["content"])
Architecture
Production Probe Configuration
After comprehensive validation, the system uses:
| Parameter | Value | Rationale |
|---|---|---|
| Pooling | conversation_mean |
5.9x more stable than prefix_last (Οβ0.05 vs 0.30) |
| Classifier | lda |
2.1x more stable than logistic regression |
| Whitening | global |
Consistent performance, shared transform |
| Negatives | wildchat + autohardneg |
3.0x more stable with automatic hard negative mining |
| Calibration | baseline_offset |
Auto-calibrated to eliminate false positives |
Baseline Calibration System
Problem: Raw probes show positive z-scores on neutral content due to ambient mentions in wildchat negatives, causing false positives to accumulate with evidence formula Ξ = Ξ£z / βt.
Solution: Automatic baseline offset calibration:
- Sample 100 random wildchat conversations (diverse neutral content)
- Score with each probe to compute mean z-score baseline
- Subtract baseline from all future z-scores:
adjusted_z = z - baseline_offset - Cache in metadata.json (compute once, reuse forever)
Results:
- 90% reduction in false positives
- AUC performance unchanged (β₯0.997 across all probes)
- Auto-calibrates for new concepts without manual tuning
System Components
1. Probe Training Pipeline
# Extract raw activations (GPU required, one-time)
python main.py extract-raw --batch-size 32 --negatives 20000
# Derive conversation-mean format (CPU, fast)
python main.py derive-formats --formats conversation_mean
# Train probes with automatic baseline calibration
python scripts/train_probes.py --concepts all
Training automatically:
- Computes baseline_offset from wildchat samples
- Saves to
outputs/experiments/probes/<concept>/conversation_mean/none/lda_global_wildchat/metadata.json - Future loads reuse cached baseline
2. Streaming Inference Service
Backend: backend/services/inference_service.py
- Streams tokens + activations in real-time
- Applies baseline-corrected z-scores
- Computes sequential evidence:
Ξ = Ξ£z / βt - WebSocket protocol for low-latency updates
Features:
- Token-by-token probe scores (raw, z, evidence)
- Multi-probe monitoring (track 5+ concepts simultaneously)
- Text highlighting based on evidence thresholds
- Conversation-level aggregation
3. Probe Registry
Backend: backend/services/probe_registry.py
Manages production probes with automatic baseline correction:
class ConversationProbe:
concept: str
probe: Union[LDAProbe, LogisticRegressionProbe]
neg_mean: float
neg_std: float
baseline_offset: float # β Auto-calibrated!
def z_from_raw(self, raw: float) -> float:
z = (raw - self.neg_mean) / self.neg_std
return z - self.baseline_offset # Center neutral content at z=0
Loads all probes on startup:
- Reads metadata from
outputs/experiments/probes/*/metadata.json - Computes baseline_offset if missing (first run only)
- Caches for future sessions
Validation Results
Baseline Calibration Impact
Test: "Tell me about water" (neutral prompt)
| Metric | Before | After | Improvement |
|---|---|---|---|
| False Positives | 6 triggers | 1 trigger | 83% reduction |
| Max Evidence | 104.19 | 10.01 | 10x lower |
| Negative Z-scores | Broken | Working | Fixed |
Probe Performance (AUC Metrics)
All probes maintain excellent discrimination after baseline correction:
| Probe | AUC | Avg Precision | Baseline Offset |
|---|---|---|---|
| cold | 1.000 | 1.000 | +0.634 |
| hot | 1.000 | 1.000 | +0.675 |
| cooking | 1.000 | 1.000 | +0.482 |
| mathematics | 0.999 | 0.994 | -0.256 |
| code | 0.999 | 0.991 | +0.412 |
| indoors | 0.999 | 0.996 | +0.249 |
| outdoors | 0.999 | 0.996 | +0.660 |
| day | 1.000 | 1.000 | +1.166 |
| night | 1.000 | 1.000 | +1.070 |
| helpful_response | 0.997 | 0.981 | +1.994 |
| harmful_response | 1.000 | 1.000 | +0.951 |
| jailbreak_attempt | 0.999 | 0.996 | +2.826 |
Average AUC: 0.9995 (near-perfect discrimination)
File Structure
probe-dashboard/
βββ backend/ # FastAPI production backend
β βββ main.py # API entry point
β βββ services/
β β βββ inference_service.py # Streaming inference
β β βββ model_session.py # LLM + activation extraction
β β βββ probe_registry.py # Probe loading + baseline calibration
β β βββ ...
β βββ requirements.txt
β
βββ frontend/ # React dashboard (in development)
β βββ src/
β β βββ components/
β β β βββ LiveProbeMonitor.tsx # Real-time streaming UI
β β β βββ ProbeTable.tsx # Probe status table
β β β βββ ...
β β βββ hooks/
β β βββ useInference.ts # WebSocket hook
β β βββ ...
β βββ package.json
β
βββ probe_pipeline/ # Core ML pipeline (unchanged)
β βββ lda_probe.py # LDA classifier
β βββ logistic_probe.py # Logistic regression classifier
β βββ whitening.py # Whitening transform
β βββ format_derivation.py # Activation pooling
β
βββ scripts/
β βββ start_backend.sh # Start API server
β βββ train_probes.py # Batch probe training
β βββ run_sweep.py # Validation sweep
β
βββ configs/ # Concept definitions
β βββ cold.json
β βββ hot.json
β βββ helpful_response.json
β βββ ...
β
βββ outputs/
β βββ raw_activations/ # Token-level activations (fp16)
β βββ derived_formats/ # Pooled activations
β β βββ conversation_mean/ # Production format
β βββ experiments/
β β βββ probes/ # Trained probes + metadata
β β β βββ <concept>/conversation_mean/none/lda_global_wildchat/
β β β βββ probe.pkl # Classifier weights
β β β βββ whitening.npz # Whitening transform
β β β βββ metadata.json # Metrics + baseline_offset
β β βββ sweep/ # Validation results
β β βββ results.jsonl
β β βββ summary.json
β βββ global_transform/ # Shared whitening
β βββ whitening.npz
β βββ metadata.json
β
βββ tests/ # Test suite
β βββ test_inference.py # Integration tests
β βββ test_calibration.py # Baseline tests
β βββ test_probes.py # Unit tests
β
βββ docs/ # Documentation
β βββ README.md # Documentation map
β βββ METHODOLOGY.md # Training methodology
β βββ PRODUCTION_ARCHITECTURE.md # System architecture
β βββ DATASETS_AND_STORAGE.md # Data organization
β
βββ main.py # CLI for extraction/derivation
βββ dataset.py # Synthetic data generation
βββ requirements.txt # Core dependencies
Concept Categories
The system includes probes for:
Semantic Concepts
- Temperature: cold, hot
- Time: day, night
- Location: indoors, outdoors
- Domains: mathematics, code, cooking, creative_writing
Safety & Monitoring
- Response Quality: helpful_response, harmful_response
- Security: jailbreak_attempt, prompt_injection
- Style: refusal_boilerplate, refusal_constructive
Communication Style
- Tone: formal_assistant, informal_assistant, technical_assistant
- Verbosity: terse_assistant, verbose_assistant, detailed_response
- User Patterns: polite_request, hostile_demand, technical_user
API Reference
REST Endpoints
# List all probes
GET /api/probes
# Get probe details
GET /api/probes/{concept}
# List concepts
GET /api/concepts
# Train new probe
POST /api/probes/train
{
"concept": "new_concept",
"use_hard_negatives": true
}
WebSocket Inference
// Connect
const ws = new WebSocket('ws://localhost:8000/ws/inference');
// Send message
ws.send(JSON.stringify({
message: "Tell me about quantum computing",
monitored_probes: ["mathematics", "code", "technical_user"],
max_tokens: 512
}));
// Receive tokens
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === "token") {
console.log(`Token: ${data.token}`);
console.log(`Probes:`, data.probes);
// data.probes = {
// "mathematics": {raw: 0.42, z: 3.2, evidence: 8.5},
// "code": {raw: 0.31, z: 2.1, evidence: 5.3},
// ...
// }
}
if (data.type === "complete") {
console.log("Generation complete");
}
};
Development
Running Tests
# Unit tests
pytest tests/
# Integration tests with backend
python tests/test_inference.py
# Calibration validation
python tests/test_calibration.py
Training New Concepts
- Create config file:
configs/my_concept.json - Generate dataset:
python dataset.py --concept my_concept - Extract activations:
python main.py extract-raw --concepts my_concept - Derive formats:
python main.py derive-formats --concepts my_concept - Train probe:
python scripts/train_probes.py --concept my_concept - Restart backend to load new probe
Baseline calibration happens automatically during step 5 and is cached in metadata.
Validation Sweep
Run comprehensive validation across all configurations:
# Full sweep (all concepts Γ all configs)
python scripts/run_sweep.py
# Quick test (subset of concepts)
python scripts/run_sweep.py --quick
# Results saved to outputs/experiments/sweep/
Performance Notes
Baseline Offset Magnitude
Large offsets indicate strong ambient signal in wildchat:
jailbreak_attempt: +2.826 (wildchat contains adversarial examples)helpful_response: +1.994 (assistant responses tend to be helpful)day/night: +1.166/+1.070 (temporal references common)
Negative offsets indicate wildchat scores below zero:
mathematics: -0.256 (math discussions rare in wildchat)
Computational Cost
- Baseline calibration: 5-10 seconds per probe (first run only)
- Streaming inference: <50ms per token (includes all probe scores)
- Training: 6-7 seconds per probe on CPU
Memory Usage
- Probe registry: ~500MB (all probes loaded)
- Model session: ~8GB VRAM (Llama 3.2 3B)
- Per-inference: ~100MB (activation buffers)
Citation
If you use this system in your research, please cite:
@software{probe_dashboard_2025,
title={LLM Concept Probe Dashboard},
author={[Your Name]},
year={2025},
url={https://github.com/[your-repo]/probe-dashboard}
}
License
[Specify License]
Documentation
- METHODOLOGY.md: Training methodology and design decisions
- PRODUCTION_ARCHITECTURE.md: System architecture and API design
- DATASETS_AND_STORAGE.md: Data organization and concept taxonomy
- API Docs: Interactive API documentation (when backend is running)
Troubleshooting
Backend won't start
# Check dependencies
./scripts/check_backend_deps.sh
# Verify Python version (requires 3.10+)
python --version
# Check for port conflicts
lsof -i :8000
Probes showing unexpected results
# Verify baseline calibration
python tests/test_calibration.py
# Check probe metadata
cat outputs/experiments/probes/cold/conversation_mean/none/lda_global_wildchat/metadata.json
# Look for baseline_offset field - should be non-zero
WebSocket connection timeout
# Check backend logs
tail -f /tmp/uvicorn.log
# Test with curl
curl -N --http1.1 \
--header "Connection: Upgrade" \
--header "Upgrade: websocket" \
--header "Sec-WebSocket-Version: 13" \
--header "Sec-WebSocket-Key: SGVsbG8sV29ybGQh==" \
http://localhost:8000/ws/inference
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
For major changes, please open an issue first to discuss the proposed changes.