2026-05-18 13:32:30 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
"""光湖代码模型→1.5B铸渊模板 蒸馏脚本
|
|
|
|
|
|
Teacher: Qwen2.5-Coder-7B (SFT后的代码模型)
|
|
|
|
|
|
Student: Qwen2.5-Coder-1.5B (将学会铸渊的执行思维)
|
2026-05-18 18:06:48 +08:00
|
|
|
|
|
|
|
|
|
|
使用方法:
|
|
|
|
|
|
nohup python3 -u distill_coder.py > distill_coder.log 2>&1 &
|
|
|
|
|
|
|
|
|
|
|
|
注意:
|
|
|
|
|
|
- 这个脚本和distill_mother.py几乎一样,但:
|
|
|
|
|
|
1. 使用Qwen2.5-Coder系列(代码能力更强)
|
|
|
|
|
|
2. 蒸馏数据使用铸渊专属语料(zhuyuan_deep_finetune.jsonl)
|
|
|
|
|
|
3. Student使用Coder-1.5B(代码执行模型)
|
2026-05-18 13:32:30 +08:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import os, json, torch, sys
|
|
|
|
|
|
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
|
|
|
|
|
|
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
|
|
|
|
|
|
|
|
|
|
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer
|
|
|
|
|
|
from datasets import Dataset
|
|
|
|
|
|
from tqdm import tqdm
|
|
|
|
|
|
import torch.nn.functional as F
|
|
|
|
|
|
|
2026-05-18 18:06:48 +08:00
|
|
|
|
# ========== 配置 ==========
|
2026-05-18 13:32:30 +08:00
|
|
|
|
TEACHER_PATH = "/root/autodl-tmp/output/qwen25-coder-7b-sft/final"
|
|
|
|
|
|
STUDENT_PATH = "/root/autodl-tmp/cache/Qwen/Qwen2___5-Coder-1___5B"
|
2026-05-18 18:06:48 +08:00
|
|
|
|
DATA = "/root/autodl-tmp/corpus/zhuyuan_deep_finetune.jsonl" # 铸渊专属语料
|
2026-05-18 13:32:30 +08:00
|
|
|
|
OUT = "/root/autodl-tmp/output/qwen25-coder-15b-zhuyuan-distill"
|
2026-05-18 18:06:48 +08:00
|
|
|
|
|
|
|
|
|
|
EPOCHS = 3
|
|
|
|
|
|
BS = 4
|
|
|
|
|
|
GA = 8
|
|
|
|
|
|
LR = 1e-5
|
|
|
|
|
|
MAX_LEN = 2048
|
|
|
|
|
|
TEMP = 2.0
|
|
|
|
|
|
ALPHA = 0.7
|
2026-05-18 13:32:30 +08:00
|
|
|
|
|
|
|
|
|
|
os.makedirs(OUT, exist_ok=True)
|
|
|
|
|
|
|
2026-05-18 18:06:48 +08:00
|
|
|
|
# ========== 1. 加载数据 ==========
|
2026-05-18 13:32:30 +08:00
|
|
|
|
print("[1/6] Loading zhuyuan corpus...")
|
|
|
|
|
|
with open(DATA) as f:
|
|
|
|
|
|
raw = [json.loads(line) for line in f]
|
2026-05-18 18:06:48 +08:00
|
|
|
|
# 铸渊语料已经去除了system prompt
|
|
|
|
|
|
print(f" {len(raw)} examples (zhuyuan deep finetune corpus)")
|
2026-05-18 13:32:30 +08:00
|
|
|
|
|
2026-05-18 18:06:48 +08:00
|
|
|
|
# ========== 2. 加载 ==========
|
2026-05-18 13:32:30 +08:00
|
|
|
|
print("[2/6] Loading teacher (Coder-7B) and student (Coder-1.5B)...")
|
2026-05-18 18:06:48 +08:00
|
|
|
|
|
2026-05-18 13:32:30 +08:00
|
|
|
|
tokenizer = AutoTokenizer.from_pretrained(STUDENT_PATH, trust_remote_code=True)
|
|
|
|
|
|
tokenizer.pad_token = tokenizer.eos_token
|
|
|
|
|
|
|
|
|
|
|
|
print(" Loading teacher...")
|
2026-05-18 18:06:48 +08:00
|
|
|
|
teacher = AutoModelForCausalLM.from_pretrained(
|
|
|
|
|
|
TEACHER_PATH, trust_remote_code=True,
|
|
|
|
|
|
torch_dtype=torch.bfloat16, attn_implementation="sdpa",
|
|
|
|
|
|
).cuda()
|
2026-05-18 13:32:30 +08:00
|
|
|
|
teacher.eval()
|
2026-05-18 18:06:48 +08:00
|
|
|
|
for p in teacher.parameters():
|
|
|
|
|
|
p.requires_grad = False
|
2026-05-18 13:32:30 +08:00
|
|
|
|
print(f" Teacher: {sum(p.numel() for p in teacher.parameters())/1e9:.2f}B")
|
|
|
|
|
|
|
|
|
|
|
|
print(" Loading student...")
|
2026-05-18 18:06:48 +08:00
|
|
|
|
student = AutoModelForCausalLM.from_pretrained(
|
|
|
|
|
|
STUDENT_PATH, trust_remote_code=True,
|
|
|
|
|
|
torch_dtype=torch.bfloat16, attn_implementation="sdpa",
|
|
|
|
|
|
).cuda()
|
2026-05-18 13:32:30 +08:00
|
|
|
|
student.train()
|
|
|
|
|
|
print(f" Student: {sum(p.numel() for p in student.parameters())/1e9:.2f}B")
|
|
|
|
|
|
|
2026-05-18 18:06:48 +08:00
|
|
|
|
# ========== 3. Tokenize + Teacher Logits ==========
|
2026-05-18 13:32:30 +08:00
|
|
|
|
print("[3/6] Tokenizing + generating teacher logits...")
|
2026-05-18 18:06:48 +08:00
|
|
|
|
|
2026-05-18 13:32:30 +08:00
|
|
|
|
processed = []
|
2026-05-18 18:06:48 +08:00
|
|
|
|
for d in tqdm(raw, desc="Tokenize+Teacher"):
|
2026-05-18 13:32:30 +08:00
|
|
|
|
ids, labs = [], []
|
|
|
|
|
|
for msg in d["messages"]:
|
|
|
|
|
|
c = msg["content"]
|
|
|
|
|
|
if not c.strip(): continue
|
|
|
|
|
|
t = f"<|im_start|>{msg['role']}\n{c}<|im_end|>\n"
|
|
|
|
|
|
tok = tokenizer.encode(t, add_special_tokens=False)
|
|
|
|
|
|
ids.extend(tok)
|
|
|
|
|
|
labs.extend(tok if msg["role"] == "assistant" else [-100] * len(tok))
|
|
|
|
|
|
if len(ids) > MAX_LEN:
|
|
|
|
|
|
ids, labs = ids[:MAX_LEN], labs[:MAX_LEN]
|
2026-05-18 18:06:48 +08:00
|
|
|
|
|
2026-05-18 13:32:30 +08:00
|
|
|
|
with torch.no_grad():
|
|
|
|
|
|
inp = torch.tensor([ids]).cuda()
|
|
|
|
|
|
t_out = teacher(input_ids=inp)
|
|
|
|
|
|
t_logits = t_out.logits[0].float().cpu()
|
2026-05-18 18:06:48 +08:00
|
|
|
|
|
|
|
|
|
|
processed.append({
|
|
|
|
|
|
"input_ids": ids, "labels": labs, "attention_mask": [1]*len(ids),
|
|
|
|
|
|
"teacher_logits": t_logits
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
ds = Dataset.from_list(processed)
|
|
|
|
|
|
print(f" Dataset: {len(ds)} ex")
|
2026-05-18 13:32:30 +08:00
|
|
|
|
|
2026-05-18 18:06:48 +08:00
|
|
|
|
# ========== 4. 配置 ==========
|
2026-05-18 13:32:30 +08:00
|
|
|
|
print("[4/6] Training config...")
|
2026-05-18 18:06:48 +08:00
|
|
|
|
|
2026-05-18 13:32:30 +08:00
|
|
|
|
def distill_collate(features):
|
|
|
|
|
|
max_len = max(len(f["input_ids"]) for f in features)
|
|
|
|
|
|
batch = {}
|
|
|
|
|
|
for k in ["input_ids", "labels", "attention_mask"]:
|
|
|
|
|
|
pad = tokenizer.pad_token_id if k != "labels" else -100
|
|
|
|
|
|
batch[k] = torch.tensor([f[k] + [pad]*(max_len-len(f[k])) for f in features])
|
|
|
|
|
|
vocab_size = features[0]["teacher_logits"].size(-1)
|
|
|
|
|
|
tl = []
|
|
|
|
|
|
for f in features:
|
|
|
|
|
|
t = f["teacher_logits"]
|
|
|
|
|
|
pad_len = max_len - t.size(0)
|
2026-05-18 18:06:48 +08:00
|
|
|
|
if pad_len > 0:
|
|
|
|
|
|
tl.append(torch.cat([t, torch.zeros(pad_len, vocab_size)], dim=0))
|
|
|
|
|
|
else:
|
|
|
|
|
|
tl.append(t[:max_len])
|
2026-05-18 13:32:30 +08:00
|
|
|
|
batch["teacher_logits"] = torch.stack(tl)
|
|
|
|
|
|
return batch
|
|
|
|
|
|
|
|
|
|
|
|
class DistillTrainer(Trainer):
|
|
|
|
|
|
def compute_loss(self, model, inputs, return_outputs=False, **kwargs):
|
2026-05-18 18:06:48 +08:00
|
|
|
|
outputs = model(
|
|
|
|
|
|
input_ids=inputs["input_ids"],
|
|
|
|
|
|
attention_mask=inputs["attention_mask"],
|
|
|
|
|
|
use_cache=False,
|
|
|
|
|
|
)
|
2026-05-18 13:32:30 +08:00
|
|
|
|
student_logits = outputs.logits
|
2026-05-18 18:06:48 +08:00
|
|
|
|
|
|
|
|
|
|
# SFT loss
|
2026-05-18 13:32:30 +08:00
|
|
|
|
shift_logits = student_logits[..., :-1, :].contiguous()
|
|
|
|
|
|
shift_labels = inputs["labels"][..., 1:].contiguous()
|
2026-05-18 18:06:48 +08:00
|
|
|
|
sft_loss = F.cross_entropy(
|
|
|
|
|
|
shift_logits.view(-1, shift_logits.size(-1)),
|
|
|
|
|
|
shift_labels.view(-1),
|
|
|
|
|
|
ignore_index=-100, reduction="mean",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# KL loss
|
2026-05-18 13:32:30 +08:00
|
|
|
|
teacher_logits = inputs["teacher_logits"]
|
|
|
|
|
|
mask = (inputs["labels"] != -100).unsqueeze(-1).float()
|
2026-05-18 18:06:48 +08:00
|
|
|
|
s_logits_soft = student_logits / TEMP
|
|
|
|
|
|
t_logits_soft = teacher_logits / TEMP
|
|
|
|
|
|
kl_loss = F.kl_div(
|
|
|
|
|
|
F.log_softmax(s_logits_soft, dim=-1),
|
|
|
|
|
|
F.softmax(t_logits_soft, dim=-1),
|
|
|
|
|
|
reduction="none",
|
|
|
|
|
|
)
|
|
|
|
|
|
kl_loss = (kl_loss * mask).sum() / mask.sum()
|
|
|
|
|
|
kl_loss = kl_loss * (TEMP ** 2)
|
|
|
|
|
|
|
2026-05-18 13:32:30 +08:00
|
|
|
|
return ALPHA * kl_loss + (1 - ALPHA) * sft_loss
|
|
|
|
|
|
|
|
|
|
|
|
args = TrainingArguments(
|
|
|
|
|
|
output_dir=OUT, num_train_epochs=EPOCHS,
|
|
|
|
|
|
per_device_train_batch_size=BS, gradient_accumulation_steps=GA,
|
|
|
|
|
|
learning_rate=LR, warmup_ratio=0.05, lr_scheduler_type="cosine",
|
|
|
|
|
|
bf16=True, tf32=True, logging_steps=10,
|
|
|
|
|
|
save_strategy="epoch", save_total_limit=3,
|
|
|
|
|
|
remove_unused_columns=False, dataloader_num_workers=4,
|
|
|
|
|
|
gradient_checkpointing=True, optim="adamw_torch",
|
|
|
|
|
|
report_to="none", ddp_find_unused_parameters=False,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-18 18:06:48 +08:00
|
|
|
|
trainer = DistillTrainer(
|
|
|
|
|
|
model=student, args=args,
|
|
|
|
|
|
train_dataset=ds, data_collator=distill_collate,
|
|
|
|
|
|
)
|
2026-05-18 13:32:30 +08:00
|
|
|
|
|
2026-05-18 18:06:48 +08:00
|
|
|
|
# ========== 5. Train ==========
|
2026-05-18 13:32:30 +08:00
|
|
|
|
print("[5/6] Starting distillation!")
|
|
|
|
|
|
gpu = torch.cuda.get_device_name(0)
|
|
|
|
|
|
mem = torch.cuda.get_device_properties(0).total_memory / 1e9
|
2026-05-18 18:06:48 +08:00
|
|
|
|
t_params = sum(p.numel() for p in teacher.parameters())
|
|
|
|
|
|
s_params = sum(p.numel() for p in student.parameters())
|
|
|
|
|
|
print(f" GPU: {gpu} ({mem:.1f}GB)")
|
|
|
|
|
|
print(f" Teacher: {t_params/1e9:.2f}B | Student: {s_params/1e9:.2f}B")
|
2026-05-18 13:32:30 +08:00
|
|
|
|
sys.stdout.flush()
|
2026-05-18 18:06:48 +08:00
|
|
|
|
|
2026-05-18 13:32:30 +08:00
|
|
|
|
trainer.train()
|
|
|
|
|
|
|
2026-05-18 18:06:48 +08:00
|
|
|
|
# ========== 6. Save ==========
|
2026-05-18 13:32:30 +08:00
|
|
|
|
print("[6/6] Saving...")
|
|
|
|
|
|
final = os.path.join(OUT, "final")
|
|
|
|
|
|
trainer.save_model(final)
|
|
|
|
|
|
tokenizer.save_pretrained(final)
|
2026-05-18 18:06:48 +08:00
|
|
|
|
|
|
|
|
|
|
# ⚠️ 关键修复:同时修复 config.json 和 generation_config.json 的 eos_token_id
|
|
|
|
|
|
model.config.eos_token_id = 151645
|
|
|
|
|
|
model.config.save_pretrained(final)
|
|
|
|
|
|
|
|
|
|
|
|
model.generation_config.eos_token_id = 151645
|
|
|
|
|
|
model.generation_config.pad_token_id = 151645
|
|
|
|
|
|
model.generation_config.save_pretrained(final)
|
|
|
|
|
|
|
|
|
|
|
|
# 修复 tokenizer 默认system prompt
|
|
|
|
|
|
import json as _json
|
|
|
|
|
|
_tok_cfg_path = os.path.join(final, "tokenizer_config.json")
|
|
|
|
|
|
with open(_tok_cfg_path) as _f:
|
|
|
|
|
|
_tok_cfg = _json.load(_f)
|
|
|
|
|
|
_tok_cfg["default_system"] = ""
|
|
|
|
|
|
with open(_tok_cfg_path, "w") as _f:
|
|
|
|
|
|
_json.dump(_tok_cfg, _f, indent=2, ensure_ascii=False)
|
|
|
|
|
|
|
2026-05-18 13:32:30 +08:00
|
|
|
|
peak = torch.cuda.max_memory_allocated() / 1e9
|
2026-05-18 18:06:48 +08:00
|
|
|
|
print(f" Model: {final}")
|
|
|
|
|
|
print(f" Peak VRAM: {peak:.2f}GB / {mem:.1f}GB")
|
2026-05-18 13:32:30 +08:00
|
|
|
|
print("DONE!")
|