Spaces:
Sleeping
Sleeping
Commit ·
5c05f37
1
Parent(s): 5f164c5
Replace Cytoscape with accordion-based layer view
Browse files- Added html.Details/Summary accordion panels for each layer
- Display plain-language headers: 'Layer L{N}: likely {token} (p={prob})'
- Added token chips showing top-3 predictions between layers
- Gated old Cytoscape graph behind USE_LEGACY_CYTOSCAPE feature flag
- Added CSS for accordion styling with ellipsis truncation
- Created extract_layer_data() utility function
- All panels closed by default for compact flow view
- app.py +64 -1
- assets/style.css +85 -0
- components/main_panel.py +223 -217
- todo.md +5 -5
- utils/__init__.py +3 -2
- utils/model_patterns.py +44 -0
app.py
CHANGED
|
@@ -9,7 +9,7 @@ import dash
|
|
| 9 |
from dash import html, dcc, Input, Output, State, callback, no_update
|
| 10 |
import dash_cytoscape as cyto
|
| 11 |
from utils import (load_model_and_get_patterns, execute_forward_pass, format_data_for_cytoscape,
|
| 12 |
-
categorize_single_layer_heads, format_categorization_summary,
|
| 13 |
compare_attention_layers, compare_output_probabilities, format_comparison_summary,
|
| 14 |
get_check_token_probabilities)
|
| 15 |
from utils.model_config import get_auto_selections, get_model_family
|
|
@@ -528,6 +528,69 @@ def update_check_token_graph(check_token_data):
|
|
| 528 |
|
| 529 |
return figure, {'flex': '1', 'minWidth': '300px', 'display': 'block'}
|
| 530 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
# Enable Run Analysis button when requirements are met
|
| 532 |
@app.callback(
|
| 533 |
Output('run-analysis-btn', 'disabled'),
|
|
|
|
| 9 |
from dash import html, dcc, Input, Output, State, callback, no_update
|
| 10 |
import dash_cytoscape as cyto
|
| 11 |
from utils import (load_model_and_get_patterns, execute_forward_pass, format_data_for_cytoscape,
|
| 12 |
+
extract_layer_data, categorize_single_layer_heads, format_categorization_summary,
|
| 13 |
compare_attention_layers, compare_output_probabilities, format_comparison_summary,
|
| 14 |
get_check_token_probabilities)
|
| 15 |
from utils.model_config import get_auto_selections, get_model_family
|
|
|
|
| 528 |
|
| 529 |
return figure, {'flex': '1', 'minWidth': '300px', 'display': 'block'}
|
| 530 |
|
| 531 |
+
# Callback to create accordion panels from layer data
|
| 532 |
+
@app.callback(
|
| 533 |
+
Output('layer-accordions-container', 'children'),
|
| 534 |
+
[Input('session-activation-store', 'data')],
|
| 535 |
+
[State('model-dropdown', 'value')]
|
| 536 |
+
)
|
| 537 |
+
def create_layer_accordions(activation_data, model_name):
|
| 538 |
+
"""Create accordion panels for each layer."""
|
| 539 |
+
if not activation_data or not model_name:
|
| 540 |
+
return html.P("Run analysis to see layer-by-layer predictions.", className="placeholder-text")
|
| 541 |
+
|
| 542 |
+
try:
|
| 543 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 544 |
+
model = AutoModelForCausalLM.from_pretrained(model_name, attn_implementation='eager')
|
| 545 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 546 |
+
|
| 547 |
+
# Extract layer data
|
| 548 |
+
layer_data = extract_layer_data(activation_data, model, tokenizer)
|
| 549 |
+
|
| 550 |
+
if not layer_data:
|
| 551 |
+
return html.P("No layer data available.", className="placeholder-text")
|
| 552 |
+
|
| 553 |
+
# Create accordion panels
|
| 554 |
+
accordions = []
|
| 555 |
+
for i, layer in enumerate(layer_data):
|
| 556 |
+
layer_num = layer['layer_num']
|
| 557 |
+
top_token = layer.get('top_token', 'N/A')
|
| 558 |
+
top_prob = layer.get('top_prob', 0.0)
|
| 559 |
+
top_3 = layer.get('top_3_tokens', [])
|
| 560 |
+
|
| 561 |
+
# Create summary header
|
| 562 |
+
if top_token:
|
| 563 |
+
summary_text = f"Layer L{layer_num}: likely '{top_token}' (p={top_prob:.3f})"
|
| 564 |
+
else:
|
| 565 |
+
summary_text = f"Layer L{layer_num}: (no prediction)"
|
| 566 |
+
|
| 567 |
+
# Create accordion panel
|
| 568 |
+
panel = html.Details([
|
| 569 |
+
html.Summary(summary_text, className="layer-summary"),
|
| 570 |
+
html.Div([
|
| 571 |
+
html.P(f"Layer {layer_num} details (placeholder for future content)")
|
| 572 |
+
], className="layer-content")
|
| 573 |
+
], className="layer-accordion")
|
| 574 |
+
|
| 575 |
+
accordions.append(panel)
|
| 576 |
+
|
| 577 |
+
# Add token chips between adjacent layers (not after last layer)
|
| 578 |
+
if i < len(layer_data) - 1 and top_3:
|
| 579 |
+
chips = html.Div([
|
| 580 |
+
html.Span("→", className="token-arrow"),
|
| 581 |
+
*[html.Span(f"{tok} ({prob:.2f})", className="token-chip")
|
| 582 |
+
for tok, prob in top_3]
|
| 583 |
+
], className="token-chips-row")
|
| 584 |
+
accordions.append(chips)
|
| 585 |
+
|
| 586 |
+
return html.Div(accordions)
|
| 587 |
+
|
| 588 |
+
except Exception as e:
|
| 589 |
+
print(f"Error creating accordions: {e}")
|
| 590 |
+
import traceback
|
| 591 |
+
traceback.print_exc()
|
| 592 |
+
return html.P(f"Error creating layer view: {str(e)}", className="placeholder-text")
|
| 593 |
+
|
| 594 |
# Enable Run Analysis button when requirements are met
|
| 595 |
@app.callback(
|
| 596 |
Output('run-analysis-btn', 'disabled'),
|
assets/style.css
CHANGED
|
@@ -434,6 +434,87 @@ body {
|
|
| 434 |
}
|
| 435 |
|
| 436 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
/* Responsive design */
|
| 438 |
@media (max-width: 768px) {
|
| 439 |
.content-container {
|
|
@@ -453,4 +534,8 @@ body {
|
|
| 453 |
.header-title {
|
| 454 |
font-size: 2rem;
|
| 455 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
}
|
|
|
|
| 434 |
}
|
| 435 |
|
| 436 |
|
| 437 |
+
/* Layer accordion styles */
|
| 438 |
+
.layer-accordions {
|
| 439 |
+
margin-top: 1rem;
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
.layer-accordion {
|
| 443 |
+
margin-bottom: 0.5rem;
|
| 444 |
+
border: 1px solid #e9ecef;
|
| 445 |
+
border-radius: 6px;
|
| 446 |
+
overflow: hidden;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.layer-summary {
|
| 450 |
+
padding: 0.75rem 1rem;
|
| 451 |
+
background-color: #f8f9fa;
|
| 452 |
+
cursor: pointer;
|
| 453 |
+
font-size: 14px;
|
| 454 |
+
font-weight: 500;
|
| 455 |
+
color: #495057;
|
| 456 |
+
list-style: none;
|
| 457 |
+
transition: background-color 0.2s ease;
|
| 458 |
+
white-space: nowrap;
|
| 459 |
+
overflow: hidden;
|
| 460 |
+
text-overflow: ellipsis;
|
| 461 |
+
user-select: none;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
.layer-summary:hover {
|
| 465 |
+
background-color: #e9ecef;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
.layer-summary::-webkit-details-marker {
|
| 469 |
+
display: none;
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
.layer-summary::before {
|
| 473 |
+
content: '▶';
|
| 474 |
+
display: inline-block;
|
| 475 |
+
margin-right: 0.5rem;
|
| 476 |
+
transition: transform 0.2s ease;
|
| 477 |
+
font-size: 0.8em;
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
details[open] .layer-summary::before {
|
| 481 |
+
transform: rotate(90deg);
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
.layer-content {
|
| 485 |
+
padding: 1rem;
|
| 486 |
+
background-color: white;
|
| 487 |
+
border-top: 1px solid #e9ecef;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
/* Token chips between layers */
|
| 491 |
+
.token-chips-row {
|
| 492 |
+
display: flex;
|
| 493 |
+
align-items: center;
|
| 494 |
+
gap: 0.5rem;
|
| 495 |
+
padding: 0.5rem 1rem;
|
| 496 |
+
margin: 0.25rem 0;
|
| 497 |
+
background-color: #f8f9fa;
|
| 498 |
+
border-left: 3px solid #667eea;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
.token-arrow {
|
| 502 |
+
color: #667eea;
|
| 503 |
+
font-size: 1.2em;
|
| 504 |
+
font-weight: bold;
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
.token-chip {
|
| 508 |
+
display: inline-block;
|
| 509 |
+
padding: 0.25rem 0.5rem;
|
| 510 |
+
background-color: white;
|
| 511 |
+
border: 1px solid #dee2e6;
|
| 512 |
+
border-radius: 4px;
|
| 513 |
+
font-size: 12px;
|
| 514 |
+
color: #495057;
|
| 515 |
+
white-space: nowrap;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
/* Responsive design */
|
| 519 |
@media (max-width: 768px) {
|
| 520 |
.content-container {
|
|
|
|
| 534 |
.header-title {
|
| 535 |
font-size: 2rem;
|
| 536 |
}
|
| 537 |
+
|
| 538 |
+
.token-chips-row {
|
| 539 |
+
flex-wrap: wrap;
|
| 540 |
+
}
|
| 541 |
}
|
components/main_panel.py
CHANGED
|
@@ -1,217 +1,223 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Main panel component for the dashboard.
|
| 3 |
-
|
| 4 |
-
This component contains:
|
| 5 |
-
- Model selector and prompt input
|
| 6 |
-
- Visualization area (placeholder for now)
|
| 7 |
-
- Status and results display
|
| 8 |
-
"""
|
| 9 |
-
|
| 10 |
-
from dash import html, dcc
|
| 11 |
-
import dash_cytoscape as cyto
|
| 12 |
-
from .model_selector import create_model_selector
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
#
|
| 27 |
-
html.Div(
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
{
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
'
|
| 72 |
-
'
|
| 73 |
-
'
|
| 74 |
-
'
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
'
|
| 96 |
-
'
|
| 97 |
-
'
|
| 98 |
-
'
|
| 99 |
-
'
|
| 100 |
-
'
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
{
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
'
|
| 145 |
-
'
|
| 146 |
-
'
|
| 147 |
-
'
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
'
|
| 169 |
-
'
|
| 170 |
-
'
|
| 171 |
-
'
|
| 172 |
-
'
|
| 173 |
-
'
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Main panel component for the dashboard.
|
| 3 |
+
|
| 4 |
+
This component contains:
|
| 5 |
+
- Model selector and prompt input
|
| 6 |
+
- Visualization area (placeholder for now)
|
| 7 |
+
- Status and results display
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from dash import html, dcc
|
| 11 |
+
import dash_cytoscape as cyto
|
| 12 |
+
from .model_selector import create_model_selector
|
| 13 |
+
|
| 14 |
+
# Feature flag: set to True to show legacy Cytoscape graph
|
| 15 |
+
USE_LEGACY_CYTOSCAPE = False
|
| 16 |
+
|
| 17 |
+
def create_main_panel():
|
| 18 |
+
"""Create the main content panel."""
|
| 19 |
+
return html.Div([
|
| 20 |
+
# Model selection section
|
| 21 |
+
html.Div([
|
| 22 |
+
html.H3("Model Configuration", className="section-title"),
|
| 23 |
+
create_model_selector()
|
| 24 |
+
], className="config-section"),
|
| 25 |
+
|
| 26 |
+
# Analysis loading indicator
|
| 27 |
+
html.Div(id="analysis-loading-indicator", className="loading-container"),
|
| 28 |
+
|
| 29 |
+
# Check Token input with probability graph
|
| 30 |
+
html.Div([
|
| 31 |
+
html.Div([
|
| 32 |
+
html.Label("Check Token (optional):", className="input-label"),
|
| 33 |
+
dcc.Input(
|
| 34 |
+
id='check-token-input',
|
| 35 |
+
type='text',
|
| 36 |
+
placeholder="Enter a token to track its probability...",
|
| 37 |
+
value="",
|
| 38 |
+
style={"width": "300px"},
|
| 39 |
+
className="prompt-input"
|
| 40 |
+
)
|
| 41 |
+
], style={"flex": "0 0 auto"}),
|
| 42 |
+
html.Div([
|
| 43 |
+
dcc.Graph(
|
| 44 |
+
id='check-token-graph',
|
| 45 |
+
figure={},
|
| 46 |
+
style={'height': '450px', 'width': '100%'},
|
| 47 |
+
config={'displayModeBar': False}
|
| 48 |
+
)
|
| 49 |
+
], id='check-token-graph-container', style={'flex': '1', 'minWidth': '300px', 'display': 'none'})
|
| 50 |
+
], className="input-container", style={"marginBottom": "1.5rem", "display": "flex", "gap": "1.5rem", "alignItems": "flex-start"}),
|
| 51 |
+
|
| 52 |
+
# Visualization section (first prompt)
|
| 53 |
+
html.Div([
|
| 54 |
+
html.H3("Model Flow Visualization", className="section-title"),
|
| 55 |
+
# New accordion-based layer view
|
| 56 |
+
html.Div(id='layer-accordions-container', className="layer-accordions"),
|
| 57 |
+
# Legacy Cytoscape graph (gated by feature flag)
|
| 58 |
+
html.Div([
|
| 59 |
+
cyto.Cytoscape(
|
| 60 |
+
id='model-flow-graph',
|
| 61 |
+
elements=[],
|
| 62 |
+
layout={'name': 'preset'},
|
| 63 |
+
style={'width': '100%', 'height': '400px'},
|
| 64 |
+
zoom=1.0,
|
| 65 |
+
pan={'x': 100, 'y': 200},
|
| 66 |
+
stylesheet=[
|
| 67 |
+
# Node styles
|
| 68 |
+
{
|
| 69 |
+
'selector': 'node',
|
| 70 |
+
'style': {
|
| 71 |
+
'width': '60px',
|
| 72 |
+
'height': '60px',
|
| 73 |
+
'background-color': '#667eea',
|
| 74 |
+
'border-color': '#5a67d8',
|
| 75 |
+
'border-width': '2px',
|
| 76 |
+
'label': 'data(label)',
|
| 77 |
+
'text-valign': 'center',
|
| 78 |
+
'color': 'white',
|
| 79 |
+
'font-size': '10px',
|
| 80 |
+
'text-wrap': 'wrap'
|
| 81 |
+
}
|
| 82 |
+
},
|
| 83 |
+
# Divergent layer node style (red border for different prompts)
|
| 84 |
+
{
|
| 85 |
+
'selector': 'node.divergent-layer',
|
| 86 |
+
'style': {
|
| 87 |
+
'border-color': '#e53e3e',
|
| 88 |
+
'border-width': '4px'
|
| 89 |
+
}
|
| 90 |
+
},
|
| 91 |
+
# Output node style (distinct from layer nodes)
|
| 92 |
+
{
|
| 93 |
+
'selector': 'node[id="output_node"]',
|
| 94 |
+
'style': {
|
| 95 |
+
'width': '80px',
|
| 96 |
+
'height': '80px',
|
| 97 |
+
'background-color': '#48bb78',
|
| 98 |
+
'border-color': '#38a169',
|
| 99 |
+
'border-width': '3px',
|
| 100 |
+
'label': 'data(label)',
|
| 101 |
+
'text-valign': 'center',
|
| 102 |
+
'color': 'white',
|
| 103 |
+
'font-size': '11px',
|
| 104 |
+
'font-weight': 'bold',
|
| 105 |
+
'text-wrap': 'wrap',
|
| 106 |
+
'shape': 'round-rectangle'
|
| 107 |
+
}
|
| 108 |
+
},
|
| 109 |
+
# Edge styles
|
| 110 |
+
{
|
| 111 |
+
'selector': 'edge',
|
| 112 |
+
'style': {
|
| 113 |
+
'width': 'data(width)',
|
| 114 |
+
'opacity': 'data(opacity)',
|
| 115 |
+
'line-color': 'data(color)',
|
| 116 |
+
'target-arrow-color': 'data(color)',
|
| 117 |
+
'target-arrow-shape': 'triangle',
|
| 118 |
+
'curve-style': 'bezier'
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
]
|
| 122 |
+
),
|
| 123 |
+
# Tooltip for edge hover
|
| 124 |
+
html.Div(id='edge-tooltip', style={'display': 'none'})
|
| 125 |
+
], id='legacy-cytoscape-container', style={'position': 'relative', 'display': 'block' if USE_LEGACY_CYTOSCAPE else 'none'})
|
| 126 |
+
], className="visualization-section"),
|
| 127 |
+
|
| 128 |
+
# Second visualization (for comparison - initially hidden, gated by feature flag)
|
| 129 |
+
html.Div([
|
| 130 |
+
html.H3("Model Flow Visualization (Prompt 2)", className="section-title"),
|
| 131 |
+
html.Div([
|
| 132 |
+
cyto.Cytoscape(
|
| 133 |
+
id='model-flow-graph-2',
|
| 134 |
+
elements=[],
|
| 135 |
+
layout={'name': 'preset'},
|
| 136 |
+
style={'width': '100%', 'height': '400px'},
|
| 137 |
+
zoom=1.0,
|
| 138 |
+
pan={'x': 100, 'y': 200},
|
| 139 |
+
stylesheet=[
|
| 140 |
+
# Node styles
|
| 141 |
+
{
|
| 142 |
+
'selector': 'node',
|
| 143 |
+
'style': {
|
| 144 |
+
'width': '60px',
|
| 145 |
+
'height': '60px',
|
| 146 |
+
'background-color': '#667eea',
|
| 147 |
+
'border-color': '#5a67d8',
|
| 148 |
+
'border-width': '2px',
|
| 149 |
+
'label': 'data(label)',
|
| 150 |
+
'text-valign': 'center',
|
| 151 |
+
'color': 'white',
|
| 152 |
+
'font-size': '10px',
|
| 153 |
+
'text-wrap': 'wrap'
|
| 154 |
+
}
|
| 155 |
+
},
|
| 156 |
+
# Divergent layer node style (red border for different prompts)
|
| 157 |
+
{
|
| 158 |
+
'selector': 'node.divergent-layer',
|
| 159 |
+
'style': {
|
| 160 |
+
'border-color': '#e53e3e',
|
| 161 |
+
'border-width': '4px'
|
| 162 |
+
}
|
| 163 |
+
},
|
| 164 |
+
# Output node style (distinct from layer nodes)
|
| 165 |
+
{
|
| 166 |
+
'selector': 'node[id="output_node"]',
|
| 167 |
+
'style': {
|
| 168 |
+
'width': '80px',
|
| 169 |
+
'height': '80px',
|
| 170 |
+
'background-color': '#48bb78',
|
| 171 |
+
'border-color': '#38a169',
|
| 172 |
+
'border-width': '3px',
|
| 173 |
+
'label': 'data(label)',
|
| 174 |
+
'text-valign': 'center',
|
| 175 |
+
'color': 'white',
|
| 176 |
+
'font-size': '11px',
|
| 177 |
+
'font-weight': 'bold',
|
| 178 |
+
'text-wrap': 'wrap',
|
| 179 |
+
'shape': 'round-rectangle'
|
| 180 |
+
}
|
| 181 |
+
},
|
| 182 |
+
# Edge styles
|
| 183 |
+
{
|
| 184 |
+
'selector': 'edge',
|
| 185 |
+
'style': {
|
| 186 |
+
'width': 'data(width)',
|
| 187 |
+
'opacity': 'data(opacity)',
|
| 188 |
+
'line-color': 'data(color)',
|
| 189 |
+
'target-arrow-color': 'data(color)',
|
| 190 |
+
'target-arrow-shape': 'triangle',
|
| 191 |
+
'curve-style': 'bezier'
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
]
|
| 195 |
+
),
|
| 196 |
+
# Tooltip for edge hover
|
| 197 |
+
html.Div(id='edge-tooltip-2', style={'display': 'none'})
|
| 198 |
+
], style={'position': 'relative'})
|
| 199 |
+
], id="second-visualization-section", className="visualization-section", style={'display': 'none' if not USE_LEGACY_CYTOSCAPE else 'none'}),
|
| 200 |
+
|
| 201 |
+
# Two-Prompt Comparison section (shown when comparing)
|
| 202 |
+
html.Div([
|
| 203 |
+
html.H3("Two-Prompt Comparison Analysis", className="section-title"),
|
| 204 |
+
html.Div([
|
| 205 |
+
html.P(
|
| 206 |
+
"Comparison analysis will appear here when two prompts are provided.",
|
| 207 |
+
className="placeholder-text"
|
| 208 |
+
)
|
| 209 |
+
], id="comparison-container", className="results-area")
|
| 210 |
+
], id="comparison-section", className="results-section", style={'display': 'none'}),
|
| 211 |
+
|
| 212 |
+
# Analysis Results section (layer-specific analysis on node click)
|
| 213 |
+
html.Div([
|
| 214 |
+
html.H3("Analysis Results", className="section-title"),
|
| 215 |
+
html.Div([
|
| 216 |
+
html.P(
|
| 217 |
+
"Click a layer node to see detailed attention analysis and head categorization.",
|
| 218 |
+
className="placeholder-text"
|
| 219 |
+
)
|
| 220 |
+
], id="results-container", className="results-area")
|
| 221 |
+
], className="results-section")
|
| 222 |
+
|
| 223 |
+
], className="main-panel-content")
|
todo.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
| 3 |
Note: Minimal-change approach. Reuse existing files (`app.py`, `components/main_panel.py`, `utils/*`). Avoid new dependencies; use native `html.Details`/`html.Summary`, existing `dcc.Graph`, and current BertViz integration.
|
| 4 |
|
| 5 |
## Feature: Switch to panels with plain-language headers
|
| 6 |
-
- [
|
| 7 |
-
- [
|
| 8 |
-
- [
|
| 9 |
-
- [
|
| 10 |
-
- [
|
| 11 |
|
| 12 |
## Feature: Keep initial panels small to preserve flow view
|
| 13 |
- [ ] Style summary rows compactly (single-line, small font, consistent height)
|
|
|
|
| 3 |
Note: Minimal-change approach. Reuse existing files (`app.py`, `components/main_panel.py`, `utils/*`). Avoid new dependencies; use native `html.Details`/`html.Summary`, existing `dcc.Graph`, and current BertViz integration.
|
| 4 |
|
| 5 |
## Feature: Switch to panels with plain-language headers
|
| 6 |
+
- [x] Replace per-layer node display with `html.Details` (accordion) per layer in `components/main_panel.py`
|
| 7 |
+
- [x] Use `html.Summary` as header: `Layer L{N}: likely '{token}' (p={prob})`
|
| 8 |
+
- [x] Truncate long tokens in header with CSS (ellipsis); keep one-line summary
|
| 9 |
+
- [x] Add lightweight top-3 tokens "chips/arrows" between adjacent panel headers (no Cytoscape)
|
| 10 |
+
- [x] Gate old Cytoscape graph behind a feature flag (keep code path but hidden by default)
|
| 11 |
|
| 12 |
## Feature: Keep initial panels small to preserve flow view
|
| 13 |
- [ ] Style summary rows compactly (single-line, small font, consistent height)
|
utils/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
from .model_patterns import load_model_and_get_patterns, execute_forward_pass, logit_lens_transformation, format_data_for_cytoscape, generate_bertviz_html, generate_category_bertviz_html, get_check_token_probabilities
|
| 2 |
from .model_config import get_model_family, get_family_config, get_auto_selections, MODEL_TO_FAMILY, MODEL_FAMILIES
|
| 3 |
from .head_detection import categorize_all_heads, categorize_single_layer_heads, format_categorization_summary, HeadCategorizationConfig
|
| 4 |
from .prompt_comparison import compare_attention_layers, compare_output_probabilities, format_comparison_summary, ComparisonConfig
|
|
@@ -7,7 +7,8 @@ __all__ = [
|
|
| 7 |
'load_model_and_get_patterns',
|
| 8 |
'execute_forward_pass',
|
| 9 |
'logit_lens_transformation',
|
| 10 |
-
'format_data_for_cytoscape',
|
|
|
|
| 11 |
'generate_bertviz_html',
|
| 12 |
'generate_category_bertviz_html',
|
| 13 |
'get_check_token_probabilities',
|
|
|
|
| 1 |
+
from .model_patterns import load_model_and_get_patterns, execute_forward_pass, logit_lens_transformation, format_data_for_cytoscape, extract_layer_data, generate_bertviz_html, generate_category_bertviz_html, get_check_token_probabilities
|
| 2 |
from .model_config import get_model_family, get_family_config, get_auto_selections, MODEL_TO_FAMILY, MODEL_FAMILIES
|
| 3 |
from .head_detection import categorize_all_heads, categorize_single_layer_heads, format_categorization_summary, HeadCategorizationConfig
|
| 4 |
from .prompt_comparison import compare_attention_layers, compare_output_probabilities, format_comparison_summary, ComparisonConfig
|
|
|
|
| 7 |
'load_model_and_get_patterns',
|
| 8 |
'execute_forward_pass',
|
| 9 |
'logit_lens_transformation',
|
| 10 |
+
'format_data_for_cytoscape',
|
| 11 |
+
'extract_layer_data',
|
| 12 |
'generate_bertviz_html',
|
| 13 |
'generate_category_bertviz_html',
|
| 14 |
'get_check_token_probabilities',
|
utils/model_patterns.py
CHANGED
|
@@ -486,6 +486,50 @@ def format_data_for_cytoscape(activation_data: Dict[str, Any], model, tokenizer)
|
|
| 486 |
return elements
|
| 487 |
|
| 488 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 489 |
def generate_bertviz_html(activation_data: Dict[str, Any], layer_index: int, view_type: str = 'full') -> str:
|
| 490 |
"""
|
| 491 |
Generate BertViz attention visualization HTML using model_view.
|
|
|
|
| 486 |
return elements
|
| 487 |
|
| 488 |
|
| 489 |
+
def extract_layer_data(activation_data: Dict[str, Any], model, tokenizer) -> List[Dict[str, Any]]:
|
| 490 |
+
"""
|
| 491 |
+
Extract layer-by-layer data for accordion display.
|
| 492 |
+
|
| 493 |
+
Returns:
|
| 494 |
+
List of dicts with: layer_num, top_token, top_prob, top_3_tokens (list of (token, prob))
|
| 495 |
+
"""
|
| 496 |
+
layer_modules = activation_data.get('block_modules', [])
|
| 497 |
+
if not layer_modules:
|
| 498 |
+
return []
|
| 499 |
+
|
| 500 |
+
# Extract and sort layers by layer number
|
| 501 |
+
layer_info = sorted(
|
| 502 |
+
[(int(re.findall(r'\d+', name)[0]), name)
|
| 503 |
+
for name in layer_modules if re.findall(r'\d+', name)]
|
| 504 |
+
)
|
| 505 |
+
|
| 506 |
+
logit_lens_enabled = activation_data.get('logit_lens_parameter') is not None
|
| 507 |
+
layer_data = []
|
| 508 |
+
|
| 509 |
+
for layer_num, module_name in layer_info:
|
| 510 |
+
top_tokens = _get_top_tokens(activation_data, module_name, model, tokenizer) if logit_lens_enabled else None
|
| 511 |
+
|
| 512 |
+
if top_tokens:
|
| 513 |
+
top_token, top_prob = top_tokens[0]
|
| 514 |
+
layer_data.append({
|
| 515 |
+
'layer_num': layer_num,
|
| 516 |
+
'module_name': module_name,
|
| 517 |
+
'top_token': top_token,
|
| 518 |
+
'top_prob': top_prob,
|
| 519 |
+
'top_3_tokens': top_tokens[:3] # Get top 3 for chips
|
| 520 |
+
})
|
| 521 |
+
else:
|
| 522 |
+
layer_data.append({
|
| 523 |
+
'layer_num': layer_num,
|
| 524 |
+
'module_name': module_name,
|
| 525 |
+
'top_token': None,
|
| 526 |
+
'top_prob': None,
|
| 527 |
+
'top_3_tokens': []
|
| 528 |
+
})
|
| 529 |
+
|
| 530 |
+
return layer_data
|
| 531 |
+
|
| 532 |
+
|
| 533 |
def generate_bertviz_html(activation_data: Dict[str, Any], layer_index: int, view_type: str = 'full') -> str:
|
| 534 |
"""
|
| 535 |
Generate BertViz attention visualization HTML using model_view.
|