Alex W. commited on
Commit
3b8a0de
·
1 Parent(s): 8d21309

update README. include all 6 UI tabs

Browse files
Files changed (1) hide show
  1. README.md +300 -58
README.md CHANGED
@@ -9,14 +9,14 @@ python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
11
  license: apache-2.0
12
- short_description: 'Compute SVD of LLM Q/K/V weights directly from Hugging Face '
13
  ---
14
 
15
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
16
 
17
  ---
18
  # Wang's Five Laws — LLM Spectral Analyzer
19
- ## 完整项目文档 README.md
20
 
21
  ---
22
 
@@ -51,6 +51,7 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
51
  9. [关键设计决策](#9-关键设计决策)
52
  10. [部署说明](#10-部署说明)
53
  11. [依赖清单](#11-依赖清单)
 
54
 
55
  ---
56
 
@@ -90,7 +91,7 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
90
  ```
91
  ┌─────────────────────────────────────────────────────┐
92
  │ app.py │
93
- │ 主入口,组装所有 Tab
94
  │ 启动时调用 init_db() │
95
  └──────┬──────────────────────────────────────────────┘
96
  │ 调用
@@ -103,10 +104,11 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
103
  │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
104
  │ │ │ │ │
105
  │ └────────────────┼────────────────┘ │
106
- │ ┌─────────────┐
107
- │ │tab_database │─────────┘
108
- │ │数据库浏览 │
109
- │ └─────────────┘
 
110
  └──────┬──────────────────────────┬───────────────────┘
111
  │ 调用 │ 调用
112
  ▼ ▼
@@ -121,6 +123,15 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
121
  │ │ │ │
122
  │ metrics.py │ │ SQLite 文件 │
123
  │ 计算五定律 │ │ /data/wang_laws.db │
 
 
 
 
 
 
 
 
 
124
  └─────────────────┘ └─────────────────────────────┘
125
  ```
126
 
@@ -139,7 +150,7 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
139
  ```
140
  项目根目录/
141
 
142
- ├── app.py # 主入口:初始化DB,组装4个Tab
143
  ├── requirements.txt # 依赖清单
144
 
145
  ├── core/ # 计算引擎(纯Python,无副作用)
@@ -148,12 +159,15 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
148
  │ ├── debug.py # 调试输出工具(受config.DEBUG控制)
149
  │ ├── fetcher.py # HTTP Range Request 读取远程权重
150
  │ ├── layer_profile.py # 自动推断模型层结构
151
- ── metrics.py # 计算王氏五定律全部指标
 
 
 
152
 
153
  ├── db/ # 数据持久化层
154
  │ ├── __init__.py # 空文件
155
  │ ├── schema.py # 建表SQL + 数据库连接
156
- │ ├── writer.py # 写入分析结果 + 断点续传检查
157
  │ └── reader.py # 查询排行榜、模型详情、原始数据
158
 
159
  └── ui/ # Gradio 界面层
@@ -161,7 +175,9 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
161
  ├── tab_inspect.py # Tab1:模型结构探测
162
  ├── tab_analyze.py # Tab2:分析模型 + 写库
163
  ├── tab_leaderboard.py # Tab3:王氏评分排行榜
164
- ── tab_database.py # Tab4:数据库浏览
 
 
165
  ```
166
 
167
  ---
@@ -426,26 +442,43 @@ def get_db_path() -> str:
426
 
427
  #### `db/writer.py`
428
 
429
- | 函数 | 签名 | 用途 |
430
- | ---------------------- | --------------------------------------------------- | ------------------------------------------------ |
431
- | `infer_layer_type` | `(kv_shared: bool)` | `True→"global"`, `False→"standard"` |
432
- | `get_analyzed_layers` | `(conn, model_id, prefix)` | 返回已完成的层号集合(点续传用) |
433
- | `is_layer_complete` | `(conn, model_id, prefix, layer, expected_records)` | 检查某层记录数是否达到预期 |
434
- | `upsert_model` | `(conn, model_id, model_type, notes)` | 写入/更新模型元数据 |
435
- | `upsert_component` | `(conn, model_id, prefix, n_layers, ...)` | 写入/更新组件信息 |
436
- | `write_layer_records` | `(conn, model_id, records: list[dict])` | 批量写入一层的逐头数据(INSERT OR REPLACE) |
437
- | `update_model_summary` | `(conn, model_id, prefix)` | 重算并写入model_summary的all/standard/global三行 |
 
 
 
 
 
 
438
 
439
  **`update_model_summary` 逻辑:**
440
  ```
441
  对 layer_type in ["all", "standard", "global"]:
442
  从 layer_head_metrics 查对应行
443
- 计算各指标的 median/mean
444
- wang_score 统一用 standard 层的 median(ssr_QK)
 
445
  (即使写 all/global 行,wang_score 也来自 standard 层)
446
  INSERT OR REPLACE 写入 model_summary
447
  ```
448
 
 
 
 
 
 
 
 
 
 
 
449
  ---
450
 
451
  #### `db/reader.py`
@@ -501,7 +534,7 @@ def build_tab_inspect() -> (inspect_model_id, inspect_token):
501
  **函数:**
502
 
503
  ```python
504
- def run_analysis(model_id, hf_token, start_layer, end_layer, progress)
505
  -> (str, pd.DataFrame):
506
  """
507
  完整工作流程:
@@ -510,31 +543,33 @@ def run_analysis(model_id, hf_token, start_layer, end_layer, progress)
510
  1. init_db() ← 获取DB连接
511
  2. check_quantization() ← 量化检测
512
  3. 读取 config.json
513
- 4. upsert_model() 写模型元数据到DB
514
- 5. load_all_shard_headers() ← 读所有分片header
515
- 6. scan_model_structure() ← 构建LayerProfile字典
 
 
 
516
  7. upsert_component() for each prefix ← 写组件信息到DB
517
- 8. 按 start_layer~end_layer 过滤层
518
 
519
  [断点续传检查]
520
- 9. get_analyzed_layers() for each prefix
521
  → done_layers: dict[prefix, set[int]]
522
  → 打印待分析层和已跳过层
523
 
524
  [逐层分析循环]
525
  for each (prefix, layer_idx) in filtered(按prefix+layer排序):
526
- 10. 检查:layer_idx in done_layers[prefix] → continue(跳过)
527
- 11. load_tensor_remote(Q) ← HTTP Range Request
528
- 12. load_tensor_remote(K)
529
- 13. kv_shared ? W_v=W_k.clone() : load_tensor_remote(V)
530
- 14. analyze_layer(W_q, W_k, W_v, prof) ← 计算五定律
531
- 15. write_layer_records(conn, model_id, records) ← 写DB
532
- 16. update_model_summary(conn, model_id, prefix) ← 更新排行榜
533
- 17. del W_q, W_k, W_v ← 释放内存
534
 
535
  [收尾]
536
- 18. 更新 models.analyze_sec(总耗时)
537
- 19. summarize_records() ← 生成汇总文本
538
  返回:(日志文本, 逐头结果DataFrame)
539
  """
540
 
@@ -544,8 +579,8 @@ def build_tab_analyze() -> (model_id_input, token_input):
544
 
545
  **UI组件:**
546
  ```
547
- 模型ID + Token + 起始层号 + 结束层号 + 分析按钮
548
- 侧边栏:推荐模型列表 + 层号说明
549
  → 分析日志文本框(逐头详情)
550
  → 逐头结果表格(37列全指标)
551
  ```
@@ -566,10 +601,12 @@ def _format_leaderboard(df: pd.DataFrame) -> pd.DataFrame:
566
  - 选择展示列(隐藏冗余列)
567
  """
568
 
569
- def load_leaderboard(prefix_filter, layer_type) -> (pd.DataFrame, str):
570
  """
 
 
571
  调用 reader.get_leaderboard()
572
- prefix_filter 空字符串 → None(不
573
  layer_type="all" → 实际查 "standard"(排行榜默认用standard)
574
  """
575
 
@@ -600,22 +637,127 @@ def load_model_detail(model_id) -> (pd.DataFrame, str):
600
  调用 get_resume_status() for each prefix → 断点续传状态文本
601
  """
602
 
 
 
 
 
 
 
 
 
603
  def load_layer_data(model_id, prefix, layer_type, start_layer, end_layer)
604
  -> (pd.DataFrame, str):
605
  """调用 get_layer_metrics(),返回逐头原始数据"""
606
 
607
  def build_tab_database():
608
  """
609
- UI分为4个区块:
610
  1. 数据库统计(行数+文件大小)
611
  2. 已分析模型列表
612
- 3. 模型详情+断点续传状态
613
- 4. 逐头原始数据查询(支持按prefix/layer_type/层号范围过滤)
 
 
614
  """
615
  ```
616
 
617
  ---
618
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
  ### 5.4 `app.py`——主入口
620
 
621
  ```python
@@ -624,13 +766,15 @@ init_db() # 建表,幂等
624
 
625
  # Gradio Blocks
626
  with gr.Blocks(...) as demo:
627
- # 标题 + 五定律表格 + DOI徽章
628
 
629
  with gr.Tabs():
630
  inspect_model_id, inspect_token = build_tab_inspect()
631
  analyze_model_id, analyze_token = build_tab_analyze()
632
  build_tab_leaderboard()
633
  build_tab_database()
 
 
634
 
635
  # Tab1 → Tab2 联动(避免重复输入)
636
  inspect_model_id.change(fn=lambda x:x,
@@ -834,9 +978,9 @@ app.py
834
  │ ├── init_db() [db/schema.py]
835
  │ ├── check_quantization() [core/fetcher.py]
836
  │ ├── extract_config_params() [core/layer_profile.py]
837
- │ ├── upsert_model() [db/writer.py]
838
- │ ├── load_all_shard_headers() [core/fetcher.py]
839
  │ ├── scan_model_structure() [core/layer_profile.py]
 
840
  │ ├── upsert_component() [db/writer.py]
841
  │ ├── get_analyzed_layers() [db/writer.py]
842
  │ ├── load_tensor_remote() ×3 [core/fetcher.py]
@@ -851,25 +995,61 @@ app.py
851
  │ ├── write_layer_records() [db/writer.py]
852
  │ │ └── infer_layer_type()
853
  │ ├── update_model_summary() [db/writer.py]
 
854
  │ │ └── _calc_summary_row()
855
  │ └── summarize_records() [core/metrics.py]
856
 
857
  ├── build_tab_leaderboard() [ui/tab_leaderboard.py]
858
  │ └── load_leaderboard()
859
  │ ├── init_db() [db/schema.py]
 
 
860
  │ ├── get_leaderboard() [db/reader.py]
861
  │ └── _format_leaderboard()
862
 
863
- ── build_tab_database() [ui/tab_database.py]
864
- ├── load_db_stats()
865
- │ └── get_db_stats() [db/schema.py]
866
- ├── load_model_list()
867
- │ └── get_analyzed_models() [db/reader.py]
868
- ├── load_model_detail()
869
- │ ├── get_model_summary() [db/reader.py]
870
- │ └── get_resume_status() [db/reader.py]
871
- └── load_layer_data()
872
- ── get_layer_metrics() [db/reader.py]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
873
  ```
874
 
875
  ---
@@ -905,6 +1085,28 @@ Gemma-4-31B 每6层有一个全局层,V权重不存在(K和V共享)。
905
  排行榜以 `(model_id, prefix)` 为单位,
906
  多模态模型(如Gemma-4)的language_model和vision_tower分别占一行。
907
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
908
  ---
909
 
910
  ## 10. 部署说明
@@ -1004,6 +1206,8 @@ numpy # 数值计算(统计汇总)
1004
  scipy # spearman相关系数
1005
  torch # SVD分解(torch.linalg.svd)
1006
  huggingface_hub # list_repo_files(文件列表)
 
 
1007
  ```
1008
 
1009
  Python 内置(无需安装):
@@ -1015,3 +1219,41 @@ re # 正则提取层号
1015
  datetime # 时间戳
1016
  dataclasses # LayerProfile数据结构
1017
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  app_file: app.py
10
  pinned: false
11
  license: apache-2.0
12
+ short_description: 'Compute SVD of LLM Q/K/V weights directly from Hugging Face'
13
  ---
14
 
15
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
16
 
17
  ---
18
  # Wang's Five Laws — LLM Spectral Analyzer
19
+ ## 完整项目文档 README.md(6-Tab Gradio App)
20
 
21
  ---
22
 
 
51
  9. [关键设计决策](#9-关键设计决策)
52
  10. [部署说明](#10-部署说明)
53
  11. [依赖清单](#11-依赖清单)
54
+ 12. [改动历史](#12-改动历史)
55
 
56
  ---
57
 
 
91
  ```
92
  ┌─────────────────────────────────────────────────────┐
93
  │ app.py │
94
+ │ 主入口,组装所有 6 个 Tab
95
  │ 启动时调用 init_db() │
96
  └──────┬──────────────────────────────────────────────┘
97
  │ 调用
 
104
  │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
105
  │ │ │ │ │
106
  │ └────────────────┼────────────────┘ │
107
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
108
+ │ │tab_database │ tab_plot │ │ tab_tables │ │
109
+ │ │数据库浏览 │ 作图导出 │ │ 论文表格 │ │
110
+ │ └────────────┘ └─────┬────── └──────┬──────┘
111
+ │ └────────────────┼────────────────┘ │
112
  └──────┬──────────────────────────┬───────────────────┘
113
  │ 调用 │ 调用
114
  ▼ ▼
 
123
  │ │ │ │
124
  │ metrics.py │ │ SQLite 文件 │
125
  │ 计算五定律 │ │ /data/wang_laws.db │
126
+ │ │ │ │
127
+ │ plotter.py │ │ │
128
+ │ matplotlib静态图 │ │ │
129
+ │ │ │ │
130
+ │ plotter_plotly │ │ │
131
+ │ Plotly交互图 │ │ │
132
+ │ │ │ │
133
+ │ table_gen.py │ │ │
134
+ │ 论文表格生成 │ │ │
135
  └─────────────────┘ └─────────────────────────────┘
136
  ```
137
 
 
150
  ```
151
  项目根目录/
152
 
153
+ ├── app.py # 主入口:初始化DB,组装6个Tab
154
  ├── requirements.txt # 依赖清单
155
 
156
  ├── core/ # 计算引擎(纯Python,无副作用)
 
159
  │ ├── debug.py # 调试输出工具(受config.DEBUG控制)
160
  │ ├── fetcher.py # HTTP Range Request 读取远程权重
161
  │ ├── layer_profile.py # 自动推断模型层结构
162
+ ── metrics.py # 计算王氏五定律全部指标
163
+ │ ├── plotter.py # matplotlib 静态图(4×3,18×20in,300dpi)
164
+ │ ├── plotter_plotly.py # Plotly 原生交互图(12×1,全宽)
165
+ │ └── table_gen.py # 论文表格生成(6张表,LaTeX/Markdown/CSV)
166
 
167
  ├── db/ # 数据持久化层
168
  │ ├── __init__.py # 空文件
169
  │ ├── schema.py # 建表SQL + 数据库连接
170
+ │ ├── writer.py # 写入分析结果 + 断点续传 + 级联删除
171
  │ └── reader.py # 查询排行榜、模型详情、原始数据
172
 
173
  └── ui/ # Gradio 界面层
 
175
  ├── tab_inspect.py # Tab1:模型结构探测
176
  ├── tab_analyze.py # Tab2:分析模型 + 写库
177
  ├── tab_leaderboard.py # Tab3:王氏评分排行榜
178
+ ── tab_database.py # Tab4:数据库浏览 + 模型删除
179
+ ├── tab_plot.py # Tab5:作图(Plotly交互 + matplotlib导出)
180
+ └── tab_tables.py # Tab6:论文表格生成
181
  ```
182
 
183
  ---
 
442
 
443
  #### `db/writer.py`
444
 
445
+ | 函数 | 签名 | 用途 |
446
+ | ------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------- |
447
+ | `infer_layer_type` | `(kv_shared: bool)` | `True→"global"`, `False→"standard"` |
448
+ | `infer_modality` | `(prefix: str)` | 从 prefix 推模态:language/vision/audio |
449
+ | `check_write_permission` | `(admin_token: str)` | 验证 WRITE_TOKEN,返回 bool |
450
+ | `get_analyzed_layers` | `(conn, model_id, prefix)` | 返回已完成的层号集合(断点续传用) |
451
+ | `is_layer_complete` | `(conn, model_id, prefix, layer, expected_records)` | 检查某层记录数是否达到预期 |
452
+ | `upsert_model` | `(conn, model_id, model_type, notes)` | 写入/更新模型元数据 |
453
+ | `upsert_component` | `(conn, model_id, prefix, n_layers, ...)` | 写入/更新组件信息 |
454
+ | `write_layer_records` | `(conn, model_id, records: list[dict])` | 批量写入一层的逐头数据(INSERT OR REPLACE) |
455
+ | `_pseudobulk_col` | `(rows, col_name: str)` | Pseudo-bulk 两步聚合:消除 GQA 伪重复计数 |
456
+ | `_calc_summary_row` | `(rows, model_id, prefix, layer_type)` | 用 pseudo-bulk 计算单行汇总统计 |
457
+ | `update_model_summary` | `(conn, model_id, prefix)` | 重算并写入 model_summary 的 all/standard/global 三行 |
458
+ | `refresh_all_summaries` | `(conn)` | 遍历所有(model_id, prefix)重跑 update_model_summary,供 Tab3 Refresh 调用 |
459
+ | `delete_model` | `(conn, model_id, admin_token)` | 级联删除模型所有数据,需 WRITE_TOKEN 验证 |
460
 
461
  **`update_model_summary` 逻辑:**
462
  ```
463
  对 layer_type in ["all", "standard", "global"]:
464
  从 layer_head_metrics 查对应行
465
+ _pseudobulk_col() 两步聚合(先按 kv_head 组内 median,再跨组 median)
466
+ 消除 GQA 模型(如 LLaMA-3 32Q/8KV)的伪重复数偏差
467
+ wang_score 统一用 standard 层的 pseudo-bulk median(ssr_QK) 计算
468
  (即使写 all/global 行,wang_score 也来自 standard 层)
469
  INSERT OR REPLACE 写入 model_summary
470
  ```
471
 
472
+ **`delete_model` 逻辑:**
473
+ ```
474
+ 1. check_write_permission(admin_token) → 失败直接返回错误
475
+ 2. 查 models 表确认模型存在
476
+ 3. 统计各子表行数(用于返回日志)
477
+ 4. 按顺序级联删除:
478
+ layer_head_metrics → model_summary → components → models
479
+ 5. 返回 (True, 详细删除日志)
480
+ ```
481
+
482
  ---
483
 
484
  #### `db/reader.py`
 
534
  **函数:**
535
 
536
  ```python
537
+ def run_analysis(model_id, hf_token, start_layer, end_layer, admin_token, progress)
538
  -> (str, pd.DataFrame):
539
  """
540
  完整工作流程:
 
543
  1. init_db() ← 获取DB连接
544
  2. check_quantization() ← 量化检测
545
  3. 读取 config.json
546
+ 4. load_all_shard_headers() 读所有分片header
547
+ (404/网络错误 → 提前返回,DB零污染)
548
+ 5. scan_model_structure() ← 构建LayerProfile字典
549
+ 6. upsert_model() ← 写模型元数据到DB
550
+ (注意:故意在 shard headers 加载成功后才写,
551
+ 防止模型名拼写错误产生脏数据)
552
  7. upsert_component() for each prefix ← 写组件信息到DB
 
553
 
554
  [断点续传检查]
555
+ 8. get_analyzed_layers() for each prefix
556
  → done_layers: dict[prefix, set[int]]
557
  → 打印待分析层和已跳过层
558
 
559
  [逐层分析循环]
560
  for each (prefix, layer_idx) in filtered(按prefix+layer排序):
561
+ 9. 检查:layer_idx in done_layers[prefix] → continue(跳过)
562
+ 10. load_tensor_remote(Q) ← HTTP Range Request
563
+ 11. load_tensor_remote(K)
564
+ 12. kv_shared ? W_v=W_k.clone() : load_tensor_remote(V)
565
+ 13. analyze_layer(W_q, W_k, W_v, prof) ← 计算五定律
566
+ 14. write_layer_records(conn, model_id, records) ← 写DB
567
+ 15. update_model_summary(conn, model_id, prefix) ← 更新排行榜
568
+ 16. del W_q, W_k, W_v ← 释放内存
569
 
570
  [收尾]
571
+ 17. 更新 models.analyze_sec(总耗时)
572
+ 18. summarize_records() ← 生成汇总文本
573
  返回:(日志文本, 逐头结果DataFrame)
574
  """
575
 
 
579
 
580
  **UI组件:**
581
  ```
582
+ 模型ID + Token + 起始层号 + 结束层号 + Admin Write Token + 分析按钮
583
+ 侧边栏:推荐模型列表 + 层号说明 + Reviewer Note(留空token可只读分析)
584
  → 分析日志文本框(逐头详情)
585
  → 逐头结果表格(37列全指标)
586
  ```
 
601
  - 选择展示列(隐藏冗余列)
602
  """
603
 
604
+ def load_leaderboard(modality, layer_type) -> (pd.DataFrame, str):
605
  """
606
+ 调用 refresh_all_summaries(conn) 静默重算所有模型汇总
607
+ → 自动将历史数据迁移到 pseudo-bulk 聚合
608
  调用 reader.get_leaderboard()
609
+ modality 控制按模态���(language/vision/audio/all
610
  layer_type="all" → 实际查 "standard"(排行榜默认用standard)
611
  """
612
 
 
637
  调用 get_resume_status() for each prefix → 断点续传状态文本
638
  """
639
 
640
+ def run_delete_model(model_id, admin_token) -> (str, pd.DataFrame):
641
+ """
642
+ 调用 db/writer.delete_model() 执行级联删除
643
+ 需要 Admin Write Token 验证
644
+ 删除成功后自动刷新 models_table
645
+ 返回 (状态文本, 刷新后的模型列表DataFrame)
646
+ """
647
+
648
  def load_layer_data(model_id, prefix, layer_type, start_layer, end_layer)
649
  -> (pd.DataFrame, str):
650
  """调用 get_layer_metrics(),返回逐头原始数据"""
651
 
652
  def build_tab_database():
653
  """
654
+ UI分为5个区块:
655
  1. 数据库统计(行数+文件大小)
656
  2. 已分析模型列表
657
+ 3. 🗑️ 删除模型(Model ID + Admin Token + Delete按钮,variant="stop"红色警示)
658
+ 删除成功后自动刷新模型列表
659
+ 4. 模型详情+断点续传状态
660
+ 5. 逐头原始数据查询(支持按modality/layer_type/层号范围过滤)
661
  """
662
  ```
663
 
664
  ---
665
 
666
+ #### `ui/tab_plot.py` — Tab5:作图
667
+
668
+ **两条独立渲染路径(无嵌套 gr.Tabs(),用两个并排按钮区分):**
669
+
670
+ | 按钮 | 引擎 | 速度 | 输出 |
671
+ | ------------- | ------------------------ | ---- | ---------------------------- |
672
+ | ⚡ Interactive | `core/plotter_plotly.py` | ~2s | 浏览器内交互,hover/zoom |
673
+ | 🖨️ Export | `core/plotter.py` | ~30s | PNG(300dpi) + PDF + SVG 下载 |
674
+
675
+ **函数:**
676
+
677
+ ```python
678
+ def gen_single_plotly(model_id, modality, start_l, end_l, show_band) -> (go.Figure, str):
679
+ """从DB加载数据,调用 plotly_single(),返回 Plotly Figure"""
680
+
681
+ def gen_single_export(model_id, modality, start_l, end_l, show_band) -> (str, img, png, pdf, svg, zip):
682
+ """从DB加载数据,调用 plot_single_model(),保存 PNG/PDF/SVG,返回下载链接"""
683
+
684
+ def gen_compare_plotly(model_a, model_b, modality, start_l, end_l, show_band, show_delta) -> (go.Figure, str):
685
+ """双模型对比,调用 plotly_compare()"""
686
+
687
+ def gen_compare_export(model_a, model_b, modality, start_l, end_l, show_band, show_delta) -> (str, img, png, pdf, svg, zip):
688
+ """双模型对比导出,调用 plot_compare_models()"""
689
+
690
+ def build_tab_plot():
691
+ """
692
+ UI分为两个 Accordion:
693
+ 1. 📊 Single Model:选模型 → ⚡Interactive / 🖨️Export
694
+ 2. 📊 Two-Model Comparison:选A+B → ⚡Interactive / 🖨️Export + Δ填充开关
695
+ 共享控件:Modality / Start Layer / End Layer / IQR band 开关
696
+ """
697
+ ```
698
+
699
+ **12子图布局(Plotly 12×1 全宽):**
700
+ ```
701
+ 行 0:pearson_QK 定律1 谱线性对齐
702
+ 行 1:ssr_QK 定律2 谱形状保真度
703
+ 行 2:alpha_QK 定律1+2 尺度因子α
704
+ 行 3:sigma_max_Q 定律3 最大奇异值(Q)
705
+ 行 4:sigma_max_K 定律3 最大奇异值(K)
706
+ 行 5:cond_Q + cond_K 定律3 条件数κ(双线,对数坐标)
707
+ 行 6:cosU_QK 定律4 输出子空间 Q-K
708
+ 行 7:cosU_QV 定律4 输出子空间 Q-V(超正交)
709
+ 行 8:cosU_KV 定律4 输出子空间 K-V(超正交)
710
+ 行 9:cosV_QK 定律5 输入子空间 Q-K
711
+ 行10:cosV_QV 定律5 输入子空间 Q-V
712
+ 行11:cosV_KV 定律5 输入子空间 K-V
713
+ ```
714
+
715
+ ---
716
+
717
+ #### `ui/tab_tables.py` — Tab6:论文表格
718
+
719
+ **一键生成6张论文表格,数据来源:language modality + standard layers only。**
720
+
721
+ **函数:**
722
+
723
+ ```python
724
+ def generate_tables(selected_models, table2_model_a, table2_model_b, group_text)
725
+ -> (status, t1~t6 DataFrames, latex_str, md_str, csv×6, latex_file, md_file, zip):
726
+ """
727
+ 工作流程:
728
+ 1. _load_all_models(selected_models) ← 从DB读取所有选中模型数据
729
+ 2. _parse_groups(group_text) ← 解析用户定义的层组(如"0-11,12-23")
730
+ 3. generate_all_tables() ← core/table_gen.py 生成6张表
731
+ 4. format_all_latex() / format_all_markdown() ← 格式化输出
732
+ 5. 保存 CSV × 6 + .tex + .md → 打包 ZIP
733
+ 返回:所有输出供 Gradio 组件展示和下载
734
+ """
735
+
736
+ def build_tab_tables():
737
+ """
738
+ UI分为:
739
+ - 模型多选框(CheckboxGroup)+ Refresh按钮
740
+ - Table2专用:Model A / Model B 下拉 + 层组输入框
741
+ - 🚀 Generate All Tables 按钮
742
+ - 6个 Accordion,每个内含 DataFrame + CSV下载
743
+ - LaTeX / Markdown 代码框(可直接复制粘贴)
744
+ - 批量下载:.tex / .md / ZIP
745
+ """
746
+ ```
747
+
748
+ **6张表说明:**
749
+
750
+ | 表格 | 内容 | 对应定律 |
751
+ | ------- | ------------------------------------------ | --------- |
752
+ | Table 1 | 跨模型汇总:Pearson r, SSR | 定律1 & 2 |
753
+ | Table 2 | SSR 层组趋势(RL改善效果,用户自定义层组) | 定律2 |
754
+ | Table 3 | 输出子空间 cosU:Q-K, Q-V, K-V + 随机基线 | 定律4 |
755
+ | Table 4 | 输入子空间 cosV:Q-K, Q-V, K-V + 随机基线 | 定律5 |
756
+ | Table 5 | 条件数κ:全层/第0层/深层 分别统计 | 定律3 |
757
+ | Table 6 | Wang Score 排行榜(按分降序) | 定律1 & 2 |
758
+
759
+ ---
760
+
761
  ### 5.4 `app.py`——主入口
762
 
763
  ```python
 
766
 
767
  # Gradio Blocks
768
  with gr.Blocks(...) as demo:
769
+ # 标题 + 五定律表格(英中双语并排)+ DOI徽章
770
 
771
  with gr.Tabs():
772
  inspect_model_id, inspect_token = build_tab_inspect()
773
  analyze_model_id, analyze_token = build_tab_analyze()
774
  build_tab_leaderboard()
775
  build_tab_database()
776
+ build_tab_plot()
777
+ build_tab_tables()
778
 
779
  # Tab1 → Tab2 联动(避免重复输入)
780
  inspect_model_id.change(fn=lambda x:x,
 
978
  │ ├── init_db() [db/schema.py]
979
  │ ├── check_quantization() [core/fetcher.py]
980
  │ ├── extract_config_params() [core/layer_profile.py]
981
+ │ ├── load_all_shard_headers() [core/fetcher.py] ← 成功后才写DB
 
982
  │ ├── scan_model_structure() [core/layer_profile.py]
983
+ │ ├── upsert_model() [db/writer.py] ← 在此之后写入
984
  │ ├── upsert_component() [db/writer.py]
985
  │ ├── get_analyzed_layers() [db/writer.py]
986
  │ ├── load_tensor_remote() ×3 [core/fetcher.py]
 
995
  │ ├── write_layer_records() [db/writer.py]
996
  │ │ └── infer_layer_type()
997
  │ ├── update_model_summary() [db/writer.py]
998
+ │ │ ├── _pseudobulk_col()
999
  │ │ └── _calc_summary_row()
1000
  │ └── summarize_records() [core/metrics.py]
1001
 
1002
  ├── build_tab_leaderboard() [ui/tab_leaderboard.py]
1003
  │ └── load_leaderboard()
1004
  │ ├── init_db() [db/schema.py]
1005
+ │ ├── refresh_all_summaries() [db/writer.py]
1006
+ │ │ └── update_model_summary() ×N
1007
  │ ├── get_leaderboard() [db/reader.py]
1008
  │ └── _format_leaderboard()
1009
 
1010
+ ── build_tab_database() [ui/tab_database.py]
1011
+ ├── load_db_stats()
1012
+ └── get_db_stats() [db/schema.py]
1013
+ ├── load_model_list()
1014
+ └── get_analyzed_models() [db/reader.py]
1015
+ ├── run_delete_model()
1016
+ ├── delete_model() [db/writer.py]
1017
+ │ │ └── check_write_permission()
1018
+ │ │ └── load_model_list() ← 删除后自动刷新
1019
+ │ ├── load_model_detail()
1020
+ │ │ ├── get_model_summary() [db/reader.py]
1021
+ │ │ └── get_resume_status() [db/reader.py]
1022
+ │ └── load_layer_data()
1023
+ │ └── get_layer_metrics() [db/reader.py]
1024
+
1025
+ ├── build_tab_plot() [ui/tab_plot.py]
1026
+ │ ├── gen_single_plotly()
1027
+ │ │ ├── get_layer_metrics() [db/reader.py]
1028
+ │ │ └── plotly_single() [core/plotter_plotly.py]
1029
+ │ ├── gen_single_export()
1030
+ │ │ ├── get_layer_metrics() [db/reader.py]
1031
+ │ │ ├── plot_single_model() [core/plotter.py]
1032
+ │ │ └── save_figure()
1033
+ │ ├── gen_compare_plotly()
1034
+ │ │ ├── get_layer_metrics() ×2 [db/reader.py]
1035
+ │ │ └── plotly_compare() [core/plotter_plotly.py]
1036
+ │ └── gen_compare_export()
1037
+ │ ├── get_layer_metrics() ×2 [db/reader.py]
1038
+ │ ├── plot_compare_models() [core/plotter.py]
1039
+ │ └── save_figure()
1040
+
1041
+ └── build_tab_tables() [ui/tab_tables.py]
1042
+ └── generate_tables()
1043
+ ├── get_layer_metrics() ×N [db/reader.py]
1044
+ ├── generate_all_tables() [core/table_gen.py]
1045
+ │ ├── make_table1()
1046
+ │ ├── make_table2()
1047
+ │ ├── make_table3()
1048
+ │ ├── make_table4()
1049
+ │ ├── make_table5()
1050
+ │ └── make_table6()
1051
+ ├── format_all_latex()
1052
+ └── format_all_markdown()
1053
  ```
1054
 
1055
  ---
 
1085
  排行榜以 `(model_id, prefix)` 为单位,
1086
  多模态模型(如Gemma-4)的language_model和vision_tower分别占一行。
1087
 
1088
+ ### 防脏数据写入(Lazy Write)
1089
+ `upsert_model()` 和 `upsert_component()` 故意推迟到 `load_all_shard_headers()` 成功之后才调用。
1090
+ 模型名拼写错误(如 "Meta-Llama-3-70B-intruct" 少一个s)会在 HF 返回 404 时提前 return,
1091
+ DB 中零污染。旧版本在量化检测通过后立即写入,会留下只有名字没有数据的孤立行,污染 Tab4/5/6。
1092
+
1093
+ ### Pseudo-bulk 两步聚合(GQA 伪重复问题)
1094
+ GQA 模型(如 LLaMA-3-8B 32Q/8KV)中,同一 KV head 下的多个 Q head 共享同一 K,
1095
+ 彼此强相关。若直接对所有头做 median,等价于对 KV head 的指标重复计数 group 次(伪重复)。
1096
+ 标准做法(Nature Comms 2021):
1097
+ ```
1098
+ Step 1: groupby(layer, kv_head).median() → 每KV head一个值,消除组内相关
1099
+ Step 2: 对Step1结果做 median/mean → 每层一个无偏代表值
1100
+ ```
1101
+ 实现在 `db/writer._pseudobulk_col()` 和 `core/plotter._aggregate_by_layer()`。
1102
+ Tab3 Refresh 按钮触发 `refresh_all_summaries()` 自动将历史数据重算为 pseudo-bulk。
1103
+
1104
+ ### 级联删除与写入权限统一验证
1105
+ `delete_model()` 和所有写库操作均通过同一个 `check_write_permission(admin_token)` 验证,
1106
+ 后者对比环境变量 `WRITE_TOKEN`(HF Space Secrets 注入)。
1107
+ 删除顺序严格遵循外键依赖:`layer_head_metrics → model_summary → components → models`。
1108
+ 删除后返回各表实际删除行数,便于审计确认。
1109
+
1110
  ---
1111
 
1112
  ## 10. 部署说明
 
1206
  scipy # spearman相关系数
1207
  torch # SVD分解(torch.linalg.svd)
1208
  huggingface_hub # list_repo_files(文件列表)
1209
+ matplotlib # 静态图导出(PNG/PDF/SVG,300dpi,论文级)
1210
+ plotly # 交互图(12×1全宽,浏览器内hover/zoom)
1211
  ```
1212
 
1213
  Python 内置(无需安装):
 
1219
  datetime # 时间戳
1220
  dataclasses # LayerProfile数据结构
1221
  ```
1222
+
1223
+ ---
1224
+
1225
+ ## 12. 改动历史
1226
+
1227
+ ### v0.1 — 初始版本(Tab1~4)
1228
+ - Tab1 Inspect:模型结构探测,零下载,仅读 safetensors header
1229
+ - Tab2 Analyze:HTTP Range Request 逐层分析,写库,断点续传
1230
+ - Tab3 Leaderboard:Wang Score 排行榜
1231
+ - Tab4 Database:数据库浏览,逐头原始数据查询
1232
+
1233
+ ### v0.2 — 作图与论文表格(Tab5~6)
1234
+ - 新增 `core/plotter.py`:matplotlib 4×3 静态图,18×20in @ 300dpi
1235
+ - 新增 `core/plotter_plotly.py`:原生 Plotly 12×1 交互图,全宽自适应
1236
+ - 新增 `ui/tab_plot.py`(Tab5):两条渲染路径(⚡Interactive / 🖨️Export)
1237
+ - 新增 `core/table_gen.py`:6张论文表格生成(LaTeX/Markdown/CSV)
1238
+ - 新增 `ui/tab_tables.py`(Tab6):一键生成,批量下载
1239
+ - `app.py` 双语改造:英文在左,中文在右,两列并排表格
1240
+
1241
+ ### v0.3 — Pseudo-bulk 聚合 + 防脏数据 + 级联删除
1242
+ **问题修复:**
1243
+ - `ui/tab_analyze.py`:`upsert_model` / `upsert_component` 推迟到 `load_all_shard_headers()` 成功后执行,防止模型名拼错产生脏数据
1244
+
1245
+ **新功能:**
1246
+ - `db/writer.py`:新增 `_pseudobulk_col()`,`update_model_summary()` 改用 pseudo-bulk 两步聚合,消除 GQA 伪重复计数偏差
1247
+ - `db/writer.py`:新增 `refresh_all_summaries()`,Tab3 Refresh 按钮触发,自动将历史数据重算为 pseudo-bulk
1248
+ - `db/writer.py`:新增 `delete_model(conn, model_id, admin_token)`,级联删除模型所有数据,需 WRITE_TOKEN 验证
1249
+ - `ui/tab_database.py`:新增 🗑️ Delete Model 区块,删除成功后自动刷新模型列表
1250
+ - `ui/tab_inspect.py`:全文翻译为英文,逻辑不变
1251
+
1252
+ **改动文件汇总:**
1253
+
1254
+ | 文件 | 类型 | 说明 |
1255
+ | -------------------- | ---- | ------------------------------------------------------- |
1256
+ | `db/writer.py` | 更新 | 新增 pseudo-bulk / refresh_all_summaries / delete_model |
1257
+ | `ui/tab_analyze.py` | 修复 | upsert_model 推迟到 shard headers 加载成功后 |
1258
+ | `ui/tab_database.py` | 更新 | 新增删除模型 UI |
1259
+ | `ui/tab_inspect.py` | 重构 | 全文英文化 |