diff --git a/tests/analytical_visualization/app.py b/tests/analytical_visualization/app.py index 3e63c6e..1bb85bc 100644 --- a/tests/analytical_visualization/app.py +++ b/tests/analytical_visualization/app.py @@ -1058,6 +1058,52 @@ with tab_layout: }) st.dataframe(pd.DataFrame(rows), width='stretch', hide_index=True) + # Row G: All tensor shapes (per PE) — one collapsed expander with + # weight, KV, and per-stage shapes for anyone who wants them. + with st.expander("All tensor shapes (per PE)", expanded=False): + st.caption( + "Every tensor this deployment holds, per PE. Weights + KV " + "show global vs per-PE shape (one row per tensor, per layer). " + "Per-stage shapes show input / weight / output tensors each " + "stage of the forward pass operates on." + ) + _lp = (model.layers + topo.pp - 1) // topo.pp + + st.markdown(f"**Attention weights** (per layer, layers/stage = {_lp})") + _attn_rows_layout = attention_weight_rows(cfg) + _display_attn_layout = [ + {k: v for k, v in r.items() if not k.startswith("_")} + for r in _attn_rows_layout + ] + st.dataframe(pd.DataFrame(_display_attn_layout), + width='stretch', hide_index=True) + + st.markdown(f"**FFN weights** (per layer, layers/stage = {_lp})") + _ffn_rows_layout = ffn_weight_rows(cfg) + _display_ffn_layout = [ + {k: v for k, v in r.items() if not k.startswith("_")} + for r in _ffn_rows_layout + ] + st.dataframe(pd.DataFrame(_display_ffn_layout), + width='stretch', hide_index=True) + + st.markdown(f"**KV cache** (per layer, S_kv = {s_kv:,})") + _kv_rows_layout = kv_cache_rows(cfg) + _display_kv_layout = [ + {k: v for k, v in r.items() if not k.startswith("_")} + for r in _kv_rows_layout + ] + st.dataframe(pd.DataFrame(_display_kv_layout), + width='stretch', hide_index=True) + + st.markdown("**Per-stage attention shapes**") + st.dataframe(pd.DataFrame(attn_stage_shape_rows(cfg)), + width='stretch', hide_index=True) + + st.markdown("**Per-stage FFN shapes**") + st.dataframe(pd.DataFrame(ffn_stage_shape_rows(cfg)), + width='stretch', hide_index=True) + # ── TAB 2: Memory breakdown ───────────────────────────────────── with tab_memory: @@ -1227,22 +1273,6 @@ with tab_stages: st.pyplot(fig_bar, width='stretch') plt.close(fig_bar) - # ── Per-stage tensor shapes (per PE) ─────────────────────────── - st.divider() - st.subheader("Per-stage tensor shapes (per PE)") - st.caption( - "Input / weight / output tensor dimensions each stage operates on, " - "*per PE* (already sharded by CP, TP, PP, EP as configured). Rows " - "match the latency table above; comm-only stages show payload in " - "the Input column." - ) - st.markdown("**Attention stages**") - st.dataframe(pd.DataFrame(attn_stage_shape_rows(cfg)), - width='stretch', hide_index=True) - st.markdown("**FFN stages**") - st.dataframe(pd.DataFrame(ffn_stage_shape_rows(cfg)), - width='stretch', hide_index=True) - # ── TAB 4: Save & compare ─────────────────────────────────────── def _snapshot_current_config(): diff --git a/tests/analytical_visualization/test_stage_shapes.py b/tests/analytical_visualization/test_stage_shapes.py index aeae835..338e1c5 100644 --- a/tests/analytical_visualization/test_stage_shapes.py +++ b/tests/analytical_visualization/test_stage_shapes.py @@ -113,3 +113,42 @@ def test_ffn_gate_up_output_matches_ffn_per_pe(): rows = ffn_stage_shape_rows(cfg) f2 = next(r for r in rows if r["Stage"] == "F2") assert str(ffn_pe) in f2["Output (per PE)"], (ffn_pe, f2) + + +# ── App wiring: consolidated shapes section lives on layout tab ─── + + +def test_shapes_section_is_on_physical_layout_tab(): + """The 'All tensor shapes (per PE)' expander must live inside + tab_layout (Physical layout), not tab_stages. Guards the move so + the section doesn't accidentally end up back on the latency tab. + """ + from pathlib import Path + app_path = ( + Path(__file__).parent / "app.py" + ).resolve() + src = app_path.read_text(encoding="utf-8") + # Section appears exactly once. + assert src.count('"All tensor shapes (per PE)"') == 1, src.count( + '"All tensor shapes (per PE)"' + ) + # And it sits inside tab_layout, before tab_memory begins. + section_pos = src.index('"All tensor shapes (per PE)"') + tab_memory_pos = src.index("with tab_memory:") + tab_stages_pos = src.index("with tab_stages:") + assert section_pos < tab_memory_pos, ( + "shapes section must be inside tab_layout (before tab_memory)" + ) + assert section_pos < tab_stages_pos, ( + "shapes section must be inside tab_layout (before tab_stages)" + ) + # The old per-stage shape rendering under tab_stages must be gone — + # no attn_stage_shape_rows / ffn_stage_shape_rows call may sit + # between 'with tab_stages:' and the next 'with tab_' block. + stages_block = src[tab_stages_pos:src.index("# ── TAB", tab_stages_pos + 1)] + assert "attn_stage_shape_rows(cfg)" not in stages_block, ( + "per-stage shape table must not remain on Per-stage latency tab" + ) + assert "ffn_stage_shape_rows(cfg)" not in stages_block, ( + "per-stage FFN shape table must not remain on Per-stage latency tab" + )