angerami Claude Sonnet 4.6 commited on
Commit
3fe2586
·
1 Parent(s): 2192ae0

Update dashboards and correlations app for experiment-namespaced output layout

Browse files

dashboard_utils: use DATA_PATH (was OUTPUT_DIR), add detect_experiments() and
get_experiment_dir(); load_dataset_with_metadata and load_eval_metrics now resolve
paths through the selected experiment directory. Cache key includes experiment_dir
so switching experiments correctly invalidates cached data.

streamlit_app: render experiment selectbox before page navigation; stores selection
in session_state so utility functions pick it up without API changes in page files.

correlations_app: fix sel_models → sel_runs reference in filter_key.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

apps/correlations_app.py ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Streamlit viewer for transformer weight correlation figures.
2
+
3
+ Discovers experiments automatically from the output directory:
4
+ - Output root: $DATA_PATH if set, otherwise ./outputs
5
+ - Experiments: any subdirectory of the root that contains figures/pairs/
6
+ - Figures: {root}/{experiment}/figures/pairs/{run_key}/*.png
7
+
8
+ Usage:
9
+ streamlit run apps/correlations_app.py
10
+ DATA_PATH=/mnt/data/outputs streamlit run apps/correlations_app.py
11
+ """
12
+
13
+ import os
14
+ import re
15
+ from pathlib import Path
16
+
17
+ import streamlit as st
18
+
19
+ # ── Page config (must be first st call) ───────────────────────────────
20
+
21
+ st.set_page_config(
22
+ page_title="Transformer Analysis",
23
+ page_icon="🔬",
24
+ layout="wide",
25
+ initial_sidebar_state="expanded",
26
+ )
27
+
28
+ # ── Constants ─────────────────────────────────────────────────────────
29
+
30
+ DEFAULT_OUTPUT_DIR = Path(os.environ.get("DATA_PATH", "outputs"))
31
+
32
+ COMPONENTS = ["W_QK", "W_OV", "b_Q", "b_K", "b_V", "b_O"]
33
+ _COMP_RE = "|".join(re.escape(c) for c in COMPONENTS)
34
+
35
+ PLOT_TYPES = [
36
+ "corr_vs_layer_distance", "dominant_eigenvectors", "MP_overlay",
37
+ "Q_heatmap", "Q_eigenvalues", "block_means", "P_Q",
38
+ "scalars_vs_layer", "scalars_heatmap", "scalar_correlations",
39
+ ]
40
+
41
+ PLOT_TYPE_DISPLAY = {
42
+ "Q_heatmap": "Correlation heatmap",
43
+ "block_means": "Block means",
44
+ "P_Q": "Overlap distribution P(Q)",
45
+ "Q_eigenvalues": "Eigenvalue spectrum",
46
+ "corr_vs_layer_distance": "Correlation vs distance",
47
+ "MP_overlay": "Marchenko-Pastur",
48
+ "dominant_eigenvectors": "Dominant eigenvectors",
49
+ "cross_heatmap": "Cross-correlation heatmap",
50
+ "cross_diagonal": "Cross-correlation diagonal",
51
+ "scalars_vs_layer": "Scalars vs layer",
52
+ "scalars_heatmap": "Scalar heatmaps",
53
+ "scalar_correlations": "Scalar correlations",
54
+ }
55
+
56
+ METRIC_DISPLAY = {
57
+ "frob_cosine": "Frobenius cosine",
58
+ "pearson_corr": "Pearson correlation",
59
+ "two_point": "Two-point function",
60
+ "connected_corr": "Connected correlation",
61
+ "hist_symmetric_kl": "Symmetric KL (hist)",
62
+ "hist_jensen_shannon": "Jensen-Shannon (hist)",
63
+ "symmetric_kl": "Symmetric KL (KDE)",
64
+ "jensen_shannon": "Jensen-Shannon (KDE)",
65
+ }
66
+
67
+ COMPONENT_COLORS = {
68
+ "weight": ("#3a1a5a", "#c06aff"),
69
+ "bias": ("#5a3a1a", "#ffb06a"),
70
+ "cross": ("#1a5a5a", "#6affff"),
71
+ "scalar": ("#3a5a1a", "#b0ff6a"),
72
+ }
73
+
74
+
75
+ # ── Filename parser (mirrors generate_viewer_index.parse_filename) ────
76
+
77
+ _CROSS_DIAG_RE = re.compile(
78
+ rf"^(.+?)_cross_diagonal_({_COMP_RE})_vs_({_COMP_RE})$"
79
+ )
80
+ _CROSS_RE = re.compile(
81
+ rf"^(.+?)_cross_({_COMP_RE})_vs_({_COMP_RE})_(.+)$"
82
+ )
83
+ _SELF_RE = re.compile(rf"^(.+?)_({_COMP_RE})_(.+)$")
84
+
85
+
86
+ def parse_filename(filename):
87
+ name = filename.removesuffix(".png")
88
+ result = {
89
+ "model": "", "component": "", "plot_type": "",
90
+ "metric": "", "cross_pair": "",
91
+ }
92
+
93
+ m = _CROSS_DIAG_RE.match(name)
94
+ if m:
95
+ result["model"] = m.group(1)
96
+ result["component"] = f"{m.group(2)} vs {m.group(3)}"
97
+ result["cross_pair"] = result["component"]
98
+ result["plot_type"] = "cross_diagonal"
99
+ return result
100
+
101
+ m = _CROSS_RE.match(name)
102
+ if m:
103
+ result["model"] = m.group(1)
104
+ result["component"] = f"{m.group(2)} vs {m.group(3)}"
105
+ result["cross_pair"] = result["component"]
106
+ result["plot_type"] = "cross_heatmap"
107
+ result["metric"] = m.group(4)
108
+ return result
109
+
110
+ for pt in ("scalars_vs_layer", "scalars_heatmap", "scalar_correlations"):
111
+ tag = f"_{pt}"
112
+ if name.endswith(tag):
113
+ result["model"] = name[:-len(tag)]
114
+ result["component"] = "scalars"
115
+ result["plot_type"] = pt
116
+ return result
117
+
118
+ m = _SELF_RE.match(name)
119
+ if m:
120
+ result["model"] = m.group(1)
121
+ result["component"] = m.group(2)
122
+ rest = m.group(3)
123
+ for pt in PLOT_TYPES:
124
+ if rest == pt:
125
+ result["plot_type"] = pt
126
+ return result
127
+ if rest.startswith(pt + "_"):
128
+ result["plot_type"] = pt
129
+ result["metric"] = rest[len(pt) + 1:]
130
+ return result
131
+ result["plot_type"] = rest
132
+ return result
133
+
134
+ result["model"] = name
135
+ return result
136
+
137
+
138
+ def component_kind(comp):
139
+ if not comp:
140
+ return "weight"
141
+ if comp == "scalars":
142
+ return "scalar"
143
+ if " vs " in comp:
144
+ return "cross"
145
+ if comp.startswith("b_"):
146
+ return "bias"
147
+ return "weight"
148
+
149
+
150
+ # ── Data loading ──────────────────────────────────────────────────────
151
+
152
+ def detect_experiments(output_dir: Path) -> list[str]:
153
+ """Return experiment names (subdirs containing figures/pairs/)."""
154
+ if not output_dir.is_dir():
155
+ return []
156
+ return sorted(
157
+ p.name for p in output_dir.iterdir()
158
+ if p.is_dir() and (p / "figures" / "pairs").is_dir()
159
+ )
160
+
161
+
162
+ @st.cache_data(show_spinner="Loading figures...")
163
+ def load_catalog(pairs_dir: str) -> list[dict]:
164
+ """Recursively scan pairs_dir/{run_key}/*.png → catalog."""
165
+ entries = []
166
+ for png in sorted(Path(pairs_dir).rglob("*.png")):
167
+ meta = parse_filename(png.name)
168
+ meta["path"] = str(png)
169
+ meta["filename"] = png.name
170
+ meta["run"] = png.parent.name # run_key subdirectory
171
+ entries.append(meta)
172
+ return entries
173
+
174
+
175
+ # ── CSS ───────────────────────────────────────────────────────────────
176
+
177
+ def inject_css():
178
+ st.markdown("""
179
+ <style>
180
+ .figure-card {
181
+ background: #1a1a2a;
182
+ border: 1px solid #3a3a4a;
183
+ border-radius: 8px;
184
+ padding: 0.75rem;
185
+ margin-bottom: 0.5rem;
186
+ }
187
+ .card-title {
188
+ font-size: 0.9rem;
189
+ font-weight: 500;
190
+ color: #e0e0e0;
191
+ margin: 0.4rem 0 0.3rem 0;
192
+ }
193
+ .card-tags { display: flex; flex-wrap: wrap; gap: 4px; }
194
+ .tag {
195
+ display: inline-block;
196
+ padding: 2px 8px;
197
+ border-radius: 3px;
198
+ font-size: 0.72rem;
199
+ font-weight: 500;
200
+ }
201
+ .tag-model { background: #1a3a5a; color: #6ab0ff; }
202
+ .tag-weight { background: #3a1a5a; color: #c06aff; }
203
+ .tag-bias { background: #5a3a1a; color: #ffb06a; }
204
+ .tag-cross { background: #1a5a5a; color: #6affff; }
205
+ .tag-scalar { background: #3a5a1a; color: #b0ff6a; }
206
+ .tag-metric { background: #1a5a3a; color: #6affc0; }
207
+ .tag-plot-type { background: #2a2a3a; color: #aab0ff; }
208
+
209
+ /* tighten Streamlit column gaps */
210
+ [data-testid="stHorizontalBlock"] { gap: 0.8rem; }
211
+ </style>
212
+ """, unsafe_allow_html=True)
213
+
214
+
215
+ # ── Rendering ─────────────────────────────────────────────────────────
216
+
217
+ def render_tags_html(entry):
218
+ kind = component_kind(entry["component"])
219
+ parts = [f'<span class="tag tag-model">{entry["run"]}</span>']
220
+ if entry["component"]:
221
+ parts.append(
222
+ f'<span class="tag tag-{kind}">{entry["component"]}</span>'
223
+ )
224
+ if entry["metric"]:
225
+ label = METRIC_DISPLAY.get(entry["metric"], entry["metric"])
226
+ parts.append(f'<span class="tag tag-metric">{label}</span>')
227
+ return "".join(parts)
228
+
229
+
230
+ def render_card(entry):
231
+ title = PLOT_TYPE_DISPLAY.get(entry["plot_type"], entry["plot_type"])
232
+ tags_html = render_tags_html(entry)
233
+ st.markdown('<div class="figure-card">', unsafe_allow_html=True)
234
+ st.image(entry["path"], use_container_width=True)
235
+ st.markdown(
236
+ f'<div class="card-title">{title}</div>'
237
+ f'<div class="card-tags">{tags_html}</div>'
238
+ f'</div>',
239
+ unsafe_allow_html=True,
240
+ )
241
+
242
+
243
+ def display_name(key, display_map):
244
+ """Format for multiselect: 'internal_key (Display Name)'."""
245
+ d = display_map.get(key)
246
+ return f"{key} — {d}" if d else key
247
+
248
+
249
+ # ── Main ──────────────────────────────────────────────────────────────
250
+
251
+ def main():
252
+ inject_css()
253
+
254
+ # ── Experiment discovery ──────────────────────────────────────
255
+ experiments = detect_experiments(DEFAULT_OUTPUT_DIR)
256
+
257
+ if not experiments:
258
+ st.warning(
259
+ f"No experiments found under `{DEFAULT_OUTPUT_DIR}`. "
260
+ "Run the pipeline first, or set `DATA_PATH` to the correct output directory."
261
+ )
262
+ return
263
+
264
+ # ── Sidebar ───────────────────────────────────────────────────
265
+ with st.sidebar:
266
+ st.title("Transformer Analysis")
267
+
268
+ experiment = st.selectbox("Experiment", experiments)
269
+ pairs_dir = DEFAULT_OUTPUT_DIR / experiment / "figures" / "pairs"
270
+
271
+ catalog = load_catalog(str(pairs_dir))
272
+
273
+ if not catalog:
274
+ st.warning(f"No figures found in `{pairs_dir}`.")
275
+ return
276
+
277
+ # Unique values for filters
278
+ all_runs = sorted({e["run"] for e in catalog})
279
+ all_components = sorted({e["component"] for e in catalog if e["component"]})
280
+ all_plot_types = sorted({e["plot_type"] for e in catalog if e["plot_type"]})
281
+ all_metrics = sorted({e["metric"] for e in catalog if e["metric"]})
282
+
283
+ with st.sidebar:
284
+ st.caption(f"`{pairs_dir}` · {len(catalog)} figures")
285
+
286
+ sel_runs = st.multiselect(
287
+ "Run", all_runs,
288
+ placeholder="All runs",
289
+ )
290
+ sel_components = st.multiselect(
291
+ "Component", all_components,
292
+ placeholder="All components",
293
+ )
294
+ sel_plot_types = st.multiselect(
295
+ "Plot type", all_plot_types,
296
+ format_func=lambda k: PLOT_TYPE_DISPLAY.get(k, k),
297
+ placeholder="All plot types",
298
+ )
299
+ sel_metrics = st.multiselect(
300
+ "Metric", all_metrics,
301
+ format_func=lambda k: METRIC_DISPLAY.get(k, k),
302
+ placeholder="All metrics",
303
+ )
304
+
305
+ st.divider()
306
+ search = st.text_input("Search filenames", "")
307
+ n_cols = st.slider("Columns", 1, 5, 3)
308
+ per_page = st.slider("Per page", 6, 60, 18, step=6)
309
+
310
+ # ── Filter ────────────────────────────────────────────────────
311
+ filtered = catalog
312
+ if sel_runs:
313
+ filtered = [e for e in filtered if e["run"] in sel_runs]
314
+ if sel_components:
315
+ filtered = [e for e in filtered if e["component"] in sel_components]
316
+ if sel_plot_types:
317
+ filtered = [e for e in filtered if e["plot_type"] in sel_plot_types]
318
+ if sel_metrics:
319
+ filtered = [e for e in filtered if e["metric"] in sel_metrics]
320
+ if search:
321
+ q = search.lower()
322
+ filtered = [e for e in filtered if q in e["filename"].lower()]
323
+
324
+ # ── Stats bar ─────────────────────────────────────────────────
325
+ stat_cols = st.columns(4)
326
+ stat_cols[0].metric("Total", len(catalog))
327
+ stat_cols[1].metric("Filtered", len(filtered))
328
+ stat_cols[2].metric("Runs", len(all_runs))
329
+ stat_cols[3].metric("Components", len(all_components))
330
+
331
+ if not filtered:
332
+ st.info("No figures match the current filters.")
333
+ return
334
+
335
+ # ── Pagination ────────────────────────────────────────────────
336
+ n_pages = max(1, (len(filtered) + per_page - 1) // per_page)
337
+
338
+ if "page" not in st.session_state:
339
+ st.session_state.page = 0
340
+ # Reset page when filters change
341
+ filter_key = (
342
+ tuple(sel_runs), tuple(sel_components),
343
+ tuple(sel_plot_types), tuple(sel_metrics), search,
344
+ )
345
+ if st.session_state.get("_filter_key") != filter_key:
346
+ st.session_state.page = 0
347
+ st.session_state["_filter_key"] = filter_key
348
+
349
+ page = st.session_state.page
350
+ page = max(0, min(page, n_pages - 1))
351
+
352
+ nav_cols = st.columns([1, 3, 1])
353
+ with nav_cols[0]:
354
+ if st.button("← Prev", disabled=(page == 0)):
355
+ st.session_state.page = page - 1
356
+ st.rerun()
357
+ with nav_cols[1]:
358
+ st.markdown(
359
+ f"<div style='text-align:center; color:#888; padding-top:6px'>"
360
+ f"Page {page + 1} / {n_pages}"
361
+ f"</div>",
362
+ unsafe_allow_html=True,
363
+ )
364
+ with nav_cols[2]:
365
+ if st.button("Next →", disabled=(page >= n_pages - 1)):
366
+ st.session_state.page = page + 1
367
+ st.rerun()
368
+
369
+ # ── Gallery grid ──────────────────────────────────────────────
370
+ start = page * per_page
371
+ page_items = filtered[start : start + per_page]
372
+
373
+ for row_start in range(0, len(page_items), n_cols):
374
+ cols = st.columns(n_cols)
375
+ for i, col in enumerate(cols):
376
+ idx = row_start + i
377
+ if idx >= len(page_items):
378
+ break
379
+ with col:
380
+ with st.container():
381
+ render_card(page_items[idx])
382
+
383
+ # ── Detail view (click filename to expand) ────────────────────
384
+ with st.sidebar:
385
+ st.divider()
386
+ filenames = [e["filename"] for e in filtered]
387
+ sel = st.selectbox(
388
+ "Detail view",
389
+ [""] + filenames,
390
+ format_func=lambda x: x if x else "Select a figure...",
391
+ )
392
+ if sel:
393
+ entry = next(e for e in filtered if e["filename"] == sel)
394
+ st.image(entry["path"], use_container_width=True)
395
+ st.caption(entry["filename"])
396
+ for k in ("run", "component", "plot_type", "metric"):
397
+ if entry.get(k):
398
+ st.text(f"{k}: {entry[k]}")
399
+
400
+
401
+ if __name__ == "__main__":
402
+ main()
dashboards/dashboard_utils.py CHANGED
@@ -13,8 +13,33 @@ import os
13
  def is_HF_environment():
14
  return "SPACE_ID" in os.environ
15
 
16
- def get_data_path():
17
- return os.environ.get("OUTPUT_DIR", "outputs")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
 
20
  def model_size_from_name(ds_name: str) -> float:
@@ -65,26 +90,22 @@ def ensure_offline_available(path: Path):
65
  return False
66
 
67
 
68
- _SKIP_DIRS = {"eval", "logs", "all_models"}
69
 
70
 
71
  def get_available_datasets(hf_version: str = None) -> list[str]:
72
- """Scan OUTPUT_DIR for available per-run datasets (run_keys).
73
-
74
- In HF Spaces mode, hf_version is used to filter hub datasets.
75
- Locally, scans OUTPUT_DIR directly — no campaign subdirectory.
76
- """
77
  if is_HF_environment():
78
  from huggingface_hub import HfApi
79
  api = HfApi()
80
  datasets = api.list_datasets(author="angerami", search=hf_version or "")
81
  return [ds.id.split('/')[-1] for ds in datasets]
82
 
83
- out = Path(get_data_path())
84
- if not out.exists():
85
  return []
86
  names = set()
87
- for item in out.iterdir():
88
  if not item.is_dir() or item.name.startswith("."):
89
  continue
90
  base = item.name.removesuffix("_refined")
@@ -94,6 +115,22 @@ def get_available_datasets(hf_version: str = None) -> list[str]:
94
 
95
 
96
  @st.cache_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  def load_dataset_with_metadata(ds_name: str, hf_version: str = None, hf_repo_id: str = None):
98
  if is_HF_environment():
99
  repo_id = hf_repo_id if hf_repo_id else f"angerami/{ds_name}_{hf_version}"
@@ -103,23 +140,10 @@ def load_dataset_with_metadata(ds_name: str, hf_version: str = None, hf_repo_id:
103
  metadata_path = hf_hub_download(repo_id=repo_id, filename="metadata.json", repo_type="dataset")
104
  with open(metadata_path) as f:
105
  metadata = json.load(f)
106
- else:
107
- out = Path(get_data_path())
108
- refined = out / f"{ds_name}_refined"
109
- dataset_path = refined if refined.exists() else out / ds_name
110
- if not dataset_path.exists():
111
- raise FileNotFoundError(f"Dataset not found: {dataset_path}")
112
- with st.spinner("Ensuring files are available offline..."):
113
- ensure_offline_available(dataset_path)
114
- with st.spinner("Loading dataset..."):
115
- df = load_from_disk(str(dataset_path))
116
- metadata = {}
117
- metadata_path = dataset_path / "metadata.json"
118
- if metadata_path.exists():
119
- with open(metadata_path) as f:
120
- metadata = json.load(f)
121
 
122
- return df.to_pandas(), metadata
 
123
 
124
 
125
  def get_unique_values(df, column):
@@ -204,10 +228,10 @@ stat_display = {
204
 
205
 
206
  def load_eval_metrics(out_dir: str = None) -> "pd.DataFrame":
207
- """Load eval_metrics.parquet if it exists; return empty DataFrame otherwise."""
208
  import pandas as pd
209
  if out_dir is None:
210
- out_dir = os.path.join(get_data_path(), "eval_metrics")
211
  path = os.path.join(out_dir, "eval_metrics.parquet")
212
  if os.path.exists(path):
213
  return pd.read_parquet(path)
 
13
  def is_HF_environment():
14
  return "SPACE_ID" in os.environ
15
 
16
+
17
+ def get_data_path() -> str:
18
+ """Root directory containing experiment subdirectories."""
19
+ return os.environ.get("DATA_PATH", "outputs")
20
+
21
+
22
+ def detect_experiments(root: Path) -> list[str]:
23
+ """Return experiment names: subdirs of root that contain a done/ sentinel directory."""
24
+ if not root.is_dir():
25
+ return []
26
+ return sorted(
27
+ p.name for p in root.iterdir()
28
+ if p.is_dir() and (p / "done").is_dir()
29
+ )
30
+
31
+
32
+ def get_experiment_dir() -> Path:
33
+ """Return the currently selected experiment directory.
34
+
35
+ Reads from st.session_state['experiment']; falls back to the first detected experiment.
36
+ """
37
+ root = Path(get_data_path())
38
+ experiment = st.session_state.get("experiment")
39
+ if experiment:
40
+ return root / experiment
41
+ experiments = detect_experiments(root)
42
+ return root / experiments[0] if experiments else root
43
 
44
 
45
  def model_size_from_name(ds_name: str) -> float:
 
90
  return False
91
 
92
 
93
+ _SKIP_DIRS = {"eval", "eval_metrics", "logs", "all_models", "correlations", "figures", "done"}
94
 
95
 
96
  def get_available_datasets(hf_version: str = None) -> list[str]:
97
+ """Scan the selected experiment directory for available per-run datasets (run_keys)."""
 
 
 
 
98
  if is_HF_environment():
99
  from huggingface_hub import HfApi
100
  api = HfApi()
101
  datasets = api.list_datasets(author="angerami", search=hf_version or "")
102
  return [ds.id.split('/')[-1] for ds in datasets]
103
 
104
+ exp_dir = get_experiment_dir()
105
+ if not exp_dir.exists():
106
  return []
107
  names = set()
108
+ for item in exp_dir.iterdir():
109
  if not item.is_dir() or item.name.startswith("."):
110
  continue
111
  base = item.name.removesuffix("_refined")
 
115
 
116
 
117
  @st.cache_data
118
+ def _load_local_dataset(experiment_dir: str, ds_name: str):
119
+ exp = Path(experiment_dir)
120
+ refined = exp / f"{ds_name}_refined"
121
+ dataset_path = refined if refined.exists() else exp / ds_name
122
+ if not dataset_path.exists():
123
+ raise FileNotFoundError(f"Dataset not found: {dataset_path}")
124
+ ensure_offline_available(dataset_path)
125
+ df = load_from_disk(str(dataset_path))
126
+ metadata = {}
127
+ metadata_path = dataset_path / "metadata.json"
128
+ if metadata_path.exists():
129
+ with open(metadata_path) as f:
130
+ metadata = json.load(f)
131
+ return df.to_pandas(), metadata
132
+
133
+
134
  def load_dataset_with_metadata(ds_name: str, hf_version: str = None, hf_repo_id: str = None):
135
  if is_HF_environment():
136
  repo_id = hf_repo_id if hf_repo_id else f"angerami/{ds_name}_{hf_version}"
 
140
  metadata_path = hf_hub_download(repo_id=repo_id, filename="metadata.json", repo_type="dataset")
141
  with open(metadata_path) as f:
142
  metadata = json.load(f)
143
+ return df.to_pandas(), metadata
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
+ with st.spinner("Loading dataset..."):
146
+ return _load_local_dataset(str(get_experiment_dir()), ds_name)
147
 
148
 
149
  def get_unique_values(df, column):
 
228
 
229
 
230
  def load_eval_metrics(out_dir: str = None) -> "pd.DataFrame":
231
+ """Load eval_metrics.parquet from the selected experiment directory."""
232
  import pandas as pd
233
  if out_dir is None:
234
+ out_dir = str(get_experiment_dir() / "eval_metrics")
235
  path = os.path.join(out_dir, "eval_metrics.parquet")
236
  if os.path.exists(path):
237
  return pd.read_parquet(path)
dashboards/streamlit_app.py CHANGED
@@ -1,5 +1,7 @@
1
  import streamlit as st
 
2
  from pages import step_evolution, weights_dashboard, singular_values, cross_model, animations
 
3
 
4
  # ---- HIDE DEFAULT MULTIPAGE MENU ----
5
  hide_default_format = """
