Spaces:
Sleeping
Sleeping
Commit ·
977a3ff
1
Parent(s): 1221d69
Add simplified attention view with top-3 attended tokens and BertViz toggle button
Browse files- app.py +63 -0
- components/__pycache__/main_panel.cpython-311.pyc +0 -0
- todo.md +4 -4
- utils/__pycache__/model_patterns.cpython-311.pyc +0 -0
- utils/model_patterns.py +72 -4
app.py
CHANGED
|
@@ -609,6 +609,37 @@ def create_layer_accordions(activation_data, model_name):
|
|
| 609 |
else:
|
| 610 |
content_items.append(html.P("No predictions available"))
|
| 611 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
panel = html.Details([
|
| 613 |
html.Summary(summary_text, className="layer-summary"),
|
| 614 |
html.Div(content_items, className="layer-content")
|
|
@@ -693,6 +724,38 @@ def toggle_comparison_mode(n_clicks, is_comparing):
|
|
| 693 |
|
| 694 |
return new_comparing, style, style, button_content, button_class
|
| 695 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 696 |
# Node click callback for analysis results
|
| 697 |
@app.callback(
|
| 698 |
Output('results-container', 'children'),
|
|
|
|
| 609 |
else:
|
| 610 |
content_items.append(html.P("No predictions available"))
|
| 611 |
|
| 612 |
+
# Add attention view (top-3 attended tokens)
|
| 613 |
+
top_attended = layer.get('top_attended_tokens', [])
|
| 614 |
+
if top_attended:
|
| 615 |
+
attention_items = []
|
| 616 |
+
for token, weight in top_attended:
|
| 617 |
+
attention_items.append(html.Div([
|
| 618 |
+
html.Span(f"'{token}'", style={'fontWeight': 'bold', 'marginRight': '8px'}),
|
| 619 |
+
html.Span(f"{weight:.3f}", style={'color': '#6c757d', 'fontSize': '13px'})
|
| 620 |
+
], style={'marginBottom': '4px'}))
|
| 621 |
+
|
| 622 |
+
content_items.append(html.Div([
|
| 623 |
+
html.Hr(style={'margin': '15px 0'}),
|
| 624 |
+
html.H6("Attention (current position)", style={'marginBottom': '8px', 'fontSize': '14px', 'color': '#495057'}),
|
| 625 |
+
html.Div(attention_items, style={'marginBottom': '10px'}),
|
| 626 |
+
html.Button(
|
| 627 |
+
[html.I(className="fas fa-expand", style={'marginRight': '5px'}), "Open full interactive view"],
|
| 628 |
+
id={'type': 'bertviz-btn', 'layer': layer_num},
|
| 629 |
+
className='bertviz-button',
|
| 630 |
+
style={
|
| 631 |
+
'padding': '6px 12px',
|
| 632 |
+
'fontSize': '12px',
|
| 633 |
+
'backgroundColor': '#667eea',
|
| 634 |
+
'color': 'white',
|
| 635 |
+
'border': 'none',
|
| 636 |
+
'borderRadius': '4px',
|
| 637 |
+
'cursor': 'pointer'
|
| 638 |
+
}
|
| 639 |
+
),
|
| 640 |
+
html.Div(id={'type': 'bertviz-container', 'layer': layer_num}, style={'marginTop': '10px'})
|
| 641 |
+
]))
|
| 642 |
+
|
| 643 |
panel = html.Details([
|
| 644 |
html.Summary(summary_text, className="layer-summary"),
|
| 645 |
html.Div(content_items, className="layer-content")
|
|
|
|
| 724 |
|
| 725 |
return new_comparing, style, style, button_content, button_class
|
| 726 |
|
| 727 |
+
# Callback to show BertViz visualization when button is clicked
|
| 728 |
+
@app.callback(
|
| 729 |
+
Output({'type': 'bertviz-container', 'layer': dash.dependencies.MATCH}, 'children'),
|
| 730 |
+
[Input({'type': 'bertviz-btn', 'layer': dash.dependencies.MATCH}, 'n_clicks')],
|
| 731 |
+
[State('session-activation-store', 'data'),
|
| 732 |
+
State({'type': 'bertviz-btn', 'layer': dash.dependencies.MATCH}, 'id')],
|
| 733 |
+
prevent_initial_call=True
|
| 734 |
+
)
|
| 735 |
+
def toggle_bertviz_view(n_clicks, activation_data, button_id):
|
| 736 |
+
"""Show/hide BertViz full model view when button is clicked."""
|
| 737 |
+
if not n_clicks or not activation_data:
|
| 738 |
+
return None
|
| 739 |
+
|
| 740 |
+
try:
|
| 741 |
+
from utils import generate_bertviz_html
|
| 742 |
+
layer_num = button_id['layer']
|
| 743 |
+
|
| 744 |
+
# Toggle: even clicks hide, odd clicks show
|
| 745 |
+
if n_clicks % 2 == 0:
|
| 746 |
+
return None
|
| 747 |
+
|
| 748 |
+
# Generate BertViz HTML for this layer
|
| 749 |
+
bertviz_html = generate_bertviz_html(activation_data, layer_num, 'full')
|
| 750 |
+
|
| 751 |
+
return html.Iframe(
|
| 752 |
+
srcDoc=bertviz_html,
|
| 753 |
+
style={'width': '100%', 'height': '500px', 'border': '1px solid #ddd', 'borderRadius': '4px', 'marginTop': '10px'}
|
| 754 |
+
)
|
| 755 |
+
|
| 756 |
+
except Exception as e:
|
| 757 |
+
return html.P(f"Error loading BertViz: {str(e)}", style={'color': '#dc3545', 'fontSize': '13px'})
|
| 758 |
+
|
| 759 |
# Node click callback for analysis results
|
| 760 |
@app.callback(
|
| 761 |
Output('results-container', 'children'),
|
components/__pycache__/main_panel.cpython-311.pyc
CHANGED
|
Binary files a/components/__pycache__/main_panel.cpython-311.pyc and b/components/__pycache__/main_panel.cpython-311.pyc differ
|
|
|
todo.md
CHANGED
|
@@ -24,10 +24,10 @@ Note: Minimal-change approach. Reuse existing files (`app.py`, `components/main_
|
|
| 24 |
- [x] Add a spinning "Loading visuals..." after loading data until all the visualizations are loaded
|
| 25 |
|
| 26 |
## Feature: Simplified attention view + open full interactive view
|
| 27 |
-
- [
|
| 28 |
-
- [
|
| 29 |
-
- [
|
| 30 |
-
- [
|
| 31 |
|
| 32 |
## Feature: Tokenization example of initial prompt (top of page)
|
| 33 |
- [ ] Add a new section above panels: "How the tokenizer splits your prompt"
|
|
|
|
| 24 |
- [x] Add a spinning "Loading visuals..." after loading data until all the visualizations are loaded
|
| 25 |
|
| 26 |
## Feature: Simplified attention view + open full interactive view
|
| 27 |
+
- [x] From `activation_data['attention_outputs']`, compute top-3 attended input tokens for current position (per layer)
|
| 28 |
+
- [x] Render a simple list: token text + attention weight (rounded), most-to-least
|
| 29 |
+
- [x] Add button/link: "Open full interactive view" → shows existing BertViz `model_view` for that layer
|
| 30 |
+
- [x] Keep current BertViz plumbing; reuse callbacks to load the selected layer
|
| 31 |
|
| 32 |
## Feature: Tokenization example of initial prompt (top of page)
|
| 33 |
- [ ] Add a new section above panels: "How the tokenizer splits your prompt"
|
utils/__pycache__/model_patterns.cpython-311.pyc
CHANGED
|
Binary files a/utils/__pycache__/model_patterns.cpython-311.pyc and b/utils/__pycache__/model_patterns.cpython-311.pyc differ
|
|
|
utils/model_patterns.py
CHANGED
|
@@ -420,12 +420,75 @@ def _compute_certainty(probs: List[float]) -> float:
|
|
| 420 |
return max(0.0, min(1.0, certainty)) # Clamp to [0, 1]
|
| 421 |
|
| 422 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
def extract_layer_data(activation_data: Dict[str, Any], model, tokenizer) -> List[Dict[str, Any]]:
|
| 424 |
"""
|
| 425 |
-
Extract layer-by-layer data for accordion display with top-5, deltas, and
|
| 426 |
|
| 427 |
Returns:
|
| 428 |
-
List of dicts with: layer_num, top_token, top_prob, top_5_tokens, deltas, certainty
|
| 429 |
"""
|
| 430 |
layer_modules = activation_data.get('block_modules', [])
|
| 431 |
if not layer_modules:
|
|
@@ -444,6 +507,9 @@ def extract_layer_data(activation_data: Dict[str, Any], model, tokenizer) -> Lis
|
|
| 444 |
for layer_num, module_name in layer_info:
|
| 445 |
top_tokens = _get_top_tokens(activation_data, module_name, model, tokenizer, top_k=5) if logit_lens_enabled else None
|
| 446 |
|
|
|
|
|
|
|
|
|
|
| 447 |
if top_tokens:
|
| 448 |
top_token, top_prob = top_tokens[0]
|
| 449 |
|
|
@@ -465,7 +531,8 @@ def extract_layer_data(activation_data: Dict[str, Any], model, tokenizer) -> Lis
|
|
| 465 |
'top_3_tokens': top_tokens[:3], # Keep for backward compatibility
|
| 466 |
'top_5_tokens': top_tokens[:5], # New: top-5 for bar chart
|
| 467 |
'deltas': deltas,
|
| 468 |
-
'certainty': certainty
|
|
|
|
| 469 |
})
|
| 470 |
|
| 471 |
# Update previous layer probabilities
|
|
@@ -479,7 +546,8 @@ def extract_layer_data(activation_data: Dict[str, Any], model, tokenizer) -> Lis
|
|
| 479 |
'top_3_tokens': [],
|
| 480 |
'top_5_tokens': [],
|
| 481 |
'deltas': {},
|
| 482 |
-
'certainty': 0.0
|
|
|
|
| 483 |
})
|
| 484 |
|
| 485 |
return layer_data
|
|
|
|
| 420 |
return max(0.0, min(1.0, certainty)) # Clamp to [0, 1]
|
| 421 |
|
| 422 |
|
| 423 |
+
def _get_top_attended_tokens(activation_data: Dict[str, Any], layer_num: int, tokenizer, top_k: int = 3) -> Optional[List[Tuple[str, float]]]:
|
| 424 |
+
"""
|
| 425 |
+
Get top-K attended input tokens for the current position (last token) in a layer.
|
| 426 |
+
Averages attention across all heads.
|
| 427 |
+
|
| 428 |
+
Args:
|
| 429 |
+
activation_data: Output from execute_forward_pass
|
| 430 |
+
layer_num: Layer number to analyze
|
| 431 |
+
tokenizer: Tokenizer for decoding tokens
|
| 432 |
+
top_k: Number of top attended tokens to return
|
| 433 |
+
|
| 434 |
+
Returns:
|
| 435 |
+
List of (token_string, attention_weight) tuples, sorted by weight (highest first)
|
| 436 |
+
"""
|
| 437 |
+
try:
|
| 438 |
+
attention_outputs = activation_data.get('attention_outputs', {})
|
| 439 |
+
input_ids = activation_data.get('input_ids', [])
|
| 440 |
+
|
| 441 |
+
if not attention_outputs or not input_ids:
|
| 442 |
+
return None
|
| 443 |
+
|
| 444 |
+
# Find attention output for this layer
|
| 445 |
+
target_module = None
|
| 446 |
+
for module_name in attention_outputs.keys():
|
| 447 |
+
numbers = re.findall(r'\d+', module_name)
|
| 448 |
+
if numbers and int(numbers[0]) == layer_num:
|
| 449 |
+
target_module = module_name
|
| 450 |
+
break
|
| 451 |
+
|
| 452 |
+
if not target_module:
|
| 453 |
+
return None
|
| 454 |
+
|
| 455 |
+
attention_output = attention_outputs[target_module]['output']
|
| 456 |
+
if not isinstance(attention_output, list) or len(attention_output) < 2:
|
| 457 |
+
return None
|
| 458 |
+
|
| 459 |
+
# Get attention weights: [batch, heads, seq_len, seq_len]
|
| 460 |
+
attention_weights = torch.tensor(attention_output[1])
|
| 461 |
+
|
| 462 |
+
# Average across heads: [seq_len, seq_len]
|
| 463 |
+
avg_attention = attention_weights[0].mean(dim=0)
|
| 464 |
+
|
| 465 |
+
# Get attention from last position to all positions
|
| 466 |
+
last_pos_attention = avg_attention[-1, :] # [seq_len]
|
| 467 |
+
|
| 468 |
+
# Get top-K attended positions
|
| 469 |
+
top_values, top_indices = torch.topk(last_pos_attention, min(top_k, len(last_pos_attention)))
|
| 470 |
+
|
| 471 |
+
# Convert to tokens
|
| 472 |
+
input_ids_tensor = torch.tensor(input_ids[0]) if isinstance(input_ids[0], list) else torch.tensor(input_ids)
|
| 473 |
+
result = []
|
| 474 |
+
for idx, weight in zip(top_indices, top_values):
|
| 475 |
+
token_id = input_ids_tensor[idx].item()
|
| 476 |
+
token_str = tokenizer.decode([token_id], skip_special_tokens=False)
|
| 477 |
+
result.append((token_str, weight.item()))
|
| 478 |
+
|
| 479 |
+
return result
|
| 480 |
+
|
| 481 |
+
except Exception as e:
|
| 482 |
+
print(f"Warning: Could not compute attended tokens for layer {layer_num}: {e}")
|
| 483 |
+
return None
|
| 484 |
+
|
| 485 |
+
|
| 486 |
def extract_layer_data(activation_data: Dict[str, Any], model, tokenizer) -> List[Dict[str, Any]]:
|
| 487 |
"""
|
| 488 |
+
Extract layer-by-layer data for accordion display with top-5, deltas, certainty, and attention.
|
| 489 |
|
| 490 |
Returns:
|
| 491 |
+
List of dicts with: layer_num, top_token, top_prob, top_5_tokens, deltas, certainty, top_attended_tokens
|
| 492 |
"""
|
| 493 |
layer_modules = activation_data.get('block_modules', [])
|
| 494 |
if not layer_modules:
|
|
|
|
| 507 |
for layer_num, module_name in layer_info:
|
| 508 |
top_tokens = _get_top_tokens(activation_data, module_name, model, tokenizer, top_k=5) if logit_lens_enabled else None
|
| 509 |
|
| 510 |
+
# Get top-3 attended tokens for this layer
|
| 511 |
+
top_attended = _get_top_attended_tokens(activation_data, layer_num, tokenizer, top_k=3)
|
| 512 |
+
|
| 513 |
if top_tokens:
|
| 514 |
top_token, top_prob = top_tokens[0]
|
| 515 |
|
|
|
|
| 531 |
'top_3_tokens': top_tokens[:3], # Keep for backward compatibility
|
| 532 |
'top_5_tokens': top_tokens[:5], # New: top-5 for bar chart
|
| 533 |
'deltas': deltas,
|
| 534 |
+
'certainty': certainty,
|
| 535 |
+
'top_attended_tokens': top_attended # New: attention view
|
| 536 |
})
|
| 537 |
|
| 538 |
# Update previous layer probabilities
|
|
|
|
| 546 |
'top_3_tokens': [],
|
| 547 |
'top_5_tokens': [],
|
| 548 |
'deltas': {},
|
| 549 |
+
'certainty': 0.0,
|
| 550 |
+
'top_attended_tokens': top_attended
|
| 551 |
})
|
| 552 |
|
| 553 |
return layer_data
|