# fine-tuning
**Repository Path**: renshengedu/fine-tuning
## Basic Information
- **Project Name**: fine-tuning
- **Description**: 模型微调自定义报销单核验数据集仓库
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 2
- **Created**: 2026-10-05
- **Last Updated**: 2026-10-28
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# fine-tuning / preview
报销制度智能助手的**微调数据处理 + 量化 + 评测**脚本集合。
整个项目围绕三类工作:
1. **造数据**:用大模型批量生成「报销单核验」训练/校验数据(Alpaca 格式)。
2. **转格式 / 评测**:把数据转成 EvalScope 评测集,并对 OpenAI 兼容接口上的模型打分。
3. **量化**:把 Qwen2.5-7B 量化成 4bit(AWQ / GPTQ),其中 GPTQ 用报销数据做校准集。
---
## 一、目录总览
```
preview/
├── .gitignore # 忽略规则(含 .env / __pycache__ / 模型产物)
├── .env.example # 环境变量模板(复制成 .env 后填自己的值)
├── .env # 本地环境变量(含 API Key,已加入 .gitignore)
├── config/ # 配置层
│ ├── env.py # 读取 .env,导出 env 单例
│ └── ai_configs.py # 各家大模型平台的配置表 ai_configs
├── utils/ # 工具层
│ └── llm_utils.py # create_llm():按 platform_code 造 chat model
├── datasets/ # 数据集与数据处理脚本
│ ├── generate_datasets_from_llm.py # 调 LLM 批量生成报销单核验数据
│ ├── to_alpaca.py # jsonl(对话格式) → Alpaca json
│ ├── to_evalscope.py # jsonl(对话格式) → EvalScope jsonl
│ ├── run_evalscope_evaluate.py # 启动 EvalScope 评测任务
│ ├── alpaca.json # 报销制度问答训练集(Alpaca)
│ ├── export_from_eds.jsonl # 从 EDS 平台导出的原始对话数据
│ ├── evalscope.jsonl # 由 export_from_eds.jsonl 转来的评测集
│ ├── reimburses/ # LLaMA-Factory 用的报销核验数据集目录
│ │ ├── dataset_info.json # 数据集注册描述文件
│ │ ├── datasets_reimburse.json # 训练集(LLM 生成,≈1400 条)
│ │ └── validates_reimburse.json # 校验集(LLM 生成,244 条)
│ └── reimburses_evalscope/ # EvalScope 评测集目录
│ ├── reimburse_evalscope.py # alpaca → EvalScope jsonl 的转换脚本
│ └── reimburses_evalscope.jsonl # 报销核验评测集(244 条)
└── quantization/ # 模型量化脚本
├── run_awq.py # AWQ 4bit 量化(无需校准数据)
└── run_gptq.py # GPTQ 4bit 量化(用报销数据做校准)
```
---
## 二、文件逐一说明
### 根目录
| 文件 | 作用 |
| --- | --- |
| `.gitignore` | 忽略 `.idea`、`__pycache__/`、`.venv/`、`.env`(含 API Key)、`models/`、`quantization_models/` 与模型权重后缀。 |
| `.env.example` | 环境变量模板:`LLM_KEY_BAILIAN`(百炼 API Key)、`BAILIAN_API_URL`(百炼兼容模式地址)。首次使用 `cp .env.example .env` 后填自己的值。 |
| `.env` | 实际生效的环境变量文件,**不入库**(`.gitignore` 已忽略)。 |
### `config/` — 配置层
| 文件 | 作用 |
| --- | --- |
| `env.py` | 用 `python-dotenv` 加载根目录下的 `.env`(路径基于 `__file__` 定位,任意目录启动都找得到),再用 `pydantic-settings` 的 `EnvSettings` 校验必填字段:`llm_key_bailian`、`bailian_api_url`,导出全局单例 `env`。缺任一变量会直接报校验错。 |
| `ai_configs.py` | 模型注册表 `ai_configs`,把平台代号映射到 `{model, url, key}`。当前注册了 4 项:`bailian-qwen-turbo`、`bailian-qwen3-max`、`bailian-qwen3.6-plus`、`bailian-embedding`。所有 URL/Key 都从 `config.env` 取值,不在代码里硬编码。 |
### `utils/` — 工具层
| 文件 | 作用 |
| --- | --- |
| `llm_utils.py` | 提供 `create_llm(platform_code='bailian-qwen3.6-plus', temperature=0.5, disable_streaming=False)`:按代号从 `ai_configs` 取配置,交给 LangChain 的 `init_chat_model(model_provider="openai")` 返回一个 OpenAI 兼容的 chat model(并对 qwen 关闭思考:`extra_body={"enable_thinking": False}`)。代号不存在时抛 `Unknown platform code`。 |
### `datasets/` — 数据生成与转换
| 文件 | 作用 |
| --- | --- |
| `generate_datasets_from_llm.py` | **数据生产脚本**。先把项目根目录加入 `sys.path`,再 `from utils.llm_utils import create_llm` 拿到模型;调用 `create_llm("bailian-qwen3-max", temperature=0.8)` 流式生成报销单核验样本:每次让模型产出 10 条(`size=10`),循环 100 轮(`page=100`),边生成边追加写入 `datasets/reimburses/datasets_reimburse.json`(路径基于 `__file__` 定位)。Prompt 要求:约 20% 样本核验通过(输出 `Y`)、其余输出不通过原因;每条的规则须由模型现写(≥300 字)且彼此不雷同;输出必须是合法 JSON 数组。入口已加 `if __name__ == "__main__":` 保护。
⚠️ 换模型请把代号改成 `config/ai_configs.py` 里注册过的名字。 |
| `to_alpaca.py` | 把 EDS 导出的对话式 jsonl(`{"messages":[{"role":"user"/"assistant",...}]}`)转成 **Alpaca 格式**:`{instruction: 用户问题, input: "", output: 助手回答}`。默认输入 `export_from_eds.jsonl` → 输出 `alpaca.json`。只保留 user/assistant 都存在的样本。 |
| `to_evalscope.py` | 同上来源,但转成 **EvalScope `general_qa` 格式**:每行 `{"query": 用户问题, "response": 助手回答}`,输出为 jsonl。默认 `export_from_eds.jsonl` → `evalscope.jsonl`。 |
| `run_evalscope_evaluate.py` | **评测入口**。构造 `TaskConfig`:被评模型走 OpenAI 兼容接口 `http://127.0.0.1:8000/v1/chat/completions`(即 vLLM/SGLang 起的服务),数据集为本地 `general_qa` 子集 `reimburses_evalscope`;裁判模型 `qwen2.5-72b-instruct`,其 `api_url` / `api_key` 从 `config.env` 读取(不再硬编码),`score_type='pattern'`、`JudgeStrategy.LLM`、并发 5。文件顶部会把项目根目录插入 `sys.path`,**任意目录启动均可**。 |
| `alpaca.json` | 由 `export_from_eds.jsonl` 转出的训练集,**92 条**报销制度问答(住宿标准、交通/培训费用、审批流程等),回答里含 Markdown 表格与决策树。 |
| `export_from_eds.jsonl` | **原始导出数据**,**92 行**,每行一个 `messages` 多轮(单轮)对话。是 `to_alpaca.py` / `to_evalscope.py` 的共同输入。 |
| `evalscope.jsonl` | 由 `export_from_eds.jsonl` 转出的评测集,**92 行**,`query`/`response` 结构。 |
### `datasets/reimburses/` — LLaMA-Factory 数据集目录
| 文件 | 作用 |
| --- | --- |
| `dataset_info.json` | LLaMA-Factory 的**数据集注册文件**。声明了两个数据集 `datasets_reimburse` 与 `validates_reimburse`,分别指向同名 json,`formatting: alpaca`。训练时在 `dataset_info.json` 中登记后,即可用 `--dataset datasets_reimburse` 引用。 |
| `datasets_reimburse.json` | **训练集**,约 **1400 条** LLM 生成的报销单核验样本。每条 `instruction` 里内嵌一整套(≥300 字、彼此不同)报销规则,`input` 是一条报销信息(金额/项目/发票类型/备注),`output` 为 `Y` 或具体不通过原因。用途:教模型「读规则 → 判合规」。 |
| `validates_reimburse.json` | **校验集**,**244 条**,格式同上,样例独立于训练集(规则文案不同)。用途:训练时看 loss/泛化、`run_gptq.py` 的校准样例、以及导出 EvalScope 评测集。 |
### `datasets/reimburses_evalscope/` — EvalScope 评测集目录
| 文件 | 作用 |
| --- | --- |
| `reimburse_evalscope.py` | 把 Alpaca 三元组拆成 EvalScope `general_qa` 三元组:`{system: instruction, query: input, response: output}`,即**把报销规则放进 system 提示**。输入 `datasets/reimburses/validates_reimburse.json`、输出 `同目录/reimburses_evalscope.jsonl`,两端路径都基于 `__file__` 定位,**在任意目录下运行都可以**。 |
| `reimburses_evalscope.jsonl` | 报销核验评测集,**244 行**,每行含 `system`(规则)+ `query`(报销信息)+ `response`(参考答案)。`run_evalscope_evaluate.py` 通过 `dataset_id` 指向本目录加载。 |
### `quantization/` — 模型量化
| 文件 | 作用 |
| --- | --- |
| `run_awq.py` | **AWQ 量化**。用 `AutoAWQForCausalLM` 加载 `/root/s1/models/qwen2.5-7b`,配置 `{w_bit:4, q_group_size:128, zero_point:True, version:"GEMM"}` 执行 `quantize(tokenizer, ...)`,保存到 `/root/s1/quantization_models/qwen2.5-7b-awq`。AWQ 不依赖校准数据集。 |
| `run_gptq.py` | **GPTQ 量化**。用 `auto_gptq` 的 `AutoGPTQForCausalLM`,配置 `bits=4, group_size=128, desc_act=False`;读取 `datasets/reimburses/validates_reimburse.json`(路径基于 `__file__` 定位,任意目录启动均可)构造 `instruction + "\n" + input` 的校准样例并 tokenize(`max_length=512`),再 `model.quantize(examples)`,最后保存到 `/root/s1/quantization_models/qwen2.5-7b-gptq`。 |
---
## 三、微调全流程
> 说明:实线 = 仓库里确凿的调用/数据依赖;虚线 = 根据配置推断或需人工操作(训练框架、`.json` 的切分来源未在仓库中体现)。
### 3.1 总览:每个文件在流水线里的位置
```mermaid
flowchart TB
subgraph P0["⓪ 环境与配置:所有脚本的公共底座"]
ENV[".env.example → .env
LLM_KEY_BAILIAN / BAILIAN_API_URL"]
ENVPY["config/env.py
load_dotenv + EnvSettings → env"]
AICFG["config/ai_configs.py
模型注册表 ai_configs"]
LLMU["utils/llm_utils.py
create_llm(platform_code, temperature)"]
ENV --> ENVPY --> AICFG --> LLMU
end
subgraph P1["① 数据集生成:教师模型离线产数据(黑盒蒸馏)"]
T["教师模型 qwen3-max
temperature=0.8 · 百炼 API"]
GEN["datasets/generate_datasets_from_llm.py
100 轮 × 10 条 · 流式拼接 JSON"]
TR["datasets/reimburses/datasets_reimburse.json
【训练集】~1400 条 · Alpaca"]
VA["datasets/reimburses/validates_reimburse.json
【校验集】244 条 · Alpaca"]
DI["datasets/reimburses/dataset_info.json
登记 datasets_reimburse / validates_reimburse"]
LLMU -.->|注入 create_llm| GEN
T -->|返回 JSON 数组| GEN
GEN --> TR
GEN -.->|后续人工切分| VA
TR --> DI
VA --> DI
end
subgraph P2["② 另一条数据来源:EDS 平台导出(真实问答,非合成)"]
EDS["datasets/export_from_eds.jsonl
92 条 messages 格式"]
TA["datasets/to_alpaca.py"]
TE["datasets/to_evalscope.py"]
ALP["datasets/alpaca.json
【制度问答训练集】92 条"]
EVJ["datasets/evalscope.jsonl
【制度问答评测集】92 条"]
EDS --> TA --> ALP
EDS --> TE --> EVJ
end
subgraph P3["③ 微调:基座 Qwen2.5-7B"]
BASE["基座模型 /root/s1/models/qwen2.5-7b"]
LF["LLaMA-Factory 训练(未入库,依 dataset_info 推断)
train=datasets_reimburse · valid=validates_reimburse"]
CKPT["微调后权重 = 学生模型"]
BASE --> LF
DI --> LF
VA -.->|epoch 间算验证 loss| LF
ALP -.->|可选:制度问答任务| LF
LF --> CKPT
end
subgraph P4["④ 部署与评测"]
SRV["推理服务 vLLM / SGLang
http://127.0.0.1:8000/v1/chat/completions"]
CONV["datasets/reimburses_evalscope/reimburse_evalscope.py
Alpaca → system/query/response"]
EVSET["reimburses_evalscope/reimburses_evalscope.jsonl
【核验评测集】244 条"]
RUNEV["datasets/run_evalscope_evaluate.py
ModelScope EvalScope general_qa"]
JUDGE["裁判模型 qwen2.5-72b-instruct
score_type=pattern · JudgeStrategy.LLM · 并发 5"]
REPORT["评测报告(打分/准确率)"]
CKPT --> SRV
VA --> CONV --> EVSET --> RUNEV
EVJ -.->|可选:换成制度问答评测集| RUNEV
RUNEV -->|被测模型=Qwen2.5-7B| SRV
RUNEV --> JUDGE --> REPORT
end
subgraph P5["⑤ 量化上线"]
AWQ["quantization/run_awq.py
w_bit=4 · group_size=128 · GEMM"]
GPTQ["quantization/run_gptq.py
bits=4 · group_size=128"]
QA["/root/s1/quantization_models/qwen2.5-7b-awq
4bit"]
QG["/root/s1/quantization_models/qwen2.5-7b-gptq
4bit"]
BASE --> AWQ --> QA
BASE --> GPTQ --> QG
VA -.->|作为校准样例| GPTQ
end
REPORT -.->|分数不达标则回到 ① 补数据/调参| GEN
```
### 3.2 时间线:谁在什么时候做了什么
```mermaid
sequenceDiagram
autonumber
participant DEV as 开发机(本机)
participant TEA as 教师模型 qwen3-max(百炼)
participant TRAIN as 训练机 · LLaMA-Factory
participant SRV as vLLM 服务 127.0.0.1 端口 8000
participant JUD as 裁判模型 qwen2.5-72b
participant QUANT as 量化机
Note over DEV: ⓪ cp .env.example .env 并填入百炼 Key
DEV->>TEA: ① generate_datasets_from_llm.py 流式请求 100 轮 × 10 条
TEA-->>DEV: 每轮返回一个 JSON 数组 → 累加写入
DEV->>DEV: 产出 datasets_reimburse.json(~1400 条)
DEV->>DEV: 人工切出 validates_reimburse.json(244 条)
DEV->>DEV: to_alpaca.py / to_evalscope.py 处理 EDS 导出的 92 条
Note over TRAIN: ② copy 数据集 + 合并 dataset_info.json
TRAIN->>TRAIN: SFT 微调 Qwen2.5-7B
DEV->>SRV: ③ 用微调后权重起 OpenAI 兼容服务
DEV->>DEV: reimburse_evalscope.py 生成 244 条评测集
DEV->>JUD: ④ run_evalscope_evaluate.py 提交 query + 参考答案
SRV->>SRV: 被测模型(微调后的 7B)作答
JUD-->>DEV: 裁判打分 → 评测报告
alt 分数不达标
DEV->>TEA: 回到 ①:补数据 / 改 prompt / 调参
else 达标
QUANT->>QUANT: ⑤ run_awq.py 或 run_gptq.py → 4bit 部署
end
```
### 3.3 参与的模型一览
| 角色 | 模型 | 出处 / 被调用的地方 |
| --- | --- | --- |
| 教师模型(造数据) | `qwen3-max`(原 `deepseek-v3` 未注册,已改) | `generate_datasets_from_llm.py` → `utils/llm_utils.py` → `config/ai_configs.py` |
| 基座 / 学生模型 | `Qwen2.5-7B`(`/root/s1/models/qwen2.5-7b`) | 训练(LLaMA-Factory,未入库);量化脚本的 `model_path` |
| 被评测对象 | 同一个 7B(微调后) | `run_evalscope_evaluate.py` 的 `model='qwen2.5-7b'`、`api_url='http://127.0.0.1:8000/...'` |
| 裁判模型(打分) | `qwen2.5-72b-instruct`(DashScope) | `run_evalscope_evaluate.py` 的 `judge_model_args` |
| Embedding(备用) | `text-embedding-v4` | `config/ai_configs.py` 的 `bailian-embedding`(当前脚本未用到) |
### 3.4 阶段 × 时间 × 执行者对照表
| # | 阶段 | 何时执行 | 执行者 | 输入 → 产出 |
| --- | --- | --- | --- | --- |
| ⓪ | 配置 | 一切开始之前(一次性) | 人 | `.env.example` → `.env`;`config/env.py`、`config/ai_configs.py`、`utils/llm_utils.py` 提供 `create_llm` |
| ① | 造训练集/校验集 | 每次要扩数据、改生成 prompt 时 | `generate_datasets_from_llm.py` + 教师模型 | 无中生有 → `datasets_reimburse.json`(~1400)+ 切出 `validates_reimburse.json`(244) |
| ② | 另一批数据 | 拿到 EDS 导出后 | `to_alpaca.py` / `to_evalscope.py` | `export_from_eds.jsonl`(92)→ `alpaca.json` / `evalscope.jsonl` |
| ③ | 注册 + 微调 | 数据定稿后 | LLaMA-Factory(仓库外) | `dataset_info.json` + 两个 json → 微调权重 |
| ④ | 评测准备 | 训练完、跑评测前 | `reimburse_evalscope.py` | `validates_reimburse.json`(244)→ `reimburses_evalscope.jsonl` |
| ④ | 跑评测 | 服务起来之后 | `run_evalscope_evaluate.py` + 裁判模型 | 评测集 + 被测服务 → 评测报告 |
| ⑤ | 量化上线 | 评测达标后 | `run_awq.py` / `run_gptq.py` | 基座 or 权重 → 4bit 模型(GPTQ 用校验集做校准) |
---
## 四、典型使用流程
1. **生成训练数据**
```bash
python generate_datasets_from_llm.py
```
2. **配置 LLaMA-Factory**:把 `reimburses/dataset_info.json` 的内容合并到框架的 `data/dataset_info.json`,然后把 `reimburses/` 整个目录拷到 `data/` 下,训练时指定 `dataset: datasets_reimburse`、`val_dataset: validates_reimburse`。
3. **起推理服务**:用 vLLM/SGLang 把微调后的模型以 OpenAI 兼容方式起在 `127.0.0.1:8000`(与 `run_evalscope_evaluate.py` 的 `api_url` 对应)。
4. **跑评测**(需先确认 `.env` 已填 Key、且 8000 端口的服务已起)
```bash
python datasets/run_evalscope_evaluate.py
```
5. **量化部署**(Linux + GPU 环境)
```bash
cd quantization
python run_awq.py # 或 python run_gptq.py
```
---
## 五、依赖环境
| 用途 | 依赖 |
| --- | --- |
| 配置加载 | `python-dotenv`、`pydantic-settings` |
| 数据生成 | `langchain`(`langchain.chat_models.init_chat_model`)、本项目 `config/` + `utils/`;运行时需要 `.env` 里的百炼 Key |
| 评测 | `evalscope`(含 `TaskConfig`、`evalscope.constants.JudgeStrategy`) |
| AWQ 量化 | `autoawq`(提供 `awq.AutoAWQForCausalLM`)、`transformers` |
| GPTQ 量化 | `auto-gptq`(提供 `auto_gptq`)、`transformers` |
---
## 六、已修复的问题(本次检查)
| 问题 | 处理 |
| --- | --- |
| `from app.config.ai_configs import ai_configs`(`utils/llm_utils.py`) | 本项目没有 `app` 包,改为 `from config.ai_configs import ai_configs` |
| `from app.config.env import env`(`config/ai_configs.py`) | 改为 `from config.env import env` |
| `from create_llm import create_llm`(`generate_datasets_from_llm.py`) | 仓库里没有 `create_llm.py`,改为 `from utils.llm_utils import create_llm`,并先把项目根目录插入 `sys.path`,任意目录启动都能 import |
| 模型代号 `deepseek-v3` 未注册 | `create_llm` 会抛 `Unknown platform code`,改用已注册的 `bailian-qwen3-max` |
| 数据文件用相对路径读写 | `generate_datasets_from_llm.py`、`run_gptq.py`、`reimburse_evalscope.py` 的输入/输出路径改为基于 `__file__` 定位;目标文件不存在时按空数组处理,不再 `FileNotFoundError` |
| 训练/校验集会重复写入 | `generate_datasets_from_llm.py` 里 `temp_list` 跨轮累积,导致每轮把同批数据重复追加进已有文件;改为每轮重新取、另外 count 日志加上当前轮次 |
| GPTQ 与 AWQ 输出目录同名 | `run_gptq.py` 输出改为 `qwen2.5-7b-gptq`,不再覆盖 AWQ 产物 |
| `.gitignore` 只有 `.idea` | 补上 `.env`、`__pycache__/`、`.venv/`、模型目录与权重后缀 |
| 脚本导入即执行 | `generate_datasets_from_llm.py` 加了 `if __name__ == "__main__":` 保护 |
| 评测脚本硬编码 API Key | `run_evalscope_evaluate.py` 改为从 `config.env` 读 `env.bailian_api_url` / `env.llm_key_bailian`,密钥不再进代码 |
| 评测脚本 `from config.env import env` 依赖 IDE/根目录 PYTHONPATH | 与 `generate_datasets_from_llm.py` 同一处理:先把项目根目录插入 `sys.path` 再 import |
---
## 七、仍然存在的注意事项
> 以下 6 条已逐条复核核验为真(2026-10-04 复检),仍未处理。
1. **本地 `.env` 属于密钥文件,别手动 `git add -f`**。
复核结果:**它目前是安全的** —— `git ls-files` 里没有 `.env`,`git check-ignore -v .env` 命中 `.gitignore:10:.env`。它和模板 `.env.example` 的哈希不同,说明里面是**真实 Key**,所以只是不能被强制入库;换机器/换人时重新 `cp .env.example .env` 即可。
2. **模型与评测集路径写死为 Linux 绝对路径**:
- `quantization/run_awq.py`:`/root/s1/models/qwen2.5-7b` → `/root/s1/quantization_models/qwen2.5-7b-awq`
- `quantization/run_gptq.py`:同上 → `/root/s1/quantization_models/qwen2.5-7b-gptq`
- `datasets/run_evalscope_evaluate.py`:`dataset_id: /root/s1/datasets/reimburses_evalscope`
换机器必须手动改;建议抽成 `.env` 里的常量或直接改成相对 `BASE_DIR` 的路径。
3. **`to_alpaca.py` / `to_evalscope.py` 仍是「导入即执行」,且路径依赖运行目录**:底部直接调用转换函数;输入 `export_from_eds.jsonl`、输出 `alpaca.json` / `./evalscope.jsonl` 都是相对路径,**必须在 `datasets/` 目录下运行**。建议加 `__main__` 保护并把路径改成基于 `__file__` 定位(其余脚本都已改过,只剩这两个)。
4. **`config/env.py` 用了 pydantic v2 已废弃的写法**:`Field(..., env="...")` 与 `class Config`。当前能跑但会有弃用告警;建议换 `Field(..., alias="...")` + `SettingsConfigDict`。另外它对必填字段校验很严,缺 `LLM_KEY_BAILIAN` / `BAILIAN_API_URL` 时是**直接抛校验错**,跑之前先确认 `.env` 填全。
5. **`config/`、`utils/` 没有 `__init__.py`**(已确认:全仓库 0 个 `__init__.py`)。现在靠 Python 3 命名空间包正常工作,但如果将来打包、或用严格要求包结构的工具(部分 lint / 打包器)可能要补上。
6. **训练环节本身不在仓库里**:`dataset_info.json` 只说明数据格式是 alpaca,具体的训练命令、基座路径、LoRA 参数、产出目录都看不到,无法复现。建议把训练脚本或 `yaml` 一并入库。