@@ -17,8 +19,15 @@ hide_default_format = """
17
  st.markdown(hide_default_format, unsafe_allow_html=True)
18
 
19
  st.sidebar.title("Transformer Weight Analysis")
20
- import os
21
- st.sidebar.write(f"SPACE_ID: {os.getenv('SPACE_ID', 'NOT SET')}")
 
 
 
 
 
 
 
22
 
23
  pages = {
24
  "Weights Dashboard": weights_dashboard,
 
1
  import streamlit as st
2
+ from pathlib import Path
3
  from pages import step_evolution, weights_dashboard, singular_values, cross_model, animations
4
+ from dashboard_utils import detect_experiments, get_data_path, is_HF_environment
5
 
6
  # ---- HIDE DEFAULT MULTIPAGE MENU ----
7
  hide_default_format = """
 
19
  st.markdown(hide_default_format, unsafe_allow_html=True)
20
 
21
  st.sidebar.title("Transformer Weight Analysis")
22
+
23
+ if not is_HF_environment():
24
+ root = Path(get_data_path())
25
+ experiments = detect_experiments(root)
26
+ if experiments:
27
+ selected = st.sidebar.selectbox("Experiment", experiments)
28
+ st.session_state["experiment"] = selected
29
+ else:
30
+ st.sidebar.warning(f"No experiments found in `{root}`.")
31
 
32
  pages = {
33
  "Weights Dashboard": weights_dashboard,