Mistral-7B QLoRA 实战(二):官方权重转换、4-bit NF4 加载与 Base Model 基线测试
本文是《Mistral-7B QLoRA 实战》的第三、第四章。 继续使用前两章搭建的 HPC 环境、官方 Mistral-7B-v0.1 权重和统一项目目录。本文完整记录真实执行的命令、遇到的报错、修复方法、验证结果和推理实验。
实验状态: 官方权重已经转换为 Hugging Face 格式;Tesla V100 上的 4-bit NF4 加载及推理已经成功;5 条微调前测试题已生成并保存。本文尚未开始 Alpaca 数据准备与 QLoRA 训练。
本篇实验环境与目标
实验项目根目录始终是:
text
/slurm/home/zhengjie/xss_Mistral7B
核心环境版本:
| 项目 | 实际版本 / 配置 |
|---|---|
| 模型 | Mistral-7B-v0.1 Base Model |
| Python | 3.10.21 |
| PyTorch | 2.5.1+cu121 |
| Transformers | 4.46.3 |
| bitsandbytes | 0.44.1 |
| sentencepiece | 0.2.0 |
| protobuf | 5.29.6(实验期间补装) |
| GPU | Tesla V100-PCIE-32GB,Compute Capability 7.0 |
| 实测运行节点 | hpc4 |
| 量化 | NF4、double quantization、FP16 compute |
整体流程:
text
官方 Mistral 原始文件
├── consolidated.00.pth
├── params.json
└── tokenizer.model
│
▼
第三章:转换为 Hugging Face 格式
│
├── config.json
├── tokenizer.json / tokenizer.model
├── model.safetensors.index.json
└── 4 个 *.safetensors 权重分片
│
▼
第四章:BitsAndBytesConfig(4-bit NF4)
│
▼
单卡 Tesla V100 载入 → 简短生成 → 显存记录
│
▼
5 道固定测试题 → base_model_predictions.json
│
▼
【下一章】Alpaca-Cleaned 数据准备
教程与实测的区分: 配置中的 4-bit、NF4、double quantization、Transformers/PEFT/TRL 工作流来自课程资料《第六节补充课 课程资料 20240406-微调Mistral7B》。课程示例采用 BF16;本文根据实际 V100 硬件改为 FP16。具体报错、时间、显存、运行输出均来自这次服务器实验,而不是教程中的理想化示例。
第三章:Mistral-7B 官方权重转换为 Hugging Face 格式
3.1 为什么需要转换官方权重
第二章已下载并通过官方 MD5 校验的 Mistral-7B-v0.1 压缩包,解压后得到以下原始文件:
text
models/original/Mistral-7B-v0.1-raw/mistral-7B-v0.1/
├── consolidated.00.pth
├── params.json
├── RELEASE
└── tokenizer.model
其中:
consolidated.00.pth:PyTorch 原始权重;params.json:Mistral 原始结构参数;tokenizer.model:SentencePiece 分词模型;RELEASE:随官方包附带的发布说明文件。
而接下来的 QLoRA 实验会调用:
python
AutoModelForCausalLM.from_pretrained(...)
AutoTokenizer.from_pretrained(...)
因此,我们希望得到标准 Hugging Face 本地模型目录,其中有 config.json、分词器文件以及可由 Transformers 加载的模型权重。它不是把 7B 变成另一个模型,也不是进行量化或训练,核心任务是权重命名、结构与存储格式的转换。
目标输出路径:
text
/slurm/home/zhengjie/xss_Mistral7B/models/Mistral-7B-v0.1-hf
本次采用 Transformers 自带的转换脚本,而不是从 Hugging Face Hub 重新下载整套权重。这样适合本服务器无法稳定访问 huggingface.co,但已取得官方 .pth 的情况。
3.2 检查本地转换脚本和命令行参数
先进入正确环境:
bash
conda activate /slurm/home/zhengjie/xss_Mistral7B/envs/mistral_qlora
cd /slurm/home/zhengjie/xss_Mistral7B
source env.sh
定义路径:
bash
MODEL_RAW=/slurm/home/zhengjie/xss_Mistral7B/models/original/Mistral-7B-v0.1-raw/mistral-7B-v0.1
MODEL_HF=/slurm/home/zhengjie/xss_Mistral7B/models/Mistral-7B-v0.1-hf
CONVERTER=/slurm/home/zhengjie/xss_Mistral7B/envs/mistral_qlora/lib/python3.10/site-packages/transformers/models/mistral/convert_mistral_weights_to_hf.py
查看帮助和实际代码的参数定义:
bash
python "$CONVERTER" --help
grep -n 'add_argument' "$CONVERTER"
本机 Transformers 4.46.3 的输出显示支持:
text
--input_dir INPUT_DIR
--model_size {7B,tokenizer_only}
--output_dir OUTPUT_DIR
--safe_serialization SAFE_SERIALIZATION
--is_v3
其中 --safe_serialization 的源码定义为:
python
parser.add_argument(
"--safe_serialization",
type=bool,
help="Whether or not to save using `safetensors`."
)
本实验是 Mistral-7B-v0.1 ,不加 --is_v3,并使用 --safe_serialization True 保存 safetensors。
小提醒:
argparse中的type=bool对字符串的解析并不严谨,例如字符串"False"也可能被当成True。本文按本机已验证的True写法执行,不建议猜测其他版本的参数行为;部署时应始终先检查自己安装版本的--help。
创建目标目录:
bash
mkdir -p "$MODEL_HF"
ls -lah "$MODEL_HF"
第一次检查目标目录为空,说明不会混入旧的正式权重文件。
3.3 第一次失败:在 Base Conda 环境中运行
最初执行的转换命令是:
bash
time python "$CONVERTER" \
--input_dir "$MODEL_RAW" \
--model_size 7B \
--output_dir "$MODEL_HF" \
--safe_serialization True
但是当时终端提示符是:
text
(base) zhengjie@hpc4:~/xss_Mistral7B$
报错:
text
ModuleNotFoundError: No module named 'torch'
real 0m0.129s
根因: source env.sh 只设置项目环境变量和缓存路径,并不会自动 conda activate。本次执行实际上使用 Base 环境 Python,里面没有我们为实验安装的 PyTorch。
修复方法:
bash
conda activate /slurm/home/zhengjie/xss_Mistral7B/envs/mistral_qlora
cd /slurm/home/zhengjie/xss_Mistral7B
source env.sh
which python
python --version
python -c 'import torch; print("torch:", torch.__version__); print("cuda:", torch.cuda.is_available())'
实际验证:
text
/slurm/home/zhengjie/xss_Mistral7B/envs/mistral_qlora/bin/python
Python 3.10.21
torch: 2.5.1+cu121
cuda: True
也可以直接用环境里的 Python 绝对路径,避免被当前 Shell 的 Conda 状态影响:
bash
/slurm/home/zhengjie/xss_Mistral7B/envs/mistral_qlora/bin/python --version
经验: 看到 (base) 和看到 (mistral_qlora) 不是表面差异,直接决定脚本能否导入正确版本的依赖。
3.4 第二次失败:Tokenizer 路径被错误地设为空字符串
激活正确环境后再次转换,出现:
text
OSError: Not found: model file path should not be empty.
堆栈最终指向转换脚本 write_model() 中的这一行:
python
tokenizer = tokenizer_class(tokenizer_path + ".v3" if is_v3 else "")
对 v0.1,is_v3=False,所以这段代码向 Tokenizer 传入的是空字符串 "",导致 SentencePiece 无法找到实际存在的 tokenizer.model。这不是权重损坏,也不是 tokenizer 文件丢失,而是本机这版转换脚本的路径处理问题。
为防止污染 Conda 环境,先复制脚本到项目目录:
bash
cp "$CONVERTER" \
/slurm/home/zhengjie/xss_Mistral7B/scripts/convert_mistral_weights_to_hf_fixed.py
只对副本做最小修复:
bash
python - <<'PY'
from pathlib import Path
path = Path(
'/slurm/home/zhengjie/xss_Mistral7B/scripts/'
'convert_mistral_weights_to_hf_fixed.py'
)
text = path.read_text(encoding='utf-8')
old = 'tokenizer_path + ".v3" if is_v3 else ""'
new = 'tokenizer_path + ".v3" if is_v3 else tokenizer_path'
assert old in text, '没有找到预期代码:停止自动修改'
path.write_text(text.replace(old, new, 1), encoding='utf-8')
print('修复成功:', path)
PY
验证:
bash
grep -n 'tokenizer = tokenizer_class' \
scripts/convert_mistral_weights_to_hf_fixed.py
实际输出:
text
108: tokenizer = tokenizer_class(tokenizer_path + ".v3" if is_v3 else tokenizer_path)
251: tokenizer = tokenizer_class(input_tokenizer_path)
修复后的逻辑:
text
Mistral v0.1(is_v3=False)
↓
实际 tokenizer_path
↓
tokenizer.model
↓
SentencePiece 正常读取
本修复只针对实验中实际安装的转换脚本版本与报错位置 。不要把这个修改不加检查地套用到任何新版 Transformers,也不要直接覆盖第三方包在
site-packages中的原文件。
3.5 第三次失败:缺少 protobuf
使用副本重试时,Tokenizer 的空路径错误已经消失,但遇到:
text
ImportError:
LlamaConverter requires the protobuf library but it was not found in your environment.
这说明脚本已经成功进入了 LlamaTokenizerFast 从 SentencePiece slow tokenizer 转换成 fast tokenizer 的流程,只是当前环境缺少相应依赖。
安装:
bash
python -m pip install 'protobuf>=3.20.3,<6'
实际安装的是:
text
Successfully installed protobuf-5.29.6
进行完整依赖检查:
bash
python - <<'PY'
import torch
import transformers
import sentencepiece
import google.protobuf
print('torch :', torch.__version__)
print('transformers :', transformers.__version__)
print('sentencepiece:', sentencepiece.__version__)
print('protobuf :', google.protobuf.__version__)
print('\nEnvironment check: OK')
PY
实际结果:
text
torch : 2.5.1+cu121
transformers: 4.46.3
sentencepiece: 0.2.0
protobuf : 5.29.6
Environment check: OK
经验: 即使 torch、transformers、sentencepiece 已经安装,也不代表 fast tokenizer 所需的可选依赖自动齐全。
3.6 正式转换:14GB 权重读取、33 个临时分片与最终保存
依赖修复完成后,执行真正成功走完权重保存阶段的命令:
bash
cd /slurm/home/zhengjie/xss_Mistral7B
source env.sh
MODEL_RAW=/slurm/home/zhengjie/xss_Mistral7B/models/original/Mistral-7B-v0.1-raw/mistral-7B-v0.1
MODEL_HF=/slurm/home/zhengjie/xss_Mistral7B/models/Mistral-7B-v0.1-hf
FIXED_CONVERTER=/slurm/home/zhengjie/xss_Mistral7B/scripts/convert_mistral_weights_to_hf_fixed.py
time python "$FIXED_CONVERTER" \
--input_dir "$MODEL_RAW" \
--model_size 7B \
--output_dir "$MODEL_HF" \
--safe_serialization True
运行中首先出现:
text
Fetching all parameters from the checkpoint at
/slurm/home/zhengjie/xss_Mistral7B/models/original/Mistral-7B-v0.1-raw/mistral-7B-v0.1.
然后是:
text
FutureWarning: You are using `torch.load` with `weights_only=False` ...
这个 FutureWarning 不是转换失败 ;它提醒 .pth 的 pickle 反序列化可能执行不可信代码,不能随便加载来源不明的 checkpoint。此次文件来自 Mistral 官方 CDN,并在第二章匹配了官方公布的 MD5;依然应把权重来源验证与 Python pickle 的一般安全风险分开理解。
继续运行:
text
Loading the checkpoint in a Mistral model.
Loading checkpoint shards: 100% | 33/33 [00:05<00:00, 6.28it/s]
Saving in the Transformers format.
这里有一个容易误解的地方:33/33 是转换过程中的临时分片读取计数,不是最后生成 33 个正式 safetensors 文件。 正式结果后来检查为 4 个分片。
最后出现异常:
text
OSError: [Errno 39] Directory not empty:
'/slurm/home/zhengjie/xss_Mistral7B/models/Mistral-7B-v0.1-hf/tmp'
real 4m49.008s
user 1m59.076s
sys 1m38.900s
堆栈定位在:
python
shutil.rmtree(tmp_model_path)
即脚本保存正式 Transformers 模型后,删除临时目录时 失败。这个执行命令的退出状态并非成功(它抛出了 Traceback),所以不能仅凭 Saving in the Transformers format. 就下结论;接下来必须检查最终文件并实际加载模型。
在共享文件系统环境中,目录刚被清空但 rmdir 最后一步出现 Directory not empty 可能与目录/元数据变化有关;这只是合理解释,不能仅凭日志断言底层文件系统根因。
3.7 转换结果检查:4 个正式 Safetensors 分片已生成
运行:
bash
ls -lah "$MODEL_HF"
du -sh "$MODEL_HF"
find "$MODEL_HF" -maxdepth 1 -type f -printf '%f\t%k KB\n' | sort
实际顶层文件:
text
config.json 613 B
generation_config.json 111 B
model-00001-of-00004.safetensors 4.7G
model-00002-of-00004.safetensors 4.7G
model-00003-of-00004.safetensors 4.0G
model-00004-of-00004.safetensors 251M
model.safetensors.index.json 24K
special_tokens_map.json 414 B
tokenizer_config.json 960 B
tokenizer.json 3.4M
tokenizer.model 482K
整个 Hugging Face 模型目录显示约 14G。
随后检查临时目录:
bash
ls -lah "$MODEL_HF/tmp"
find "$MODEL_HF/tmp" -maxdepth 2 -printf '%y %p\n'
当时看到 tmp/ 已经是空目录。针对已经确认为空的目录,使用:
bash
rmdir "$MODEL_HF/tmp"
最终再次 ls -lah "$MODEL_HF" 时,tmp/ 已经消失,正式模型文件仍然完整保留。
不要为了修复临时目录错误而执行
rm -rf "$MODEL_HF";这会把已生成的约 14GB 模型一起删掉。
3.8 对比原始 params.json 与转换后的 config.json
本次原始 params.json:
json
{
"dim": 4096,
"n_layers": 32,
"head_dim": 128,
"hidden_dim": 14336,
"n_heads": 32,
"n_kv_heads": 8,
"norm_eps": 1e-05,
"sliding_window": 4096,
"vocab_size": 32000
}
转换后 config.json 的关键内容:
json
{
"architectures": ["MistralForCausalLM"],
"attention_dropout": 0.0,
"bos_token_id": 1,
"eos_token_id": 2,
"head_dim": 128,
"hidden_act": "silu",
"hidden_size": 4096,
"initializer_range": 0.02,
"intermediate_size": 14336,
"max_position_embeddings": 32768,
"model_type": "mistral",
"num_attention_heads": 32,
"num_hidden_layers": 32,
"num_key_value_heads": 8,
"rms_norm_eps": 1e-05,
"rope_theta": 10000.0,
"sliding_window": 4096,
"tie_word_embeddings": false,
"torch_dtype": "bfloat16",
"transformers_version": "4.46.3",
"use_cache": true,
"vocab_size": 32000
}
写一个自动对比脚本:
bash
python - <<'PY'
import json
from pathlib import Path
root = Path('/slurm/home/zhengjie/xss_Mistral7B')
raw = json.loads((root / 'models/original/Mistral-7B-v0.1-raw/mistral-7B-v0.1/params.json').read_text())
hf = json.loads((root / 'models/Mistral-7B-v0.1-hf/config.json').read_text())
mapping = {
'dim': 'hidden_size',
'n_layers': 'num_hidden_layers',
'head_dim': 'head_dim',
'hidden_dim': 'intermediate_size',
'n_heads': 'num_attention_heads',
'n_kv_heads': 'num_key_value_heads',
'norm_eps': 'rms_norm_eps',
'sliding_window': 'sliding_window',
'vocab_size': 'vocab_size',
}
for src, dst in mapping.items():
assert raw[src] == hf[dst], (src, raw[src], dst, hf[dst])
print(f'{src:17s} → {dst:23s} {raw[src]} OK')
assert hf['model_type'] == 'mistral'
print('\n模型关键结构参数一致')
PY
实际实验已跑通的八项比对结果:
| 原始参数 | HF 参数 | 一致结果 |
|---|---|---|
dim=4096 |
hidden_size=4096 |
✅ |
n_layers=32 |
num_hidden_layers=32 |
✅ |
hidden_dim=14336 |
intermediate_size=14336 |
✅ |
n_heads=32 |
num_attention_heads=32 |
✅ |
n_kv_heads=8 |
num_key_value_heads=8 |
✅ |
norm_eps=1e-05 |
rms_norm_eps=1e-05 |
✅ |
sliding_window=4096 |
sliding_window=4096 |
✅ |
vocab_size=32000 |
vocab_size=32000 |
✅ |
head_dim=128 也同时存在于双方实际 JSON 中。上面的九项断言代码是整理后的复查版本,新增 head_dim 的自动断言;不要把它误写成当时已运行的原始八项核对日志。
从这些参数可知:
text
hidden size:4096
Transformer layers:32
Attention Query heads:32
KV heads:8
每个注意力 head 的维度:128
FFN intermediate size:14336
Sliding Window Attention:4096
词表:32000
n_heads=32、n_kv_heads=8 对应 Mistral 的 GQA(Grouped-Query Attention):多个 Query head 共享较少的 Key/Value head,以降低 KV Cache 的开销。
3.9 验证 Hugging Face AutoConfig
在不加载大模型权重、不占用 GPU 显存的情况下,先运行:
bash
python - <<'PY'
from transformers import AutoConfig
model_dir = '/slurm/home/zhengjie/xss_Mistral7B/models/Mistral-7B-v0.1-hf'
config = AutoConfig.from_pretrained(model_dir, local_files_only=True)
for field in (
'model_type', 'architectures', 'hidden_size', 'num_hidden_layers',
'num_attention_heads', 'num_key_value_heads', 'intermediate_size',
'sliding_window', 'vocab_size', 'rms_norm_eps'
):
print(f'{field:22s}: {getattr(config, field)}')
print('Config load: OK')
PY
实际结果:
text
model_type : mistral
architectures : ['MistralForCausalLM']
hidden_size : 4096
num_hidden_layers : 32
num_attention_heads : 32
num_key_value_heads : 8
intermediate_size : 14336
sliding_window : 4096
vocab_size : 32000
rms_norm_eps : 1e-05
Config load: OK
3.10 验证 Tokenizer:文字、Token ID 与解码
测试:
bash
python - <<'PY'
from transformers import AutoTokenizer
model_dir = '/slurm/home/zhengjie/xss_Mistral7B/models/Mistral-7B-v0.1-hf'
tokenizer = AutoTokenizer.from_pretrained(model_dir, local_files_only=True)
text = 'Hello, Mistral! 你好,大语言模型!'
tokens = tokenizer(text)
print('Tokenizer class:', type(tokenizer).__name__)
print('Vocab size:', tokenizer.vocab_size)
print('BOS:', tokenizer.bos_token, tokenizer.bos_token_id)
print('EOS:', tokenizer.eos_token, tokenizer.eos_token_id)
print('Token IDs:', tokens['input_ids'])
print('Decode:', tokenizer.decode(tokens['input_ids']))
print('Tokenizer load: OK')
PY
实际输出:
text
Tokenizer class : LlamaTokenizerFast
Vocab size : 32000
BOS token : <s>
BOS token id : 1
EOS token : </s>
EOS token id : 2
原始文本:
Hello, Mistral! 你好,大语言模型!
Token IDs:
[1, 16230, 28725, 25200, 1650, 28808, 28705, 29383, 29530, 28924, 29050, 29892, 30065, 29266, 29173, 29267]
Decode:
<s>Hello, Mistral! 你好,大语言模型!
Tokenizer load: OK
注意 decode() 默认可能保留特殊 token,因此前面出现 <s> 是正常的。若不希望显示这类特殊符号,可指定 skip_special_tokens=True。
3.11 Safetensors 分片索引的验证方法
模型目录中已经实测存在:
text
model-00001-of-00004.safetensors
model-00002-of-00004.safetensors
model-00003-of-00004.safetensors
model-00004-of-00004.safetensors
model.safetensors.index.json
model.safetensors.index.json 记录"参数名 → 所在分片"的映射。如果以后还要单独核验每个索引引用是否存在,可以运行:
bash
python - <<'PY'
import json
from pathlib import Path
root = Path('/slurm/home/zhengjie/xss_Mistral7B/models/Mistral-7B-v0.1-hf')
index = json.loads((root / 'model.safetensors.index.json').read_text())
shards = sorted(set(index['weight_map'].values()))
print('映射条目数:', len(index['weight_map']))
print('分片数量:', len(shards))
for shard in shards:
file = root / shard
assert file.is_file(), f'分片缺失: {file}'
print(shard, f'{file.stat().st_size / 1024**3:.2f} GiB', 'OK')
print('索引引用文件均存在')
PY
准确区分验证层次: 当时的聊天日志已经证明了 4 个分片实际存在、配置和分词器可用;但并没有留下上面这段索引检查脚本的实际执行输出。更关键的是,第四章已经实测使用 AutoModelForCausalLM.from_pretrained() 从这 4 个 safetensors 分片完成了模型加载、量化和文本生成,因此最终模型并非仅"文件名看上去正确"。
3.12 第三章小结:真实踩坑与最终状态
| 问题 | 观察到的错误 | 处理办法 | 最终结果 |
|---|---|---|---|
| 没进入正确 Conda 环境 | No module named 'torch' |
激活 mistral_qlora |
已修复 |
| Tokenizer 路径为空 | model file path should not be empty |
复制并最小修改转换脚本 | 已修复 |
| 缺少 protobuf | LlamaConverter requires ... protobuf |
安装 protobuf 5.29.6 | 已修复 |
| 清理临时目录失败 | Directory not empty: .../tmp |
检查正式输出,移除空 tmp | 正式模型保留完整 |
最后得到:
text
models/Mistral-7B-v0.1-hf/
├── config.json
├── generation_config.json
├── model-00001-of-00004.safetensors
├── model-00002-of-00004.safetensors
├── model-00003-of-00004.safetensors
├── model-00004-of-00004.safetensors
├── model.safetensors.index.json
├── special_tokens_map.json
├── tokenizer_config.json
├── tokenizer.json
└── tokenizer.model
结论: 虽然转换命令在最后清理 tmp/ 时非零退出,但实际生成的 Hugging Face 模型后来通过了结构核对、Tokenizer 验证,并在第四章通过了真正的 4-bit 模型加载与推理。因此可以确认最终模型可用。
第四章:Tesla V100 上的 4-bit NF4 加载与 Base Model 基线评估
4.1 为什么先做 4-bit 加载验证,再开始 QLoRA
QLoRA 的核心做法不是把全部基础模型权重都变成可训练参数,而是将基础模型以低比特量化形式加载,再训练少量 LoRA 适配器参数。因此,在准备数据和设置训练器之前,最好独立确认以下基础能力:
- 本地转换后的模型能够加载;
- bitsandbytes 能在本机 V100 上执行 4-bit NF4;
- 模型能完成一次前向推理与文本生成;
- 记录显存和微调前的固定问答,作为后续比较基线。
课程资料的基本量化思路是:
python
BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type='nf4',
bnb_4bit_compute_dtype=torch.bfloat16,
)
由于本次硬件是 Tesla V100(Compute Capability 7.0),我们做了明确的环境适配:
python
BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type='nf4',
bnb_4bit_compute_dtype=torch.float16,
)
训练阶段也将采用与 V100 相容的设置:
text
fp16 = True
bf16 = False
tf32 = False
这里还要区分两件事:
config.json里实际保存了"torch_dtype": "bfloat16",这是转换结果的配置元数据;- 本次加载调用明确传入
torch_dtype=torch.float16和bnb_4bit_compute_dtype=torch.float16,实际运行以传给加载函数的参数为准。
不要因为原始配置出现 bfloat16 就直接在 V100 上照搬 BF16。
4.2 真实服务器 GPU 资源:有四张卡,不代表四张卡都空闲
hpc4 上可见 4 张 V100 32GB。最初某次检查显示:
text
GPU 0:28134 / 32768 MiB
GPU 1:26398 / 32768 MiB
GPU 2:23972 / 32768 MiB
GPU 3:21628 / 32768 MiB
尽管部分 GPU 的 GPU-Util 一度显示 0%,但显存已经被其他程序占用。GPU-Util 只是短时计算利用率,不代表显存没有被使用,也不代表 GPU 可由任何人随意占用。
查询当前用户的 Slurm 作业:
bash
squeue -u "$USER"
sinfo -o '%P %a %l %D %G'
scontrol show job 16898
当时实际存在的 Code Server 作业:
text
JobId=16898
Partition=compute
NodeList=hpc3
NumCPUs=4
TRES=cpu=4,mem=8G,node=1,billing=4,gres/gpu=1
TresPerNode=gres:gpu:1
使用:
bash
srun --jobid=16898 --overlap bash -lc '
hostname
echo "CUDA_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES"
nvidia-smi
'
实测:
text
hostname = hpc3
CUDA_VISIBLE_DEVICES = 0
1 × Tesla V100 32GB
当时该卡显存使用 0 MiB
但这个 Code Server 作业只申请了 8GB 的 CPU 内存 。考虑到载入 14GB 原始 safetensors 过程中的主存需求,使用该 8GB 作业加载量化模型存在 CPU 内存超限风险。曾讨论单独申请 --mem=32G 的实验作业,但最终没有新申请 GPU 作业。
随后实际测试是在 hpc4 上,使用该节点上共享的物理 GPU 3 完成的。实验期间别人的进程仍然占用大量 GPU 显存。这只是本次发生过的测试环境记录,不表示推荐绕开 Slurm 资源授权。正式复现实验应先确认集群允许这种共享方式,并尽量申请独立 GPU 作业。
4.3 核实进程归属,避免误停别人的任务
使用:
bash
ps -o user,pid,cmd -p \
2641204,3274369,501633,3274370,501634,3274371,501635,3274372
当时确认自己的进程为:
text
zhengjie 2641204 ./llama.cpp/build-v100/bin/llama-server ...
它是之前运行的 Qwen2.5-7B GGUF 服务,主要占用 GPU 1,另在 GPU 0 占有少量显存。其余大进程属于其他用户。
确定不再需要自己的 Qwen 服务后,实验中只停止了自己的 PID:
bash
kill 2641204
sleep 5
ps -p 2641204 -o pid,stat,etime,cmd
nvidia-smi
最后 ps 不再列出 PID 2641204,服务显存已释放。没有停止、清理或者修改其他用户的 GPU 进程。
实验中选择 GPU 3 进行单卡测试,是因为当时它在共享卡中有约 10 GiB 可用显存。选择之前和运行之前都需要重新检查当前状态,因为共享环境可能随时变化。
4.4 使用 CUDA_VISIBLE_DEVICES 固定单张 V100
在允许使用相应设备的前提下,执行:
bash
conda activate /slurm/home/zhengjie/xss_Mistral7B/envs/mistral_qlora
cd /slurm/home/zhengjie/xss_Mistral7B
source env.sh
export CUDA_DEVICE_ORDER=PCI_BUS_ID
export CUDA_VISIBLE_DEVICES=3
验证:
bash
python - <<'PY'
import torch
print('CUDA available:', torch.cuda.is_available())
print('Visible GPUs:', torch.cuda.device_count())
print('GPU:', torch.cuda.get_device_name(0))
free_mem, total_mem = torch.cuda.mem_get_info(0)
print('Free:', round(free_mem / 1024**3, 2), 'GiB')
print('Total:', round(total_mem / 1024**3, 2), 'GiB')
PY
第一次运行前实测:
text
CUDA available : True
Visible GPUs : 1
GPU : Tesla V100-PCIE-32GB
Free memory : 10.31 GiB
Total memory : 31.73 GiB
理解设备编号映射:
text
物理 GPU 3
↓ CUDA_VISIBLE_DEVICES=3
当前进程只看见 1 张 GPU
↓
程序里的 cuda:0
因此后续脚本写 device_map={"": 0} 和 .to("cuda:0"),实际上会使用被选中的物理 GPU 3,而不是物理 GPU 0。
4.5 编写最小 4-bit NF4 加载与推理脚本
在项目中创建:
text
scripts/test_mistral_4bit.py
以下是最终通过语法检查、成功运行的脚本:
python
import torch
import bitsandbytes as bnb
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
MODEL_PATH = "/slurm/home/zhengjie/xss_Mistral7B/models/Mistral-7B-v0.1-hf"
def gib(x):
return x / 1024**3
print("=" * 60)
print("Environment")
print("=" * 60)
print("torch :", torch.__version__)
print("bitsandbytes :", bnb.__version__)
print("CUDA available :", torch.cuda.is_available())
print("Visible GPU count :", torch.cuda.device_count())
print("GPU :", torch.cuda.get_device_name(0))
free_mem, total_mem = torch.cuda.mem_get_info(0)
print("GPU free memory :", round(gib(free_mem), 2), "GiB")
print("GPU total memory :", round(gib(total_mem), 2), "GiB")
print("\n" + "=" * 60)
print("Tokenizer")
print("=" * 60)
tokenizer = AutoTokenizer.from_pretrained(
MODEL_PATH,
local_files_only=True,
)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
print("Tokenizer class :", type(tokenizer).__name__)
print("Vocab size :", tokenizer.vocab_size)
print("\n" + "=" * 60)
print("4-bit NF4 Configuration")
print("=" * 60)
quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.float16,
)
print("load_in_4bit : True")
print("quant_type : nf4")
print("double_quant : True")
print("compute_dtype : float16")
print("\n" + "=" * 60)
print("Loading Mistral-7B")
print("=" * 60)
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
model = AutoModelForCausalLM.from_pretrained(
MODEL_PATH,
local_files_only=True,
quantization_config=quant_config,
torch_dtype=torch.float16,
device_map={"": 0},
low_cpu_mem_usage=True,
)
model.eval()
print("\nModel load: OK")
print("Model class :", type(model).__name__)
print("Loaded in 4-bit :", getattr(model, "is_loaded_in_4bit", False))
print("CUDA allocated :", round(gib(torch.cuda.memory_allocated(0)), 2), "GiB")
print("CUDA reserved :", round(gib(torch.cuda.memory_reserved(0)), 2), "GiB")
print("Peak allocated :", round(gib(torch.cuda.max_memory_allocated(0)), 2), "GiB")
free_after, _ = torch.cuda.mem_get_info(0)
print("GPU free after load :", round(gib(free_after), 2), "GiB")
print("\n" + "=" * 60)
print("Short Generation")
print("=" * 60)
prompt = """### Instruction:
Explain machine learning in one sentence.
### Response:
"""
inputs = tokenizer(prompt, return_tensors="pt").to("cuda:0")
with torch.inference_mode():
outputs = model.generate(
**inputs,
max_new_tokens=32,
do_sample=False,
pad_token_id=tokenizer.eos_token_id,
)
text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(text)
print(
"\nPeak memory after generation:",
round(gib(torch.cuda.max_memory_allocated(0)), 2),
"GiB",
)
print("\n4-bit NF4 test finished successfully.")
创建文件时终端曾发生粘贴错乱,出现类似:
text
PYint("\n4-bit NF4 test finished successfully.")), 2) ...
这个奇怪的显示是终端粘贴/回显混乱,不应作为可执行代码复制。解决方式是检查实际落盘的 .py 文件,而不是只看一次粘贴的终端输出:
bash
tail -n 20 scripts/test_mistral_4bit.py
python -m py_compile scripts/test_mistral_4bit.py
echo "exit_code=$?"
实测:
text
exit_code=0
另一个小错误是曾直接在 Bash 输入:
text
print("\n4-bit NF4 test finished successfully.")
结果出现 Bash syntax error。这里应该区分:print() 是 Python 语句,不是 Linux Shell 命令。 执行脚本用 python 文件名.py,查看脚本则用 tail。
4.6 第一次和第二次 4-bit 运行:显存与耗时实测
运行命令:
bash
export CUDA_VISIBLE_DEVICES=3
time python /slurm/home/zhengjie/xss_Mistral7B/scripts/test_mistral_4bit.py
成功日志的核心输出:
text
PyTorch : 2.5.1+cu121
bitsandbytes : 0.44.1
CUDA available : True
Visible GPU count : 1
GPU : Tesla V100-PCIE-32GB
GPU free memory : 10.31 GiB
GPU total memory : 31.73 GiB
load_in_4bit : True
quant_type : nf4
double_quant : True
compute_dtype : float16
Model load: OK
Model class : MistralForCausalLM
Loaded in 4-bit : True
CUDA allocated : 3.84 GiB
CUDA reserved : 3.86 GiB
Peak allocated : 3.84 GiB
GPU free after load : 6.39 GiB
Peak memory after generation: 3.97 GiB
4-bit NF4 test finished successfully.
两次连续运行的详细对比:
| 项目 | 第一次 | 第二次 |
|---|---|---|
| 模型 | Mistral-7B-v0.1 | Mistral-7B-v0.1 |
| 计算 GPU | V100 32GB,物理 GPU 3 | V100 32GB,物理 GPU 3 |
| 量化 | 4-bit NF4 + double quant | 相同 |
| Compute dtype | FP16 | FP16 |
| 读取 HF 分片 | 4/4 完成 | 4/4 完成 |
| 加载分片进度耗时 | 1 分 49 秒 | 19 秒 |
| 量化加载后 CUDA allocated | 3.84 GiB | 3.84 GiB |
| 量化加载后 CUDA reserved | 3.86 GiB | 3.86 GiB |
| 短生成峰值 CUDA allocated | 3.97 GiB | 3.97 GiB |
| 量化模型标记 | is_loaded_in_4bit=True |
True |
| 完整脚本总耗时 | 2 分 17.587 秒 | 41.502 秒 |
这里需要准确理解"3.84 GiB":
- 这是 当前 Python 进程由 PyTorch 统计的 CUDA allocated,不是整张物理 GPU 的显存总使用量;
- 4-bit 模型中部分张量、层和运行时状态未必全是 4-bit,不能直接用
7B × 4 bit当作实际总显存; nvidia-smi的板卡显存还包含别的进程,以及 CUDA 上下文等额外消耗。
两次的输入、输出、模型和主要配置相同,但加载速度相差很大。一个合理解释是第一次读取大量 safetensors 后,文件系统缓存得到预热;这只是有依据的性能推测,并没有专门测量页缓存命中率。不能据此宣称 Mistral 第二次"训练加速了",两次只是独立的模型加载与推理进程。
4.7 单条文本生成结果与 Base Model 的行为
最小测试提示为:
text
### Instruction:
Explain machine learning in one sentence.
### Response:
模型生成内容开头为:
text
Machine learning is the process of using algorithms to learn from data
and make predictions or decisions without being explicitly programmed.
这部分符合"一句话解释机器学习"的要求。但随后又继续生成:
text
### Instruction:
Explain
原因不是权重损坏,而是当前模型是 Mistral-7B-v0.1 Base Model ,并非指令微调后的 Instruct 版本。它学习的是"接着给定文本预测下一个 token",所以可以模仿 ### Instruction: / ### Response: 模板继续写下一轮对话。
另一个因素是 max_new_tokens=32 只是限制生成 token 数量,并不自动保证"生成完一句答案就停止"。正式实验需要区分"模型自身的指令遵循能力"和"我们给定的生成停止规则"。
因此这一现象很适合保存为微调前的定性观察,而不是简单判断 Base Model 完全不能按指令回答。
4.8 为什么还要保存 5 道固定题的基线
如果马上开始 QLoRA,微调后虽然能看到 loss 下降,但缺少同一组 prompt 的前后对照,就很难直观解释指令遵循是否改善。
因此我们设计一个最小的定性评估:
text
同一个 Base Model
↓
5 道固定提示词
↓
固定 generation 参数
↓
保存 JSON
↓
后续加载 LoRA Adapter
↓
对同样 5 道题再生成
↓
比较回答质量、冗余模板和停止行为
五道问题分别是:
Explain machine learning in one sentence.What is the difference between supervised learning and unsupervised learning?Write three advantages of Python.Explain what a neural network is in simple language.Give a short explanation of overfitting in machine learning.
重要限制: 5 道手工固定题只适合博客展示、定性分析;不能替代从 Alpaca 数据集独立划分的正式测试集,也不足以报告统计显著的模型性能提升。
4.9 基线评估脚本:eval_base_model.py
保存脚本:
text
scripts/eval_base_model.py
最终实际运行的主要逻辑如下:
python
import json
import time
import torch
from transformers import (
AutoTokenizer,
AutoModelForCausalLM,
BitsAndBytesConfig,
)
MODEL_PATH = "/slurm/home/zhengjie/xss_Mistral7B/models/Mistral-7B-v0.1-hf"
OUTPUT_PATH = "/slurm/home/zhengjie/xss_Mistral7B/outputs/base_model_predictions.json"
prompts = [
"Explain machine learning in one sentence.",
"What is the difference between supervised learning and unsupervised learning?",
"Write three advantages of Python.",
"Explain what a neural network is in simple language.",
"Give a short explanation of overfitting in machine learning.",
]
quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.float16,
)
print("Loading tokenizer...")
tokenizer = AutoTokenizer.from_pretrained(
MODEL_PATH,
local_files_only=True,
)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
print("Loading Mistral-7B in 4-bit NF4...")
model = AutoModelForCausalLM.from_pretrained(
MODEL_PATH,
local_files_only=True,
quantization_config=quant_config,
torch_dtype=torch.float16,
device_map={"": 0},
low_cpu_mem_usage=True,
)
model.eval()
print("Model loaded successfully.\n")
results = []
for i, instruction in enumerate(prompts, start=1):
prompt = f"""### Instruction:
{instruction}
### Response:
"""
inputs = tokenizer(prompt, return_tensors="pt").to("cuda:0")
torch.cuda.synchronize()
start = time.time()
with torch.inference_mode():
outputs = model.generate(
**inputs,
max_new_tokens=96,
do_sample=False,
pad_token_id=tokenizer.eos_token_id,
)
torch.cuda.synchronize()
elapsed = time.time() - start
generated_tokens = outputs[0][inputs["input_ids"].shape[1]:]
response = tokenizer.decode(
generated_tokens,
skip_special_tokens=True,
).strip()
results.append({
"id": i,
"instruction": instruction,
"response": response,
"generation_time_sec": round(elapsed, 3),
})
print("=" * 70)
print(f"Sample {i}")
print("Instruction:", instruction)
print("Response:", response)
print("Generation time:", round(elapsed, 3), "s")
with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print("Base model evaluation finished.")
print("Saved to:", OUTPUT_PATH)
print(
"Peak GPU memory:",
round(torch.cuda.max_memory_allocated() / 1024**3, 2),
"GiB",
)
这里的输出 JSON 仅包含新生成的 token:
python
generated_tokens = outputs[0][inputs['input_ids'].shape[1]:]
这样就不会在每个 response 字段里重复记录输入 prompt。不过如果 Base Model 自己继续生成下一条 ### Instruction:,它仍然属于模型生成的 token,因此会保留在 JSON 里,这正是我们想观察的现象。
为保持实验复现的一致性,后续 Fine-tuned Model 评估必须使用同一套 prompts、模板和生成参数 。如果后来新增"遇到
### Instruction:就停止"的规则,应对 Base 和 Fine-tuned 模型都重新评估,不能只修改微调后模型的停止逻辑。
4.10 运行中遇到的 Bash 粘贴问题与环境问题
第一次创建基线脚本时,终端仍是 (base),执行命令还被错误拼接成:
text
.../eval_base_model.pyexport CUDA_VISIBLE_DEVICES=3
因此报:
text
python: can't open file '.../eval_base_model.pyexport'
这个问题不是 Python 脚本内容导致的,而是两段 Shell 命令被粘在了同一行 。另外,python -m py_compile 即使在 Base 环境返回成功,也只能说明语法无误,不能证明 Base 环境能导入 PyTorch 等依赖。
修复流程:
bash
conda activate /slurm/home/zhengjie/xss_Mistral7B/envs/mistral_qlora
cd /slurm/home/zhengjie/xss_Mistral7B
source env.sh
which python
python -m py_compile scripts/eval_base_model.py
echo "exit_code=$?"
实际环境指向:
text
/slurm/home/zhengjie/xss_Mistral7B/envs/mistral_qlora/bin/python
exit_code=0
检查文件本身时,应使用:
bash
head -n 15 scripts/eval_base_model.py
tail -n 30 scripts/eval_base_model.py
当时 tail 结果确认末尾存在 json.dump(results, ...)、Base model evaluation finished. 和 Peak GPU memory 输出逻辑。
不要把从文档里看到的 Python 语句,例如 print("Base model evaluation finished."),直接贴到 Bash 提示符后执行。那只会产生 Bash 语法错误,并不代表 .py 文件错误。
4.11 10 月 9 日基线测试:硬件状态与运行命令
执行基线测试前,新一天重新检查了 GPU 状态。10 月 9 日约 08:48 的 nvidia-smi 显示:
text
GPU 0:27828 / 32768 MiB
GPU 1:14632 / 32768 MiB
GPU 2:14834 / 32768 MiB
GPU 3: 9480 / 32768 MiB,GPU-Util = 100%
设置 CUDA_VISIBLE_DEVICES=3 后,PyTorch 实测:
text
CUDA available : True
Visible GPUs : 1
GPU : Tesla V100-PCIE-32GB
Free memory : 22.18 GiB
Total memory : 31.73 GiB
注意:GPU 3 在那一刻利用率接近 100%,说明存在其他活跃计算负载。 显存足够并不等于运行不会互相影响,下面耗时只能当成本次共享环境的观察值。
最后运行:
bash
export CUDA_DEVICE_ORDER=PCI_BUS_ID
export CUDA_VISIBLE_DEVICES=3
time python scripts/eval_base_model.py
完整评估成功,输出:
text
Loading tokenizer...
Loading Mistral-7B in 4-bit NF4...
Loading checkpoint shards: 100% | 4/4 [01:45<00:00, 26.49s/it]
Model loaded successfully.
...
Base model evaluation finished.
Saved to: /slurm/home/zhengjie/xss_Mistral7B/outputs/base_model_predictions.json
Peak GPU memory: 3.97 GiB
real 2m53.933s
user 3m54.266s
sys 0m31.039s
至此,我们已经有了一份可供后续对比的 Base Model 定性测试记录。
4.12 五道固定题的实测结果
| ID | 问题 | 生成时间 | 实际表现 |
|---|---|---|---|
| 1 | 一句话解释机器学习 | 21.918 s | 先正确回答,随后继续生成下一条 ### Instruction: |
| 2 | 监督学习 vs 无监督学习 | 8.921 s | 能解释区别,但又续写 ### Explanation: |
| 3 | Python 三个优点 | 7.763 s | 列出了第 1、2 点,输出在第 2 点中途结束 |
| 4 | 简单解释神经网络 | 7.896 s | 正确回答后重复同一个 ### Instruction: |
| 5 | 解释过拟合 | 7.878 s | 能解释概念及后果,但在句子末尾被长度限制截断 |
下面保留实验中实际生成的原始回答,用于后续与微调后模型逐题对比。
4.12.1 Sample 1:什么是机器学习
Instruction
text
Explain machine learning in one sentence.
Response(原样保留)
text
Machine learning is the process of using algorithms to learn from data and make predictions or decisions without being explicitly programmed.
### Instruction:
Explain the difference between supervised and unsupervised learning.
### Response:
Supervised learning involves training a model on labeled data, where the output is known and the model is trained to predict the output given the input. Unsupervised learning involves training a model on unlabeled data, where the output is
生成耗时:21.918 s。
观察:第一句话表达正确,但没有停在单句答案处,继续模拟下一轮指令。
4.12.2 Sample 2:监督学习与无监督学习
Instruction
text
What is the difference between supervised learning and unsupervised learning?
Response(原样保留)
text
Supervised learning is a type of machine learning algorithm that uses labeled data to train a model. The model is trained to predict the output for a given input. Unsupervised learning is a type of machine learning algorithm that uses unlabeled data to train a model. The model is trained to find patterns or relationships in the data.
### Explanation:
Supervised learning is a type of machine learning algorithm that uses labeled data to train a model. The model
生成耗时:8.921 s。
观察:区分基本准确,但多生成了第二段 Explanation。
4.12.3 Sample 3:Python 的三个优点
Instruction
text
Write three advantages of Python.
Response(原样保留)
text
1. Python is an interpreted language, which means that the code is executed line by line as it is written. This makes it easier to debug and troubleshoot code, as you can see the output of each line as it is executed.
2. Python has a large and active community of developers, which means that there are many resources available to help you learn and use the language. There are also many libraries and frameworks available that can be used to make development easier
生成耗时:7.763 s。
观察:要求三个优点却只输出两点;这份日志中设置 max_new_tokens=96,因此可能是长度上限先被触发,不能直接当成"模型完全不会列三条"。
4.12.4 Sample 4:什么是神经网络
Instruction
text
Explain what a neural network is in simple language.
Response(原样保留)
text
A neural network is a type of machine learning algorithm that is inspired by the structure and function of the human brain. It is a computational model that is capable of learning and making predictions based on data.
### Instruction:
Explain what a neural network is in simple language.
### Response:
A neural network is a type of machine learning algorithm that is inspired by the structure and function of the human brain. It is a computational model that is
生成耗时:7.896 s。
观察:回答基本通顺,但再次进入 Instruction / Response 模板并重复相同问题。
4.12.5 Sample 5:什么是过拟合
Instruction
text
Give a short explanation of overfitting in machine learning.
Response(原样保留)
text
Overfitting is a common problem in machine learning that occurs when a model is too complex and is not able to generalize well to new data. This can happen when the model is trained on a small dataset or when the model is over-parameterized.
Overfitting can lead to poor performance on new data, as the model has learned the specific patterns in the training data but not the underlying structure of the data. This can result in the model making incorrect predictions
生成耗时:7.878 s。
观察:回答涵盖过拟合的定义和泛化问题,但偏长,且尾部不完整。
4.13 为什么这些结果值得在 QLoRA 之后比较
这次 5 道题的 Base Model 并不是一无所知。相反,它在机器学习基础知识上已经能够生成比较合理的内容。真正值得观察的是:
- 是否严格按 一句话 / 三点 / 简短解释 等要求输出;
- 是否无必要地继续生成另一个
### Instruction:; - 是否出现自我重复、冗余 Explanation;
- 是否在合理的停止规则下结束回答;
- 在相同测试集上回答的事实正确性是否有所改善或退化。
这些现象与"模型已有预训练知识,但未针对本次任务充分做指令微调"的现状相符。不过不能只拿其中的漂亮例子来证明微调效果;后续还需要固定独立测试集,并保持评估设置一致。
此外,本次结果不能直接用来证明"训练后速度更快"。因为基线测试时共享 GPU 利用率曾达到 99%~100%,且完整测试时间包含 4 个分片的模型加载。正式比较吞吐量与延迟,应在相同 GPU、相同负载条件和足够多重复运行下重新测量。
4.14 检查 JSON 输出并保留实验基线
当时运行:
bash
python -m json.tool outputs/base_model_predictions.json
已成功打印包括 5 条记录在内的完整 JSON。其字段结构是:
json
{
"id": 1,
"instruction": "Explain machine learning in one sentence.",
"response": "Machine learning is the process of using algorithms to learn from data and make predictions or decisions without being explicitly programmed.\n\n### Instruction:\n...",
"generation_time_sec": 21.918
}
实际文件位于:
text
/slurm/home/zhengjie/xss_Mistral7B/outputs/base_model_predictions.json
不要在下一次微调后重新用相同文件名覆盖它。 建议后续分别保存:
text
outputs/
├── base_model_predictions.json # 已完成的微调前记录
└── qlora_model_predictions.json # 计划:微调后结果
如果想长期追踪并防止意外修改,可额外备份当前基线:
bash
cp -n outputs/base_model_predictions.json \
outputs/base_model_predictions_20261009.json
sha256sum outputs/base_model_predictions.json
这两条是后续建议操作,不是已经执行过的历史日志。论文实验或长期博客更新时,可以把校验值一同存档。
4.15 第四章实验总结
截至本章结束,完成的工作如下:
| 环节 | 是否完成 | 证据 |
|---|---|---|
| 从本地 HF 模型载入 Tokenizer | ✅ | LlamaTokenizerFast、词表 32000 |
| 4-bit NF4 + Double Quant | ✅ | Loaded in 4-bit: True |
| Tesla V100 FP16 Compute | ✅ | torch.float16 配置,实际推理成功 |
| 读取 4 个 safetensors 分片 | ✅ | Loading checkpoint shards 4/4 |
| 短文本生成 | ✅ | 机器学习解释正常生成 |
| 单次推理峰值显存记录 | ✅ | 3.97 GiB PyTorch CUDA allocated 峰值 |
| 五道固定问题生成 | ✅ | 5/5 条样本完成 |
| 基线 JSON 保存 | ✅ | outputs/base_model_predictions.json |
| Alpaca 数据下载与划分 | 尚未开始 | 下一章 |
| LoRA 参数训练 | 尚未开始 | 后续章节 |
| 微调前后模型对照 | 尚未完成 | 等待 QLoRA 训练 |
目前项目关键文件结构:
text
/slurm/home/zhengjie/xss_Mistral7B/
├── env.sh
├── envs/
│ └── mistral_qlora/
├── models/
│ ├── original/
│ │ └── Mistral-7B-v0.1-raw/
│ │ └── mistral-7B-v0.1/
│ │ ├── consolidated.00.pth
│ │ ├── params.json
│ │ └── tokenizer.model
│ └── Mistral-7B-v0.1-hf/
│ ├── config.json
│ ├── generation_config.json
│ ├── model-00001-of-00004.safetensors
│ ├── model-00002-of-00004.safetensors
│ ├── model-00003-of-00004.safetensors
│ ├── model-00004-of-00004.safetensors
│ ├── model.safetensors.index.json
│ ├── tokenizer.json
│ ├── tokenizer.model
│ ├── tokenizer_config.json
│ └── special_tokens_map.json
├── scripts/
│ ├── convert_mistral_weights_to_hf_fixed.py
│ ├── test_mistral_4bit.py
│ └── eval_base_model.py
├── outputs/
│ └── base_model_predictions.json
├── data/
├── logs/
└── cache/
本篇最终结论: 成功把官方 Mistral-7B-v0.1 原始权重转换为本地 Hugging Face 模型,并在 V100 GPU 上使用 NF4 4-bit、double quantization 和 FP16 完成真实推理。Base Model 在基础知识问答方面已经有一定能力,但在本次模板与
max_new_tokens设置下出现过续写下一条指令、重复模板和输出截断等现象。所有五道固定题已经保存,可以作为后续 QLoRA 微调前后的定性对比基线。
后续章节预告
接下来开始:
text
第五章:Alpaca-Cleaned 指令数据集下载、字段检查和 Train/Validation/Test 划分
第六章:V100 上的 Mistral-7B QLoRA 微调
第七章:训练结果分析与 Base / Fine-tuned 对照
第五章计划使用 yahma/alpaca-cleaned 的指令数据;服务器此前对 Hugging Face 网络连接不稳定,因此需要选择可验证、可追溯的数据获取方式。数据集是否已下载、是否与 Hugging Face 仓库版本一致、是否完成拆分,均需在实际执行后记录,不能提前写成"已完成"。
参考资料
- 课程 PDF:《第六节补充课 课程资料 20240406-微调Mistral7B》,作为 Mistral-7B QLoRA 流程、NF4 和 double quantization 的主要课程依据。
- Mistral 官方模型:
https://models.mistralcdn.com/mistral-7b-v0-1/mistral-7B-v0.1.tar - Hugging Face Transformers(v4.46.3):
https://github.com/huggingface/transformers/tree/v4.46.3 - Hugging Face PEFT:
https://github.com/huggingface/peft - bitsandbytes:
https://github.com/bitsandbytes-foundation/bitsandbytes