# Minimind **Repository Path**: BUAA-CI-LAB/minimind ## Basic Information - **Project Name**: Minimind - **Description**: Lab2 Minimind 训练与 W8A8 量化 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-26 - **Last Updated**: 2026-09-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Lab2 Minimind 训练与 W8A8 量化 北京航空航天大学计算机学院 ## 1 实验目标 本次实验以开源项目 [MiniMind](https://github.com/jingyaogong/minimind) 的 `MiniMind Zero` 训练路线为对象,完成小型语言模型「数据准备 → 预训练 → 指令微调 → 量化 → 量化效果评估」的完整流程。具体要求: - 使用 Python 语言,版本 >= 3.13,项目使用 [uv](https://docs.astral.sh/uv/) 管理 Python 环境与依赖 - 使用深度学习框架 PyTorch 完成模型的训练与推理 - 从 [ModelScope](https://www.modelscope.cn/datasets/gongjy/minimind_dataset/files) 下载 MiniMind Zero 训练所需的数据集 - 完成预训练(Pretrain)与有监督指令微调(Supervised Fine-Tuning, SFT)两个训练任务,使用 [SwanLab](https://swanlab.cn) 记录训练过程,并使用 `eval_llm.py` 脚本验证模型效果 - **自行编写量化脚本**,将训练好的模型量化为 W8A8 格式(8bit 权重 + 8bit 激活) - 使用 `scripts/eval_ppl.py` 评估量化前后模型困惑度(Perplexity, PPL)的变化 - 使用 `scripts/inspect_w8a8_dtypes.py` 检查量化后模型的参数类型 - 使用 `eval_llm.py` 验证模型量化后的输出效果 本次实验的模型规模(约 64M 参数)、数据规模(约 2.8GB 语料)与工程链条均显著超过前一次实验(LeNet + MNIST),需预留充足的机器时间,并提前阅读第 7 节的训练注意事项。 ## 2 环境配置 本项目使用 uv 管理 Python 环境,克隆项目后执行以下命令完成环境配置: ```bash git clone <本实验仓库地址> cd minimind uv sync ``` uv 根据 `pyproject.toml` 与 `uv.lock` 自动创建虚拟环境并安装依赖,主要依赖包括: - torch:深度学习框架(本项目默认使用 CUDA 13.0 构建,版本选择见 2.1、2.2 节) - transformers / datasets:模型与数据集加载 - llmcompressor:模型量化框架,提供 SmoothQuant、GPTQ 等量化算法 - swanlab:训练过程可视化 - numpy:数据处理 运行代码统一使用 `uv run` 命令,例如 `uv run eval_llm.py`,无需手动激活虚拟环境。`uv run` 会自动定位项目根目录下的 `pyproject.toml`,因此在子目录中执行同样有效(下文将在 `trainer/` 与 `scripts/` 目录下执行命令)。 对于未写入项目依赖的包,可使用 `uv run --with <包名>` 临时安装并运行,无需修改 `pyproject.toml`,下文下载数据集即采用该方式。 ### 2.1 torch 与 CUDA 版本 「CUDA 版本」在不同语境下有三种不同含义,配置环境时需加以区分: | 名称 | 查看方式 | 含义 | | --- | --- | --- | | 显卡驱动版本 | `nvidia-smi` 输出的 `Driver Version` | NVIDIA 显卡驱动本身的版本 | | 驱动支持的 CUDA 版本 | `nvidia-smi` 右上角的 `CUDA Version` | 该驱动最高能支持到的 CUDA 版本,并非本机已安装的 CUDA 版本 | | PyTorch 构建的 CUDA 版本 | `torch.version.cuda` | 所安装的 PyTorch 包在编译时链接的 CUDA 运行时版本 | 前两者由 NVIDIA 驱动决定,与 Python 环境无关;第三者取决于 `uv sync` 时安装的是哪一个 PyTorch 构建。 PyTorch 官方为每个 CUDA 版本分别构建了独立的 wheel 包,托管于 PyTorch 自有包索引,各构建之间互不通用: | 包索引地址 | 对应构建 | | --- | --- | | `https://download.pytorch.org/whl/cu130` | CUDA 13.0 | | `https://download.pytorch.org/whl/cu128` | CUDA 12.8 | | `https://download.pytorch.org/whl/cu126` | CUDA 12.6 | | `https://download.pytorch.org/whl/cu118` | CUDA 11.8 | | `https://download.pytorch.org/whl/cpu` | 纯 CPU(不含 CUDA 支持) | 版本不匹配时的典型表现有两种:`torch.cuda.is_available()` 返回 `False`;或可检测到显卡但运行时报错,驱动过旧时提示 `CUDA driver version is insufficient for CUDA runtime version`,显卡架构与构建不匹配时提示 `CUDA error: no kernel image is available for execution on the device`。可正常使用 GPU 的条件为: > **本机驱动支持的 CUDA 版本 ≥ PyTorch 构建的 CUDA 版本。** 本项目的 `pyproject.toml` 将 torch 指向 PyTorch 官方的 **cu130** 源,这也是 `uv sync` 的默认行为: ```toml [tool.uv.sources] torch = { index = "pytorch-cu130" } [[tool.uv.index]] name = "pytorch-cu130" url = "https://download.pytorch.org/whl/cu130" explicit = true ``` > **注意**:`torch` 必须在 `[project] dependencies` 中显式声明,而不能依赖 `llmcompressor` 等包将其作为传递依赖引入。原因是 uv 的 `[tool.uv.sources]` 索引路由不作用于传递依赖,只有直接依赖才能被指向自定义索引。`pyproject.toml` 中该行 `"torch"` 未指定版本号,正是为了交由 cu130 源解析。 ### 2.2 设备与环境的选择 cu130 构建对驱动版本的要求较高,配置环境前需先确认本机驱动的支持情况: ```bash nvidia-smi # 关注 Driver Version 与右上角的 CUDA Version ``` 按下表选择对应的构建: | 设备情况 | 应使用的构建 | 说明 | | --- | --- | --- | | NVIDIA 显卡,`CUDA Version` ≥ 13.0 | `cu130`(默认,无需修改) | 与本实验默认配置一致 | | NVIDIA 显卡,`CUDA Version` 为 12.x | `cu126` 或 `cu128` | 需按下文修改索引 | | NVIDIA 显卡,`CUDA Version` 为 11.x | `cu118` | 驱动较旧 | | 无 NVIDIA 显卡(含 macOS / Apple Silicon) | `cpu` | 可运行,但训练耗时不可接受,见下文 | | Windows 平台 | 建议在 WSL2 下进行 | 原生 Windows 下 pyarrow 与 torch 存在 DLL 冲突 | 更换方式为编辑 `pyproject.toml`,将索引名与 URL 一并修改(下例改为 CUDA 12.6): ```toml [tool.uv.sources] torch = { index = "pytorch-cu126" } [[tool.uv.index]] name = "pytorch-cu126" url = "https://download.pytorch.org/whl/cu126" explicit = true ``` `uv.lock` 中锁定了具体的 torch 构建(本项目为 `2.12.0+cu130`),修改索引后需重新解析依赖: ```bash uv lock uv sync ``` > **注意**:纯 CPU 环境下本实验不具可行性。模型规模约 64M 参数,但预训练语料为 1.2GB,CPU 虽可完成全部流程,但速度与 GPU 相差一到两个数量级,完成预训练需数百小时量级的时间。无 NVIDIA 显卡时应改用实验室机器或租用云 GPU;租用机器的驱动版本通常低于本机,需按上表改为 `cu126` 或 `cu118`。 > **说明**:任务三的量化同样依赖 GPU 加速。llmcompressor 在校准与 GPTQ 误差补偿阶段计算量较大,纯 CPU 下耗时会显著增加。 ### 2.3 环境验证 完成 `uv sync` 后,执行以下命令确认 torch、CUDA 与 GPU 三者是否匹配: ```bash uv run python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.is_available())" ``` 默认环境(CUDA 13.0 构建)下的输出为: ```text 2.12.0+cu130 13.0 True ``` 三个字段的含义如下: - `2.12.0+cu130`:torch 版本号,`+` 后的 `cu130` 表示这是 CUDA 13.0 构建(若为 `+cpu` 则表示安装的是纯 CPU 版本) - `13.0`:`torch.version.cuda`,即 PyTorch 链接的 CUDA 运行时版本 - `True`:`torch.cuda.is_available()`,表示 GPU 可被正常调用 第三项为 `True` 即表示环境配置完成;若为 `False`,按「2.2 设备与环境的选择」核对索引与驱动的匹配情况。 训练与推理脚本均会自动检测 GPU:训练脚本的 `--device` 默认为 `cuda:0`(不可用时回退 `cpu`),`eval_llm.py` 的 `--device` 默认为 `cuda`(不可用时回退 `cpu`),无需额外配置。显存不足时的处理方法见 7.1 节。 ## 3 数据集获取与使用 本次实验采用 MiniMind Zero 训练所需的核心数据集,托管于 ModelScope: **数据集主页**: 从零复现一个 Zero 对话模型仅需其中两个文件: | 文件名 | 大小 | 用途 | | --- | --- | --- | | `pretrain_t2t_mini.jsonl` | 1.2 GB | 预训练语料,纯文本 | | `sft_t2t_mini.jsonl` | 1.6 GB | 指令微调语料,多轮对话(已混入部分 Tool Call 样本) | 两个文件均需下载,并放置到项目的 `./dataset/` 目录下。`modelscope` 未写入项目依赖,可使用 `uv run --with` 临时安装官方命令行工具下载(在项目根目录执行): ```bash uv run --with modelscope modelscope download \ --repo-type dataset gongjy/minimind_dataset \ pretrain_t2t_mini.jsonl sft_t2t_mini.jsonl \ --local-dir ./dataset ``` 也可从上述 ModelScope 页面手动下载文件,再放入 `./dataset/` 目录。 下载完成后 `./dataset/` 的目录结构如下: ```text ./dataset/ ├── pretrain_t2t_mini.jsonl (1.2GB) ├── sft_t2t_mini.jsonl (1.6GB) ├── lm_dataset.py └── dataset.md ``` 两个文件的格式差异如下(均为逐行 JSON): ```jsonc // pretrain_t2t_mini.jsonl:纯文本 {"text": "..."} // sft_t2t_mini.jsonl:多轮对话 {"conversations": [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]} ``` > **注意**:`sft_t2t_mini.jsonl` 的部分样本带有 `tools` / `tool_calls` 字段,取值为 JSON 字符串而非对象。构造量化校准数据时需用 `json.loads` 还原,否则 chat 模板渲染会出错(详见任务三)。 数据集在训练时由 `dataset/lm_dataset.py` 读取:预训练样本在首尾拼接 `bos` / `eos` 并按 `max_seq_len` 截断;SFT 样本按 chat 模板渲染后,仅对 assistant 回复部分计算损失(用户提问部分的标签置 `-100`)。 ## 4 代码介绍 ### 4.1 代码结构 本项目基于 PyTorch 编写,与本次实验相关的代码结构如下: - `model/model_minimind.py`:模型结构文件,包含 `MiniMindConfig` 配置类与 `MiniMindForCausalLM` 模型实现 - `dataset/lm_dataset.py`:数据类文件,包含 `PretrainDataset` 与 `SFTDataset` 两个数据集实现 - `trainer/train_pretrain.py`:预训练主文件,包含预训练流程与权重保存 - `trainer/train_full_sft.py`:指令微调主文件,包含 SFT 流程与权重保存 - `trainer/trainer_utils.py`:训练工具函数,包含学习率调度、日志打印、检查点保存与续训等 - `eval_llm.py`:模型推理与对话脚本,支持加载原生 PyTorch 权重或 Transformers 格式模型 - `scripts/convert_model.py`:权重格式转换脚本,将训练得到的 `.pth` 权重转换为 Transformers 格式模型目录 - `scripts/quantize_w8a8.py`:W8A8 量化脚本(**需要同学们完成的部分**,见任务三) - `scripts/eval_ppl.py`:困惑度评估脚本,用于对比量化前后的模型 - `scripts/inspect_w8a8_dtypes.py`:量化检查点参数类型打印脚本 ### 4.2 模型结构 MiniMind Zero 是一个 Decoder-Only 的 Transformer 语言模型,采用与 `Qwen3` 对齐的结构设计,整体配置如下: | 配置项 | 取值 | | --- | --- | | 隐藏层维度 `hidden_size` | 768 | | 层数 `num_hidden_layers` | 8 | | 注意力头数 / KV 头数 | 8 / 4(GQA 分组查询注意力) | | 词表大小 `vocab_size` | 6400 | | 参数量 | 约 63.91 M | 模型的技术要点: - 采用**预标准化(Pre-Norm)+ RMSNorm**,训练更稳定 - 使用 **SwiGLU** 激活函数 - 使用 **RoPE** 旋转位置编码(`rope_theta=1e6`),并支持 YaRN 长度外推 - 注意力层使用 **GQA**,KV 头数少于 Q 头数,降低推理时的 KV Cache 开销 数据流:`input_ids (batch, seq_len)` → Embedding → 8 × Transformer Block(RMSNorm → Attention → 残差 → RMSNorm → SwiGLU FFN → 残差)→ RMSNorm → `lm_head` → `logits (batch, seq_len, 6400)`。 `MiniMindConfig` 中与本次实验最相关的三个参数是 `hidden_size`、`num_hidden_layers` 与 `use_moe`,它们决定了模型规模与权重文件名中的后缀(例如 `full_sft_768.pth`)。 ### 4.3 代码流程 训练流程在 `trainer/train_pretrain.py` 与 `trainer/train_full_sft.py` 中,两者结构完全一致,均包含:初始化分布式环境与随机种子 → 构建模型、数据集与优化器 → (可选)恢复检查点 → 逐 epoch 训练并在 `save_interval` 步保存权重。 - 预训练权重保存为 `out/pretrain_768.pth` - 微调权重保存为 `out/full_sft_768.pth`,且默认基于 `out/pretrain_768.pth` 继续训练(`--from_weight pretrain`) - 学习率采用余弦退火调度(`trainer/trainer_utils.py` 中的 `get_lr`) 两个训练脚本均通过 `--use_wandb` 开关启用 SwanLab 记录,脚本内部以 `import swanlab as wandb` 方式调用,因此参数名为 `use_wandb`。 `eval_llm.py` 的加载逻辑分两条路径: ```python if 'model' in args.load_from: # --load_from model:加载原生 PyTorch 权重 ./out/_768.pth ... else: # --load_from <路径>:加载 Transformers 格式模型目录 model = AutoModelForCausalLM.from_pretrained(args.load_from, trust_remote_code=True) ``` 量化后的模型目录同样走第二条路径,脚本通过 `is_quantized` 属性识别量化模型并跳过 `.half()` 调用: ```python if getattr(model, 'is_quantized', False): # 量化模型已处于正确 dtype,transformers 禁止再调用 .half() return model.eval().to(args.device), tokenizer ``` ## 5 实验任务 以下任务按顺序完成,后一任务的输入依赖前一任务的输出。 ### 任务一:数据集准备 按第 3 节说明,从 ModelScope 下载 `pretrain_t2t_mini.jsonl` 与 `sft_t2t_mini.jsonl` 到 `./dataset/` 目录。 **完成标志**:`./dataset/` 下出现上述两个文件,大小分别约为 1.2GB 与 1.6GB。 ### 任务二:预训练与指令微调 #### 2.1 预训练(Pretrain) ```bash cd trainer uv run python train_pretrain.py --use_wandb --wandb_project MiniMind-Pretrain ``` 预训练阶段的目标是使模型从大规模文本中习得语言的统计规律与基础事实知识,其训练任务是依据上文预测下一个 token。该阶段结束后,模型具备文本续写能力,但尚不具备对话能力。 关键参数(脚本默认值,可在命令行覆盖): | 参数 | 默认值 | 说明 | | --- | --- | --- | | `--epochs` | 2 | 训练轮数 | | `--batch_size` | 32 | 单卡批次大小 | | `--learning_rate` | 5e-4 | 初始学习率 | | `--max_seq_len` | 340 | 最大截断长度(token 数) | | `--accumulation_steps` | 8 | 梯度累积步数 | **预期结果**:训练过程中终端每 `log_interval` 步打印一次 `loss / logits_loss / lr`,SwanLab 网页端的 loss 曲线持续下降;训练结束后在 `./out/` 目录下得到 `pretrain_768.pth`(约 137MB)。 单卡 `RTX 3090` 上 1 个 epoch 约需 1.2 小时(上游项目给出的经验值),可作为时间规划的参考。 预训练完成后可查看模型的文本续写效果: ```bash cd .. # 回到项目根目录 uv run eval_llm.py --weight pretrain ``` > **注意**:此时模型尚未经过指令微调,不具备对话能力,输出形式为与话题相关的文本续写,属正常现象。 #### 2.2 有监督指令微调(SFT) ```bash cd trainer uv run python train_full_sft.py --use_wandb --wandb_project MiniMind-Full-SFT ``` SFT 阶段通过有监督的指令—回答数据,使模型习得按指令作答的输出格式,将预训练阶段获得的语言能力转化为对话能力。脚本默认 `--from_weight pretrain`,即在预训练权重的基础上继续训练。 关键参数默认值:`--epochs 2`、`--batch_size 16`、`--learning_rate 1e-5`、`--max_seq_len 768`、`--accumulation_steps 1`。 **预期结果**:`./out/` 目录下得到 `full_sft_768.pth`(约 137MB),SwanLab 上的 loss 曲线整体低于预训练阶段。 微调完成后查看对话效果: ```bash cd .. uv run eval_llm.py --weight full_sft ``` 脚本首先提示选择输入模式(`0` 自动测试 / `1` 手动输入),随后逐个 prompt 流式输出回答,程序输出示例如下: ```text 💬: 请介绍一下自己。 🧠: 我是 MiniMind,一个小巧但有用的语言模型,我可以回答问题、提供信息、协助写作…… 💬: 为什么天空是蓝色的 🧠: 天空呈现蓝色是因为太阳光进入大气层后,波长较短的蓝光更容易被空气分子散射…… ``` > **说明**:本实验仅训练 2 个 epoch,模型的事实性知识与泛化能力有限,输出中可能出现答非所问或事实错误。实验报告应如实记录该现象,并在「分析与讨论」部分分析其原因。 #### 2.3 训练过程记录(SwanLab) 训练脚本通过 `--use_wandb` 启用 SwanLab。首次使用需登录并获取 API Key(注册地址:): ```bash uv run swanlab login ``` 登录后重新执行训练命令即可。SwanLab 会记录 `loss`、`logits_loss`、`aux_loss`、`learning_rate` 等指标,可在网页端查看曲线。 如无法使用云端服务,可采用本地模式,日志将保存在 `trainer/swanlog/` 目录下: ```bash uv run swanlab local # 本地模式 uv run swanlab offline # 离线模式,事后用 swanlab sync 同步至云端 ``` **实验报告需包含预训练与 SFT 两个阶段的 loss 曲线截图。** ### 任务三:编写 W8A8 量化脚本 #### 3.1 量化原理 模型量化的基本方法是以定点整数近似表示浮点数。对称均匀量化将浮点张量 $x$ 映射为 int8: $$s = \frac{\max|x|}{127}, \qquad x_q = \mathrm{round}\left(\frac{x}{s}\right) \in [-127, 127], \qquad x \approx x_q \cdot s$$ W8A8 指**权重(Weight)与激活(Activation)均量化为 8bit**。相较于只量化权重的 W8A16,W8A8 可使矩阵乘法完全运行于整数域(int8 × int8 → int32),在支持整数运算的硬件上获得更大的加速收益;但激活的动态范围随输入变化,量化难度更高。 为降低量化误差,实际流程中通常引入以下两种技术: - **SmoothQuant**:通过数学等价变换 $Y = (X / s) \cdot (s \cdot W)$,将激活的量化难度部分转移至权重,使两者的动态范围均更利于量化 - **GPTQ**:逐列量化权重,并利用校准数据估计的 Hessian 矩阵对已量化列造成的误差进行补偿 本实验要求使用 `llmcompressor` 框架提供的上述算法完成量化。 #### 3.2 前置步骤:转换为 Transformers 格式 量化框架需要读取 Transformers 格式的模型,而非训练脚本输出的 `.pth` 权重,因此需先执行格式转换: ```bash cd scripts uv run python convert_model.py ``` > **注意**:`convert_model.py` 中的路径(`../out/...`、`../minimind-3`)相对于当前工作目录,必须在 `scripts/` 目录下执行,或在代码中改写为绝对路径。 **预期结果**:项目根目录下生成 `minimind-3/` 目录,包含 `config.json`、`model.safetensors`(约 128MB)、`tokenizer.json`、`chat_template.jinja` 等文件。 转换后可先用对话脚本验证模型是否正常: ```bash cd .. uv run eval_llm.py --load_from ./minimind-3 ``` 其输出效果应与 `--weight full_sft` 时一致。 #### 3.3 完成量化脚本 仓库中的 `scripts/quantize_w8a8.py` 提供了脚本骨架,并以注释形式保留了关键的调用提示与参数说明。**需补全的核心逻辑如下**: 1. **加载待量化的模型与分词器** 使用 `AutoModelForCausalLM.from_pretrained` 与 `AutoTokenizer.from_pretrained` 读取上一步生成的 `../minimind-3` 目录。 2. **构造校准数据集(Calibration Dataset)** 量化需使用一小批真实数据统计权重与激活的动态范围,构造流程为: - 从 `../dataset/sft_t2t_mini.jsonl` 加载数据,打乱后采样 **512** 条样本 - 使用 tokenizer 的 chat 模板将 `conversations` 渲染为文本,再进行 tokenize - tokenize 时设置 `max_length=2048`、`truncation=True`、`add_special_tokens=False` - `tools` / `tool_calls` 字段为 JSON 字符串,渲染前需用 `json.loads` 还原为对象,否则模板渲染将失败 3. **配置量化 recipe** 使用两个 modifier: - `SmoothQuantModifier(smoothing_strength=0.8)` - `GPTQModifier(targets="Linear", scheme="W8A8")` 4. **执行量化** 调用 `llmcompressor.oneshot(model=..., dataset=..., recipe=..., max_seq_length=..., num_calibration_samples=...)`。 5. **保存量化结果** 使用 `save_pretrained` 将量化模型与 tokenizer 保存到 `../minimind-3-w8a8` 目录。 补全后运行: ```bash cd scripts uv run python quantize_w8a8.py ``` **预期结果**: - 项目根目录下生成 `minimind-3-w8a8/` 目录 - `model.safetensors` 约 **74MB**(未量化的 fp16 模型约 128MB,压缩比约 1.7×) - `config.json` 中出现 `quantization_config` 字段,其中 `weights` 与 `input_activations` 的 `num_bits` 均为 8,`quant_method` 为 `compressed-tensors` > **思考题**(需写入实验报告):量化后模型体积为何未降至 fp16 的一半?可结合未被量化的层进行分析。 ### 任务四:量化前后困惑度(PPL)对比 困惑度(Perplexity, PPL)用于衡量语言模型对文本的建模能力,定义为 token 级平均负对数似然的指数: $$\mathrm{PPL} = \exp\left(-\frac{1}{N}\sum_{i=1}^{N} \log p(x_i \mid x_{ **思考题**(需写入实验报告):若将 GPTQ 的 `scheme` 改为 `W8A16`(仅量化权重),PPL 将如何变化?为什么? ### 任务五:量化模型参数类型检查 `config.json` 中虽已声明量化配置,仍需确认检查点中的张量确实以 int8 存储。使用仓库提供的脚本进行检查: ```bash uv run scripts/inspect_w8a8_dtypes.py --ckpt ./minimind-3-w8a8/model.safetensors ``` > **说明**:脚本的 `--ckpt` 默认值为 `../minimind-3-w8a8/model.safetensors`(相对 `scripts/` 目录)。在项目根目录执行时需按上式显式指定路径。 脚本逐个打印张量的名称、dtype 与 shape,并给出汇总与告警。 **预期结果**(参考值): ```text === 汇总 === torch.float16: 91 个张量, 4,999,680 参数 torch.int8: 57 个张量, 63,897,600 参数 总计: 148 个张量, 68,897,280 参数 ``` 各类型的含义: | 类型 | 张量数 | 说明 | | --- | --- | --- | | `torch.int8` | 57 个 | 8 层 × 7 个线性层(q/k/v/o_proj、gate/up/down_proj)+ `lm_head`,共 63.9M 参数 | | `torch.float16` | 91 个 | 词嵌入 `embed_tokens`、各层 RMSNorm 权重(`input_layernorm`、`post_attention_layernorm`、`q_norm`、`k_norm`),以及每个量化权重配套的 `weight_scale` | 脚本末尾会列出所有非 int8 张量。嵌入层与 RMSNorm 未被量化属预期行为,其余张量需逐一核对。汇总结果需截图写入实验报告。 > **思考题**(需写入实验报告):`weight_scale` 的作用是什么?若要求其同样以 int8 存储,需对量化尺度 $s$ 施加什么约束?这样做会带来什么代价? ### 任务六:量化模型输出效果验证 使用与任务二相同的对话脚本查看量化模型的生成效果: ```bash uv run eval_llm.py --load_from ./minimind-3-w8a8 ``` 脚本会自动识别量化模型(`quantized=True`)并跳过 `.half()` 调用。 **预期结果**:模型正常加载并流式输出回答,生成质量与量化前(任务 2.2)基本一致,语句通顺、格式正确,仅存在个别用词差异。 实验报告应并排给出同一组 prompt 在量化前(`./minimind-3`)与量化后(`./minimind-3-w8a8`)的回答截图,并对比其差异。 ## 6 实验提交 - 若程序正常运行,训练得到的权重将保存至 `./out/` 目录,量化模型将保存至 `./minimind-3-w8a8/` 目录,无需进行额外操作 - 撰写实验报告,包括但不限于**量化原理说明、量化脚本实现代码介绍、训练过程截图(SwanLab loss 曲线)、量化前后 PPL 对比结果、量化模型参数类型统计截图、量化前后对话效果对比截图等**。**建议采用 Markdown 格式撰写报告,最终导出 PDF 提交**。报告命名格式:学号 + 姓名 + 作业二报告.docx(.pdf),如 XXXXXXXX + 张三 + 作业二报告.docx(.pdf)(实验报告的撰写格式要求与建议请参考 [实验报告撰写格式](docs/实验报告撰写格式.md)) - 将完整的工程文件夹(**需要包含训练权重 `./out/` 与量化模型 `./minimind-3-w8a8/`,不需要数据文件夹 `./dataset/`**)、补全后的量化脚本 `scripts/quantize_w8a8.py` 与实验报告打包成压缩文件。压缩文件命名格式:学号 + 姓名,如 XXXXXXXX + 张三 + 作业二.zip(.rar) ## 7 其他 ### 7.1 训练注意事项 **显存不足(OOM)的处理** 训练过程中若出现 `torch.cuda.OutOfMemoryError`,处理方法为降低 `batch_size`: ```bash uv run python train_pretrain.py --batch_size 8 --use_wandb ``` 若需在降低 `batch_size` 的同时保持原有的有效批次大小(以维持训练稳定性),可同步提高梯度累积步数,使两者乘积不变: ```bash # batch_size 32 × accumulation_steps 8 = 256 的有效批次 uv run python train_pretrain.py --batch_size 8 --accumulation_steps 32 --use_wandb ``` 其他可选的显存优化手段: - 降低 `--max_seq_len`(会损失长文本能力,需权衡) - 关闭 `--use_compile`(`torch.compile` 会额外占用显存) - 增加 `--num_workers` 可缓解数据加载瓶颈,但不降低显存占用 **其他注意事项** - 训练脚本默认从 `./out/` 读取权重、向 `./out/` 保存权重,该目录下的 `.pth` 文件不应随意删除 - 所有训练脚本均支持**检查点续训**:添加 `--from_resume 1` 参数后会自动检测 `./checkpoints/` 下的检查点并恢复训练进度(含优化器状态与 SwanLab run),适用于长时间训练 - 训练脚本同时支持单卡与多卡(`torchrun --nproc_per_node N train_pretrain.py`),单卡训练直接使用 `uv run python` 即可 - 训练过程中可随时中断(`Ctrl+C`),已保存的权重文件不会损坏 ### 7.2 常见问题 **Q1:`torch.cuda.is_available()` 返回 `False`?** A:当前 torch 构建与本机驱动不匹配。先用 `nvidia-smi` 查看驱动支持的 CUDA 版本,再按「2.2 设备与环境的选择」更换对应的包索引(`cu126` / `cu118` / `cpu`),并执行 `uv lock && uv sync`。若更换索引后仍为 `False`,可用 `uv run python -c "import torch; print(torch.version.cuda)"` 确认实际装入的构建版本。 **Q2:`modelscope` 命令找不到?** A:`modelscope` 未写入项目依赖,需使用 `uv run --with modelscope modelscope download ...` 调用(见第 3 节)。 **Q3:`convert_model.py` 或 `quantize_w8a8.py` 报错找不到 `../out/` 或 `../dataset/`?** A:这两个脚本中的路径相对于当前工作目录,需在 `scripts/` 目录下执行。 **Q4:量化脚本报错 `KeyError: 'tools'` 或 chat 模板渲染失败?** A:`sft_t2t_mini.jsonl` 中 `tools` / `tool_calls` 字段为 JSON 字符串,渲染前需用 `json.loads` 还原为对象,详见任务三第 2 步。 **Q5:量化耗时过长?** A:GPTQ 需对每个线性层估计 Hessian 并逐列量化,耗时与校准样本数、序列长度成正比。减小 `NUM_CALIBRATION_SAMPLES`(例如改为 32)可缩短耗时,流程验证通过后再恢复为 512。 **Q6:`eval_llm.py` 加载量化模型时报 dtype 相关错误?** A:量化模型已处于正确 dtype,不能再次调用 `.half()`。`eval_llm.py` 已通过 `is_quantized` 属性做了判断,若修改该脚本,应保留这一分支。 **Q7:训练 loss 不下降或出现 `NaN`?** A:应检查数据集文件与 `--max_seq_len` 是否匹配(预训练推荐 340,SFT 推荐 768),以及 `--from_weight` 是否加载了不匹配的权重。必要时可降低学习率重试。