已合并
[Feature] msAgent Skills迁移msModelSlim仓 #907
[Feature] msAgent Skills迁移msModelSlim仓 #907
已合并
joejoezhou创建于 26 天前
共 102 个文件变更+9256-10
@@ -34,6 +34,7 @@ msmodelslim/
34├── msmodelslim/ # 源代码34├── msmodelslim/ # 源代码
35├── precision_tool/ # 伪量化精度评估工具(V0框架)35├── precision_tool/ # 伪量化精度评估工具(V0框架)
36├── security/ # 安全检查基础模块(V0框架)36├── security/ # 安全检查基础模块(V0框架)
37+├── skills/ # Agent Skills(资料管理、量化安装/适配/量化/策略/调优/测评等)
37├── test/ # 测试用例与测试脚本38├── test/ # 测试用例与测试脚本
38├── install.sh # 安装脚本39├── install.sh # 安装脚本
39├── requirements.txt # 第三方依赖列表40├── requirements.txt # 第三方依赖列表
@@ -54,6 +55,7 @@ msmodelslim/
54├── lab_calib/ # 量化校准数据集示例,如json、jsonl、图片等55├── lab_calib/ # 量化校准数据集示例,如json、jsonl、图片等
55├── lab_practice/ # 量化最佳实践仓库,管理各模型已验证的量化配置56├── lab_practice/ # 量化最佳实践仓库,管理各模型已验证的量化配置
56├── msmodelslim/ # 源代码57├── msmodelslim/ # 源代码
58+├── skills/ # Agent Skills(含量化安装/适配/量化/策略/调优/测评)
57├── test/ # 测试用例与测试脚本59├── test/ # 测试用例与测试脚本
58├── install.sh # 安装脚本60├── install.sh # 安装脚本
59├── requirements.txt # 第三方依赖列表61├── requirements.txt # 第三方依赖列表
@@ -0,0 +1,134 @@
1+# msModelSlim Skills
2+ 
3+本目录存放可供 Agent 加载的 Skills,覆盖模型量化与精度调优的完整闭环:安装、适配、调优策略、量化执行、评测与端到端编排。
4+ 
5+## 目录结构
6+ 
7+| 领域 | 顶层 Skill | 说明 | 可委派子 Skill(供编排) |
8+|---|---|---|---|
9+| 端到端编排 | [tuning/](tuning/) | 全自动量化调优编排者(workflow) | — |
10+| 安装与环境 | [installation/](installation/) | 安装与环境校验 | `installation`(自身) |
11+| 模型适配 | [adaptation/](adaptation/) | 校准适配 + EP 并行适配 | `adaptation/calibration`、`adaptation/calibration/analyze`、`adaptation/calibration/dequant`、`adaptation/calibration/verify`、`adaptation/ep` |
12+| 敏感层分析 | [sensitive-layer-analysis/](sensitive-layer-analysis/) | 敏感层分析执行 | `sensitive-layer-analysis`(自身) |
13+| 调优策略 | [strategy/](strategy/) | 调优策略与 Practice 生成 | `strategy/practice-cfg`、`strategy/standing_high_with_experience/expert-rules` |
14+| 量化执行 | [quantization/](quantization/) | 执行 `msmodelslim quant` | `quantization`(自身) |
15+| 评测 | [evaluation/](evaluation/) | 评测配置 / 执行 / 评测集 herding 压缩 | `evaluation/evaluation-cfg`、`evaluation/evaluate`、`evaluation/dataset-compression-herding` |
16+| 资料管理 | [docs-management/](docs-management/) | 资料管理 | — |
17+ 
18+调优闭环脚本共享库(无 SKILL):[tuning-loop-lib/](tuning-loop-lib/)。
19+ 
20+## 调用方式
21+ 
22+同一套 Skill 支持两种入口,共用同一批子 Skill 与脚本,差异仅在「由谁驱动」。
23+ 
24+**1. 直接使用(独立完成任务)**
25+ 
26+用户针对单一需求(安装、写适配器、生成 Practice、执行量化、跑评测等)直接触发对应 Skill,由 Skill 按自身流程独立完成,不依赖 `tuning` 编排。
27+ 
28+- 领域 Skill(`adaptation` / `strategy` / `evaluation`)是**领域入口**,内部路由到子 Skill 完成具体工作;
29+- 单任务 Skill(`installation` / `quantization` / `sensitive-layer-analysis`)自身即可独立完成,无子级。
30+ 
31+**2. 端到端调优(由 `tuning` 编排)**
32+ 
33+用户要求「自动量化调优」时,由 `tuning` 作为编排者接管,按「环境准备 → 模型准备 → 量化配置调优 → 结果输出」四阶段,委派各可委派子 Skill 协作完成:
34+ 
35+- 委派对象是带 `metadata.subagent` 声明的**可委派子 Skill**(见「subagent ↔ skill 绑定」);
36+- 顶层领域 Skill 不直接参与委派,作为领域入口供直接使用;
37+- 编排层与子 Skill 通过 SUBAGENT_IO v1 契约衔接(字段定义见 `tuning/references/`)。
38+ 
39+## 内容组织
40+ 
41+skills 文档遵循「docs 为权威、skill 只写增量」的分工,避免重复:
42+ 
43+1. **docs(权威)**:安装指南、CLI 参数、YAML schema、算法原理、模型接入等完整细节以 `docs/` 为准,skills 内只链接、不复写;
44+2. **skill(增量)**:只保留执行所需的行为增量——触发条件、执行步骤、门禁与实战经验(docs 未覆盖项);
45+3. **编排契约**:`tuning/references/` 只定义委派时机、input/output 字段与判定标准,指向各子 Skill,不重复实现细节。
46+ 
47+## subagent ↔ skill 绑定
48+ 
49+调优闭环中可作为 subagent 委派的 skill(如 `strategy/practice-cfg`、`adaptation/ep`、`quantization` 等),其绑定关系**内置于各自 SKILL.md 的 frontmatter**:`metadata.subagent` 声明 `id`(委派标识,即 `subagent_type`)与 `bind`(委派时须绑定加载的 skill 根目录列表,含自身)。绑定清单与解析规则见 [tuning/references/subagent_io_protocol.md](tuning/references/subagent_io_protocol.md)「subagent ↔ skill 绑定」章节。
50+ 
51+部分被委派 subagent 可**再委派**其 `bind` 内的子 skill(嵌套委派,链深上限 2),如 `strategy/practice-cfg` 步骤②再委派 `sensitive-layer-analysis` 完成敏感层分析,主 Agent 无需感知;规则见该文档「子任务再委派」章节。
52+ 
53+## Skill 编写规范
54+ 
55+所有 `SKILL.md` 遵循统一结构与命名,保证**人类可读、Agent 可执行、经验可积累**。任何新增 / 修改 skill 均须满足本规范。
56+ 
57+### 1. 标题与命名
58+ 
59+- 一级标题统一为 `# <中文名>(<frontmatter name>)`,如 `# 敏感层分析(sensitive-layer-analysis)`;其中 name 必须与 frontmatter `name` 一致。
60+- 小节名一律使用中文;`## Overview`、`## Skill: xxx` 等英文标题统一改写为 `## 概述` 等中文小节。
61+- 同级小节标题保持同构:同为动作就同为动宾(如 `## 检查清单` 不混用 `## 清单检查`),同为门禁就同用规范门禁名(见下)。
62+ 
63+### 2. 统一小节骨架
64+ 
65+**执行 / 决策型 skill**(会被委派或独立执行完整任务的 skill)按需选用以下小节,顺序推荐为:
66+ 
67+```markdown
68+ 
69+## 职责(或 ## 概述) # 解决什么 / 不解决什么 / 权威参考指向 docs
70+ 
71+## 输入 / ## 输出 # 委派或直用契约的字段表(类型/必填/说明)
72+ 
73+## 流程 # 编号步骤(1. 2. 3. 或 ①②③),可用决策树/流程图画清分支
74+ 
75+## 硬门禁 / ## 红线 / ## 检查清单 / ## 通过-失败标准 # 见「门禁范式」
76+ 
77+## 常见错误 / ## 错误处理 # 本 skill 高频错误 → 处置
78+ 
79+## 经验条目(Experiences) # 追加制登记表,见「经验条目规范」
80+ 
81+## 参考资料 # 链接 references/ 与 docs/
82+```
83+ 
84+**路由 / 入口型 skill**(`adaptation`、`evaluation`、`strategy`):只保留「子目录表 + 使用方式」即可,**不加**经验条目区(见经验条目适用层)。
85+ 
86+### 3. 门禁范式(统一命名,不得使用异名)
87+ 
88+| 范式 | 语义 | 格式 |
89+|------|------|------|
90+| `## 硬门禁` | 流程**启动前**必须满足;不满足即停、**不得带病进入**后续步骤 | 条目化「必须…,否则…」 |
91+| `## 红线` | **贯穿全流程**的禁止行为 / 边界 | `- 不得…` / `- 禁止…` 清单 |
92+| `## 检查清单` | 执行前逐项自检 | `- [ ]` 勾选项 |
93+| `## 通过/失败标准` | 判定本 skill 是否完成 | 显式「通过 = …;失败 = …」 |
94+| `## 常见错误` / `## 错误处理` | 高频异常 → 处置 | 表格(错误 / 原因 / 修复) |
95+ 
96+- 同语义门禁不得再自造名称(如「硬性门禁」「红线和原则」须归入上表);正文内的「必须 / 禁止」措辞不受限。
97+- 门禁必须是**可判定**的:给出判定输入(exit code、文件存在性、schema 校验、数值门限等),不能只写"注意"。
98+ 
99+### 4. 经验条目规范(显式、可追加)
100+ 
101+统一格式为一张**追加制登记表**,置于各 skill 的 `## 经验条目(Experiences)` 小节:
102+ 
103+```markdown
104+ 
105+## 经验条目(Experiences,追加制)
106+ 
107+> 追加规范:连续编号 `[E-序号]`(跨 skill 不要求全局唯一,按文件内递增);正文保留权威展开,本表只做索引 + 元数据登记(结论一句话);来源三选一:实测编号(F0-x / Dx / Tx)| 用户反馈 | 代码实证;验证状态三选一:已回归 | 待验证 | 已上流 docs。经验上流到 docs 后改为 `已上流 docs`,可在表内保留指针。
108+ 
109+| 条目 | 主题 | 适用条件 / 触发信号 | 结论要点(一句话) | 正文位置 | 来源 | 验证状态 |
110+|------|------|------|------|------|------|------|
111+```
112+ 
113+- **不记录日期/时间**:经验条目表不含日期列;时间戳对检索与复用无意义,勿追加。
114+- **经验条目 ≠ 常见错误**:常见错误表解决"出错怎么修";经验条目沉淀"正确认知 / 最佳做法 / 边界结论"(含其适用条件与验证状态)。
115+- **新增经验必须登记**:任何实测中发现、被用户反馈纠正、或经代码实证的结论,应追加为一条 `[E-序号]`,而非仅散落在正文。
116+- **淘汰与上流**:随版本过期的条目整行删除;被 docs 收录的经验改验证状态为 `已上流 docs` 并保留指针,遵循「docs 权威、skill 只写增量」原则。
117+ 
118+### 5. 经验条目的适用层
119+ 
120+| 适用层 | skill | 说明 |
121+|------|------|------|
122+| **强适用**(必须带经验条目区) | `sensitive-layer-analysis`、`strategy/practice-cfg`、`evaluation/evaluation-cfg`、`evaluation/evaluate`、`quantization`、`adaptation/calibration/analyze`、`adaptation/calibration/dequant`、`adaptation/calibration/verify`、`adaptation/calibration/layer_wise`、`evaluation/dataset-compression-herding` | 反复实操、每次运行都可能产出新经验(环境坑 / CLI 差异 / schema 边界 / 结构判定信号) |
123+| **部分适用**(沉淀原则级条目) | `tuning`、`docs-management`、`adaptation/calibration`(主流程)、`adaptation/ep` | 只登记编排 / 文档管理 / 适配流程级原则;具体操作坑**下沉**给其子 skill 登记,避免编排层膨胀 |
124+| **弱适用 / 不加** | `adaptation`、`evaluation`、`strategy`(路由入口) | 只做路由分发;如确有"哪类模型走哪条路径"的教训,登记到对应子 skill |
125+| **经验库载体** | `strategy/standing_high_with_experience/expert-rules` | 全体系经验的 L1/L2/L3 检索索引与专家库,见其 SKILL.md「三级知识结构」;新模型个案按 L3 约定追加 `models/<vendor>/<model>.md` |
126+ 
127+### 6. 风格约定
128+ 
129+- 语言以中文为主;代码、命令、CLI 参数、YAML 字段名保留原文(不加翻译)。
130+- 决策树 / 流程优先用文本图(text 代码块)或 Markdown 表格表达,少用无法在终端阅读的图形字符。
131+- "权威参考以 docs 为准"的内容一律用链接指向 `docs/`,skill 内不复写长文。
132+- **遵从资料规范公共校验 CE-05 / CE-06**(《[公共校验清单](../docs/zh/contributing/development_guide/docs_standards/00_common_checklist.md)》):引用 `docs/` 标题写作 `《[正式标题](相对路径)》`;中文语境数字与量词/单位不留空格(如 `前2层`、`≤2卡`)。
133+- **CE-04**:全文(不含 fenced code 内示例)仅一个一级标题。analyze 的「分析报告」是子 agent **落盘产物模板**(`{save_path}/model_analysis_report.md`),其 `# 分析报告` 仅出现在代码块示例中,不是本 skill 的第二 H1。
134+- 对用户 / 编排层的**必做沟通**(阻塞、需确认、风险提示)用「必须 / 不得」显式写出,并给出话术要点。
@@ -0,0 +1,66 @@
1+---
2+name: adaptation
3+description: |
4+ msModelSlim 模型适配总入口。适配准备由 4个子任务组成,每个子任务自带门禁,主 Agent 在收尾统一验收:
5+ ① analyze(分析,门禁:next_step 判定);
6+ ② calibration(写 adapter,门禁:verify 四步;含条件前置 dequant 反量化);
7+ ③ ep(多卡 MoE EP 并行改造,门禁:[EP_CHECK]+[EP_ACT_GATE],多卡才触发);
8+ ④ verify(主 Agent 适配验收,门禁:四步全 PASS = NPU 前向推理判定基准;兼作 calibration 内部门禁)。
9+ 触发:新模型接入、写 adapter、适配器验证、FP8 反量化接入、逐层加载、MoE EP 并行适配等。
10+metadata:
11+ version: 0.2.0
12+ domain: quant
13+ framework: msmodelslim
14+ skill_class: workflow
15+---
16+ 
17+# 模型适配(adaptation)
18+ 
19+## 是什么
20+ 
21+「模型适配」= 让目标模型能在 msModelSlim 量化流程中被正确加载、遍历与量化导出。按适配目标分为两类:
22+ 
23+| 适配类型 | 目录 | 目标 |
24+|---|---|---|
25+| **校准适配**(Calibration Adaptation) | [calibration/](calibration/SKILL.md) | 把模型接入**校准量化**(PTQ / W8A8 / W4A16)流程:写 Model Adapter,使 `msmodelslim quant` 可加载、校准、量化、导出 |
26+| **并行适配**(Parallel Adaptation) | [ep/](ep/SKILL.md) | 把 MoE 模型改造成**多卡 EP** 并行状态,保证后续量化全程跑在 EP 上 |
27+ 
28+> **命名澄清**:`calibration/` 是「校准适配」的缩写,指为**校准量化流程**做适配(写 Model Adapter),**不是**「跑校准集计算 scale 的那一步」。后者在量化执行时由 `msmodelslim quant` 内部完成,不在此目录。同理,`ep/` 指「EP 并行适配」,两者都是模型适配(`adaptation`)的子类。
29+ 
30+## 使用方式
31+ 
32+- **直接使用**:用户要求「接入新模型 / 写适配器 / 适配器验证 / FP8 反量化 / 逐层加载 / MoE EP 并行适配」时,按下方决策树与路由速查执行,由本 Skill 独立完成适配(含各子任务门禁),不依赖 `tuning`。
33+- **被 `tuning` 编排**:端到端调优在「模型准备」阶段委派本 Skill 的可委派子 Skill(`analyze` / `calibration` / `dequant` / `ep` / `verify`),委派时机与 input/output 契约见 `tuning/references/prepare_model.md`。本 Skill 是**领域入口**,自身不作为 subagent 被委派。
34+ 
35+## 适配顺序(决策树)
36+ 
37+适配准备由 4个适配子任务构成;每个子任务完成时自带**门禁**,通过后才进入下一步;主 Agent 在适配准备收尾统一验收(触发 verify 门禁),**不跳步**:
38+ 
39+```text
40+适配准备
41+├─ ① analyze(分析) 门禁: next_step 判定
42+├─ ② calibration(写适配器) 门禁: verify 四步(内部门禁,随 verification_steps 回传)
43+│ └─ (条件) dequant(反量化) 门禁: adapter_updated=true → 回到 ②
44+├─ ③ ep(EP 并行改造,多卡才触发) 门禁: [EP_CHECK] + [EP_ACT_GATE]
45+└─ ④ verify(主 Agent 适配验收) 门禁: 四步全 PASS(NPU 前向推理判定基准)
46+```
47+ 
48+**门禁说明**:
49+ 
50+- **① analyze**:判断适配可行性(实现来源解析),输出风险结论与 `next_step`(`model-adapt` / `dequant` / `blocked` / `need_user_input`)。`blocked` / `need_user_input` 即停,回到本子 skill 修复或向用户索要材料,不得跳过继续。
51+- **② calibration**:写 Model Adapter(`handle_dataset` / `init_model` / `generate_model_visit` / `generate_model_forward` / `enable_kv_cache` + `config.ini` 注册 + `bash install.sh`)。完成时**自带 verify 四步作为内部门禁**,随 `verification_steps` 回传。
52+- **②' dequant(条件前置)**:仅当 ① 判定为原生量化模型(`next_step: dequant`)时,先执行反量化(FP8 per-block / per-channel → 写 `convert_*_to_bf16.py` 接入 adapter);门禁 `adapter_updated=true`,完成后**回到 ②** 继续写 Model Adapter;普通 FP 模型从 ① 直接到 ②。
53+- **③ ep(条件触发)**:仅当调优需多卡(≥2卡)且命中 EP 路由时走;MoE 检查 → EP 就绪检查/改造,门禁 `[EP_CHECK]` + `[EP_ACT_GATE]`;非 MoE / 单卡跳过。
54+- **④ verify(主 Agent 适配验收)**:适配准备收尾的统一验收,也是「NPU 前向推理」判定基准——若 ② 的内部门禁缺失或未全过,主 Agent 独立下发 ④,四步全 PASS(Step2 全回退量化 + Step3 权重一致性/可加载保存)才视为模型可在目标设备正常前向推理。
55+- **可选高阶 layer_wise**:逐层量化(CPU 内存不足或用户明确要求时)依赖 ② 完成后启用,不占主链路。
56+ 
57+> **verify 双重角色**:既是 ② calibration 的内部门禁(随 `verification_steps` 回传),又是 ④ 主 Agent 的适配验收门禁(独立下发,不得另造命令代替)。两层共用同一套四步验证。
58+ 
59+## 路由速查
60+ 
61+| 用户诉求 | 入口 |
62+|---|---|
63+| 新模型接入 / 写 adapter / FP8 反量化 / 逐层加载 | `calibration/`(必要时先 `analyze/`) |
64+| 适配器验证 / 主 Agent 适配验收(四步验证) | `verify/` |
65+| MoE 多卡 EP 并行 / EP 就绪检查 | `ep/` |
66+| 调优多卡 MoE:先校准再 EP | 先 `calibration/` 完成校准适配(含四步验证),再 `ep/` |
@@ -0,0 +1,120 @@
1+---
2+name: calibration
3+description:
4+ 为 msModelSlim 创建基础 Transformers 模型适配器(Model Adapter)。
5+ 包含创建适配器、实现必需接口与注册安装流程。
6+ 适用:Decoder-only LLM、理解类 VLM(仅 LLM/text 部分)。
7+ 不适用:多模态生成模型(图像/视频/语音生成)、Encoder-only、非 Transformers 架构。
8+metadata:
9+ # subagent 绑定声明:id = 委派标识(subagent_type);bind = 委派时须绑定加载的 skill 根目录(含 references/、scripts/)
10+ subagent:
11+ id: "adaptation/calibration"
12+ bind:
13+ 
14+ - "adaptation/calibration"
15+ 
16+---
17+ 
18+# 校准适配(calibration)
19+ 
20+本 Skill 是模型适配(`adaptation`)中的**校准适配**环节:指导如何为新模型创建基础适配器,使其跑通 W8A8/W4A16 校准量化流程。它是「模型适配」的一个子类,另一子类(EP 并行适配)见 [`adaptation/ep`](../ep/SKILL.md);适配顺序见 [`adaptation` 决策树](../SKILL.md)。
21+ 
22+资料:《[ModelSlimPipelineInterfaceV1 / PTQ](../../../docs/zh/knowledge_base/ptq/README.md)》、《[LLM 接入](../../../docs/zh/knowledge_base/model/integrating_models.md)》。
23+ 
24+> 说明:逐层量化(按层加载/懒加载)属于高阶可选特性,不是基础适配必需项。
25+> 仅当 CPU 内存无法全量加载权重,或用户明确要求时,再在基础适配和四步验证(由 `adaptation/calibration/verify` 执行)完成后启用。
26+ 
27+## 适用范围
28+ 
29+- **支持**:Decoder-only LLM、理解类 VLM(只处理文本/LLM 主干)
30+- **不支持**:多模态生成(如 Stable Diffusion/Flux/Wan)、Encoder-only、非 Transformers
31+ 
32+## 核心工作流
33+ 
34+### 1. 权重来源确认(新增必做提示)
35+ 
36+- **先询问并优先使用用户自有权重**:要求用户先提供本地模型权重路径(或已下载模型目录)。
37+- **仅在用户确认“没有权重”时再下载**:再协助用户执行下载流程,不要默认直接下载。
38+- **下载建议**:
39+ - 若先做结构分析,可先下载非权重文件:`modelscope download --model <org>/<model> --local_dir ./models/<name> --exclude '*.safetensors'`
40+ - 若进入完整量化/验证流程,需补齐可用权重文件。
41+ 
42+### 2. 准备工作
43+ 
44+- **分析模型**:阅读 `config.json` 与 `modeling_*.py`,确认结构与实现。
45+ - 详见:[模型结构分析指南](references/model_analysis.md)
46+ 
47+### 3. 创建适配器
48+ 
49+- **使用模板**:
50+ - LLM: `llm/model_adapter_template.py`
51+ - VLM: `vlm/vlm_model_adapter_template.py`
52+- **实现接口**:实现 `handle_dataset`, `init_model`, `generate_model_visit`, `generate_model_forward`, `enable_kv_cache`。
53+- **关键原则**:
54+ - `visit` 与 `forward` 必须严格一致。
55+ - MoE 模型建议 unpack 为纯线性层。
56+ - 若原始模型存在需要保留的 buffer 权重,需在适配器中将其转换为 `nn.Parameter`;否则量化导出阶段通常不会保存 buffer 权重。
57+ - **Tokenizer pad_token 兼容性必查**:若 `tokenizer.pad_token` / `pad_token_id` 为 `None`,必须在适配器中重写 `_load_tokenizer`,将 `pad_token` 回退到 `eos_token`,避免量化过程中在 `padding=True` 时直接报错。
58+ - 常见报错:
59+ - `ValueError: Asking to pad but the tokenizer does not have a padding token.`
60+ - 根因链路:
61+ - `adapter.handle_dataset(...) -> _get_tokenized_data(...) -> tokenizer(..., padding=True, ...)`
62+ - 某些模型(如 MiniMax 系列)原生 tokenizer 未设置 `pad_token`。
63+ - 推荐修复模板:
64+ 
65+ ```python
66+ def _load_tokenizer(self, trust_remote_code=False):
67+ """Ensure tokenizer has a pad token for quantization dataset padding."""
68+ tokenizer = super()._load_tokenizer(trust_remote_code=trust_remote_code)
69+ if tokenizer.pad_token is None:
70+ tokenizer.pad_token = tokenizer.eos_token
71+ return tokenizer
72+ ```
73+ 
74+ - 说明:优先使用 `eos_token` 作为回退;若目标模型有更合适的专用 pad token,可按模型官方约定替换。
75+ - 详见:[适配器实现指南](references/implementation_guide.md)
76+ 
77+### 4. 注册与安装
78+ 
79+- 在 `config/config.ini` 注册模型与入口,并执行 `bash install.sh` 安装msModelSlim。
80+- 详见:[适配器注册指南](references/registration_guide.md)
81+ 
82+### 5. 功能性验证(独立 Skill)
83+ 
84+- 适配器开发完成后,告知用户可自动执行功能性验证。
85+- 验证流程已独立为:`adaptation/calibration/verify`。
86+- 该验证 Skill 会自动按四步执行:结构覆盖减层测试模型 -> 全回退量化 -> 权重一致性与可加载/保存验证 -> 实际量化与描述文件规则校验。
87+- **验证语义**:减层覆盖全部结构类型即可(非全量权重);详见 verify skill「验证语义(硬约定)」。
88+ 
89+### 6. 可选高阶特性:逐层量化
90+ 
91+- 触发时机:
92+ - CPU 内存无法全量加载模型权重。
93+ - 用户明确要求“逐层量化/逐层加载/懒加载/按层加载”。
94+- 启用顺序:
95+ - 必须先完成基础适配与四步验证,再进入逐层量化改造。
96+- 实现与验证指引:
97+ - 详见独立 Skill:`adaptation/calibration/layer_wise`
98+ 
99+## 参考资料
100+ 
101+- 权威文档:《[ModelSlimPipelineInterfaceV1 / PTQ](../../../docs/zh/knowledge_base/ptq/README.md)》、《[LLM 大模型接入指南](../../../docs/zh/knowledge_base/model/integrating_models.md)》、《[LLM 量化集成指南](../../../docs/zh/knowledge_base/ptq/llm/integration_guide_large_language_model_quantization.md)》
102+- [模型结构分析指南](references/model_analysis.md)
103+- [适配器实现指南](references/implementation_guide.md)
104+- [适配器注册指南](references/registration_guide.md)
105+- [接口检查清单](references/interface_checklist.md)
106+- [核心工作流](references/core_workflow.md)
107+ 
108+> 上表前两项 docs 文档为适配流程与接口的权威来源;`references/` 下文件仅记录 docs 未覆盖的实战经验(pad_token 兼容、buffer 转 Parameter、MoE unpack 建议等)。
109+ 
110+## 经验条目(Experiences,追加制)
111+ 
112+> 追加规范见 `skills/README.md`「经验条目」。连续编号 `[E-序号]`;正文保留权威展开,本表只做索引 + 元数据登记;来源:实测编号(F0-x / Dx / Tx)| 用户反馈 | 代码实证;验证状态:已回归 | 待验证 | 已上流 docs。本 skill 为**主流程**,仅登记适配流程级原则;具体子任务的操作级经验登记在各子 skill(analyze/dequant/verify/layer_wise)。
113+ 
114+| 条目 | 主题 | 适用条件 / 触发信号 | 结论要点(一句话) | 正文位置 | 来源 | 验证状态 |
115+|------|------|------|------|------|------|------|
116+| E-001 | 权重来源先问后下 | 开始适配前 | 先询问并优先使用用户自有权重路径;仅在用户确认"没有权重"时才协助下载;先做结构分析可 `--exclude '*.safetensors'` 只下非权重文件 | 1. 权重来源确认 | 用户反馈 | 已回归 |
117+| E-002 | pad_token 兼容必查 | 新模型 tokenizer 无 pad | `pad_token`/`pad_token_id` 为 `None` 时必须在 adapter 重写 `_load_tokenizer` 回退到 `eos_token`,否则 `padding=True` 报 `ValueError: Asking to pad...` | 3. 创建适配器 | 实测 | 已回归 |
118+| E-003 | buffer 权重转 Parameter | 模型含需保留的 buffer | 适配器将关键 buffer 转为 `nn.Parameter`;否则量化导出阶段通常不保存 buffer 权重,导致产物缺键 | 3. 创建适配器 | 代码实证 | 已回归 |
119+| E-004 | MoE unpack 与 visit/forward 一致 | MoE 模型适配 | MoE 建议 unpack 为纯线性层;`visit` 与 `forward` 必须严格一致(防遍历与计算错位) | 3. 创建适配器 | 代码实证 | 已回归 |
120+| E-005 | 高阶特性后置 | 用户要求逐层量化时 | 逐层量化(layer_wise)必须建立在基础适配 + 四步验证完成后,不占主链路;仅 CPU 内存无法全量加载或用户明确要求时启用 | 6. 可选高阶特性 | 用户反馈 | 已回归 |
@@ -0,0 +1,189 @@
1+---
2+name: analyze
3+description: 在实现适配器前对候选模型做分析。确定模型实现来源(transformers 或模型目录)、结构特征、内存约束下的逐层量化建议(可选)及 MoE 融合权重风险。适用于用户询问模型适配可行性或做适配前分析时使用。
4+metadata:
5+ # subagent 绑定声明:id = 委派标识(subagent_type);bind = 委派时须绑定加载的 skill 根目录(含 references/、scripts/)
6+ subagent:
7+ id: "adaptation/calibration/analyze"
8+ bind:
9+ 
10+ - "adaptation/calibration/analyze"
11+ 
12+---
13+ 
14+# 模型分析(analyze)
15+ 
16+## 适用范围
17+ 
18+- 支持:
19+ - Decoder-only LLM
20+ - VLM 文本主干分析(仅 LLM/文本路径)
21+- 不支持:
22+ - 既非 transformers 也非模型目录内 `modeling_*.py` 的实现
23+ - 多模态生成模型(图像/视频/语音生成)
24+ 
25+## 输入
26+ 
27+- 模型路径或模型仓库标识
28+- `config.json`
29+- 可选:模型目录内 `modeling_*.py`、`model.safetensors.index.json`
30+- 若本地缺少上述文件,先补齐输入:
31+ - 使用 `modelscope download --model <org>/<model> --local_dir ./models/<name> --exclude '*.safetensors'` 下载非权重文件。
32+ - 在下载目录中读取 `config.json` 与 `modeling_*.py`,作为后续分析输入。
33+ 
34+## 硬门禁:先解析实现来源
35+ 
36+在进行任何结构分析前必须完成。Agent 应按以下步骤手动解析,无需脚本:
37+ 
38+1. **读取 `config.json`**:
39+ - 获取 `model_type`
40+ - 获取 `auto_map`(如有)
41+ 
42+2. **尝试从 transformers 解析实现**:
43+ - 检查 `transformers` 库是否支持该 `model_type`。
44+ - 检查路径:`transformers/models/<model_type>/modeling_<model_type>.py` 是否存在。
45+ - 如果存在,记录为 `transformers` 实现。
46+ 
47+3. **若未解析到,尝试模型目录内实现**:
48+ - 检查 `auto_map` 指向的文件是否存在于模型目录。
49+ - 检查模型目录内是否有 `modeling_*.py` 文件。
50+ - 如果存在,记录为 `model-local` 实现。
51+ 
52+4. **若两种路径均不可用**:
53+ - 停止分析。
54+ - 要求用户提供可读的模型实现代码。
55+ 
56+## 最小工作流
57+ 
58+1. 解析实现来源(完成上述硬门禁)。
59+2. 判断模型类型、结构差异与连接关系:
60+ - 类型:纯 LLM / 多模态理解模型 / 多模态生成模型
61+ - 对比常见 Qwen2类 LLM,记录特殊结构设计(如 MoE、非标准 attention、SSM/混合块、额外头部或并行分支)
62+ - 检查特殊结构连接关系(接入位置、前后依赖、串联/并联/残差连接、是否影响主干遍历)
63+3. 识别结构特征:
64+ - decoder 层类、attention/MLP 模块命名、forward 签名
65+ - **供 verify 减层覆盖**:记录结构标签种类及各自首次出现的层下标(如 `attn:linear_attention+ffn:dense @0`、`attn:full_attention+ffn:dense @3`、`attn:default+ffn:moe @3`);verify Step1 用「覆盖完备最小前缀」,不是全量、也不是盲目前 2层
66+4. 确定影响适配的特征:
67+ - 层遍历路径与顺序
68+ - 是否存在内存约束导致需要逐层量化(可选高阶特性)
69+ - MoE 融合专家权重风险
70+ - 量化模型反量化脚本风险
71+ - MTP 结构实现可得性与权重处理风险
72+5. 产出结构化分析结果(参考下方模板)。
73+6. 给出后续动作:
74+ - 若判定为原生量化模型,优先进入 `adaptation/calibration/dequant`
75+ - 进入 `adaptation/calibration`
76+ - 或阻塞并说明需用户提供的内容
77+ 
78+### 模型类型、结构差异与连接关系判定(相对常见 Qwen2)
79+ 
80+- `纯 LLM`:仅文本 token 输入,主干为 decoder-only 语言模型。
81+- `多模态理解模型`:含视觉/音频等编码器,但生成路径以文本主干为核心,允许仅分析并适配文本部分。
82+- `多模态生成模型`:核心目标是图像/视频/语音生成,当前流程不支持,应直接阻塞并说明原因。
83+- 结构差异只需记录“是否存在 + 影响方向”,不要求深入实现细节。
84+- 连接关系至少记录:特殊结构位于主干的哪个阶段、与哪些模块相连、连接方式(串联/并联/残差)及对遍历/forward 对齐的影响。
85+ 
86+### MoE 布局判定
87+ 
88+- `MoE 非融合`:专家按模块/列表展开(常见为每个 expert 各自持有 `gate/up/down` 线性层)。
89+- `MoE 融合`:多个 expert 的权重被打包为张量参数,不再是一组独立线性层。
90+- 若看到 `gate/up/down` 中任一或多项以 `[..., num_experts, ...]` 或 `[num_experts, ...]` 形式存储,应按“融合”处理。
91+- 对三维专家权重(如 gate/up/down 分别融合成 3D 参数)统一归类为 `MoE 融合`,并在报告中标注“可能需要 unpack”。
92+ 
93+## 输出:分析报告
94+ 
95+Agent 应直接生成分析报告(Markdown 格式),内容必须包含以下要素。请参考下方模板:
96+ 
97+> **产物路径约定**:报告以 Markdown 写入 `{save_path}/model_analysis_report.md`(与 `tuning/references/prepare_model.md` 委派契约、`adaptation/calibration/dequant` 的 `analysis_report_path` 字段一致)。未写盘即视为未完成输出。
98+ 
99+```markdown
100+ 
101+# 分析报告
102+ 
103+## 模型标识
104+- 模型路径/仓库:{model_path}
105+- `model_type`:{model_type}
106+- `architectures`:{architectures}
107+ 
108+## 实现来源解析
109+- 结果:`transformers` | `model-local` | `unsupported`
110+- 依据:
111+ - 解析到的文件路径:{path}
112+ - 相关配置字段(`model_type`、`auto_map`):{details}
113+ 
114+## 模型特征与规格
115+- Hidden size:{hidden_size}
116+- 层数:{num_layers}
117+- Attention heads / KV heads:{num_heads} / {num_kv_heads}
118+- 是否仅分析 VLM 文本部分:是/否
119+ 
120+## 模型类型、结构差异与连接关系
121+- 模型类型:纯 LLM | 多模态理解模型 | 多模态生成模型
122+- 相对常见 Qwen2 的特殊结构:{special_structures}
123+- 特殊结构连接关系:{special_structure_connections}
124+- 对适配流程的影响:{structure_impact}
125+ 
126+## 逐层量化建议(可选高阶特性)
127+- 是否建议逐层量化:是/否
128+- 触发原因(如 CPU 内存无法全量加载权重):{reason}
129+- 约束(内存/运行环境):{constraints}
130+- 说明:逐层量化不是基础适配必需项,建议在基础适配与四步验证完成后再进入 `adaptation/calibration/layer_wise`
131+ 
132+## MoE 评估
133+- 是否含 MoE:是/否
134+- 布局类型:无 MoE | MoE 非融合 | MoE 融合
135+- 疑似融合的键/模块:{keys}
136+- 专家权重形态:独立线性层 | 打包张量(含 3D 专家权重)
137+- 是否需要 unpack:是/否
138+ 
139+## 适配影响要点
140+- Decoder 遍历路径:{traversal_path}
141+- Attention 模块命名:{attn_module}
142+- MLP 模块命名:{mlp_module}
143+- `visit/forward` 严格对齐点:{alignment_points}
144+ 
145+## 量化与 MTP 风险评估
146+- 模型是否已量化:是/否
147+- 量化判定依据:{quant_evidence}
148+- 是否已提供反量化脚本:是/否
149+- 反量化脚本状态说明:{dequant_status}
150+- 是否存在 MTP 结构:是/否
151+- MTP 实现代码可获取性:可获取/不可获取
152+- MTP 风险说明:{mtp_risk}
153+ 
154+## 风险与后续动作
155+- 风险等级:低 | 中 | 高
156+- 阻塞项:{blockers}
157+- 建议下一步:
158+ - 进入 `adaptation/calibration`
159+ - 或要求用户提供实现代码
160+```
161+ 
162+### 风险识别与用户沟通要求(必须执行)
163+ 
164+- 若识别为“原生量化模型”(模型权重本身已量化),必须先调用 `adaptation/calibration/dequant` skill 再继续后续适配流程。
165+- 若识别为“模型本身已量化”,必须将“缺少反量化脚本”标记为阻塞项,并明确要求用户主动提供反量化脚本后再继续适配。
166+- 若识别到存在 MTP 结构但无法获取其实现代码,必须明确告知:
167+ - Agent 可能无法完整实现 MTP 结构适配;
168+ - 如需继续,用户需要自行复制 MTP 相关权重(按用户侧已有实现进行映射)。
169+- 上述两类风险至少有一项命中时,`风险等级` 不得低于“中”。
170+ 
171+## 通过/失败标准
172+ 
173+- 通过:实现来源为 `transformers` 或 `model-local`,模型类型为纯 LLM 或多模态理解模型,且报告完整;若命中量化/MTP 风险,已在报告中给出明确用户动作要求。
174+- 失败:来源未解析、为不支持的实现类型、判定为多模态生成模型,或命中“量化模型但无反量化脚本”阻塞条件。
175+ 
176+## 参考
177+ 
178+- [分析检查清单](references/analysis_checklist.md)
179+ 
180+## 经验条目(Experiences,追加制)
181+ 
182+> 追加规范见 `skills/README.md`「经验条目」。连续编号 `[E-序号]`;正文保留权威展开,本表只做索引 + 元数据登记;来源:实测编号(F0-x / Dx / Tx)| 用户反馈 | 代码实证;验证状态:已回归 | 待验证 | 已上流 docs。
183+ 
184+| 条目 | 主题 | 适用条件 / 触发信号 | 结论要点(一句话) | 正文位置 | 来源 | 验证状态 |
185+|------|------|------|------|------|------|------|
186+| E-001 | 实现来源解析顺序 | 任何结构分析前 | 先查 transformers 是否支持该 `model_type`,再查模型目录 `auto_map` / `modeling_*.py`(model-local),两者均不可用才要求用户提供实现代码 | 硬门禁:先解析实现来源 | 代码实证 | 已回归 |
187+| E-002 | MoE 融合判定 | 含 MoE 的模型 | `gate/up/down` 以 `[..., num_experts, ...]` 或 3D 参数存储即按「融合」处理,报告标注"可能需要 unpack";非融合=专家各自持有独立线性层 | MoE 布局判定 | 代码实证 | 已回归 |
188+| E-003 | 原生量化模型分流 | 权重本身已量化 | 识别为原生量化模型必须先走 `adaptation/calibration/dequant`;缺反量化脚本=阻塞项,须要求用户提供;命中量化/MTP 风险时风险等级不得低于"中" | 风险识别与用户沟通要求 | 用户反馈 | 已回归 |
189+| E-004 | 结构标签交 verify | 产出分析报告时 | 须写出各结构标签首次层下标,供 verify「减层结构覆盖」用;verify 非全量、禁止盲目前 N 层 | 流程 步骤3 | 用户反馈 | 待验证 |
@@ -0,0 +1,60 @@
1+# 分析检查清单
2+ 
3+在开始实现适配器前使用本清单。
4+ 
5+## 1)实现来源
6+ 
7+- [ ] 读取 `config.json`
8+- [ ] 仅解析一种来源:
9+ - [ ] `transformers/models/<model_type>/modeling_<model_type>.py`
10+ - [ ] 通过 `auto_map` 定位模型目录内 `modeling_*.py`
11+- [ ] 若无法解析,停止并要求用户提供实现代码
12+ 
13+## 2)模型类型、结构差异与连接关系(相对常见 Qwen2)
14+ 
15+- [ ] 判定模型类型:纯 LLM / 多模态理解模型 / 多模态生成模型
16+- [ ] 若为多模态理解模型,确认仅分析文本主干范围
17+- [ ] 若为多模态生成模型,标记为不支持并停止后续适配分析
18+- [ ] 记录相对常见 Qwen2 的特殊结构设计及可能影响
19+- [ ] 记录特殊结构连接关系(接入位置、前后依赖、串联/并联/残差)及其对遍历/forward 的影响
20+ 
21+## 3)结构特征
22+ 
23+- [ ] 确认 decoder 层类
24+- [ ] 确认 attention 与 MLP 模块命名
25+- [ ] 确认 forward 签名与关键返回值
26+- [ ] 确认用于遍历的层容器路径
27+ 
28+## 4)逐层量化建议(可选高阶特性)
29+ 
30+- [ ] 评估全量加载内存与当前环境
31+- [ ] 决定是否建议逐层量化(按层加载)
32+- [ ] 记录理由与预期影响
33+- [ ] 若建议启用,备注:先完成基础适配与四步验证,再进入 `adaptation/calibration/layer_wise`
34+ 
35+## 5)MoE 融合风险
36+ 
37+- [ ] 判断模型是否含 MoE
38+- [ ] 若含 MoE,检查专家权重布局(独立线性层 vs 打包张量)
39+- [ ] 检查 `gate/up/down` 是否存在按 expert 维度打包的 3D 权重
40+- [ ] 标记为:无 MoE / MoE 非融合 / MoE 融合
41+- [ ] 记录可能的 unpack 需求
42+ 
43+## 6)适配影响要点
44+ 
45+- [ ] 记录 `generate_model_visit` 的遍历顺序预期
46+- [ ] 记录 `generate_model_forward` 的对齐约束
47+- [ ] 列出可能影响权重一致性检查的风险模块
48+ 
49+## 7)量化模型风险(反量化脚本)
50+ 
51+- [ ] 判断模型是否“本身已量化”
52+- [ ] 若已量化,要求用户主动提供反量化脚本
53+- [ ] 若用户未提供反量化脚本,标记为阻塞并停止后续适配实现
54+ 
55+## 8)MTP 结构风险(实现缺失)
56+ 
57+- [ ] 判断模型是否包含 MTP 结构
58+- [ ] 若存在 MTP,检查是否能获取 MTP 实现代码
59+- [ ] 若无法获取实现代码,明确告知 agent 可能无法完整实现 MTP 结构适配
60+- [ ] 若用户仍需继续,提示用户需自行复制 MTP 权重
@@ -0,0 +1,91 @@
1+#!/usr/bin/env python
2+# -*- coding: UTF-8 -*-
3+"""
4+Adapter-side unpack example for MoE fused weights.
5+ 
6+Goal:
7+- Detect whether experts are packed 3D tensors.
8+- Unpack packed weights into per-expert Linear parameter keys.
9+- Replace original MoE block with unpacked Linear-expert module.
10+"""
11+ 
12+from typing import Dict
13+ 
14+import torch
15+ 
16+from .moe_unpacked_module_example import SparseMoeBlockWithLinearExperts # pylint: disable=relative-beyond-top-level
17+ 
18+ 
19+def is_packed_moe_tensor(key: str, tensor: torch.Tensor) -> bool:
20+ """Heuristic: packed experts are usually 3D for gate_up/down projections."""
21+ if not isinstance(tensor, torch.Tensor):
22+ return False
23+ if tensor.dim() != 3:
24+ return False
25+ return key.endswith("experts.gate_up_proj") or key.endswith("experts.down_proj")
26+ 
27+ 
28+def unpack_packed_moe_weights(state_dict: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]:
29+ """
30+ Unpack common packed MoE keys into Linear-expert keys.
31+ 
32+ Expected packed patterns:
33+ - *.experts.gate_up_proj: [num_experts, 2*intermediate, hidden]
34+ - *.experts.down_proj: [num_experts, hidden, intermediate]
35+ """
36+ unpacked = dict(state_dict)
37+ 
38+ for key, tensor in state_dict.items():
39+ if not is_packed_moe_tensor(key, tensor):
40+ continue
41+ 
42+ if key.endswith("experts.gate_up_proj"):
43+ prefix = key.replace("experts.gate_up_proj", "experts.")
44+ num_experts = tensor.shape[0]
45+ for i in range(num_experts):
46+ gate_w, up_w = tensor[i].chunk(2, dim=0)
47+ unpacked[f"{prefix}{i}.gate_proj.weight"] = gate_w
48+ unpacked[f"{prefix}{i}.up_proj.weight"] = up_w
49+ 
50+ elif key.endswith("experts.down_proj"):
51+ prefix = key.replace("experts.down_proj", "experts.")
52+ num_experts = tensor.shape[0]
53+ for i in range(num_experts):
54+ unpacked[f"{prefix}{i}.down_proj.weight"] = tensor[i]
55+ 
56+ return unpacked
57+ 
58+ 
59+def replace_moe_module_if_needed(layer, cfg, act_fn):
60+ """
61+ Replace fused MoE module with unpacked Linear-expert module.
62+ Call this during layer construction/loading in model_adapter.py.
63+ """
64+ if not hasattr(layer, "mlp") or not hasattr(layer.mlp, "experts"):
65+ return layer
66+ 
67+ # Example: original layer.mlp uses packed expert layout.
68+ unpacked_moe = SparseMoeBlockWithLinearExperts(
69+ hidden_size=cfg.hidden_size,
70+ intermediate_size=cfg.moe_intermediate_size,
71+ num_experts=cfg.num_experts,
72+ top_k=cfg.num_experts_per_tok,
73+ act_fn=act_fn,
74+ )
75+ layer.mlp = unpacked_moe
76+ return layer
77+ 
78+ 
79+def load_layer_with_unpack_example(layer, layer_state_dict: Dict[str, torch.Tensor], strict: bool = False):
80+ """
81+ Example load sequence inside adapter:
82+ 1) unpack packed MoE tensors
83+ 2) load state dict into replaced module structure
84+ """
85+ unpacked_state_dict = unpack_packed_moe_weights(layer_state_dict)
86+ missing, unexpected = layer.load_state_dict(unpacked_state_dict, strict=strict)
87+ return {
88+ "missing_keys": list(missing),
89+ "unexpected_keys": list(unexpected),
90+ "loaded_keys": len(unpacked_state_dict),
91+ }
@@ -0,0 +1,89 @@
1+#!/usr/bin/env python
2+# -*- coding: UTF-8 -*-
3+"""
4+MoE unpacked module example.
5+ 
6+Purpose:
7+- Provide a pure-nn.Linear MoE implementation for models whose original experts
8+ are fused/packed in 3D tensors.
9+- Keep routing logic and forward behavior consistent with the source model.
10+ 
11+Note:
12+- This is an adaptation example, not a drop-in module for every model.
13+- You must align tensor layouts and routing semantics with the original modeling file.
14+"""
15+ 
16+from typing import Callable
17+ 
18+import torch
19+from torch import nn
20+ 
21+ 
22+class MoeExpertMLP(nn.Module):
23+ """Single expert with pure Linear layers."""
24+ 
25+ def __init__(self, hidden_size: int, intermediate_size: int, act_fn: Callable[[torch.Tensor], torch.Tensor]):
26+ super().__init__()
27+ self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
28+ self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
29+ self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
30+ self.act_fn = act_fn
31+ 
32+ def forward(self, x: torch.Tensor) -> torch.Tensor:
33+ gate = self.act_fn(self.gate_proj(x))
34+ up = self.up_proj(x)
35+ return self.down_proj(gate * up)
36+ 
37+ 
38+class TopKRouter(nn.Module):
39+ """Generic top-k router example."""
40+ 
41+ def __init__(self, hidden_size: int, num_experts: int, top_k: int):
42+ super().__init__()
43+ self.top_k = top_k
44+ self.weight = nn.Parameter(torch.empty((num_experts, hidden_size)))
45+ 
46+ def forward(self, hidden_states: torch.Tensor):
47+ logits = torch.nn.functional.linear(hidden_states, self.weight) # pylint: disable=not-callable
48+ probs = torch.softmax(logits, dim=-1, dtype=torch.float)
49+ topk_prob, topk_idx = torch.topk(probs, self.top_k, dim=-1)
50+ topk_prob = topk_prob / topk_prob.sum(dim=-1, keepdim=True)
51+ return logits, topk_prob.to(logits.dtype), topk_idx
52+ 
53+ 
54+class SparseMoeBlockWithLinearExperts(nn.Module):
55+ """
56+ MoE block where each expert is explicit Linear layers.
57+ This is the target structure after unpack.
58+ """
59+ 
60+ def __init__(
61+ self,
62+ hidden_size: int,
63+ intermediate_size: int,
64+ num_experts: int,
65+ top_k: int,
66+ act_fn: Callable[[torch.Tensor], torch.Tensor],
67+ ):
68+ super().__init__()
69+ self.num_experts = num_experts
70+ self.router = TopKRouter(hidden_size=hidden_size, num_experts=num_experts, top_k=top_k)
71+ self.experts = nn.ModuleList([MoeExpertMLP(hidden_size, intermediate_size, act_fn) for _ in range(num_experts)])
72+ 
73+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
74+ batch_size, seq_len, hidden_size = hidden_states.shape
75+ flat = hidden_states.reshape(-1, hidden_size)
76+ _, routing_weights, selected_experts = self.router(flat)
77+ 
78+ out = torch.zeros_like(flat)
79+ # pylint: disable-next=not-callable
80+ expert_mask = torch.nn.functional.one_hot(selected_experts, num_classes=self.num_experts).permute(2, 1, 0)
81+ active_experts = torch.nonzero(expert_mask.sum(dim=(-1, -2)) > 0).flatten()
82+ 
83+ for expert_idx in active_experts.tolist():
84+ top_k_pos, token_idx = torch.where(expert_mask[expert_idx])
85+ expert_out = self.experts[expert_idx](flat[token_idx])
86+ expert_out = expert_out * routing_weights[token_idx, top_k_pos, None]
87+ out.index_add_(0, token_idx, expert_out.to(out.dtype))
88+ 
89+ return out.reshape(batch_size, seq_len, hidden_size)
@@ -0,0 +1,196 @@
1+---
2+name: dequant
3+description: 为 msModelSlim 适配流程注入反量化能力。先识别模型权重是否可反量化,再实现反量化脚本并接入 model_adapter。当前仅覆盖 FP8 的 per-block 与 per-channel 两类;若格式不确定或无公开反量化规则,要求用户提供反量化脚本或浮点权重。
4+metadata:
5+ # subagent 绑定声明:id = 委派标识(subagent_type);bind = 委派时须绑定加载的 skill 根目录(含 references/、scripts/)
6+ subagent:
7+ id: "adaptation/calibration/dequant"
8+ bind:
9+ 
10+ - "adaptation/calibration/dequant"
11+ 
12+---
13+ 
14+# 反量化接入(dequant)
15+ 
16+本 Skill 用于在模型适配前处理 FP8 量化权重,将可识别权重恢复为 bf16/fp16 再进入后续适配与量化流程。
17+ 
18+## 适用范围
19+ 
20+- 支持:
21+ - FP8 per-block(块级 scale)
22+ - FP8 per-channel(通道级 scale)
23+- 不支持(默认阻塞):
24+ - 无法确认反量化规则的自定义量化格式
25+ - 缺少必要 scale/shape 元数据的量化权重
26+ - 仅有推理框架专用格式且无训练侧可逆映射说明
27+ - 非 FP8(例如 INT4/INT8/FP4)在本 Skill 中默认不处理
28+ 
29+## 输入
30+ 
31+- 模型目录(至少含 `config.json`、`model.safetensors.index.json`)
32+- 目标模型的 `model_adapter.py`
33+ 
34+## 硬门禁:先识别“可接受反量化权重”
35+ 
36+在编写任何脚本前,必须先判定是否属于可接受类型:
37+ 
38+1. 读取 `config.json`,提取可用量化配置字段(如量化类型、group/block 相关参数)
39+2. 读取 `model.safetensors.index.json` 的 `weight_map`
40+3. 按键名模式识别:
41+ - **FP8 per-block 可接受**:存在与权重匹配的块级 scale(常见键后缀如 `xxx.weight_scale_inv` / `xxx.scale`),且可推导块大小 `block_size`
42+ - **FP8 per-channel 可接受**:存在与权重某一维匹配的通道级 scale(常见形态如 `[out_features]`、`[in_features]` 或可广播到对应通道维)
43+4. 用 scale 形状对权重形状做一致性检查:
44+ - per-block:`M` 与 `N` 可按 `block_size` 分块并与 scale 网格对应
45+ - per-channel:scale 的长度或广播维度与指定 channel axis 一致
46+5. 若未命中上述模式,标记为 **未知/不接受格式** 并停止实现
47+ 
48+> 必须输出判定依据(命中的键模式、样例键名、对应文件)。
49+ 
50+## 工作流
51+ 
52+### 1) 识别与分流
53+ 
54+- `未量化`:直接跳过反量化步骤,进入后续适配
55+- `可接受量化(FP8 per-block/per-channel)`:进入脚本实现与适配器接入
56+- `未知/不接受量化`:阻塞并向用户提出材料请求(见“阻塞话术”)
57+ 
58+### 2) 实现反量化脚本(`convert_*_to_bf16.py`)
59+ 
60+按 FP8 模式创建脚本,最小要求:
61+ 
62+- 提供 `convert_*` 主转换函数(必须带可见进度条与异常提示)
63+- 脚本只需覆盖当前模型确认的量化模式(可仅实现 per-block)
64+- 将结果统一转换到 `torch.bfloat16`(或项目默认浮点 dtype)
65+- 参数回写必须 dtype-safe(强约束):
66+ - 若原始权重存储 dtype 为 FP8,禁止直接将反量化得到的 BF16/F16 张量回写到该 FP8 存储;否则会发生静默类型回退
67+ - 必须先将“原参数存储”提升到目标浮点 dtype(如 BF16),再执行赋值/拷贝
68+ - 允许不同代码风格实现(原地改写或整体替换),但必须保证 dtype 与 device 一致
69+ - 回写后必须有显式校验(断言或日志)确认最终参数 dtype 已是目标浮点类型,而非 FP8
70+- 对 index 与 safetensors 不匹配场景给出 warning 并安全跳过
71+- 进度条要求:
72+ - 必须使用 `tqdm`(或等价方案)显示转换进度,禁止仅打印日志让用户等待
73+ - `total` 必须可追踪(例如按待转换参数个数/层数),并实时更新 `desc`(如当前层名)
74+ - 长耗时阶段(扫描权重、逐层反量化、保存结果)至少覆盖主耗时环节中的一个;优先覆盖逐层反量化主循环
75+- 导出件要求:
76+ 
77+ -如果反量化脚本可以离线运行,即可以先运行反量化导出浮点权重,则导出件中必须包含原生权重中的所有文件,**特别**是 `chat_template.jinja` 文件,该文件缺失会导致服务化异常。
78+ 
79+建议函数骨架:
80+ 
81+```python
82+@lru_cache(maxsize=1)
83+def get_weight_map(model_path: str) -> Dict[str, str]:
84+ ...
85+ 
86+@torch.no_grad()
87+def convert_module_xxx_to_bf16(name: str, module: nn.Module, model_path: str, weight_map: Dict[str, str]):
88+ ...
89+```
90+ 
91+per-block 示例:
92+ 
93+```python
94+def decode_fp8_per_block(weight: torch.Tensor, scale: torch.Tensor, block_size: int = 128) -> torch.Tensor:
95+ m, n = weight.shape
96+ scale_expanded = scale.repeat_interleave(block_size, dim=0).repeat_interleave(block_size, dim=1)[:m, :n]
97+ return (weight.float() * scale_expanded.float()).to(torch.bfloat16)
98+```
99+ 
100+参数回写(通用伪代码):
101+ 
102+```python
103+dequant_weight = dequantize(fp8_weight, scale_info) # dequant_weight: BF16/F16
104+if original_param_storage_is_fp8:
105+ promote_original_param_storage_to_target_float_dtype()
106+assign_or_copy_with_device_dtype_alignment(dequant_weight)
107+verify_param_dtype_is_target_float_dtype_not_fp8()
108+```
109+ 
110+可选工具脚本(用于判定权重/scale 形状):
111+ 
112+- `scripts/inspect_safetensors.py`
113+- 用法:
114+ - `python scripts/inspect_safetensors.py -m /path/to/model`
115+ - `python scripts/inspect_safetensors.py -m /path/to/model -p "model.layers.*.mlp*.weight*"`
116+ 
117+### 3) 接入 `model_adapter.py`
118+ 
119+在适配器中注入反量化调用,要求:
120+ 
121+- 在调用 transformers 加载模型前,先检查模型目录 `config.json` 是否包含量化字段(如 `quantization_config`、`quant_method`、`fmt`、`weight_block_size`、`modules_to_not_convert` 等)
122+- 若检测到量化字段,必须主动删除这些字段后再进行加载,避免 transformers 按量化方式装载权重
123+- 删除原因必须在日志中说明:昇腾当前不支持 FP8 量化权重直载,若不清理配置会导致错误加载路径
124+- 可接受实现方式:
125+ - 直接修改模型目录下 `config.json` 后加载;或
126+ - 生成去量化字段的临时 `config` 并确保后续加载明确使用该配置
127+- 无论采用哪种方式,都必须保证“最终传给 transformers 的 config 不包含量化相关字段”
128+- 在模型权重可用后、量化流程前调用 `convert_*_to_bf16`
129+- 仅对命中的量化子模块执行,避免全模型无谓遍历
130+- 转换失败不应静默吞掉;至少记录 warning 与原因
131+ 
132+接入示例:
133+ 
134+```python
135+from .convert_fp8_to_bf16 import convert_module_fp8_to_bf16
136+ 
137+def init_model(...):
138+ model = ...
139+ convert_module_fp8_to_bf16(name="", module=model, model_path=model_path, weight_map=...)
140+ return model
141+```
142+ 
143+### 4) 最小验证
144+ 
145+- 校验可读性:脚本能读取 index 与目标 safetensors
146+- 校验转换:目标层权重 dtype 由 FP8 变为 bf16/fp16
147+- 校验接入:adapter 初始化路径已触发自动转换函数
148+- 校验回退:非量化权重或不匹配键时不报致命错误
149+- 校验模式:输出明确 `per-block` 或 `per-channel` 判定结果
150+- 校验赋值安全:确认未出现“BF16 copy_ 到 FP8 存储后仍为 FP8”的回退问题
151+ 
152+## 阻塞话术(必须原样遵循语义)
153+ 
154+当量化格式不确定或缺反量化规则时,必须明确告知:
155+ 
156+- 当前权重格式无法确认可逆反量化规则,不能安全实现通用反量化。
157+- 请用户二选一提供:
158+ 1. 官方/已验证的反量化脚本(含权重键映射与scale定义)
159+ 2. 对应浮点权重(bf16/fp16/fp32)
160+- 在补齐材料前,不进入模型适配代码改写阶段。
161+ 
162+## 输出模板
163+ 
164+Agent 每次执行本 Skill 后,输出以下结构:
165+ 
166+```markdown
167+ 
168+## 反量化判定结果
169+- 量化状态:未量化 | FP8 per-block(可接受) | FP8 per-channel(可接受) | 未知(阻塞)
170+- 判定依据:{keys/file evidence}
171+ 
172+## 反量化实现动作
173+- 新增/复用脚本:{path}
174+- 核心函数:{functions}
175+- 适配器接入点:{adapter_path + function}
176+ 
177+## 验证结果
178+- 读取校验:通过/失败
179+- dtype 校验:通过/失败
180+- 适配器触发校验:通过/失败
181+ 
182+## 后续动作
183+- 若阻塞:请求用户提供反量化脚本或浮点权重
184+- 若通过:进入模型适配主流程
185+ 
186+## 经验条目(Experiences,追加制)
187+ 
188+> 追加规范见 `skills/README.md`「经验条目」。连续编号 `[E-序号]`;正文保留权威展开,本表只做索引 + 元数据登记;来源:实测编号(F0-x / Dx / Tx)| 用户反馈 | 代码实证;验证状态:已回归 | 待验证 | 已上流 docs。
189+ 
190+| 条目 | 主题 | 适用条件 / 触发信号 | 结论要点(一句话) | 正文位置 | 来源 | 验证状态 |
191+|------|------|------|------|------|------|------|
192+| E-001 | dtype-safe 参数回写 | FP8 权重反量化回写 | 原参数存储为 FP8 时,必须先提升存储到目标浮点 dtype 再赋值,否则 BF16/F16 `copy_` 到 FP8 存储发生静默类型回退;回写后显式校验最终 dtype | 2) 实现反量化脚本-参数回写 | 代码实证 | 已回归 |
193+| E-002 | 导出件完整性 | 反量化可离线运行时 | 导出件必须包含原生权重全部文件,**尤其 `chat_template.jinja`**——缺失会导致服务化异常 | 2) 实现反量化脚本-导出件要求 | 用户反馈 | 待验证 |
194+| E-003 | 加载前清理量化字段 | adapter 接入反量化 | 加载前检查并删除 `config.json` 量化字段(`quantization_config`/`quant_method`/`fmt`/`weight_block_size` 等),昇腾不支持 FP8 量化权重直载;删除原因须日志说明 | 3) 接入 model_adapter | 代码实证 | 已回归 |
195+| E-004 | 格式不确定即阻塞 | 无法确认可逆反量化规则 | 不得安全实现通用反量化:按「阻塞话术」要求用户二选一提供官方反量化脚本或浮点权重,补齐前不进入适配改写 | 阻塞话术 | 用户反馈 | 已回归 |
196+```
@@ -0,0 +1,81 @@
1+#!/usr/bin/env python3
2+# -*- coding: UTF-8 -*-
3+ 
4+import argparse
5+import fnmatch
6+import json
7+import os
8+from collections import defaultdict
9+ 
10+from safetensors import safe_open
11+ 
12+ 
13+def parse_args():
14+ parser = argparse.ArgumentParser(description="Inspect tensor shape and dtype from safetensors by wildcard pattern.")
15+ parser.add_argument(
16+ "-m",
17+ "--model-path",
18+ required=True,
19+ help="Model directory containing model.safetensors.index.json",
20+ )
21+ parser.add_argument(
22+ "-p",
23+ "--pattern",
24+ default="*",
25+ help="Wildcard pattern for tensor names, e.g. 'model.layers.*.mlp*.weight*'",
26+ )
27+ parser.add_argument(
28+ "--limit",
29+ type=int,
30+ default=0,
31+ help="Limit output rows, 0 means no limit",
32+ )
33+ return parser.parse_args()
34+ 
35+ 
36+def load_weight_map(model_path: str):
37+ index_path = os.path.join(model_path, "model.safetensors.index.json")
38+ with open(index_path, "r", encoding="utf-8") as f:
39+ model_index = json.load(f)
40+ return model_index.get("weight_map", {})
41+ 
42+ 
43+def main():
44+ args = parse_args()
45+ weight_map = load_weight_map(args.model_path)
46+ if not weight_map:
47+ print("No weight_map found in model.safetensors.index.json")
48+ return
49+ 
50+ filtered_names = [name for name in weight_map if fnmatch.fnmatch(name, args.pattern)]
51+ if not filtered_names:
52+ print(f"No tensors matched pattern: {args.pattern}")
53+ return
54+ 
55+ file_to_names = defaultdict(list)
56+ for name in filtered_names:
57+ file_to_names[weight_map[name]].append(name)
58+ 
59+ rows = []
60+ for file_name, names in sorted(file_to_names.items()):
61+ file_path = os.path.join(args.model_path, file_name)
62+ with safe_open(file_path, framework="pt", device="cpu") as f:
63+ available = set(f.keys())
64+ for name in sorted(names):
65+ if name not in available:
66+ rows.append((name, "N/A", "N/A", file_name))
67+ continue
68+ tensor = f.get_tensor(name)
69+ rows.append((name, str(tuple(tensor.shape)), str(tensor.dtype), file_name))
70+ 
71+ if args.limit > 0:
72+ rows = rows[: args.limit]
73+ 
74+ print(f"Matched tensors: {len(rows)} (pattern={args.pattern})")
75+ print("tensor_name\tshape\tdtype\tfile")
76+ for name, shape, dtype, file_name in rows:
77+ print(f"{name}\t{shape}\t{dtype}\t{file_name}")
78+ 
79+ 
80+if __name__ == "__main__":
81+ main()
@@ -0,0 +1,64 @@
1+---
2+name: layer-wise
3+description: 为 msModelSlim 适配器实现逐层量化(按层加载/懒加载)能力。仅在用户明确要求逐层量化或基础适配因 CPU 内存不足无法全量加载权重时使用。该特性为高阶可选项,不是基础适配必需项。
4+---
5+ 
6+# 逐层量化(layer-wise)
7+ 
8+用于在基础适配完成后,为超大模型增加“逐层量化(按层加载)”能力,以降低 CPU 内存峰值。
9+ 
10+## 触发条件
11+ 
12+- 用户明确要求实现逐层量化/逐层加载/懒加载/按层加载。
13+- 或基础适配已完成,但 CPU 内存无法全量加载权重。
14+ 
15+## 必须前置条件
16+ 
17+- 已完成基础适配器开发(5个基础接口可用)。
18+- 已完成四步验证流程(生成测试模型 -> 全回退量化 -> 权重一致性 -> 实际量化验证)。
19+- 模型目录存在 `model.safetensors.index.json`,且可按层定位权重。
20+ 
21+## 约束声明(必须告知用户)
22+ 
23+- 逐层量化是高阶可选特性,不是基础适配必需项。
24+- 该方案用于“内存受限时继续开发”,不保证一定适配成功。
25+- 常见失败点:层构造参数不一致、权重键名不匹配、自定义模块副作用、MTP/MoE 特殊路径未覆盖。
26+ 
27+## 最小实现步骤
28+ 
29+1. 增加权重索引与按需读取能力:
30+ - `get_weight_map`(缓存 `model.safetensors.index.json`)
31+ - `get_state_dict(module, prefix)`(只读取当前层所需权重)
32+2. 实现按层实例化与加载:
33+ - `load_decoder_if_not_exist(model, name, idx)`
34+ - 缺层时按模板层构造并加载该层 state_dict。
35+3. 实现逐层遍历器:
36+ - `generate_decoder_layer(model)` 按 `num_hidden_layers` 逐层 `yield`。
37+4. 在 `generate_model_visit` 与 `generate_model_forward` 统一使用逐层遍历器,保持严格同序。
38+ 
39+## 最小验证
40+ 
41+- `generate_decoder_layer` 能完整遍历全部层。
42+- `generate_model_visit` 与 `generate_model_forward` 无层序错位。
43+- 抽样 1-2层前向结果 shape 连续、无异常。
44+ 
45+## 参考资料
46+ 
47+- [逐层量化工作流](references/workflow.md)
48+- [逐层量化实现指南](references/implementation_guide.md)
49+- [逐层适配示例代码](references/layerwise_adapter_example.py)
50+ 
51+## 结束输出要求
52+ 
53+- 明确告知用户是否满足触发条件。
54+- 若满足,给出接入点、最小改造范围与验证结果。
55+- 若不满足,建议先完成基础适配与四步验证,不提前进入逐层量化。
56+ 
57+## 经验条目(Experiences,追加制)
58+ 
59+> 追加规范见 `skills/README.md`「经验条目」。连续编号 `[E-序号]`;正文保留权威展开,本表只做索引 + 元数据登记;来源:实测编号(F0-x / Dx / Tx)| 用户反馈 | 代码实证;验证状态:已回归 | 待验证 | 已上流 docs。
60+ 
61+| 条目 | 主题 | 适用条件 / 触发信号 | 结论要点(一句话) | 正文位置 | 来源 | 验证状态 |
62+|------|------|------|------|------|------|------|
63+| E-001 | 逐层量化常见失败点 | 实现逐层加载时 | 层构造参数不一致、权重键名不匹配、自定义模块副作用、MTP/MoE 特殊路径未覆盖是最常见失败点;该方案不保证一定适配成功 | 约束声明 | 代码实证 | 待验证 |
64+| E-002 | visit/forward 同序 | 改造逐层遍历器 | `generate_model_visit` 与 `generate_model_forward` 必须统一使用同一逐层遍历器,保持严格同序,防止层序错位 | 最小实现步骤 4 | 代码实证 | 已回归 |
@@ -0,0 +1,36 @@
1+# 逐层量化实现指南
2+ 
3+## 关键实现点
4+ 
5+### 1) 权重索引缓存
6+ 
7+- 从 `model.safetensors.index.json` 读取 `weight_map`。
8+- 对 `weight_map` 做缓存,避免重复解析。
9+ 
10+### 2) 单层按需加载
11+ 
12+- 实现 `get_state_dict(module, prefix)`。
13+- 按 `prefix` 过滤当前层参数,只读取当前层所需 safetensors 切片。
14+ 
15+### 3) 缺层按需构造
16+ 
17+- 实现 `load_decoder_if_not_exist(model, name, idx)`。
18+- 当目标层不存在时,用模板层构造 `idx` 层并加载该层 state_dict。
19+- 不要硬编码层构造参数,按目标模型真实 block 构造签名适配。
20+ 
21+### 4) 统一层遍历器
22+ 
23+- 实现 `generate_decoder_layer(model)`。
24+- 在 `generate_model_visit` 与 `generate_model_forward` 统一调用该遍历器,保持严格同序。
25+ 
26+## 典型风险点
27+ 
28+- 层构造参数不兼容(例如无 `layer_idx`)。
29+- state_dict 键名前缀与模型层路径不一致。
30+- 远程代码中的自定义模块在懒加载时有副作用。
31+- MTP/MoE 特殊路径未接入逐层逻辑。
32+ 
33+## 完成判定
34+ 
35+- 全层可遍历、visit/forward 同序、抽样前向正常。
36+- 不因逐层改造破坏基础四步验证能力。
@@ -0,0 +1,53 @@
1+import os.path
2+from collections import defaultdict
3+from functools import lru_cache
4+from unittest.mock import patch
5+ 
6+from safetensors import safe_open
7+from torch import nn
8+ 
9+from msmodelslim.utils.security import json_safe_load
10+ 
11+ 
12+class LayerwiseMixin:
13+ @lru_cache(maxsize=1)
14+ def get_weight_map(self):
15+ index_path = os.path.join(self.model_path, "model.safetensors.index.json")
16+ model_index = json_safe_load(index_path)
17+ return model_index["weight_map"]
18+ 
19+ def get_state_dict(self, module: nn.Module, prefix: str = ""):
20+ weight_map = self.get_weight_map()
21+ file_to_names = defaultdict(list)
22+ for name, _ in module.named_parameters():
23+ full_name = f"{prefix}.{name}" if prefix else name
24+ if full_name in weight_map:
25+ file_to_names[weight_map[full_name]].append(name)
26+ 
27+ state_dict = {}
28+ for file_name, names in file_to_names.items():
29+ file_path = os.path.join(self.model_path, file_name)
30+ with safe_open(file_path, framework="pt", device="cpu") as f:
31+ for name in names:
32+ full_name = f"{prefix}.{name}" if prefix else name
33+ state_dict[name] = f.get_tensor(full_name)
34+ return state_dict
35+ 
36+ def load_decoder_if_not_exist(self, model: nn.Module, name: str, idx: int):
37+ try:
38+ return model.get_submodule(name)
39+ except AttributeError:
40+ with patch.object(nn.Linear, "reset_parameters", lambda _self: None):
41+ module_list: nn.ModuleList = model.model.layers
42+ template_module = module_list[0]
43+ decoder = template_module.__class__(config=self.config, layer_idx=idx)
44+ state_dict = self.get_state_dict(decoder, prefix=name)
45+ decoder.load_state_dict(state_dict)
46+ decoder.eval()
47+ module_list.append(decoder)
48+ return decoder
49+ 
50+ def generate_decoder_layer(self, model: nn.Module):
51+ for idx in range(self.config.num_hidden_layers):
52+ name = f"model.layers.{idx}"
53+ yield name, self.load_decoder_if_not_exist(model, name=name, idx=idx)
@@ -0,0 +1,27 @@
1+# 逐层量化工作流
2+ 
3+用于在基础适配与四步验证通过后,开启按层加载量化路径。
4+ 
5+## 阶段 A:确认是否需要启用
6+ 
7+- 用户明确要求逐层量化/逐层加载/懒加载。
8+- 或 CPU 内存无法全量加载权重。
9+- 若不满足,保持基础适配流程,不进入本工作流。
10+ 
11+## 阶段 B:逐层改造
12+ 
13+1. 增加权重映射读取能力(`model.safetensors.index.json`)。
14+2. 增加按需读取单层权重能力(按 prefix 过滤)。
15+3. 增加缺层时的按层实例化与加载逻辑。
16+4. 统一 `generate_model_visit` 与 `generate_model_forward` 的层遍历来源,确保严格同序。
17+ 
18+## 阶段 C:最小验证
19+ 
20+1. 可完整遍历所有 decoder 层。
21+2. visit/forward 层序一一对应,无错位。
22+3. 抽样前向无 shape 断裂与异常。
23+ 
24+## 验收规则
25+ 
26+- 若阶段 B/C 全通过,可标记“逐层量化改造完成”。
27+- 若失败,回退到基础适配路径并记录阻塞点(层构造参数、键名映射、模块副作用等)。
@@ -0,0 +1,20 @@
1+apiversion: modelslim_v1
2+spec:
3+ process:
4+ - type: "linear_quant"
5+ qconfig:
6+ act:
7+ scope: "per_token"
8+ dtype: "int8"
9+ symmetric: True
10+ method: "minmax"
11+ weight:
12+ scope: "per_channel"
13+ dtype: "int8"
14+ symmetric: True
15+ method: "minmax"
16+ include: ["*"]
17+ exclude: ["*"] # 全部回退,不实际量化任何层
18+ save:
19+ - type: "ascendv1_saver"
20+ part_file_size: 4
@@ -0,0 +1,46 @@
1+#!/usr/bin/env python
2+# -*- coding: UTF-8 -*-
3+ 
4+"""
5+基础模型适配器模板
6+适用于 HuggingFace Transformers 的 Decoder-only LLM。
7+"""
8+ 
9+from typing import List, Any, Generator
10+ 
11+from torch import nn
12+from msmodelslim.core.base.protocol import ProcessRequest
13+from msmodelslim.core.const import DeviceType
14+from msmodelslim.model.common.layer_wise_forward import (
15+ generated_decoder_layer_visit_func,
16+ transformers_generated_forward_func,
17+)
18+from msmodelslim.model.common.transformers import TransformersModel
19+from msmodelslim.model.interface_hub import ModelInfoInterface, ModelSlimPipelineInterfaceV1
20+from msmodelslim.utils.logging import logger_setter
21+ 
22+ 
23+@logger_setter()
24+class MyModelAdapter(TransformersModel, ModelInfoInterface, ModelSlimPipelineInterfaceV1): # pylint: disable=too-many-ancestors
25+ # ==================== ModelInfoInterface ====================
26+ def get_model_pedigree(self) -> str:
27+ return "my_model"
28+ 
29+ def get_model_type(self) -> str:
30+ return self.model_type
31+ 
32+ # ==================== ModelSlimPipelineInterfaceV1 ====================
33+ def handle_dataset(self, dataset: Any, device: DeviceType = DeviceType.NPU) -> List[Any]:
34+ return self._get_tokenized_data(dataset, device)
35+ 
36+ def init_model(self, device: DeviceType = DeviceType.NPU) -> nn.Module:
37+ return self._load_model(device)
38+ 
39+ def generate_model_visit(self, model: nn.Module) -> Generator[ProcessRequest, Any, None]:
40+ yield from generated_decoder_layer_visit_func(model)
41+ 
42+ def generate_model_forward(self, model: nn.Module, inputs: Any) -> Generator[ProcessRequest, Any, None]:
43+ yield from transformers_generated_forward_func(model, inputs)
44+ 
45+ def enable_kv_cache(self, model: nn.Module, need_kv_cache: bool) -> None:
46+ return self._enable_kv_cache(model, need_kv_cache)
@@ -0,0 +1,22 @@
1+apiversion: modelslim_v1
2+ 
3+spec:
4+ process:
5+ - type: "linear_quant"
6+ qconfig:
7+ act:
8+ scope: "per_token" # dynamic
9+ dtype: "int8"
10+ symmetric: True
11+ method: "minmax"
12+ weight:
13+ scope: "per_channel"
14+ dtype: "int8"
15+ symmetric: True
16+ method: "minmax"
17+ include: ["*"] # 全模型量化
18+ exclude: [] # 不回退
19+ 
20+ save:
21+ - type: "ascendv1_saver"
22+ part_file_size: 4
@@ -0,0 +1,22 @@
1+apiversion: modelslim_v1
2+ 
3+spec:
4+ process:
5+ - type: "linear_quant"
6+ qconfig:
7+ act:
8+ scope: "per_tensor" # static
9+ dtype: "int8"
10+ symmetric: False
11+ method: "minmax"
12+ weight:
13+ scope: "per_channel"
14+ dtype: "int8"
15+ symmetric: True
16+ method: "minmax"
17+ include: ["*"] # 全模型量化
18+ exclude: [] # 不回退
19+ 
20+ save:
21+ - type: "ascendv1_saver"
22+ part_file_size: 4
@@ -0,0 +1,28 @@
1+# 核心工作流(创建 + 独立验证触发)
2+ 
3+本 Skill 负责基础适配器创建,并在完成后触发独立验证 Skill。
4+ 
5+## 阶段 0:权重来源确认(先确认再下载)
6+ 
7+1. 先要求用户提供模型权重(本地目录或可访问路径)。
8+2. 若用户已提供权重,直接使用用户权重继续后续流程。
9+3. 仅当用户明确确认“没有可用权重”时,再协助下载模型。
10+4. 结构分析可先下载非权重文件;进入完整量化与验证前,需确保权重已补齐。
11+ 
12+## 阶段 A:创建适配器
13+ 
14+1. 选择模板:
15+ - LLM 使用 `model_adapter_template.py`
16+ - VLM 文本路径使用 `vlm_model_adapter_template.py`
17+2. 实现必需接口。
18+3. 在 `config/config.ini` 中注册模型类型与入口。
19+ 
20+## 阶段 B:触发功能性验证(独立 Skill)
21+ 
22+1. 适配器开发完成后,明确告知用户可自动执行功能性验证。
23+2. 调用独立 Skill:`adaptation/calibration/verify`。
24+3. 由该 Skill 按四步自动完成验证并返回结果。
25+ 
26+## 验收规则
27+ 
28+仅当阶段 A 完成,且阶段 B 的独立验证 Skill 返回通过时,才将适配器标记为完成。
@@ -0,0 +1,143 @@
1+# 适配器实现指南
2+ 
3+## 目录结构(建议)
4+ 
5+一个模型适配器目录通常至少包含以下文件:
6+ 
7+```text
8+msmodelslim/model/<model_type>/
9+├── __init__.py
10+├── model_adapter.py
11+└── model.py
12+```
13+ 
14+- `__init__.py`:必须存在,保证目录可作为 Python 包被导入
15+- `model_adapter.py`:适配器入口与 5个必需接口实现
16+- `model.py`:模型结构相关实现(如分层访问/前向辅助逻辑)
17+ 
18+如果该模型已有其他依赖文件(如 `utils.py`、`configuration_*.py`),按实际需要补充,但不要省略 `__init__.py`。
19+ 
20+## 必需接口
21+ 
22+1. `handle_dataset`
23+2. `init_model`
24+3. `generate_model_visit`
25+4. `generate_model_forward`
26+5. `enable_kv_cache`
27+ 
28+## 按模板区分:LLM / VLM 必要接口
29+ 
30+以下结论基于 `assets/model_adapter_template.py` 与 `assets/vlm_model_adapter_template.py`。
31+ 
32+### LLM(Decoder-only)
33+ 
34+- **推荐继承**:`TransformersModel + ModelSlimPipelineInterfaceV1`(`ModelInfoInterface` 可选但建议)
35+- **必须实现**(5个):`handle_dataset`、`init_model`、`generate_model_visit`、`generate_model_forward`、`enable_kv_cache`
36+- **模板中常见辅助方法**(非框架强制):`_create_model_instance` 等模型初始化辅助函数
37+ 
38+### VLM(多模态理解,仅图文理解)
39+ 
40+- **推荐继承**:`VLMBaseModelAdapter + ModelSlimPipelineInterfaceV1`(`ModelInfoInterface` 可选但建议)
41+- **必须实现**(5个):`handle_dataset`、`init_model`、`generate_model_visit`、`generate_model_forward`、`enable_kv_cache`
42+- **模板中常见辅助方法**(非框架强制):`_create_model_instance` 等模型初始化辅助函数
43+ 
44+## 特殊情况(需要单独处理)
45+ 
46+### LLM 特殊情况
47+ 
48+- **decoder 路径不一致**:不一定是 `model.layers`,也可能是 `model.decoder.layers` 或其他路径;必须改 `_decoder_layer_prefix`。
49+- **MoE packed 权重**:若为 3D packed experts,需先 unpack,再替换为线性层专家模块。
50+- **非标准配置字段**:若无 `num_hidden_layers` 或字段名不同,`init_model` 要按目标 config 改写。
51+- **tokenizer 无 pad_token**:若 `tokenizer.pad_token` / `pad_token_id` 为 `None`,Step2 全回退量化会在 `padding=True` 时报错。需在适配器中重写 `_load_tokenizer`,将 `pad_token` 回退为 `eos_token`(或模型官方推荐 pad token)。
52+ 
53+### VLM 特殊情况
54+ 
55+- **数据必须图文成对**:`handle_dataset` 需要同时有 `text` 和 `image`,纯文本样本不适配该模板。
56+- **视觉/文本路径差异**:模板假设 `model.visual` 与 `model.language_model.layers`,目标模型可能不同,需按真实 `modeling` 改。
57+- **融合逻辑不可套模板**:`generate_model_forward` 中 image embeds 注入规则(token id、位置编码、mask)模型差异大,必须对齐官方 forward。
58+- **text_config 结构差异**:若不存在 `config.text_config`,需改为模型实际文本配置路径并同步层数字段。
59+- **processor 行为差异**:不同模型 `AutoProcessor` 的输入键和 `apply_chat_template` 返回字段不同,需按真实返回值调整 keys。
60+ 
61+### 接口功能说明(必须落实到代码)
62+ 
63+#### 1) `handle_dataset(dataset, device) -> List[Any]`
64+ 
65+- **职责**:把原始校准样本转成模型可直接消费的输入列表。
66+- **输入**:原始数据集(通常是文本 list)和目标设备。
67+- **输出**:`List[Any]`,每个元素可直接用于一次前向(如 `model(*data)` 或 `model(**data)`)。
68+- **实现建议**:优先复用基类 tokenization 能力(如 `_get_tokenized_data`),保证字段名与模型 forward 参数对齐。
69+- **完成判定**:量化流程读取该列表后,无需额外数据转换即可进入 `generate_model_forward`。
70+ 
71+#### 2) `init_model(device) -> nn.Module`
72+ 
73+- **职责**:初始化并返回可参与量化流程的模型实例。
74+- **输入**:目标设备(NPU/CPU)。
75+- **输出**:`nn.Module`(`eval()` 状态)。
76+- **实现建议**:按模型真实结构加载,确保后续 visit/forward 可访问到目标层。
77+- **完成判定**:返回模型后,`generate_model_visit` 能遍历目标量化层,且前向可执行。
78+ 
79+#### 3) `generate_model_visit(model) -> Generator[ProcessRequest, Any, None]`
80+ 
81+- **职责**:定义“按什么顺序遍历哪些模块”进行逐层处理。
82+- **输入**:初始化后的模型。
83+- **输出**:按顺序 `yield ProcessRequest`(每个 request 对应一个待处理模块)。
84+- **实现建议**:以真实 decoder/block 顺序输出,不跳层、不重排;名称路径应可唯一定位模块。
85+- **完成判定**:产出的层序列与 `generate_model_forward` 一一对应。
86+ 
87+#### 4) `generate_model_forward(model, inputs) -> Generator[ProcessRequest, Any, None]`
88+ 
89+- **职责**:定义与 `visit` 对齐的分段前向,用于逐层校准。
90+- **输入**:模型 + 单条校准输入。
91+- **输出**:按顺序 `yield ProcessRequest`(包含该层执行所需输入)。
92+- **实现建议**:层顺序、分段边界、张量传递路径与 `generate_model_visit` 严格一致。
93+- **完成判定**:同一层在 visit/forward 的索引和语义完全匹配,不出现错位。
94+ 
95+#### 5) `enable_kv_cache(model, need_kv_cache) -> None`
96+ 
97+- **职责**:统一控制 KV Cache 开关。
98+- **输入**:模型实例与布尔开关。
99+- **输出**:无返回(原地修改)。
100+- **实现建议**:优先复用基类 `_enable_kv_cache`;至少确保主干模型 config 中 `use_cache` 被正确设置。
101+- **完成判定**:开关后模型行为与预期一致,校准场景下通常可关闭以降低内存占用。
102+ 
103+## 关键实现原则
104+ 
105+### 1) `generate_model_visit` 与 `generate_model_forward` 必须严格一致
106+ 
107+- 遍历层集合一致
108+- 顺序一致
109+- 分层输入输出传递一致
110+ 
111+这是最容易出错、也最影响量化正确性的部分。
112+ 
113+### 2) 不要靠模型名猜结构
114+ 
115+必须以真实 `modeling` 代码为准,确认层路径、命名和 forward 行为后再写适配器。
116+ 
117+### 3) VLM 只走“视觉整体 + 文本逐层”
118+ 
119+- 优先复用 VLM 基类
120+- visit/forward 中视觉模块与文本层顺序保持一致
121+- 图文融合逻辑需对齐目标模型官方 forward
122+ 
123+### 4) MoE 融合结构优先按“unpack 后纯线性层”适配
124+ 
125+很多新模型的 MoE 使用融合/打包权重(常见为 3D 张量),而量化流程通常更适合 `nn.Linear` 形式的专家实现。
126+ 
127+实现要求:
128+ 
129+- 先判断原始实现是否为 3D packed experts(不要假设所有 MoE 都一样)
130+- 若是 packed 结构,不仅要在加载时 unpack,还要实现对应的 MoE 拆分 module
131+- unpack 后专家应落到纯线性层(`gate_proj` / `up_proj` / `down_proj`),避免后续流程直接依赖 3D 权重
132+- 推荐结构:`moe_utils.py` 提供拆分后的 MoE module,`model_adapter.py` 负责权重 unpack 与模块替换
133+ 
134+可参考 `qwen3_5` 的实现思路(`moe_utils.py`、`modeling_qwen3_5_mtp.py`):先识别 packed 权重,再拆分为逐 expert 线性层。
135+ 
136+示例代码请参考:
137+ 
138+- `assets/moe_unpacked_module_example.py`
139+- `assets/moe_unpacked_adapter_example.py`
140+ 
141+## 可选高阶特性
142+ 
143+逐层量化(按层加载/懒加载)相关实现、工作流与示例见独立 Skill `adaptation/calibration/layer_wise`。
@@ -0,0 +1,22 @@
1+# 必需接口检查清单
2+ 
3+在跑验证前,确认以下方法已实现并正确接入:
4+ 
5+- [ ] `handle_dataset`
6+- [ ] `init_model`
7+- [ ] `generate_model_visit`
8+- [ ] `generate_model_forward`
9+- [ ] `enable_kv_cache`
10+ 
11+## 对齐检查
12+ 
13+- [ ] `generate_model_visit` 与 `generate_model_forward` 遍历的层一致
14+- [ ] 遍历顺序一致
15+- [ ] 层间输入输出传递一致
16+- [ ] 检查 tokenizer 的 `pad_token` / `pad_token_id`:若为 `None`,已在适配器中重写 `_load_tokenizer` 并设置 `pad_token = eos_token`
17+ 
18+## 注册检查
19+ 
20+- [ ] `config/config.ini` 的 `[ModelAdapter]` 下已配置模型别名
21+- [ ] `config/config.ini` 的 `[ModelAdapterEntryPoints]` 下已配置入口
22+- [ ] 代码修改后已重新安装包
@@ -0,0 +1,93 @@
1+# 模型适配基础接口参考
2+ 
3+本文档只保留模型适配开发所需的基础接口。
4+不包含 SmoothQuant、QuaRot、FA3、FlatQuant 等高阶算法接口。
5+ 
6+## 1) IModel(基础模型属性)
7+ 
8+**位置**: `msmodelslim/model/interface.py`
9+ 
10+所有适配器的基础属性接口:
11+ 
12+```python
13+class IModel:
14+ @property
15+ def model_type(self) -> str
16+ 
17+ @property
18+ def model_path(self) -> Path
19+ 
20+ @property
21+ def trust_remote_code(self) -> bool
22+```
23+ 
24+实现要求:
25+ 
26+- `model_type`:返回模型类型标识。
27+- `model_path`:返回模型目录路径。
28+- `trust_remote_code`:返回是否允许远程代码。
29+ 
30+## 2) ModelSlimPipelineInterfaceV1(必需)
31+ 
32+**位置**: `msmodelslim/core/runner/pipeline_interface.py`
33+ 
34+基础量化适配必须实现的核心接口:
35+ 
36+```python
37+class PipelineInterface(IModel):
38+ @abstractmethod
39+ def handle_dataset(self, dataset: Any, device: DeviceType = DeviceType.NPU) -> List[Any]:
40+ ...
41+ 
42+ @abstractmethod
43+ def init_model(self, device: DeviceType = DeviceType.NPU) -> nn.Module:
44+ ...
45+ 
46+ @abstractmethod
47+ def generate_model_visit(self, model: nn.Module) -> Generator[ProcessRequest, Any, None]:
48+ ...
49+ 
50+ @abstractmethod
51+ def generate_model_forward(self, model: nn.Module, inputs: Any) -> Generator[ProcessRequest, Any, None]:
52+ ...
53+ 
54+ @abstractmethod
55+ def enable_kv_cache(self, model: nn.Module, need_kv_cache: bool) -> None:
56+ ...
57+```
58+ 
59+实现重点:
60+ 
61+- `generate_model_visit` 与 `generate_model_forward` 的层顺序必须严格一致。
62+- `handle_dataset` 输出必须可直接用于前向。
63+- `init_model` 返回可执行前向且可被逐层访问的模型。
64+ 
65+## 3) ModelInfoInterface(推荐)
66+ 
67+**位置**: `msmodelslim/app/naive_quantization/model_info_interface.py`
68+(部分场景也在 `msmodelslim/app/auto_tuning/model_info_interface.py` 使用)
69+ 
70+用于提供模型基础信息:
71+ 
72+```python
73+def get_model_pedigree(self) -> str
74+def get_model_type(self) -> str
75+```
76+ 
77+说明:
78+ 
79+- 该接口通常与 `TransformersModel + ModelSlimPipelineInterfaceV1` 组合使用。
80+- 若你的适配流程或导出流程依赖模型家族信息,建议实现。
81+ 
82+## 推荐继承组合
83+ 
84+基础模型适配(LLM/VLM 文本主干)建议:
85+ 
86+```python
87+class MyModelAdapter(TransformersModel,
88+ ModelInfoInterface,
89+ ModelSlimPipelineInterfaceV1):
90+ pass
91+```
92+ 
93+若当前场景不需要模型信息能力,可省略 `ModelInfoInterface`,但 `ModelSlimPipelineInterfaceV1` 不可省略。
@@ -0,0 +1,19 @@
1+# 模型结构分析指南
2+ 
3+## 1. 确认模型结构来源
4+ 
5+- 读取 `config.json` 的 `model_type`、`architectures`、`auto_map`
6+- 确定结构来自模型仓内 `modeling_*.py` 还是 transformers 官方实现
7+ 
8+## 2. 定位并阅读模型实现(必须)
9+ 
10+- **自定义实现**:如果 `auto_map` 指向自定义实现(如 `modeling_xxx.XXXForCausalLM`),优先阅读模型目录中的 `modeling_*.py`
11+- **官方实现**:如果使用 transformers 官方实现,通常在:
12+ - 源码路径:`transformers/src/transformers/models/<model_type>/modeling_<model_type>.py`
13+ - 导入路径:`transformers.models.<model_type>.modeling_<model_type>`
14+- **重点阅读**:
15+ - DecoderLayer 定义
16+ - attention/MLP 命名
17+ - `forward` 入参与返回值
18+- **MoE 模型额外检查**:
19+ - `experts` 权重是否为 3D packed 结构(如 `experts.gate_up_proj` / `experts.down_proj`)
@@ -0,0 +1,39 @@
1+# 适配器注册指南
2+ 
3+在 `config/config.ini` 中注册模型与入口。
4+ 
5+## 机制(setup.py entry point 如何生效)
6+ 
7+`install.sh` 走 `setup.py` 的 develop/editable 安装,把 `config.ini` 中的注册项生成为 Python **entry point**(console 插件),`msmodelslim` 运行期通过 `PluginModelFactory` 按 `--model_type` 的值查找插件:
8+ 
9+- `[ModelAdapter]`:`<适配组> = <型号值1>, <型号值2>, ...` —— 每个型号值就是用户 `--model_type` 会传入的字符串(如 `Qwen3-4B`)。同一适配组的多个型号共享同一套适配器实现。
10+- `[ModelAdapterEntryPoints]`:`<适配组> = <loader 的 python 导入路径>:<Loader 类名>` —— loader 指向键即适配组名(与 `[ModelAdapter]` 的组名一致);Loader 负责根据具体型号实例化对应的 `ModelAdapter`。
11+- `[ModelAdapterDependencies]`:`<适配组> = {"transformers": "==4.51.0"}` —— **语义是"依赖告警 + 包装",不是硬失败**:环境版本不匹配时通常告警并尝试继续(或做轻量兼容包装),不会因版本锁直接拒载。不要误以为必须精确装到锁的版本;也不要因为"只是告警"就无视,运行期异常往往由此而来。
12+ 
13+## 示例
14+ 
15+```ini
16+[ModelAdapter]
17+my_model = MyModel-7B, MyModel-13B
18+ 
19+[ModelAdapterEntryPoints]
20+my_model = msmodelslim.model.my_model.loader:MyModelLoader
21+```
22+ 
23+## 注册 → 重装 → 验证配方
24+ 
25+1. 新建适配器源码目录(如 `msmodelslim/model/my_model/`,含 `__init__.py`、`loader.py`、`model_adapter.py`)。
26+2. 在 `config/config.ini` 补上述三个小节(按需)的条目。
27+3. 执行 `bash install.sh` 重新安装,让 entry point 生效。
28+4. **验证:在仓库根目录之外**执行(源码树 import 遮蔽会假失败,见 installation 技能 F0-2):
29+ - 查看注册是否生效:列出 entry points,确认新适配器出现在列表中且数量 +1;
30+ - 负向对照:用一个未注册型号触发,确认回落 `default` 适配器(证明正向命中不是 default 兜底)。
31+ 
32+## 适配面提示(V0 vs V1)
33+ 
34+`adaptation/calibration` 模板实现的是 `ModelSlimPipelineInterfaceV1` 五接口。同仓 dense 模型适配器普遍继承 `DefaultModelAdapter`,其中还含 **V0 兼容方法**(如 `load_model`、`handle_dataset_by_batch`)。是否补 V0 方法取决于下游走哪套配置:
35+ 
36+- 调优/量化走 `modelslim_v1`(apiversion 为 `modelslim_v1`)→ 只需 V1 五接口;
37+- 若需消费 `modelslim_v0` 的 best-practice 配置(`lab_practice` 中的旧格式文件)或旧接口调用方 → 需补 V0 方法。
38+ 
39+判断依据以实际要复用的配置/调用方为准,技能未在别处强制。
@@ -0,0 +1,98 @@
1+---
2+name: verify
3+description: 为 msModelSlim 适配器执行功能性验证。语义为减层结构覆盖(非全量):Step1 按结构类型生成覆盖完备的最小前缀测试模型,再跑全回退量化、权重一致性、W8A8 描述校验四步门禁。
4+metadata:
5+ # subagent 绑定声明:id = 委派标识(subagent_type);bind = 委派时须绑定加载的 skill 根目录(含 references/、scripts/)
6+ # 双重角色:① calibration 子任务的内部门禁(适配完成时自带四步验证);② 主 Agent 的适配验收门禁(NPU 前向推理判定基准)
7+ subagent:
8+ id: "adaptation/calibration/verify"
9+ bind:
10+ 
11+ - "adaptation/calibration/verify"
12+ 
13+---
14+ 
15+# 适配器功能性验证(verify)
16+ 
17+用于在基础适配器开发完成后,自动帮助用户进行功能性验证。
18+ 
19+## 验证语义(硬约定)
20+ 
21+- **减层结构覆盖验证,不是全量验证。**
22+- 大模型由若干**结构类型**(如 `attn:linear_attention` / `attn:full_attention` × `ffn:dense` / `ffn:moe`)堆叠而成;验证只需每种结构至少出现一次。
23+- Step1 默认取「覆盖完备的**最小前缀层数**」生成随机权重测试模型;**禁止**盲目前 N 层(会漏 MoE、full_attn 等)。
24+- **不要求**加载或量化原始全层权重;全量精度/性能属于后续量化调优,不在本 skill 范围。
25+ 
26+## 触发条件
27+ 
28+- `adaptation/calibration` 已完成适配器开发与注册安装。
29+- 用户希望确认适配器是否可用,或要求执行标准验证流程。
30+ 
31+## 执行要求
32+ 
33+- 必须按顺序执行四步验证,不可跳步。
34+- 每一步失败都要立即停止并返回失败原因与下一步修复建议。
35+- 仅当四步全部通过时,返回“功能性验证通过”。
36+- 修改代码后要执行 `bash install.sh` 重新安装msModelslim
37+- 若验证过程中出现模型实现文件(如权重目录内 `modeling_*.py`)报错,必须先判断是否为 `transformers` 版本不契合导致。
38+- 对疑似版本不契合问题,必须先告知用户并确认版本需求(目标版本或可接受版本区间);未确认前不得切换版本。
39+- 仅在用户确认后,才可执行 `transformers` 版本切换与重试验证。
40+ 
41+## 四步验证流程
42+ 
43+1. Step1:按结构覆盖计划生成减层随机权重测试模型(见下方硬门禁)。
44+2. Step2:执行全回退量化,验证流程与注册生效。
45+3. Step3:验证 Step2 与 Step1 的权重严格一致,且产物可完整加载/保存。
46+4. Step4:执行实际量化(W8A8 静态/动态)并校验描述文件规则。
47+ 
48+## 硬门禁:Step1 结构覆盖
49+ 
50+- 执行前可用 `--plan-only` 打印覆盖计划;正式生成须落盘 `structure_cover_plan.json`。
51+- 覆盖完备:`incomplete=false`,且日志中每种结构标签均有「首次覆盖 layer[i]」。
52+- 若人为传入 `--num-layers` 导致漏盖 → **FAIL**(除非显式 `--allow-incomplete-cover`,仅调试)。
53+- 常见漏盖:MoE 从 layer≥K 才开始、hybrid attn 的 full 层按 interval 出现——前缀必须延伸到首次出现该类结构的层。
54+ 
55+## Buffer 权重说明(Step3 常见问题)
56+ 
57+- 若 Step3 出现“全回退权重缺失/键不一致”,需优先检查缺失项是否来自模型 `buffer`。
58+- `msmodelslim` 通常不会保存 `buffer` 类型权重,因此可能导致全回退产物缺少对应键。
59+- 适配器需要主动将这类关键 `buffer` 转为 `nn.Parameter`,以确保量化导出和一致性校验可覆盖该权重。
60+ 
61+## transformers 版本兼容处理(验证期)
62+ 
63+- 触发条件:验证阶段出现模型实现文件导入/模型forward错误,且报错指向 `transformers` API 变更、缺失符号或签名不匹配。
64+- 必做沟通:向用户说明“当前报错疑似版本兼容问题”、给出关键报错摘要、请求确认目标版本策略(指定版本或版本区间)。
65+- 搜索策略:获用户确认后,使用二分法在确认范围内搜索可用 `transformers` 版本(每次切换版本后需重装并重跑触发失败的验证步骤)。
66+- 收敛标准:找到“可成功加载并通过对应验证步骤”的版本后停止搜索,并将最终版本写入msModelslim的config.ini。
67+- 失败处理:若二分搜索后仍无可用版本,返回阻塞结论并要求用户提供官方建议版本或模型实现修订方案。
68+ 
69+## 自动化脚本
70+ 
71+- `scripts/step1_generate_test_model.py`
72+- `scripts/step2_run_quantization.py`
73+- `scripts/step3_verify_weights.py`
74+- `scripts/step4_verify_quant_description.py`
75+ 
76+## 参考资料
77+ 
78+- [适配器验证指南](references/verification_guide.md)
79+ 
80+## 输出格式要求
81+ 
82+- 给出每一步的执行结果(PASS/FAIL)。
83+- 若失败,标注失败步骤、错误要点、建议修复方向。
84+- 最后给出总结结论:通过 / 未通过。
85+ 
86+## 经验条目(Experiences,追加制)
87+ 
88+> 追加规范见 `skills/README.md`「经验条目」。连续编号 `[E-序号]`;正文保留权威展开,本表只做索引 + 元数据登记;来源:实测编号(F0-x / Dx / Tx)| 用户反馈 | 代码实证;验证状态:已回归 | 待验证 | 已上流 docs。
89+ 
90+| 条目 | 主题 | 适用条件 / 触发信号 | 结论要点(一句话) | 正文位置 | 来源 | 验证状态 |
91+|------|------|------|------|------|------|------|
92+| E-001 | buffer 缺失优先自查 | Step3 权重缺失/键不一致 | 缺失项多来自模型 `buffer`(msmodelslim 通常不保存 buffer);适配器须将关键 buffer 主动转为 `nn.Parameter` 使其可被导出与校验覆盖 | Buffer 权重说明 | 代码实证 | 已回归 |
93+| E-002 | tie 权重等价克隆豁免 | Step3 严格键集合比对 | tie 权重模型(如 Qwen3)量化产物会克隆 `lm_head.weight = embed_tokens.weight`,属等价克隆而非真不一致;比对前先判 shape+数值容差内相等再豁免 | Step3(脚本 `step3_verify_weights.py`) | 实测 G6 | 已回归 |
94+| E-003 | transformers 版本兼容二分 | 验证期模型实现报错疑似版本不契合 | 先向用户说明并确认版本策略(目标版本或区间),确认后才二分搜索可用版本;收敛后把最终版本写入 config.ini;无可用版本返回阻塞并要官方建议 | transformers 版本兼容处理 | 用户反馈 | 待验证 |
95+| E-004 | 修改代码后重装 | 任何 adapter 代码变更后 | 必须执行 `bash install.sh` 重新安装 msmodelslim 后再重试验证(脚本路径为 console 命令,非 `python -m msmodelslim`) | 执行要求 | 实测 G5 | 已回归 |
96+| E-005 | 减层结构覆盖非全量 | Step1 生成测试模型 | 验证=结构覆盖完备的最小前缀减层,不是全量、也不是盲目前 N 层;MoE/hybrid 等晚出现结构必须被前缀覆盖 | 验证语义 / 硬门禁:Step1 结构覆盖 | 用户反馈 | 待验证 |
97+| E-006 | 禁止盲目前 N 层 | `--num-layers` 过小 | 若上限导致 `incomplete`,step1 须 FAIL;扩大前缀或去掉上限,勿用 `--allow-incomplete-cover` 混过门禁 | 硬门禁:Step1 结构覆盖 | 用户反馈 | 待验证 |
98+| E-007 | remote tied_weights list | Step1 `save_pretrained` AttributeError keys | 旧式 remote modeling 的 `_tied_weights_keys` 可能是 list;测试模型可清空为 `{}` 再保存 | Step1 脚本 | 实测 | 已回归 |
@@ -0,0 +1,20 @@
1+apiversion: modelslim_v1
2+spec:
3+ process:
4+ - type: "linear_quant"
5+ qconfig:
6+ act:
7+ scope: "per_token"
8+ dtype: "int8"
9+ symmetric: True
10+ method: "minmax"
11+ weight:
12+ scope: "per_channel"
13+ dtype: "int8"
14+ symmetric: True
15+ method: "minmax"
16+ include: ["*"]
17+ exclude: ["*"] # 全部回退,不实际量化任何层
18+ save:
19+ - type: "ascendv1_saver"
20+ part_file_size: 4
@@ -0,0 +1,22 @@
1+apiversion: modelslim_v1
2+ 
3+spec:
4+ process:
5+ - type: "linear_quant"
6+ qconfig:
7+ act:
8+ scope: "per_token" # dynamic
9+ dtype: "int8"
10+ symmetric: True
11+ method: "minmax"
12+ weight:
13+ scope: "per_channel"
14+ dtype: "int8"
15+ symmetric: True
16+ method: "minmax"
17+ include: ["*"] # 全模型量化
18+ exclude: [] # 不回退
19+ 
20+ save:
21+ - type: "ascendv1_saver"
22+ part_file_size: 4
@@ -0,0 +1,22 @@
1+apiversion: modelslim_v1
2+ 
3+spec:
4+ process:
5+ - type: "linear_quant"
6+ qconfig:
7+ act:
8+ scope: "per_tensor" # static
9+ dtype: "int8"
10+ symmetric: False
11+ method: "minmax"
12+ weight:
13+ scope: "per_channel"
14+ dtype: "int8"
15+ symmetric: True
16+ method: "minmax"
17+ include: ["*"] # 全模型量化
18+ exclude: [] # 不回退
19+ 
20+ save:
21+ - type: "ascendv1_saver"
22+ part_file_size: 4
@@ -0,0 +1,145 @@
1+# 适配器验证指南
2+ 
3+## 验证语义
4+ 
5+- **减层结构覆盖,不是全量。** 模型由结构类型堆叠;测试模型只需每种结构至少一层。
6+- Step1 按 `layer_types` ×(dense/MoE)计算覆盖完备的最小前缀层数,写入 `structure_cover_plan.json`。
7+- 后续 Step2~4 只在该减层模型上执行;不加载原始全层权重。
8+ 
9+## 核心验证流程 (必须)
10+ 
11+必须按顺序执行以下四步验证:
12+ 
13+1. **生成测试模型** (Step 1)
14+ - 结构覆盖计划 → 减层 config → 随机权重小模型
15+ - 门禁:覆盖完备(`incomplete=false`);禁止盲目前 N 层漏掉晚出现的 MoE/full_attn
16+2. **全回退量化** (Step 2)
17+ - 验证量化流程是否能跑通(不涉及具体精度,仅跑通流程)
18+ - 检查 `model_adapter` 注册是否生效
19+3. **全回退模型一致性与可加载/保存验证** (Step 3)
20+ - 基于 Step2 生成的全回退模型,验证其与 Step1 浮点模型权重严格一致(键、形状、数值)
21+ - 验证该模型产物具备完整加载/保存能力(可被后续流程读取并继续处理)
22+ - 若发现全回退权重缺失,优先检查缺失项是否为模型 `buffer`(`msmodelslim` 默认不会保存 buffer 权重)
23+ - 对必须保留的 buffer 权重,需在适配器中主动转换为 `nn.Parameter` 后再参与验证
24+4. **实际量化流程验证** (Step 4)
25+ - 运行实际 W8A8 静态/动态量化流程(非回退流程)并产出量化结果
26+ - 验证量化描述文件是否符合预期规则,检查线性层量化标签是否正确
27+ 
28+## 验证命令
29+ 
30+```bash
31+ 
32+# 1) 先看结构覆盖计划(推荐)
33+python scripts/step1_generate_test_model.py \
34+ --model-path /path/to/your/model \
35+ --output-path /tmp/test_model \
36+ --plan-only
37+ 
38+# 1') 生成减层测试模型(默认:覆盖完备最小前缀,勿再默认传 --num-layers 2)
39+python scripts/step1_generate_test_model.py \
40+ --model-path /path/to/your/model \
41+ --output-path /tmp/test_model
42+ 
43+# 可选:设上限;若导致漏盖则失败
44+ 
45+# --num-layers 8
46+ 
47+# 调试才允许不完备:
48+ 
49+# --num-layers 2 --allow-incomplete-cover
50+ 
51+# 2) 全回退量化
52+python scripts/step2_run_quantization.py \
53+ --model-path /tmp/test_model \
54+ --output-path /tmp/quantized_model \
55+ --model-type YourModelType \
56+ --model-family llm
57+ 
58+# 多模态模型请使用:
59+ 
60+# --model-family vlm
61+ 
62+# 3) 全回退模型一致性验证(与浮点权重严格对齐)
63+python scripts/step3_verify_weights.py \
64+ --original-path /tmp/test_model \
65+ --quantized-path /tmp/quantized_model \
66+ --tolerance 1e-5
67+```
68+ 
69+### Step 4:全模型量化检查
70+ 
71+执行全模型 W8A8 静态量化并检查描述文件:
72+ 
73+```bash
74+ 
75+# 执行量化
76+msmodelslim quant \
77+ --model_type <your_model_type> \
78+ --model_path /tmp/test_model \
79+ --save_path /tmp/quantized_w8a8_static \
80+ --device cpu \
81+ --config references/llm/w8a8_static_full_model.yaml \
82+ --trust_remote_code True
83+ 
84+# 验证描述文件
85+python scripts/step4_verify_quant_description.py \
86+ --desc-path /tmp/quantized_w8a8_static \
87+ --rules-path /path/to/your_verify_rules_static.json
88+```
89+ 
90+执行全模型 W8A8 动态量化并检查描述文件:
91+ 
92+```bash
93+ 
94+# 执行量化
95+msmodelslim quant \
96+ --model_type <your_model_type> \
97+ --model_path /tmp/test_model \
98+ --save_path /tmp/quantized_w8a8_dynamic \
99+ --device cpu \
100+ --config references/llm/w8a8_dynamic_full_model.yaml \
101+ --trust_remote_code True
102+ 
103+# 验证描述文件
104+python scripts/step4_verify_quant_description.py \
105+ --desc-path /tmp/quantized_w8a8_dynamic \
106+ --rules-path /path/to/your_verify_rules_dynamic.json
107+```
108+ 
109+多模态模型(VLM)建议使用以下配置模板(含校准数据字段):
110+ 
111+```bash
112+references/vlm/w8a8_static_full_model.yaml
113+references/vlm/w8a8_dynamic_full_model.yaml
114+```
115+ 
116+说明:不再内置 `verify_rules_w8a8_static.json` / `verify_rules_w8a8_dynamic.json`,请 agent 按目标模型层名自行生成规则文件并传入 `--rules-path`。
117+ 
118+## 通过标准
119+ 
120+- **核心验证**:Step 1/2/3/4 均成功执行无报错。
121+- **Step 1 通过条件**:`structure_cover_plan.json` 存在且 `incomplete=false`;减层模型可加载保存。
122+- **Step 3 通过条件**:全回退模型与浮点模型权重检查 PASS,且量化产物可被后续流程正常加载/使用。
123+- **Step 4 通过条件**:实际量化流程执行成功,描述文件规则校验通过。
124+ 
125+## 快速排错 / 失败分流
126+ 
127+- **Step 1 失败**:
128+ - 结构覆盖不完备(exit 2):`--num-layers` 过小,漏掉晚出现的 MoE/attn 类型;去掉上限或增大层数后重跑 `--plan-only` 确认
129+ - 模型加载失败:检查 `transformers` 版本或 `trust_remote_code` 设置
130+ - `save_pretrained` 报 `_tied_weights_keys` / `list` 无 `.keys`:旧式 remote modeling;step1 脚本已对 list 清空为 dict,确认使用最新脚本
131+ - 类型不支持:检查 `model_type` 是否在支持列表中
132+- **Step 2 失败**:
133+ - 找不到适配器:检查 `config.ini` 注册是否正确,是否执行了 `install.sh`
134+ - 量化入口报错:检查 `handle_dataset` 数据处理是否正确
135+- **Step 3 失败 (全回退模型与浮点不一致/不可完整加载)**:
136+ - 检查量化前后权重键名、形状与映射关系(应一一对应)
137+ - 检查数值差异是否超出阈值(默认 `tolerance=1e-5`)
138+ - 检查量化目录内权重与必要配置文件是否完整,确保可被后续流程读取
139+ - 若报“缺少权重键”,检查该键在原模型中是否是 `buffer`;若是,需在适配器中将其转成 `nn.Parameter`
140+ - **MoE 模型**:若使用 packed 权重,检查 `packed -> unpacked` 拆分逻辑是否正确(维度、转置);并确认 Step1 前缀已覆盖至少一层 MoE
141+- **Step 4 失败 (实际量化流程或描述文件异常)**:
142+ - 检查实际量化配置是否正确(W8A8 静态/动态、校准参数等)
143+ - 检查是否误用了回退配置
144+ - 检查验证规则 JSON 中的关键字是否覆盖了模型实际层名;动态量化类型名为 `W8A8_DYNAMIC`
145+ - 规则应从产物 `quant_model_description.json` 中实际量化条目提取,勿把未 traverse 的 `lm_head`/`embed` 强行期望为 W8A8
@@ -0,0 +1,22 @@
1+apiversion: multimodal_vlm_modelslim_v1
2+spec:
3+ process:
4+ - type: "linear_quant"
5+ qconfig:
6+ act:
7+ scope: "per_token"
8+ dtype: "int8"
9+ symmetric: True
10+ method: "minmax"
11+ weight:
12+ scope: "per_channel"
13+ dtype: "int8"
14+ symmetric: True
15+ method: "minmax"
16+ include: ["*"]
17+ exclude: ["*"] # 全部回退,不实际量化任何层
18+ save:
19+ - type: "ascendv1_saver"
20+ part_file_size: 4
21+ dataset: "calibImages"
22+ default_text: "Describe this image in detail."
@@ -0,0 +1,26 @@
1+apiversion: multimodal_vlm_modelslim_v1
2+ 
3+spec:
4+ process:
5+ - type: "linear_quant"
6+ qconfig:
7+ act:
8+ scope: "per_token" # dynamic
9+ dtype: "int8"
10+ symmetric: True
11+ method: "minmax"
12+ weight:
13+ scope: "per_channel"
14+ dtype: "int8"
15+ symmetric: True
16+ method: "minmax"
17+ include: ["*"] # 全模型量化
18+ exclude: [] # 不回退
19+ 
20+ save:
21+ - type: "ascendv1_saver"
22+ part_file_size: 4
23+ 
24+ # 多模态校准数据(需按实际数据集名称调整)
25+ dataset: "calibImages"
26+ default_text: "Describe this image in detail."
@@ -0,0 +1,26 @@
1+apiversion: multimodal_vlm_modelslim_v1
2+ 
3+spec:
4+ process:
5+ - type: "linear_quant"
6+ qconfig:
7+ act:
8+ scope: "per_tensor" # static
9+ dtype: "int8"
10+ symmetric: False
11+ method: "minmax"
12+ weight:
13+ scope: "per_channel"
14+ dtype: "int8"
15+ symmetric: True
16+ method: "minmax"
17+ include: ["*"] # 全模型量化
18+ exclude: [] # 不回退
19+ 
20+ save:
21+ - type: "ascendv1_saver"
22+ part_file_size: 4
23+ 
24+ # 多模态校准数据(需按实际数据集名称调整)
25+ dataset: "calibImages"
26+ default_text: "Describe this image in detail."
@@ -0,0 +1,297 @@
1+#!/usr/bin/env python3
2+"""步骤1:生成结构覆盖完备的减层随机权重测试模型。
3+ 
4+验证语义(非全量):
5+ 模型由若干结构类型堆叠而成;减层只需覆盖每种结构至少一次。
6+ 默认取「覆盖完备的最小前缀层数」L,再 from_config 建随机权重模型。
7+ 禁止盲目前 N 层(可能漏 MoE / full_attn 等)。
8+"""
9+ 
10+from __future__ import annotations
11+ 
12+import argparse
13+import json
14+import os
15+import shutil
16+import sys
17+from typing import Any, Dict, List, Optional, Set, Tuple
18+ 
19+import torch
20+from transformers import AutoConfig
21+import transformers
22+ 
23+# 配置中按层对齐的 list 字段(减层时一并截断)
24+_PER_LAYER_LIST_KEYS = (
25+ "layer_types",
26+ "swiglu_limits",
27+ "swiglu_limits_shared",
28+)
29+ 
30+ 
31+def _read_json(path: str) -> dict:
32+ with open(path, "r", encoding="utf-8") as f:
33+ return json.load(f)
34+ 
35+ 
36+def _write_json(path: str, data: dict) -> None:
37+ with open(path, "w", encoding="utf-8") as f:
38+ json.dump(data, f, ensure_ascii=False, indent=2)
39+ 
40+ 
41+def _copy_non_weight_files(src_dir: str, dst_dir: str) -> None:
42+ os.makedirs(dst_dir, exist_ok=True)
43+ for name in os.listdir(src_dir):
44+ src = os.path.join(src_dir, name)
45+ dst = os.path.join(dst_dir, name)
46+ if os.path.isdir(src):
47+ continue
48+ if name.endswith(".safetensors"):
49+ continue
50+ if name.endswith(".index.json"):
51+ continue
52+ shutil.copy2(src, dst)
53+ 
54+ 
55+def _text_cfg_view(cfg: dict) -> Tuple[dict, bool]:
56+ """返回 (text_cfg_dict, is_nested_under_text_config)。"""
57+ if isinstance(cfg.get("text_config"), dict):
58+ return cfg["text_config"], True
59+ return cfg, False
60+ 
61+ 
62+def _parse_moe_layer_indices(text_cfg: dict, n_layers: int) -> Set[int]:
63+ """从 config 解析 MoE 层下标集合;无 MoE 信号则返回空集。"""
64+ enum = text_cfg.get("moe_layers_enum")
65+ if enum is not None:
66+ if isinstance(enum, str):
67+ return {int(x) for x in enum.split(",") if str(x).strip() != ""}
68+ if isinstance(enum, (list, tuple)):
69+ return {int(x) for x in enum}
70+ if not text_cfg.get("use_moe") and not text_cfg.get("num_experts") and not text_cfg.get("moe_num_experts"):
71+ return set()
72+ # 常见回退:除第 0 层外均为 MoE,或按 moe_every_n_layer / moe_layer_offset
73+ every = text_cfg.get("moe_every_n_layer")
74+ offset = int(text_cfg.get("moe_layer_offset") or 0)
75+ if every:
76+ every = int(every)
77+ return {i for i in range(n_layers) if i >= offset and (i - offset) % every == 0}
78+ # DeepSeek 类:首层 dense,其余 MoE
79+ return set(range(1, n_layers))
80+ 
81+ 
82+def _structure_label(layer_idx: int, layer_types: Optional[List[Any]], moe_indices: Set[int]) -> str:
83+ """单层结构标签:注意力类型 × FFN 类型(堆叠重复则标签相同)。"""
84+ if layer_types and layer_idx < len(layer_types):
85+ attn = f"attn:{layer_types[layer_idx]}"
86+ else:
87+ attn = "attn:default"
88+ if moe_indices:
89+ ffn = "ffn:moe" if layer_idx in moe_indices else "ffn:dense"
90+ else:
91+ ffn = "ffn:dense"
92+ return f"{attn}+{ffn}"
93+ 
94+ 
95+def plan_structure_cover(
96+ cfg: dict,
97+ min_layers: int = 1,
98+ max_layers: Optional[int] = None,
99+) -> Dict[str, Any]:
100+ """计算结构覆盖完备的最小前缀层数。
101+ 
102+ Returns:
103+ dict: cover_layers, first_occurrence, all_labels, incomplete (若 max 截断导致漏盖)
104+ """
105+ text_cfg, _ = _text_cfg_view(cfg)
106+ n_layers = int(text_cfg.get("num_hidden_layers") or 1)
107+ layer_types = text_cfg.get("layer_types")
108+ if not isinstance(layer_types, list):
109+ layer_types = None
110+ moe_indices = _parse_moe_layer_indices(text_cfg, n_layers)
111+ 
112+ first_occ: Dict[str, int] = {}
113+ for i in range(n_layers):
114+ label = _structure_label(i, layer_types, moe_indices)
115+ if label not in first_occ:
116+ first_occ[label] = i
117+ 
118+ if not first_occ:
119+ cover = max(min_layers, 1)
120+ return {
121+ "cover_layers": cover,
122+ "first_occurrence": {},
123+ "all_labels": [],
124+ "incomplete": False,
125+ "original_layers": n_layers,
126+ }
127+ 
128+ needed = max(first_occ.values()) + 1
129+ cover = max(needed, min_layers)
130+ incomplete = False
131+ missing: List[str] = []
132+ if max_layers is not None and cover > max_layers:
133+ # 在 max 前缀内仍未出现的标签 → 不完备
134+ covered_in_max = {lab for lab, idx in first_occ.items() if idx < max_layers}
135+ missing = sorted(set(first_occ) - covered_in_max)
136+ cover = max_layers
137+ incomplete = bool(missing)
138+ 
139+ return {
140+ "cover_layers": cover,
141+ "first_occurrence": first_occ,
142+ "all_labels": sorted(first_occ.keys()),
143+ "incomplete": incomplete,
144+ "missing_labels": missing,
145+ "original_layers": n_layers,
146+ "moe_layer_count": len(moe_indices),
147+ }
148+ 
149+ 
150+def _truncate_per_layer_lists(text_cfg: dict, num_layers: int) -> dict:
151+ out = dict(text_cfg)
152+ out["num_hidden_layers"] = num_layers
153+ for key in _PER_LAYER_LIST_KEYS:
154+ val = out.get(key)
155+ if isinstance(val, list):
156+ out[key] = val[:num_layers]
157+ # moe_layers_enum:过滤到减层范围内,保持原类型(str / list)
158+ enum = out.get("moe_layers_enum")
159+ if isinstance(enum, str):
160+ kept = [x.strip() for x in enum.split(",") if x.strip() and int(x) < num_layers]
161+ out["moe_layers_enum"] = ",".join(kept)
162+ elif isinstance(enum, list):
163+ out["moe_layers_enum"] = [int(x) for x in enum if int(x) < num_layers]
164+ return out
165+ 
166+ 
167+def _shrink_config(cfg: dict, num_layers: int) -> dict:
168+ """按结构覆盖层数减层(前缀截断 + 同步 per-layer / moe 枚举)。"""
169+ out = dict(cfg)
170+ text_cfg, nested = _text_cfg_view(out)
171+ shrunk = _truncate_per_layer_lists(text_cfg, num_layers)
172+ if nested:
173+ out["text_config"] = shrunk
174+ else:
175+ out.update(shrunk)
176+ return out
177+ 
178+ 
179+def _build_random_model_from_config(config):
180+ candidate_auto_model_names = [
181+ "AutoModelForCausalLM",
182+ "AutoModelForImageTextToText",
183+ "AutoModel",
184+ ]
185+ errors = []
186+ for cls_name in candidate_auto_model_names:
187+ auto_cls = getattr(transformers, cls_name, None)
188+ if auto_cls is None:
189+ continue
190+ try:
191+ model = auto_cls.from_config(config, trust_remote_code=True, torch_dtype=torch.float32)
192+ return model, cls_name
193+ except Exception as e: # pragma: no cover - best-effort fallback chain
194+ errors.append(f"{cls_name}: {repr(e)}")
195+ 
196+ raise RuntimeError(
197+ "无法根据配置构建模型。已尝试: " + ", ".join(candidate_auto_model_names) + "\n错误详情:\n" + "\n".join(errors)
198+ )
199+ 
200+ 
201+def main() -> int:
202+ parser = argparse.ArgumentParser(description="Step1: 结构覆盖完备的减层随机权重测试模型(非全量验证)")
203+ parser.add_argument("--model-path", required=True)
204+ parser.add_argument("--output-path", required=True)
205+ parser.add_argument(
206+ "--num-layers",
207+ type=int,
208+ default=None,
209+ help="可选:减层上限。默认不设上限,取结构覆盖完备的最小前缀。"
210+ "若上限导致结构覆盖不完备则失败(除非 --allow-incomplete-cover)。",
211+ )
212+ parser.add_argument(
213+ "--min-layers",
214+ type=int,
215+ default=1,
216+ help="减层下限(默认 1)。结构种类少于此时仍至少保留该层数。",
217+ )
218+ parser.add_argument(
219+ "--allow-incomplete-cover",
220+ action="store_true",
221+ help="允许在 --num-layers 截断后结构覆盖不完备(不推荐;仅调试)。",
222+ )
223+ parser.add_argument(
224+ "--plan-only",
225+ action="store_true",
226+ help="只打印结构覆盖计划,不生成模型。",
227+ )
228+ parser.add_argument("--device", default="cpu")
229+ args = parser.parse_args()
230+ 
231+ src_cfg = os.path.join(args.model_path, "config.json")
232+ if not os.path.exists(src_cfg):
233+ print(f"[ERROR] 缺少配置文件: {src_cfg}")
234+ return 1
235+ 
236+ cfg = _read_json(src_cfg)
237+ plan = plan_structure_cover(cfg, min_layers=args.min_layers, max_layers=args.num_layers)
238+ 
239+ print("[INFO] 验证语义: 减层结构覆盖(非全量)")
240+ print(f"[INFO] 原层数: {plan['original_layers']} → 覆盖层数: {plan['cover_layers']}")
241+ print(f"[INFO] 结构标签全集 ({len(plan['all_labels'])}): {plan['all_labels']}")
242+ for lab, idx in sorted(plan["first_occurrence"].items(), key=lambda x: x[1]):
243+ print(f"[INFO] 首次覆盖 layer[{idx}] = {lab}")
244+ if plan.get("moe_layer_count"):
245+ print(f"[INFO] MoE 层计数(原配置): {plan['moe_layer_count']}")
246+ 
247+ if plan["incomplete"]:
248+ msg = (
249+ f"结构覆盖不完备: --num-layers={args.num_layers} 漏掉 {plan.get('missing_labels')}. "
250+ "请去掉上限或增大 --num-layers,使前缀覆盖全部结构标签。"
251+ )
252+ if args.allow_incomplete_cover:
253+ print(f"[WARN] {msg} (已 --allow-incomplete-cover,继续)")
254+ else:
255+ print(f"[ERROR] {msg}")
256+ return 2
257+ 
258+ if args.plan_only:
259+ print("[OK] plan-only 完成")
260+ return 0
261+ 
262+ _copy_non_weight_files(args.model_path, args.output_path)
263+ shrunk = _shrink_config(cfg, plan["cover_layers"])
264+ _write_json(os.path.join(args.output_path, "config.json"), shrunk)
265+ # 落盘覆盖计划,供 step2~4 / 评审引用
266+ _write_json(
267+ os.path.join(args.output_path, "structure_cover_plan.json"),
268+ {
269+ "semantics": "reduced_layer_structure_cover",
270+ "not_full_model": True,
271+ **plan,
272+ },
273+ )
274+ 
275+ config = AutoConfig.from_pretrained(args.output_path, trust_remote_code=True)
276+ model, used_cls_name = _build_random_model_from_config(config)
277+ print(f"[INFO] 使用模型类: {used_cls_name}")
278+ model = model.to(args.device).eval()
279+ 
280+ # remote-code 兼容:部分旧式 modeling 的 `_tied_weights_keys` 是 list,
281+ # transformers>=5.5 期望 dict(见 modeling_utils._get_tied_weight_keys 内 .keys())。
282+ # 测试模型无 tie 精度诉求,统一清空避免 save_pretrained 崩溃。
283+ for _, submodule in model.named_modules():
284+ tied = getattr(submodule, "_tied_weights_keys", None)
285+ if isinstance(tied, list):
286+ submodule._tied_weights_keys = {}
287+ 
288+ model.save_pretrained(args.output_path)
289+ stale_index = os.path.join(args.output_path, "model.safetensors.index.json")
290+ if os.path.exists(stale_index) and os.path.exists(os.path.join(args.output_path, "model.safetensors")):
291+ os.remove(stale_index)
292+ print(f"[OK] step1完成: {args.output_path} (cover_layers={plan['cover_layers']}, labels={len(plan['all_labels'])})")
293+ return 0
294+ 
295+ 
296+if __name__ == "__main__":
297+ sys.exit(main())
@@ -0,0 +1,105 @@
1+#!/usr/bin/env python3
2+"""步骤2:执行全回退量化(精简版,支持 LLM/VLM)。"""
3+ 
4+import argparse
5+import os
6+import subprocess # nosec B404 - 执行 msmodelslim quant 是 skill 的本质需求
7+import sys
8+ 
9+ 
10+LLM_FALLBACK_YAML = """apiversion: modelslim_v1
11+spec:
12+ process:
13+ - type: "linear_quant"
14+ qconfig:
15+ act:
16+ scope: "per_token"
17+ dtype: "int8"
18+ symmetric: True
19+ method: "minmax"
20+ weight:
21+ scope: "per_channel"
22+ dtype: "int8"
23+ symmetric: True
24+ method: "minmax"
25+ include: ["*"]
26+ exclude: ["*"]
27+ save:
28+ - type: "ascendv1_saver"
29+ part_file_size: 4
30+"""
31+ 
32+ 
33+VLM_FALLBACK_YAML = """apiversion: multimodal_vlm_modelslim_v1
34+spec:
35+ process:
36+ - type: "linear_quant"
37+ qconfig:
38+ act:
39+ scope: "per_token"
40+ dtype: "int8"
41+ symmetric: True
42+ method: "minmax"
43+ weight:
44+ scope: "per_channel"
45+ dtype: "int8"
46+ symmetric: True
47+ method: "minmax"
48+ include: ["*"]
49+ exclude: ["*"]
50+ save:
51+ - type: "ascendv1_saver"
52+ part_file_size: 4
53+ dataset: "calibImages"
54+ default_text: "Describe this image in detail."
55+"""
56+ 
57+ 
58+def _write_fallback_yaml(path, model_family: str):
59+ os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
60+ content = VLM_FALLBACK_YAML if model_family == "vlm" else LLM_FALLBACK_YAML
61+ with open(path, "w", encoding="utf-8") as f:
62+ f.write(content)
63+ 
64+ 
65+def main():
66+ parser = argparse.ArgumentParser()
67+ parser.add_argument("--model-path", required=True)
68+ parser.add_argument("--output-path", required=True)
69+ parser.add_argument("--model-type", required=True)
70+ parser.add_argument("--device", default="cpu")
71+ parser.add_argument("--config-path", default="")
72+ parser.add_argument("--model-family", choices=["llm", "vlm"], default="llm")
73+ args = parser.parse_args()
74+ 
75+ config_path = args.config_path or os.path.join(args.output_path, "fallback_config.yaml")
76+ if not os.path.exists(config_path):
77+ _write_fallback_yaml(config_path, args.model_family)
78+ 
79+ os.makedirs(args.output_path, exist_ok=True)
80+ cmd = [
81+ "msmodelslim", # console script:本包无顶层 __main__.py,不能用 python -m msmodelslim
82+ "quant",
83+ "--model_path",
84+ args.model_path,
85+ "--save_path",
86+ args.output_path,
87+ "--device",
88+ args.device,
89+ "--model_type",
90+ args.model_type,
91+ "--config",
92+ config_path,
93+ "--trust_remote_code",
94+ "True",
95+ ]
96+ rc = subprocess.run(cmd, check=False).returncode # nosec B603 - 命令为固定参数列表,无 shell 注入面
97+ if rc != 0:
98+ print("[ERROR] step2失败")
99+ return rc
100+ print(f"[OK] step2完成: {args.output_path}")
101+ return 0
102+ 
103+ 
104+if __name__ == "__main__":
105+ sys.exit(main())
@@ -0,0 +1,115 @@
1+#!/usr/bin/env python3
2+"""步骤3:验证权重一致性(精简版,支持 tie-weight 等价克隆豁免)。"""
3+ 
4+import argparse
5+import glob
6+import os
7+import sys
8+ 
9+import torch
10+ 
11+ 
12+def _load_weights(model_path):
13+ try:
14+ from safetensors.torch import load_file
15+ 
16+ files = sorted(glob.glob(os.path.join(model_path, "*.safetensors")))
17+ if files:
18+ merged = {}
19+ for file in files:
20+ merged.update(load_file(file))
21+ return merged
22+ except Exception: # nosec B110 - safetensors 缺失时回退到 pytorch_model.bin
23+ pass
24+ 
25+ pt_path = os.path.join(model_path, "pytorch_model.bin")
26+ if os.path.exists(pt_path):
27+ return torch.load(pt_path, map_location="cpu", weights_only=True)
28+ return {}
29+ 
30+ 
31+def _find_equivalent(key, source_map, target_map, tolerance):
32+ """在 target_map 中寻找与 source_map[key] 同 shape 且数值全等的克隆键。
33+ 
34+ tie-word-embeddings 模型(如 Qwen3):msmodelslim saver 有意把 embed_tokens.weight
35+ 克隆为 lm_head.weight 以兼容推理,而 transformers 原模型只保存一份 —— 严格键集
36+ 比对会对这类模型误报 FAIL。数值全等(max diff <= tolerance)即视为等价克隆,属可接受差异。
37+ """
38+ target_tensor = source_map[key]
39+ for cand_key in target_map:
40+ cand = target_map[cand_key]
41+ if cand.shape != target_tensor.shape:
42+ continue
43+ if torch.abs(cand.float() - target_tensor.float()).max().item() <= tolerance:
44+ return cand_key
45+ return None
46+ 
47+ 
48+def main():
49+ parser = argparse.ArgumentParser()
50+ parser.add_argument("--original-path", required=True)
51+ parser.add_argument("--quantized-path", required=True)
52+ parser.add_argument("--tolerance", type=float, default=1e-5)
53+ args = parser.parse_args()
54+ 
55+ left = _load_weights(args.original_path)
56+ right = _load_weights(args.quantized_path)
57+ if not left or not right:
58+ print("[ERROR] step3失败: 权重加载失败")
59+ return 1
60+ 
61+ left_keys, right_keys = set(left.keys()), set(right.keys())
62+ 
63+ # 1) 公共键直接纳入数值比较;单侧键先尝试等价克隆豁免,无克隆对应才算真不一致
64+ pairs = [(k, k) for k in sorted(left_keys & right_keys)]
65+ accepted_clones = [] # (单侧键, 对应克隆键, 方向说明)
66+ missing = [] # 仅左侧且无克隆对应(真缺失)
67+ unexpected = [] # 仅右侧且无克隆对应(真多余)
68+ 
69+ for key in sorted(left_keys - right_keys):
70+ eq = _find_equivalent(key, left, right, args.tolerance)
71+ if eq is not None:
72+ accepted_clones.append((key, eq, "quantized 侧含数值全等克隆"))
73+ pairs.append((key, eq))
74+ else:
75+ missing.append(key)
76+ for key in sorted(right_keys - left_keys):
77+ eq = _find_equivalent(key, right, left, args.tolerance)
78+ if eq is not None:
79+ accepted_clones.append((key, eq, "original 侧含数值全等克隆"))
80+ pairs.append((eq, key))
81+ else:
82+ unexpected.append(key)
83+ 
84+ if missing or unexpected:
85+ print("[ERROR] step3失败: 权重键不一致(已排除等价克隆)")
86+ print(f"[INFO] 仅左侧数量(真缺失): {len(missing)}")
87+ for k in missing[:10]:
88+ print(f" - {k}")
89+ print(f"[INFO] 仅右侧数量(真多余): {len(unexpected)}")
90+ for k in unexpected[:10]:
91+ print(f" - {k}")
92+ return 1
93+ 
94+ # 2) 数值一致性:公共键 + 被豁免的克隆配对
95+ max_diff = 0.0
96+ for left_key, right_key in pairs:
97+ left_t = left[left_key]
98+ right_t = right[right_key]
99+ if left_t.shape != right_t.shape:
100+ print(f"[ERROR] step3失败: 形状不一致 {left_key} vs {right_key}")
101+ return 1
102+ diff = torch.abs(left_t.float() - right_t.float()).max().item()
103+ max_diff = max(max_diff, diff)
104+ if diff > args.tolerance:
105+ print(f"[ERROR] step3失败: 权重差异超阈值 {left_key} diff={diff:.2e}")
106+ return 1
107+ 
108+ print(f"[OK] step3完成: max_diff={max_diff:.2e}, 等价克隆豁免 {len(accepted_clones)} 项")
109+ for key, eq, why in accepted_clones:
110+ print(f"[INFO] 豁免: {key} == {eq} ({why})")
111+ return 0
112+ 
113+ 
114+if __name__ == "__main__":
115+ sys.exit(main())
@@ -0,0 +1,135 @@
1+#!/usr/bin/env python
2+# -*- coding: UTF-8 -*-
3+"""
4+验证流程步骤4:验证量化描述文件
5+根据规则文件检查 quant_model_description.json 中的层量化类型是否符合预期。
6+"""
7+ 
8+import os
9+import sys
10+import json
11+import argparse
12+from typing import Any
13+ 
14+ 
15+def load_json(path: str) -> Any:
16+ with open(path, "r", encoding="utf-8") as f:
17+ return json.load(f)
18+ 
19+ 
20+def find_description_file(path: str) -> str:
21+ """在指定路径查找描述文件"""
22+ if os.path.isfile(path):
23+ return path
24+ 
25+ p = os.path.join(path, "quant_model_description.json")
26+ 
27+ if os.path.exists(p):
28+ return p
29+ 
30+ return None
31+ 
32+ 
33+def verify_description(desc_path: str, rules_path: str) -> bool:
34+ print("=" * 60)
35+ print("步骤4: 验证量化描述文件")
36+ print("=" * 60)
37+ 
38+ real_desc_path = find_description_file(desc_path)
39+ if not real_desc_path:
40+ print(f"[ERROR] 未找到量化描述文件 (在路径: {desc_path})")
41+ print(" 期望文件: quant_model_description.json 或 quant_model_description.json")
42+ return False
43+ 
44+ print(f"[INFO] 描述文件: {real_desc_path}")
45+ try:
46+ desc_data = load_json(real_desc_path)
47+ except Exception as e:
48+ print(f"[ERROR] 加载描述文件失败: {e}")
49+ return False
50+ 
51+ if not isinstance(desc_data, dict):
52+ print("[ERROR] 描述文件格式错误: 期望为 JSON Object (dict)")
53+ return False
54+ 
55+ print(f"[INFO] 规则文件: {rules_path}")
56+ if not os.path.exists(rules_path):
57+ print(f"[ERROR] 规则文件不存在: {rules_path}")
58+ return False
59+ 
60+ try:
61+ rules = load_json(rules_path)
62+ except Exception as e:
63+ print(f"[ERROR] 加载规则文件失败: {e}")
64+ return False
65+ 
66+ if not isinstance(rules, list):
67+ print("[ERROR] 规则文件格式错误: 期望为 JSON Array (list)")
68+ return False
69+ 
70+ print("\n[CHECK] 开始匹配规则...")
71+ all_passed = True
72+ total_checked_keys = 0
73+ 
74+ for i, rule in enumerate(rules):
75+ quant_type = rule.get("quant_type")
76+ keywords = rule.get("keywords", [])
77+ 
78+ if not quant_type or not keywords:
79+ print(f"[WARNING] 规则 #{i + 1} 格式无效 (缺少 quant_type 或 keywords),跳过")
80+ continue
81+ 
82+ print(f" > 规则 #{i + 1}: 期望包含 {keywords} 的权重为 '{quant_type}'")
83+ 
84+ matched_keys = []
85+ failed_keys = []
86+ 
87+ for key, value in desc_data.items():
88+ if not isinstance(key, str):
89+ continue
90+ 
91+ is_match = any(kw in key for kw in keywords)
92+ if is_match:
93+ if value != quant_type:
94+ failed_keys.append((key, value))
95+ else:
96+ matched_keys.append(key)
97+ 
98+ total_checked_keys += len(matched_keys) + len(failed_keys)
99+ 
100+ if failed_keys:
101+ all_passed = False
102+ print(f" [FAILED] 发现 {len(failed_keys)} 个不匹配项 (展示前10个):")
103+ for k, v in failed_keys[:10]:
104+ print(f" - {k}: 实际值='{v}', 期望值='{quant_type}'")
105+ if len(failed_keys) > 10:
106+ print(f" ... 还有 {len(failed_keys) - 10} 个")
107+ elif not matched_keys:
108+ print(" [WARNING] 未找到匹配该规则关键字的任何权重键 (可能是关键字有误?)")
109+ else:
110+ print(f" [OK] {len(matched_keys)} 个权重项验证通过")
111+ 
112+ print("-" * 60)
113+ if all_passed and total_checked_keys > 0:
114+ print("[SUCCESS] 验证通过!所有匹配项均符合预期量化类型。")
115+ return True
116+ if total_checked_keys == 0:
117+ print("[FAILED] 验证失败:未匹配到任何符合规则的权重项,请检查规则关键字。")
118+ return False
119+ print("[FAILED] 验证失败:存在量化类型不匹配的权重项。")
120+ return False
121+ 
122+ 
123+def main():
124+ parser = argparse.ArgumentParser(description="验证量化描述文件内容")
125+ parser.add_argument("--desc-path", required=True, help="量化输出目录或描述文件路径")
126+ parser.add_argument("--rules-path", required=True, help="校验规则JSON文件路径")
127+ 
128+ args = parser.parse_args()
129+ 
130+ success = verify_description(args.desc_path, args.rules_path)
131+ sys.exit(0 if success else 1)
132+ 
133+ 
134+if __name__ == "__main__":
135+ main()
@@ -0,0 +1,22 @@
1+apiversion: multimodal_vlm_modelslim_v1
2+spec:
3+ process:
4+ - type: "linear_quant"
5+ qconfig:
6+ act:
7+ scope: "per_token"
8+ dtype: "int8"
9+ symmetric: True
10+ method: "minmax"
11+ weight:
12+ scope: "per_channel"
13+ dtype: "int8"
14+ symmetric: True
15+ method: "minmax"
16+ include: ["*"]
17+ exclude: ["*"] # 全部回退,不实际量化任何层
18+ save:
19+ - type: "ascendv1_saver"
20+ part_file_size: 4
21+ dataset: "calibImages"
22+ default_text: "Describe this image in detail."
@@ -0,0 +1,118 @@
1+#!/usr/bin/env python
2+# -*- coding: UTF-8 -*-
3+ 
4+"""
5+多模态理解模型(VLM)适配器模板。
6+"""
7+ 
8+from pathlib import Path
9+from typing import Any, Generator, List
10+ 
11+from torch import nn
12+from transformers import AutoProcessor
13+ 
14+from msmodelslim.app.naive_quantization.model_info_interface import ModelInfoInterface
15+from msmodelslim.core.base.protocol import ProcessRequest
16+from msmodelslim.core.const import DeviceType
17+from msmodelslim.model.common.layer_wise_forward import (
18+ generated_decoder_layer_visit_func,
19+ transformers_generated_forward_func,
20+)
21+from msmodelslim.model.common.vlm_base import VLMBaseModelAdapter
22+from msmodelslim.model.interface_hub import ModelSlimPipelineInterfaceV1
23+from msmodelslim.utils.exception import InvalidModelError
24+from msmodelslim.utils.logging import logger_setter
25+from msmodelslim.utils.security import get_valid_read_path
26+ 
27+ 
28+@logger_setter()
29+class MyVLMModelAdapter(
30+ VLMBaseModelAdapter,
31+ ModelInfoInterface,
32+ ModelSlimPipelineInterfaceV1,
33+):
34+ """VLM 基础模板。"""
35+ 
36+ def __init__(self, model_type: str, model_path: Path, trust_remote_code: bool = False):
37+ self._processor = None
38+ super().__init__(model_type, model_path, trust_remote_code)
39+ 
40+ # ==================== ModelInfoInterface ====================
41+ def get_model_pedigree(self) -> str:
42+ return "my_vlm"
43+ 
44+ def get_model_type(self) -> str:
45+ return self.model_type
46+ 
47+ # ==================== ModelSlimPipelineInterfaceV1 ====================
48+ def handle_dataset(self, dataset: Any, device: DeviceType = DeviceType.NPU) -> List[Any]:
49+ """
50+ 将图文样本转换为校准输入。
51+ 样本建议格式:item.text + item.image(或 dict: {"text": ..., "image": ...})。
52+ """
53+ self._processor = AutoProcessor.from_pretrained( # nosec B615 - local_files_only=True 仅加载本地模型,不触发远程下载
54+ self.model_path,
55+ trust_remote_code=self.trust_remote_code,
56+ local_files_only=True,
57+ )
58+ processed = []
59+ for item in dataset:
60+ text = item.text if hasattr(item, "text") else item.get("text")
61+ image = item.image if hasattr(item, "image") else item.get("image")
62+ if text is None or image is None:
63+ raise InvalidModelError(
64+ "VLM 校准样本需要同时包含 text 和 image。",
65+ action="请提供 image+text 数据,避免纯文本样本。",
66+ )
67+ 
68+ image = get_valid_read_path(str(image))
69+ messages = [
70+ {
71+ "role": "user",
72+ "content": [
73+ {"type": "image", "image": str(image)},
74+ {"type": "text", "text": str(text)},
75+ ],
76+ }
77+ ]
78+ inputs = self._processor.apply_chat_template(
79+ messages,
80+ tokenize=True,
81+ add_generation_prompt=True,
82+ return_dict=True,
83+ return_tensors="pt",
84+ )
85+ processed.append(
86+ self._collect_inputs_to_device(
87+ inputs,
88+ device,
89+ keys=[
90+ "input_ids",
91+ "attention_mask",
92+ "position_ids",
93+ "pixel_values",
94+ "pixel_values_videos",
95+ "image_grid_thw",
96+ "video_grid_thw",
97+ "cache_position",
98+ ],
99+ defaults={},
100+ )
101+ )
102+ return processed
103+ 
104+ def init_model(self, device: DeviceType = DeviceType.NPU) -> nn.Module:
105+ return self._load_model(device)
106+ 
107+ def _load_model(self, device: DeviceType) -> nn.Module:
108+ """Placeholder: implement VLM loading (see e.g. qwen2_5_vl adapter)."""
109+ raise NotImplementedError("Implement _load_model for the target VLM.")
110+ 
111+ def generate_model_visit(self, model: nn.Module) -> Generator[ProcessRequest, Any, None]:
112+ yield from generated_decoder_layer_visit_func(model)
113+ 
114+ def generate_model_forward(self, model: nn.Module, inputs: Any) -> Generator[ProcessRequest, Any, None]:
115+ yield from transformers_generated_forward_func(model, inputs)
116+ 
117+ def enable_kv_cache(self, model: nn.Module, need_kv_cache: bool) -> None:
118+ return self._enable_kv_cache(model, need_kv_cache)
@@ -0,0 +1,26 @@
1+apiversion: multimodal_vlm_modelslim_v1
2+ 
3+spec:
4+ process:
5+ - type: "linear_quant"
6+ qconfig:
7+ act:
8+ scope: "per_token" # dynamic
9+ dtype: "int8"
10+ symmetric: True
11+ method: "minmax"
12+ weight:
13+ scope: "per_channel"
14+ dtype: "int8"
15+ symmetric: True
16+ method: "minmax"
17+ include: ["*"] # 全模型量化
18+ exclude: [] # 不回退
19+ 
20+ save:
21+ - type: "ascendv1_saver"
22+ part_file_size: 4
23+ 
24+ # 多模态校准数据(需按实际数据集名称调整)
25+ dataset: "calibImages"
26+ default_text: "Describe this image in detail."
@@ -0,0 +1,26 @@
1+apiversion: multimodal_vlm_modelslim_v1
2+ 
3+spec:
4+ process:
5+ - type: "linear_quant"
6+ qconfig:
7+ act:
8+ scope: "per_tensor" # static
9+ dtype: "int8"
10+ symmetric: False
11+ method: "minmax"
12+ weight:
13+ scope: "per_channel"
14+ dtype: "int8"
15+ symmetric: True
16+ method: "minmax"
17+ include: ["*"] # 全模型量化
18+ exclude: [] # 不回退
19+ 
20+ save:
21+ - type: "ascendv1_saver"
22+ part_file_size: 4
23+ 
24+ # 多模态校准数据(需按实际数据集名称调整)
25+ dataset: "calibImages"
26+ default_text: "Describe this image in detail."