Spaces:
Running
Running
Alex W. commited on
Commit ·
6f797b1
1
Parent(s): 88f2eb5
add README for project.
Browse files
README.md
CHANGED
|
@@ -13,3 +13,1005 @@ 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
|
| 23 |
+
|
| 24 |
+
# 🔬 Wang's Five Laws — LLM Spectral Analyzer
|
| 25 |
+
|
| 26 |
+
**静态分析 LLM 注意力权重,无需推理,无需 benchmark,直接评估推理能力。**
|
| 27 |
+
|
| 28 |
+
通过对 Q/K/V 权重矩阵做 SVD 分解,验证王氏五定律,
|
| 29 |
+
计算 Wang Score(= 1 − median SSR_QK),实现跨模型推理能力排行。
|
| 30 |
+
|
| 31 |
+
[](https://doi.org/10.5281/zenodo.19707844)
|
| 32 |
+
[](https://hal.science/hal-05609398)
|
| 33 |
+
[](https://github.com/emis-framework/math-under-llm)
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
## 目录
|
| 38 |
+
|
| 39 |
+
1. [项目背景](#1-项目背景)
|
| 40 |
+
2. [王氏五定律速查](#2-王氏五定律速查)
|
| 41 |
+
3. [整体架构](#3-整体架构)
|
| 42 |
+
4. [目录结构](#4-目录结构)
|
| 43 |
+
5. [各层详细说明](#5-各层详细说明)
|
| 44 |
+
- 5.1 [core 层](#51-core-层——计算引擎)
|
| 45 |
+
- 5.2 [db 层](#52-db-层——数据持久化)
|
| 46 |
+
- 5.3 [ui 层](#53-ui-层——用户界面)
|
| 47 |
+
- 5.4 [app.py 入口](#54-apppy——主入口)
|
| 48 |
+
6. [数据库表结构](#6-数据库表结构)
|
| 49 |
+
7. [数据流全链路](#7-数据流全链路)
|
| 50 |
+
8. [函数调用关系图](#8-函数调用关系图)
|
| 51 |
+
9. [关键设计决策](#9-关键设计决策)
|
| 52 |
+
10. [部署说明](#10-部署说明)
|
| 53 |
+
11. [依赖清单](#11-依赖清单)
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## 1. 项目背景
|
| 58 |
+
|
| 59 |
+
传统评估 LLM 推理能力需要跑 benchmark(耗时、昂贵、可刷榜)。
|
| 60 |
+
|
| 61 |
+
本项目发现:**只看权重矩阵的奇异值分解(SVD)结构,
|
| 62 |
+
就能静态评估模型推理质量**,无需任何推理。
|
| 63 |
+
|
| 64 |
+
核心原理:
|
| 65 |
+
- 对每一层注意力的 Q、K、V 权重矩阵做 SVD
|
| 66 |
+
- 计算奇异值谱之间的相关性、形状残差、子空间对齐度
|
| 67 |
+
- 这些指标与模型推理能力高度相关(经多个模型验证)
|
| 68 |
+
|
| 69 |
+
**运行方式**:HTTP Range Request 直接读取 HuggingFace 远程权重,
|
| 70 |
+
无需下载整个模型(一个 14B 模型只需读取约 200MB 数据而非 28GB)。
|
| 71 |
+
|
| 72 |
+
---
|
| 73 |
+
|
| 74 |
+
## 2. 王氏五定律速查
|
| 75 |
+
|
| 76 |
+
| 定律 | 名称 | 公式 | 理论极值 | 实测范围 |
|
| 77 |
+
| -------- | ------------------ | ----------------------------------- | ---------- | ------------ |
|
| 78 |
+
| 第一定律 | 谱线性对齐 | Pearson r(s_Q, s_K) | → 1 | 0.94~0.99 |
|
| 79 |
+
| 第二定律 | 谱形状残差 | SSR = mean\|ŝ_Q − ŝ_K\| | → 0 | 0.006~0.016 |
|
| 80 |
+
| 第三定律 | 精度-深度约束 | L_max = min(L_info, L_quant, L_dyn) | 由精度决定 | FP16→16层 |
|
| 81 |
+
| 第四定律 | 输出子空间解耦 | cosU(U_Q,U_V) < 1/√d_head | 超正交 | ~20%低于随机 |
|
| 82 |
+
| 第五定律 | 输入子空间随机正交 | cosV ≈ 1/√d_model | ≈随机基线 | 符合理论 |
|
| 83 |
+
|
| 84 |
+
**Wang Score = 1 − median(SSR_QK)**(越高越好,理论极值=1)
|
| 85 |
+
|
| 86 |
+
---
|
| 87 |
+
|
| 88 |
+
## 3. 整体架构
|
| 89 |
+
|
| 90 |
+
```
|
| 91 |
+
┌─────────────────────────────────────────────────────┐
|
| 92 |
+
│ app.py │
|
| 93 |
+
│ 主入口,组装所有 Tab │
|
| 94 |
+
│ 启动时调用 init_db() │
|
| 95 |
+
└──────┬──────────────────────────────────────────────┘
|
| 96 |
+
│ 调用
|
| 97 |
+
▼
|
| 98 |
+
┌──────────────────────────────────────────────────────┐
|
| 99 |
+
│ ui/ 层 │
|
| 100 |
+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
| 101 |
+
│ │tab_inspect │ │tab_analyze │ │tab_leaderbd │ │
|
| 102 |
+
│ │结构探测 │ │分析+写库 │ │排行榜 │ │
|
| 103 |
+
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
| 104 |
+
│ │ │ │ │
|
| 105 |
+
│ └────────────────┼────────────────┘ │
|
| 106 |
+
│ ┌─────────────┐ │ │
|
| 107 |
+
│ │tab_database │─────────┘ │
|
| 108 |
+
│ │数据库浏览 │ │
|
| 109 |
+
│ └─────────────┘ │
|
| 110 |
+
└─────��┬──────────────────────────┬───────────────────┘
|
| 111 |
+
│ 调用 │ 调用
|
| 112 |
+
▼ ▼
|
| 113 |
+
┌─────────────────┐ ┌─────────────────────────────┐
|
| 114 |
+
│ core/ 层 │ │ db/ 层 │
|
| 115 |
+
│ │ │ │
|
| 116 |
+
│ fetcher.py │ │ schema.py writer.py │
|
| 117 |
+
│ 远程读取权重 │ │ 建表 写入数据 │
|
| 118 |
+
│ │ │ │
|
| 119 |
+
│ layer_profile.py│ │ reader.py │
|
| 120 |
+
│ 推断层结构 │ │ 查询数据 │
|
| 121 |
+
│ │ │ │
|
| 122 |
+
│ metrics.py │ │ SQLite 文件 │
|
| 123 |
+
│ 计算五定律 │ │ /data/wang_laws.db │
|
| 124 |
+
└─────────────────┘ └─────────────────────────────┘
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
**三层职责:**
|
| 128 |
+
|
| 129 |
+
| 层 | 职责 | 不做什么 |
|
| 130 |
+
| ------- | --------------------------- | ---------------------- |
|
| 131 |
+
| `core/` | 纯计算,无 UI,无 DB | 不写数据库,不渲染界面 |
|
| 132 |
+
| `db/` | 纯数据库操作 | 不做计算,不渲染界面 |
|
| 133 |
+
| `ui/` | 纯界面逻辑,调用 core 和 db | 不做底层计算 |
|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
## 4. 目录结构
|
| 138 |
+
|
| 139 |
+
```
|
| 140 |
+
项目根目录/
|
| 141 |
+
│
|
| 142 |
+
├── app.py # 主入口:初始化DB,组装4个Tab
|
| 143 |
+
├── requirements.txt # 依赖清单
|
| 144 |
+
│
|
| 145 |
+
├── core/ # 计算引擎(纯Python,无副作用)
|
| 146 |
+
│ ├── __init__.py # 空文件
|
| 147 |
+
│ ├── config.py # 全局开关(DEBUG=True/False)
|
| 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 界面层
|
| 160 |
+
├── __init__.py # 空文件
|
| 161 |
+
├── tab_inspect.py # Tab1:模型结构探测
|
| 162 |
+
├── tab_analyze.py # Tab2:分析模型 + 写库
|
| 163 |
+
├── tab_leaderboard.py # Tab3:王氏评分排行榜
|
| 164 |
+
└── tab_database.py # Tab4:数据库浏览
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
---
|
| 168 |
+
|
| 169 |
+
## 5. 各层详细说明
|
| 170 |
+
|
| 171 |
+
### 5.1 `core/` 层——计算引擎
|
| 172 |
+
|
| 173 |
+
#### `core/config.py`
|
| 174 |
+
|
| 175 |
+
```python
|
| 176 |
+
DEBUG = False # True → 打印详细调试信息;False → 静默运行
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
全局唯一开关。所有调试输出都受这个控制。
|
| 180 |
+
|
| 181 |
+
---
|
| 182 |
+
|
| 183 |
+
#### `core/debug.py`
|
| 184 |
+
|
| 185 |
+
| 函数 | 签名 | 用途 |
|
| 186 |
+
| -------- | ------------------------------ | -------------------------------------- |
|
| 187 |
+
| `dlog` | `(lines: list[str], msg: str)` | 向日志列表追加调试信息(仅DEBUG=True) |
|
| 188 |
+
| `dprint` | `(msg: str)` | 打印到stdout(仅DEBUG=True) |
|
| 189 |
+
|
| 190 |
+
`dlog` 用于 `metrics.py` 和 `tab_analyze.py`(有 lines 列表的地方)。
|
| 191 |
+
`dprint` 用于 `fetcher.py`(没有 lines 列表的地方)。
|
| 192 |
+
|
| 193 |
+
---
|
| 194 |
+
|
| 195 |
+
#### `core/fetcher.py`
|
| 196 |
+
|
| 197 |
+
**核心思想**:safetensors 文件头记录了每个 tensor 的字节偏移,
|
| 198 |
+
用 HTTP Range Request 只下载需要的字节,无需下载整个文件。
|
| 199 |
+
|
| 200 |
+
| 函数 | 签名 | 返回 | 用途 |
|
| 201 |
+
| ------------------------- | ------------------------------------------------ | -------------------------------- | -------------------------- |
|
| 202 |
+
| `get_file_url` | `(model_id, filename)` | `str` | 拼接 HF 下载 URL |
|
| 203 |
+
| `read_safetensors_header` | `(url, token)` | `(header_dict, header_size)` | 读取文件头(两次HTTP请求) |
|
| 204 |
+
| `load_tensor_remote` | `(url, tensor_name, header, header_size, token)` | `torch.Tensor` | 按名读取单个tensor |
|
| 205 |
+
| `get_safetensor_files` | `(model_id, token)` | `list[str]` | 列出所有.safetensors文件 |
|
| 206 |
+
| `find_index_file` | `(model_id, token)` | `dict\|None` | 读取分片索引文件 |
|
| 207 |
+
| `get_all_shard_files` | `(model_id, token)` | `list[str]` | 获取全部分片文件名 |
|
| 208 |
+
| `load_all_shard_headers` | `(model_id, token)` | `dict[filename, (header, size)]` | 读取所有分片的header |
|
| 209 |
+
| `check_quantization` | `(model_id, token)` | `(is_blocked, message)` | 三重量化检测 |
|
| 210 |
+
| `http_error_msg` | `(e, model_id)` | `str` | HTTP错误码转中文提示 |
|
| 211 |
+
|
| 212 |
+
**`load_all_shard_headers` 返回结构:**
|
| 213 |
+
```python
|
| 214 |
+
{
|
| 215 |
+
"model-00001-of-00006.safetensors": (header_dict, header_size),
|
| 216 |
+
"model-00002-of-00006.safetensors": (header_dict, header_size),
|
| 217 |
+
...
|
| 218 |
+
}
|
| 219 |
+
# header_dict 结构:
|
| 220 |
+
{
|
| 221 |
+
"model.layers.0.self_attn.q_proj.weight": {
|
| 222 |
+
"dtype": "BF16",
|
| 223 |
+
"shape": [4096, 4096],
|
| 224 |
+
"data_offsets": [0, 33554432]
|
| 225 |
+
},
|
| 226 |
+
...
|
| 227 |
+
}
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
**量化检测四重逻辑(按顺序):**
|
| 231 |
+
1. 检测 `config.json` 中的 `quantization_config` 字段
|
| 232 |
+
2. 检测模型名是否含 `gptq/awq/gguf` 关键词
|
| 233 |
+
3. 检测文件列表是否有 `.gguf` 文件
|
| 234 |
+
4. 检测 header 中是否有量化专用 key(如 `qweight`, `qzeros`)
|
| 235 |
+
|
| 236 |
+
---
|
| 237 |
+
|
| 238 |
+
#### `core/layer_profile.py`
|
| 239 |
+
|
| 240 |
+
**核心思想**:从权重文件的 key 名自动推断模型结构,零 hard coding,
|
| 241 |
+
不依赖模型名称或配置文件(配置文件只是辅助参考)。
|
| 242 |
+
|
| 243 |
+
**关键数据结构:**
|
| 244 |
+
|
| 245 |
+
```python
|
| 246 |
+
@dataclass
|
| 247 |
+
class QKVKey:
|
| 248 |
+
shard: str # 所在分片文件名,如 "model-00001-of-00006.safetensors"
|
| 249 |
+
key: str # 完整tensor名,如 "model.layers.0.self_attn.q_proj.weight"
|
| 250 |
+
shape: list # tensor形状,如 [4096, 4096]
|
| 251 |
+
|
| 252 |
+
@dataclass
|
| 253 |
+
class LayerProfile:
|
| 254 |
+
prefix: str # 组件前缀,如 "model.language_model."
|
| 255 |
+
layer_idx: int # 层号(原始safetensors key中的N)
|
| 256 |
+
q: QKVKey # Q权重位置
|
| 257 |
+
k: QKVKey # K权重位置
|
| 258 |
+
v: QKVKey|None # V权重位置(None表示K=V共享)
|
| 259 |
+
head_dim: int # 每个head的维度
|
| 260 |
+
n_q_heads: int # Q head数量
|
| 261 |
+
n_kv_heads: int # KV head数量(GQA时 < n_q_heads)
|
| 262 |
+
d_model: int # 模型隐层维度(= q_shape[1])
|
| 263 |
+
kv_shared: bool # True = K和V共享(如Gemma全局层)
|
| 264 |
+
complete: bool # True = Q/K都存在且head_dim推断成功
|
| 265 |
+
infer_ok: bool # head_dim推断是否成功
|
| 266 |
+
head_dim_source: str # 推断来源:"k_norm"/"q_norm"/"config"/"enum"
|
| 267 |
+
```
|
| 268 |
+
|
| 269 |
+
| 函数 | 签名 | 返回 | 用途 |
|
| 270 |
+
| ----------------------- | ------------------------------------ | ------------------------------------ | --------------------------------------------------- |
|
| 271 |
+
| `classify_qkv_suffix` | `(suffix: str)` | `'q'/'k'/'v'/None` | 从key后缀判断是Q/K/V |
|
| 272 |
+
| `is_norm_key` | `(suffix: str)` | `bool` | 判断是否为norm key(辅助推断head_dim) |
|
| 273 |
+
| `scan_model_structure` | `(all_shard_headers, config_params)` | `dict[(prefix,layer), LayerProfile]` | **核心函数**:扫描全部headers,构建LayerProfile字典 |
|
| 274 |
+
| `summarize_structure` | `(profiles)` | `str` | 生成人类可读的结构报告(Tab1使用) |
|
| 275 |
+
| `extract_config_params` | `(config: dict)` | `dict` | 从config.json提取关键参数(兼容Gemma4嵌套结构) |
|
| 276 |
+
|
| 277 |
+
**`scan_model_structure` 工作流程:**
|
| 278 |
+
```
|
| 279 |
+
第一遍扫描:遍历所有shard的所有key
|
| 280 |
+
→ 用正则 r'layers\.(\d+)\.' 提取层号
|
| 281 |
+
→ prefix = key的layers.N.之前部分
|
| 282 |
+
→ suffix = key的layers.N.之后部分
|
| 283 |
+
→ classify_qkv_suffix(suffix) → 归类为Q/K/V
|
| 284 |
+
→ is_norm_key(suffix) → 收集k_norm/q_norm形状(辅助推断head_dim)
|
| 285 |
+
|
| 286 |
+
第二遍构建:对每个(prefix, layer_idx)槽
|
| 287 |
+
→ 检查Q/K是否都存在(必要条件)
|
| 288 |
+
→ V不存在 → kv_shared=True
|
| 289 |
+
→ _infer_head_dim() → 推断head_dim(5个优先级)
|
| 290 |
+
→ 计算n_q_heads, n_kv_heads, d_model
|
| 291 |
+
→ 构建LayerProfile
|
| 292 |
+
```
|
| 293 |
+
|
| 294 |
+
**head_dim 推断优先级:**
|
| 295 |
+
```
|
| 296 |
+
1. k_norm.shape[0] ← 最可靠(Gemma系列有这个)
|
| 297 |
+
2. q_norm.shape[0] ← 备用
|
| 298 |
+
3. config["head_dim"] ← config.json直接给出
|
| 299 |
+
4. config["hidden_size"] / config["num_attention_heads"] ← 计算得出
|
| 300 |
+
5. 枚举候选值 [512,256,128,96,80,64,48,40,32,16] ← 最后手段
|
| 301 |
+
```
|
| 302 |
+
|
| 303 |
+
**KV共享检测(Gemma全局层):**
|
| 304 |
+
```
|
| 305 |
+
V的key不存在于任何shard header → kv_shared=True → layer_type="global"
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
---
|
| 309 |
+
|
| 310 |
+
#### `core/metrics.py`
|
| 311 |
+
|
| 312 |
+
对一层的Q/K/V权重矩阵计算所有指标。
|
| 313 |
+
|
| 314 |
+
**底层计算函数:**
|
| 315 |
+
|
| 316 |
+
| 函数 | 签名 | 返回 | 对应定律 |
|
| 317 |
+
| ------------- | ---------------------- | ------------------- | ---------------------- |
|
| 318 |
+
| `pearson` | `(a, b: Tensor)` | `float` | 第一定律 |
|
| 319 |
+
| `spearman_r` | `(a, b: Tensor)` | `float` | 第一定律(补充) |
|
| 320 |
+
| `ssr` | `(a, b: Tensor)` | `float` | 第二定律 |
|
| 321 |
+
| `svr` | `(a, b: Tensor)` | `(alpha, residual)` | 尺度因子 |
|
| 322 |
+
| `cos_U` | `(U_a, U_b: Tensor)` | `float` | 第四定律(左奇异向量) |
|
| 323 |
+
| `cos_V` | `(Vt_a, Vt_b: Tensor)` | `float` | 第五定律(右奇异向量) |
|
| 324 |
+
| `sigma_stats` | `(s: Tensor)` | `(max, min, cond)` | 第三定律 |
|
| 325 |
+
|
| 326 |
+
**`ssr` 计算细节:**
|
| 327 |
+
```python
|
| 328 |
+
# 归一化后逐元素绝对差的均值
|
| 329 |
+
n = min(len(a), len(b))
|
| 330 |
+
an = a[:n] / ||a[:n]|| # L2归一化
|
| 331 |
+
bn = b[:n] / ||b[:n]||
|
| 332 |
+
SSR = mean(|an - bn|)
|
| 333 |
+
```
|
| 334 |
+
|
| 335 |
+
**主分析函数:**
|
| 336 |
+
|
| 337 |
+
```python
|
| 338 |
+
def analyze_layer(
|
| 339 |
+
W_q: torch.Tensor, # shape: [n_q_heads * head_dim, d_model]
|
| 340 |
+
W_k: torch.Tensor, # shape: [n_kv_heads * head_dim, d_model]
|
| 341 |
+
W_v: torch.Tensor, # shape: [n_kv_heads * head_dim, d_model]
|
| 342 |
+
profile: LayerProfile,
|
| 343 |
+
) -> tuple[list[dict], str]:
|
| 344 |
+
# 返回:(records列表, 格式化日志字符串)
|
| 345 |
+
```
|
| 346 |
+
|
| 347 |
+
**`analyze_layer` 工作流程:**
|
| 348 |
+
```
|
| 349 |
+
对每个 kv_head(0 ~ n_kv_heads-1):
|
| 350 |
+
切片:k_t = W_k[kv_h*d_head : (kv_h+1)*d_head, :]
|
| 351 |
+
SVD:U_k, s_k, Vt_k = svd(k_t)
|
| 352 |
+
计算 sigma_stats(s_k)
|
| 353 |
+
|
| 354 |
+
对每个 q_head(属于这个kv_head的group,GQA时 group = n_q/n_kv):
|
| 355 |
+
切片:q_t = W_q[h*d_head : (h+1)*d_head, :]
|
| 356 |
+
SVD:U_q, s_q, Vt_q = svd(q_t)
|
| 357 |
+
|
| 358 |
+
计算所有指标:
|
| 359 |
+
pearson_QK, spearman_QK, ssr_QK, alpha_QK ← Q vs K奇异值
|
| 360 |
+
pearson_QV, ssr_QV, alpha_QV ← Q vs V奇异值
|
| 361 |
+
ssr_KV, alpha_KV ← K vs V奇异值
|
| 362 |
+
cosU_QK, cosU_QV, cosU_KV ← 左奇异向量
|
| 363 |
+
cosV_QK, cosV_QV, cosV_KV ← 右奇异向量
|
| 364 |
+
sigma_max/min/cond for Q, K, V
|
| 365 |
+
|
| 366 |
+
append到records
|
| 367 |
+
|
| 368 |
+
特殊处理:kv_shared=True时,KV指标设为理论值(ssr=0, pearson=1, cosU=1等)
|
| 369 |
+
```
|
| 370 |
+
|
| 371 |
+
**`records` 每条记录的字段(共37个字段):**
|
| 372 |
+
```python
|
| 373 |
+
{
|
| 374 |
+
"prefix": str, "layer": int,
|
| 375 |
+
"kv_head": int, "q_head": int,
|
| 376 |
+
"kv_shared": bool, "head_dim": int,
|
| 377 |
+
"d_model": int, "n_q_heads": int, "n_kv_heads": int,
|
| 378 |
+
# 第一定律
|
| 379 |
+
"pearson_QK": float, "spearman_QK": float,
|
| 380 |
+
"pearson_QV": float, "pearson_KV": float,
|
| 381 |
+
# 第二定律
|
| 382 |
+
"ssr_QK": float, "ssr_QV": float, "ssr_KV": float,
|
| 383 |
+
# 第三定律
|
| 384 |
+
"sigma_max_Q": float, "sigma_min_Q": float, "cond_Q": float,
|
| 385 |
+
"sigma_max_K": float, "sigma_min_K": float, "cond_K": float,
|
| 386 |
+
"sigma_max_V": float, "sigma_min_V": float, "cond_V": float,
|
| 387 |
+
# 第四定律
|
| 388 |
+
"cosU_QK": float, "cosU_QV": float, "cosU_KV": float,
|
| 389 |
+
# 第五定律
|
| 390 |
+
"cosV_QK": float, "cosV_QV": float, "cosV_KV": float,
|
| 391 |
+
# 尺度因子
|
| 392 |
+
"alpha_QK": float, "alpha_QV": float, "alpha_KV": float,
|
| 393 |
+
"alpha_res_QK": float, "alpha_res_QV": float, "alpha_res_KV": float,
|
| 394 |
+
}
|
| 395 |
+
```
|
| 396 |
+
|
| 397 |
+
```python
|
| 398 |
+
def summarize_records(records: list[dict], model_id: str) -> str:
|
| 399 |
+
# 对records做统计汇总,返回格式化文本
|
| 400 |
+
# 按prefix分组,对每个指标计算 Median/Mean/Min/Max
|
| 401 |
+
# KV指标自动排除kv_shared=True的行(避免理论值污染统计)
|
| 402 |
+
```
|
| 403 |
+
|
| 404 |
+
---
|
| 405 |
+
|
| 406 |
+
### 5.2 `db/` 层——数据持久化
|
| 407 |
+
|
| 408 |
+
#### `db/schema.py`
|
| 409 |
+
|
| 410 |
+
数据库路径逻辑:
|
| 411 |
+
```python
|
| 412 |
+
def get_db_path() -> str:
|
| 413 |
+
if os.path.exists("/data"): # HF Space bucket挂载点
|
| 414 |
+
return "/data/wang_laws.db"
|
| 415 |
+
return "wang_laws.db" # 本地开发回退
|
| 416 |
+
```
|
| 417 |
+
|
| 418 |
+
| 函数 | 签名 | 用途 |
|
| 419 |
+
| ---------------- | -------- | ---------------------------------- |
|
| 420 |
+
| `get_db_path` | `()` | 返回数据库文件路径 |
|
| 421 |
+
| `get_connection` | `()` | 返回SQLite连接(WAL模式,Row工厂) |
|
| 422 |
+
| `init_db` | `()` | 建表+建索引,幂等,返回连接 |
|
| 423 |
+
| `get_db_stats` | `(conn)` | 返回各表行数+文件大小 |
|
| 424 |
+
|
| 425 |
+
---
|
| 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`
|
| 452 |
+
|
| 453 |
+
| 函数 | 签名 | 返回 | 用途 |
|
| 454 |
+
| --------------------- | -------------------------------------------------------------- | -------------- | ---------------------------- |
|
| 455 |
+
| `get_leaderboard` | `(conn, prefix_filter, layer_type, limit)` | `pd.DataFrame` | 排行榜查询,按wang_score降序 |
|
| 456 |
+
| `get_model_summary` | `(conn, model_id)` | `pd.DataFrame` | 某模型所有组件的汇总统计 |
|
| 457 |
+
| `get_layer_metrics` | `(conn, model_id, prefix, layer_type, start_layer, end_layer)` | `pd.DataFrame` | 逐头原始数据查询 |
|
| 458 |
+
| `get_analyzed_models` | `(conn)` | `pd.DataFrame` | 所有已分析模型列表 |
|
| 459 |
+
| `get_resume_status` | `(conn, model_id, prefix)` | `dict` | 断点续传状态:已完成层号集合 |
|
| 460 |
+
|
| 461 |
+
---
|
| 462 |
+
|
| 463 |
+
### 5.3 `ui/` 层——用户界面
|
| 464 |
+
|
| 465 |
+
#### `ui/tab_inspect.py` — Tab1:结构探测
|
| 466 |
+
|
| 467 |
+
**函数:**
|
| 468 |
+
|
| 469 |
+
```python
|
| 470 |
+
def inspect_model(model_id, hf_token, progress) -> (str, pd.DataFrame):
|
| 471 |
+
"""
|
| 472 |
+
工作流程:
|
| 473 |
+
1. check_quantization() ← 量化检测,失败则返回
|
| 474 |
+
2. 读取 config.json ← extract_config_params()
|
| 475 |
+
3. load_all_shard_headers() ← 读取所有分片header
|
| 476 |
+
4. scan_model_structure() ← 构建LayerProfile字典
|
| 477 |
+
5. summarize_structure() ← 生成文本报告
|
| 478 |
+
6. 构建概览DataFrame ← 每层一行
|
| 479 |
+
返回:(日志文本, 层结构DataFrame)
|
| 480 |
+
"""
|
| 481 |
+
|
| 482 |
+
def build_tab_inspect() -> (inspect_model_id, inspect_token):
|
| 483 |
+
"""
|
| 484 |
+
构建Tab1的Gradio组件
|
| 485 |
+
返回:(model_id文本框, token文本框)
|
| 486 |
+
← 返回值供app.py做Tab1→Tab2的联动同步
|
| 487 |
+
"""
|
| 488 |
+
```
|
| 489 |
+
|
| 490 |
+
**UI组件:**
|
| 491 |
+
```
|
| 492 |
+
模型ID输入框 + Token输入框 + 探测按钮
|
| 493 |
+
→ 日志文本框(结构报告)
|
| 494 |
+
→ 层结构表格(prefix/layer/d_model/head_dim/n_q/n_kv/kv_shared等)
|
| 495 |
+
```
|
| 496 |
+
|
| 497 |
+
---
|
| 498 |
+
|
| 499 |
+
#### `ui/tab_analyze.py` — Tab2:分析(核心Tab)
|
| 500 |
+
|
| 501 |
+
**函数:**
|
| 502 |
+
|
| 503 |
+
```python
|
| 504 |
+
def run_analysis(model_id, hf_token, start_layer, end_layer, progress)
|
| 505 |
+
-> (str, pd.DataFrame):
|
| 506 |
+
"""
|
| 507 |
+
完整工作流程:
|
| 508 |
+
|
| 509 |
+
[准备阶段]
|
| 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 |
+
|
| 541 |
+
def build_tab_analyze() -> (model_id_input, token_input):
|
| 542 |
+
"""构建Tab2的Gradio组件,返回值供app.py联动"""
|
| 543 |
+
```
|
| 544 |
+
|
| 545 |
+
**UI组件:**
|
| 546 |
+
```
|
| 547 |
+
模型ID + Token + 起始层号 + 结束层号 + 分析按钮
|
| 548 |
+
侧边栏:推荐模型列表 + 层号说明
|
| 549 |
+
→ 分析日志文本框(逐头详情)
|
| 550 |
+
→ 逐头结果表格(37列全指标)
|
| 551 |
+
```
|
| 552 |
+
|
| 553 |
+
---
|
| 554 |
+
|
| 555 |
+
#### `ui/tab_leaderboard.py` — Tab3:排行榜
|
| 556 |
+
|
| 557 |
+
**函数:**
|
| 558 |
+
|
| 559 |
+
```python
|
| 560 |
+
def _format_leaderboard(df: pd.DataFrame) -> pd.DataFrame:
|
| 561 |
+
"""
|
| 562 |
+
格式化显示:
|
| 563 |
+
- model_id → model_name(取最后一段)
|
| 564 |
+
- wang_score → wang_score_pct(百分制字符串)
|
| 565 |
+
- 数值列 → 6位小数字符串
|
| 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 |
+
|
| 576 |
+
def build_tab_leaderboard():
|
| 577 |
+
"""
|
| 578 |
+
UI:组件过滤输入框 + 层类型下拉 + 刷新按钮
|
| 579 |
+
→ 状态文本 + 排行榜表格 + 指标说明
|
| 580 |
+
用户手动点刷新(不自动加载)
|
| 581 |
+
"""
|
| 582 |
+
```
|
| 583 |
+
|
| 584 |
+
---
|
| 585 |
+
|
| 586 |
+
#### `ui/tab_database.py` — Tab4:数据库浏览
|
| 587 |
+
|
| 588 |
+
**函数:**
|
| 589 |
+
|
| 590 |
+
```python
|
| 591 |
+
def load_db_stats() -> str:
|
| 592 |
+
"""调用 get_db_stats(),返回各表行数+文件大小"""
|
| 593 |
+
|
| 594 |
+
def load_model_list() -> pd.DataFrame:
|
| 595 |
+
"""调用 get_analyzed_models(),返回模型列表"""
|
| 596 |
+
|
| 597 |
+
def load_model_detail(model_id) -> (pd.DataFrame, str):
|
| 598 |
+
"""
|
| 599 |
+
调用 get_model_summary() → summary_df
|
| 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
|
| 622 |
+
# 启动时执行(模块级)
|
| 623 |
+
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,
|
| 637 |
+
inputs=inspect_model_id, outputs=analyze_model_id)
|
| 638 |
+
inspect_token.change(fn=lambda x:x,
|
| 639 |
+
inputs=inspect_token, outputs=analyze_token)
|
| 640 |
+
```
|
| 641 |
+
|
| 642 |
+
---
|
| 643 |
+
|
| 644 |
+
## 6. 数据库表结构
|
| 645 |
+
|
| 646 |
+
共4张表,SQLite 存储于 `/data/wang_laws.db`。
|
| 647 |
+
|
| 648 |
+
### `models` — 模型基本信息
|
| 649 |
+
```sql
|
| 650 |
+
CREATE TABLE models (
|
| 651 |
+
model_id TEXT PRIMARY KEY, -- "google/gemma-4-e2b"
|
| 652 |
+
model_type TEXT, -- "gemma4" / "qwen2" 等
|
| 653 |
+
analyzed_at TIMESTAMP, -- 最后分析时间
|
| 654 |
+
analyze_sec REAL, -- 本次分析总耗时(秒)
|
| 655 |
+
notes TEXT -- 备注
|
| 656 |
+
);
|
| 657 |
+
```
|
| 658 |
+
|
| 659 |
+
### `components` — 组件信息
|
| 660 |
+
```sql
|
| 661 |
+
CREATE TABLE components (
|
| 662 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 663 |
+
model_id TEXT NOT NULL,
|
| 664 |
+
prefix TEXT NOT NULL, -- "model.language_model."
|
| 665 |
+
n_layers INTEGER, -- 该组件完整层数
|
| 666 |
+
head_dim_min INTEGER, -- 最小head_dim(异构层存在时有意义)
|
| 667 |
+
head_dim_max INTEGER, -- 最大head_dim
|
| 668 |
+
has_kv_shared INTEGER DEFAULT 0, -- 是否有K=V共享层
|
| 669 |
+
has_global INTEGER DEFAULT 0, -- 是否有global层
|
| 670 |
+
d_model INTEGER, -- 输入维度
|
| 671 |
+
UNIQUE(model_id, prefix)
|
| 672 |
+
);
|
| 673 |
+
```
|
| 674 |
+
|
| 675 |
+
### `layer_head_metrics` — 逐头原始数据(主数据表)
|
| 676 |
+
```sql
|
| 677 |
+
CREATE TABLE layer_head_metrics (
|
| 678 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 679 |
+
model_id TEXT NOT NULL,
|
| 680 |
+
prefix TEXT NOT NULL,
|
| 681 |
+
layer INTEGER NOT NULL,
|
| 682 |
+
layer_type TEXT DEFAULT 'standard', -- "standard" / "global"
|
| 683 |
+
kv_head INTEGER NOT NULL,
|
| 684 |
+
q_head INTEGER NOT NULL,
|
| 685 |
+
kv_shared INTEGER DEFAULT 0, -- 1=K=V共享(理论值),0=正常
|
| 686 |
+
head_dim INTEGER,
|
| 687 |
+
d_model INTEGER,
|
| 688 |
+
n_q_heads INTEGER,
|
| 689 |
+
n_kv_heads INTEGER,
|
| 690 |
+
-- 第一定律
|
| 691 |
+
pearson_QK REAL, spearman_QK REAL, pearson_QV REAL, pearson_KV REAL,
|
| 692 |
+
-- 第二定律
|
| 693 |
+
ssr_QK REAL, ssr_QV REAL, ssr_KV REAL,
|
| 694 |
+
-- 第三定律
|
| 695 |
+
sigma_max_Q REAL, sigma_min_Q REAL, cond_Q REAL,
|
| 696 |
+
sigma_max_K REAL, sigma_min_K REAL, cond_K REAL,
|
| 697 |
+
sigma_max_V REAL, sigma_min_V REAL, cond_V REAL,
|
| 698 |
+
-- 第四定律
|
| 699 |
+
cosU_QK REAL, cosU_QV REAL, cosU_KV REAL,
|
| 700 |
+
-- 第五定律
|
| 701 |
+
cosV_QK REAL, cosV_QV REAL, cosV_KV REAL,
|
| 702 |
+
-- 尺度因子
|
| 703 |
+
alpha_QK REAL, alpha_res_QK REAL,
|
| 704 |
+
alpha_QV REAL, alpha_res_QV REAL,
|
| 705 |
+
alpha_KV REAL, alpha_res_KV REAL,
|
| 706 |
+
|
| 707 |
+
UNIQUE(model_id, prefix, layer, kv_head, q_head)
|
| 708 |
+
);
|
| 709 |
+
```
|
| 710 |
+
|
| 711 |
+
### `model_summary` — 汇总统计(排行榜用)
|
| 712 |
+
```sql
|
| 713 |
+
CREATE TABLE model_summary (
|
| 714 |
+
model_id TEXT NOT NULL,
|
| 715 |
+
prefix TEXT NOT NULL,
|
| 716 |
+
layer_type TEXT NOT NULL DEFAULT 'all', -- all/standard/global
|
| 717 |
+
-- 第一定律
|
| 718 |
+
median_pearson_QK REAL, mean_pearson_QK REAL,
|
| 719 |
+
-- 第二定律
|
| 720 |
+
median_ssr_QK REAL, mean_ssr_QK REAL,
|
| 721 |
+
median_ssr_QV REAL, mean_ssr_QV REAL,
|
| 722 |
+
-- 第三定律
|
| 723 |
+
median_cond_Q REAL, mean_cond_Q REAL,
|
| 724 |
+
-- 第四定律
|
| 725 |
+
median_cosU_QK REAL, median_cosU_QV REAL,
|
| 726 |
+
-- 第五定律
|
| 727 |
+
median_cosV_QK REAL, median_cosV_QV REAL,
|
| 728 |
+
-- 王氏评分(始终用standard层计算,即使layer_type=all/global)
|
| 729 |
+
wang_score REAL,
|
| 730 |
+
n_layers INTEGER,
|
| 731 |
+
n_records INTEGER,
|
| 732 |
+
updated_at TIMESTAMP,
|
| 733 |
+
|
| 734 |
+
PRIMARY KEY(model_id, prefix, layer_type)
|
| 735 |
+
);
|
| 736 |
+
```
|
| 737 |
+
|
| 738 |
+
**每个(model_id, prefix)在model_summary中有3行:**
|
| 739 |
+
```
|
| 740 |
+
(model_id, prefix, "all") ← 全部层混合统计
|
| 741 |
+
(model_id, prefix, "standard") ← 只含standard层
|
| 742 |
+
(model_id, prefix, "global") ← 只含global层(如Gemma全局层)
|
| 743 |
+
```
|
| 744 |
+
|
| 745 |
+
**layer_type 推断规则(零hard coding):**
|
| 746 |
+
```
|
| 747 |
+
kv_shared=True → layer_type="global"
|
| 748 |
+
kv_shared=False → layer_type="standard"
|
| 749 |
+
```
|
| 750 |
+
|
| 751 |
+
---
|
| 752 |
+
|
| 753 |
+
## 7. 数据流全链路
|
| 754 |
+
|
| 755 |
+
```
|
| 756 |
+
用户输入模型ID(如 "google/gemma-4-e2b")
|
| 757 |
+
│
|
| 758 |
+
▼
|
| 759 |
+
[Tab1 或 Tab2]
|
| 760 |
+
check_quantization()
|
| 761 |
+
→ 检测config.json / 模型名 / 文件列表 / header内容
|
| 762 |
+
→ 量化模型直接拒绝
|
| 763 |
+
│
|
| 764 |
+
▼
|
| 765 |
+
load_all_shard_headers()
|
| 766 |
+
→ 对每个.safetensors文件:
|
| 767 |
+
HTTP GET bytes=0-7 → header_size(8字节小端整数)
|
| 768 |
+
HTTP GET bytes=8-{8+size} → JSON header
|
| 769 |
+
→ 返回 {filename: (header_dict, header_size)}
|
| 770 |
+
│
|
| 771 |
+
▼
|
| 772 |
+
scan_model_structure()
|
| 773 |
+
→ 两遍扫描所有key → 构建 {(prefix,layer): LayerProfile}
|
| 774 |
+
→ 自动推断:head_dim / n_q_heads / n_kv_heads / kv_shared
|
| 775 |
+
│
|
| 776 |
+
▼(Tab2专有)
|
| 777 |
+
断点续传检查
|
| 778 |
+
→ get_analyzed_layers() → done_layers: dict[prefix, set[int]]
|
| 779 |
+
│
|
| 780 |
+
▼(逐层循环)
|
| 781 |
+
load_tensor_remote(W_q) → HTTP GET bytes={abs_start}-{abs_end}
|
| 782 |
+
load_tensor_remote(W_k) → 同上
|
| 783 |
+
load_tensor_remote(W_v) → 同上(kv_shared时直接clone W_k)
|
| 784 |
+
│
|
| 785 |
+
▼
|
| 786 |
+
analyze_layer(W_q, W_k, W_v, profile)
|
| 787 |
+
→ 按head切片
|
| 788 |
+
→ SVD分解每个head
|
| 789 |
+
→ 计算37个指标
|
| 790 |
+
→ 返回 records: list[dict]
|
| 791 |
+
│
|
| 792 |
+
▼
|
| 793 |
+
write_layer_records(conn, model_id, records)
|
| 794 |
+
→ INSERT OR REPLACE 批量写入 layer_head_metrics
|
| 795 |
+
│
|
| 796 |
+
▼
|
| 797 |
+
update_model_summary(conn, model_id, prefix)
|
| 798 |
+
→ 查询 layer_head_metrics
|
| 799 |
+
→ 计算 median/mean
|
| 800 |
+
→ wang_score = 1 - median(ssr_QK) [用standard层]
|
| 801 |
+
→ INSERT OR REPLACE 写入 model_summary(all/standard/global 3行)
|
| 802 |
+
│
|
| 803 |
+
▼
|
| 804 |
+
[Tab3 排行榜]
|
| 805 |
+
get_leaderboard()
|
| 806 |
+
→ SELECT from model_summary WHERE layer_type='standard'
|
| 807 |
+
→ ORDER BY wang_score DESC
|
| 808 |
+
→ 格式化展示
|
| 809 |
+
```
|
| 810 |
+
|
| 811 |
+
---
|
| 812 |
+
|
| 813 |
+
## 8. 函数调用关系图
|
| 814 |
+
|
| 815 |
+
```
|
| 816 |
+
app.py
|
| 817 |
+
├── init_db() [db/schema.py]
|
| 818 |
+
├── build_tab_inspect() [ui/tab_inspect.py]
|
| 819 |
+
│ └── inspect_model()
|
| 820 |
+
│ ├── check_quantization() [core/fetcher.py]
|
| 821 |
+
│ ├── extract_config_params() [core/layer_profile.py]
|
| 822 |
+
│ ├── load_all_shard_headers() [core/fetcher.py]
|
| 823 |
+
│ │ ├── get_all_shard_files()
|
| 824 |
+
│ │ │ └── find_index_file()
|
| 825 |
+
│ │ └── read_safetensors_header()
|
| 826 |
+
│ ├── scan_model_structure() [core/layer_profile.py]
|
| 827 |
+
│ │ ├── classify_qkv_suffix()
|
| 828 |
+
│ │ ├── is_norm_key()
|
| 829 |
+
│ │ └── _infer_head_dim()
|
| 830 |
+
│ └── summarize_structure()
|
| 831 |
+
│
|
| 832 |
+
├── build_tab_analyze() [ui/tab_analyze.py]
|
| 833 |
+
│ └── run_analysis()
|
| 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]
|
| 843 |
+
│ ├── analyze_layer() [core/metrics.py]
|
| 844 |
+
│ │ ├── pearson()
|
| 845 |
+
│ │ ├── spearman_r()
|
| 846 |
+
│ │ ├── ssr()
|
| 847 |
+
│ │ ├── svr()
|
| 848 |
+
│ │ ├── cos_U()
|
| 849 |
+
│ │ ├── cos_V()
|
| 850 |
+
│ │ └── sigma_stats()
|
| 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 |
+
---
|
| 876 |
+
|
| 877 |
+
## 9. 关键设计决策
|
| 878 |
+
|
| 879 |
+
### 零 hard coding 原则
|
| 880 |
+
任何模型相关的参数(head_dim、层数、组件结构)
|
| 881 |
+
都从权重文件的 key 名自动推断,不写死任何模型名或层号。
|
| 882 |
+
|
| 883 |
+
### GQA 支持
|
| 884 |
+
当 `n_q_heads > n_kv_heads` 时(如 Llama-3-8B 的 32Q/8KV),
|
| 885 |
+
`group = n_q / n_kv`,每个KV head对应group个Q head,
|
| 886 |
+
全部独立计算,每个Q head一条记录。
|
| 887 |
+
|
| 888 |
+
### K=V 共享(Gemma全局层)
|
| 889 |
+
Gemma-4-31B 每6层有一个全局层,V权重不存在(K和V共享)。
|
| 890 |
+
检测方式:V的key不在任何shard的header中。
|
| 891 |
+
处理方式:`W_v = W_k.clone()`,KV相关指标设为理论值。
|
| 892 |
+
存储方式:`kv_shared=1`,`layer_type="global"`。
|
| 893 |
+
|
| 894 |
+
### 断点续传粒度
|
| 895 |
+
以 `(model_id, prefix, layer)` 为粒度。
|
| 896 |
+
某层的所有head全部写入才算完成。
|
| 897 |
+
允许随时中断,下次从未完成的层继续。
|
| 898 |
+
|
| 899 |
+
### 排行榜的 wang_score
|
| 900 |
+
无论 `model_summary` 的 `layer_type` 是 all/standard/global,
|
| 901 |
+
`wang_score` 统一从 standard 层的 `ssr_QK` 计算,
|
| 902 |
+
避免全局层(K=V共享,SSR=0)人为拉高评分。
|
| 903 |
+
|
| 904 |
+
### 每个(model_id, prefix)在排行榜中是一行
|
| 905 |
+
排行榜以 `(model_id, prefix)` 为单位,
|
| 906 |
+
多模态模型(如Gemma-4)的language_model和vision_tower分别占一行。
|
| 907 |
+
|
| 908 |
+
---
|
| 909 |
+
|
| 910 |
+
## 10. 部署说明
|
| 911 |
+
|
| 912 |
+
### HuggingFace Space 部署
|
| 913 |
+
|
| 914 |
+
1. 创建 Space,选择 Gradio SDK
|
| 915 |
+
2. 在 Space Settings 中添加 **Persistent Storage**(挂载到 `/data`)
|
| 916 |
+
- `wang_laws.db` 重启后不丢失
|
| 917 |
+
3. 上传所有文件(保持目录结构)
|
| 918 |
+
4. 如需访问私有模型,在 Space Secrets 中设置 `HF_TOKEN`
|
| 919 |
+
|
| 920 |
+
#### 配置管理员写入权限(重要)
|
| 921 |
+
|
| 922 |
+
在 **Space Settings → Secrets** 中添加:
|
| 923 |
+
|
| 924 |
+
| Secret 名称 | 值 | 说明 |
|
| 925 |
+
| ------------- | ---------------- | ------------------------------- |
|
| 926 |
+
| `WRITE_TOKEN` | 你自己设置的密码 | 管理员写库密钥,不进入 git repo |
|
| 927 |
+
|
| 928 |
+
**工作原理:**
|
| 929 |
+
```
|
| 930 |
+
HF Space Secrets(加密存储,不在 git 中)
|
| 931 |
+
↓ HF 运行时自动注入
|
| 932 |
+
Docker 容器环境变量 WRITE_TOKEN
|
| 933 |
+
↓ 服务端读取
|
| 934 |
+
os.environ.get("WRITE_TOKEN")
|
| 935 |
+
↓ 与用户输入的 Admin Token 比对(纯服务端,前端不可见)
|
| 936 |
+
True → 写入数据库
|
| 937 |
+
False → 只读模式,分析正常运行
|
| 938 |
+
```
|
| 939 |
+
|
| 940 |
+
**三类用户的体验:**
|
| 941 |
+
|
| 942 |
+
| 用户 | Admin Write Token | 行为 |
|
| 943 |
+
| --------------- | ----------------- | ---------------------------------- |
|
| 944 |
+
| 你(管理员) | 填写正确密钥 | 分析结果写入数据库,排行榜更新 |
|
| 945 |
+
| 审稿人 / 复现者 | 留空 | 分析正常运行,指标完整显示,不写库 |
|
| 946 |
+
| 恶意用户 | 随意填写 | 分析可以跑,写库被拒绝 |
|
| 947 |
+
|
| 948 |
+
**未配置 `WRITE_TOKEN` 时:**
|
| 949 |
+
```python
|
| 950 |
+
# check_write_permission() 的行为:
|
| 951 |
+
server_token = os.environ.get("WRITE_TOKEN", "")
|
| 952 |
+
if not server_token:
|
| 953 |
+
return False # 服务端未配置 → 拒绝所有写入
|
| 954 |
+
```
|
| 955 |
+
即使有人猜到任意字符串也无法写入。
|
| 956 |
+
|
| 957 |
+
### 本地运行
|
| 958 |
+
|
| 959 |
+
```bash
|
| 960 |
+
pip install -r requirements.txt
|
| 961 |
+
|
| 962 |
+
# 可选:设置写入权限
|
| 963 |
+
export WRITE_TOKEN="your_secret_password"
|
| 964 |
+
|
| 965 |
+
python app.py
|
| 966 |
+
# 浏览器打开 http://127.0.0.1:7860
|
| 967 |
+
```
|
| 968 |
+
|
| 969 |
+
本地运行时数据库存于当前目录的 `wang_laws.db`。
|
| 970 |
+
不设置 `WRITE_TOKEN` 则所有人都是只读模式。
|
| 971 |
+
```
|
| 972 |
+
|
| 973 |
+
---
|
| 974 |
+
|
| 975 |
+
## 改动汇总
|
| 976 |
+
|
| 977 |
+
| 文件 | 改动 |
|
| 978 |
+
| ----------------------- | ---------------------------------------------------------------------------- |
|
| 979 |
+
| `db/writer.py` | 末尾追加 `check_write_permission()`,其余不变 |
|
| 980 |
+
| `ui/tab_analyze.py` | 完整重写:加 `admin_token` 参数,所有写库操作加 `can_write` 判断,日志改英文 |
|
| 981 |
+
| `README.md` | 第10节部署说明扩充写权限配置说明 |
|
| 982 |
+
| `db/schema.py` | 不变 |
|
| 983 |
+
| `db/reader.py` | 不变 |
|
| 984 |
+
| `ui/tab_inspect.py` | 不变 |
|
| 985 |
+
| `ui/tab_leaderboard.py` | 不变 |
|
| 986 |
+
| `ui/tab_database.py` | 不变 |
|
| 987 |
+
| `app.py` | 不变 |
|
| 988 |
+
|
| 989 |
+
|
| 990 |
+
### 注意事项
|
| 991 |
+
|
| 992 |
+
- 分析大模型(如 70B)时每层需要约 30 秒(受 HF CDN 网速限制)
|
| 993 |
+
- HF Space 免费版有 48 小时超时限制,建议开启断点续传分批分析
|
| 994 |
+
- 量化模型(GPTQ/AWQ/GGUF)自动拒绝,需使用原始 BF16 版本
|
| 995 |
+
|
| 996 |
+
---
|
| 997 |
+
|
| 998 |
+
## 11. 依赖清单
|
| 999 |
+
|
| 1000 |
+
```
|
| 1001 |
+
gradio>=4.0.0 # Web UI 框架
|
| 1002 |
+
requests # HTTP Range Request 读取远程权重
|
| 1003 |
+
numpy # 数值计算(统计汇总)
|
| 1004 |
+
scipy # spearman相关系数
|
| 1005 |
+
torch # SVD分解(torch.linalg.svd)
|
| 1006 |
+
huggingface_hub # list_repo_files(文件列表)
|
| 1007 |
+
```
|
| 1008 |
+
|
| 1009 |
+
Python 内置(无需安装):
|
| 1010 |
+
```
|
| 1011 |
+
sqlite3 # 数据库
|
| 1012 |
+
struct # 解析safetensors header的8字节整数
|
| 1013 |
+
json # 解析safetensors header JSON
|
| 1014 |
+
re # 正则提取层号
|
| 1015 |
+
datetime # 时间戳
|
| 1016 |
+
dataclasses # LayerProfile数据结构
|
| 1017 |
+
```
|