guanghulab/scripts/distill/distill_coder.py
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

174 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""光湖 代码模型(7B)->1.5B铸渊蒸馏 v1
teacher: Qwen2.5-Coder-7B (COS下载)
student: Qwen2.5-1.5B-Instruct
data: sft.jsonl
"""
import os, json, torch, sys
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
from transformers import AutoModelForCausalLM, AutoTokenizer
from torch.utils.data import Dataset, DataLoader
from tqdm import tqdm
import torch.nn.functional as F
from qcloud_cos import CosConfig, CosS3Client
# 代码模型teacher路径从COS下载
TCH = '/root/autodl-tmp/output/coder-7b-sft-cache/final'
STU = '/root/autodl-tmp/models/Qwen/Qwen2___5-1___5B-Instruct'
DATA = '/root/autodl-tmp/data/sft.jsonl'
OUT = '/root/autodl-tmp/output/qwen25-15b-coder-distill'
E, B, GA, LR, ML = 3, 4, 8, 1e-5, 2048
TEMP, ALPHA = 2.0, 0.7
BKT, RG = 'sy-finetune-corpus-1317346199', 'ap-guangzhou'
CK = os.environ.get('ZY_OSS_KEY', 'AKIDkQuBQhoiS2OYXWebXLwMbdT7cvAScbbU')
CS = os.environ.get('ZY_OSS_SECRET', 'nPoZKArgUJBA4nJenjSxJSQBj5FCj3A4')
os.makedirs(OUT, exist_ok=True)
os.makedirs(TCH, exist_ok=True)
# 1. Tokenize
print('[1/5] Tokenize...')
with open(DATA) as f:
raw = [json.loads(l) for l in f]
tok = AutoTokenizer.from_pretrained(STU, trust_remote_code=True)
tok.pad_token = tok.eos_token
data = []
for d in tqdm(raw):
msgs = [m for m in d['messages'] if m['role'] != 'system']
ii, ll = [], []
for m in msgs:
c = m['content']
if not c.strip(): continue
txt = '<|im_start|>' + m['role'] + '\n' + c + '<|im_end|>\n'
tk = tok.encode(txt, add_special_tokens=False)
ii.extend(tk)
ll.extend(tk if m['role'] == 'assistant' else [-100]*len(tk))
if len(ii) > ML:
ii, ll = ii[:ML], ll[:ML]
data.append({'input_ids': ii, 'labels': ll})
print(f' {len(data)} examples')
class DS(Dataset):
def __init__(self, d): self.d = d
def __len__(self): return len(self.d)
def __getitem__(self, i): return self.d[i]
def collate(batch):
ml = max(len(x['input_ids']) for x in batch)
pad_id = tok.pad_token_id
ii = torch.stack([torch.tensor(x['input_ids'] + [pad_id]*(ml-len(x['input_ids']))) for x in batch])
ll = torch.stack([torch.tensor(x['labels'] + [-100]*(ml-len(x['labels']))) for x in batch])
am = (ii != pad_id).long()
return {'input_ids': ii.cuda(), 'labels': ll.cuda(), 'attention_mask': am.cuda()}
loader = DataLoader(DS(data), B, shuffle=True, collate_fn=collate, num_workers=0)
# 2. Load teacher from COS
print('[2/5] Load models...')
torch.cuda.empty_cache()
if not os.path.isdir(STU) or not os.path.isfile(STU + '/config.json'):
from modelscope import snapshot_download
snapshot_download('Qwen/Qwen2.5-1.5B-Instruct', cache_dir='/root/autodl-tmp/models')
if not os.path.isfile(TCH + '/model.safetensors'):
print(' DL teacher (code model) from COS...')
cfg = CosConfig(Region=RG, SecretId=CK, SecretKey=CS)
cl = CosS3Client(cfg)
for obj in cl.list_objects(Bucket=BKT, Prefix='models/qwen25-coder-7b-sft/final/').get('Contents', []):
fn = obj['Key'].split('/')[-1]
if fn == 'DEPLOY_NOTES.md': continue
loc = os.path.join(TCH, fn)
if not os.path.isfile(loc):
print(f' Downloading {fn}...', flush=True)
cl.download_file(Bucket=BKT, Key=obj['Key'], DestFilePath=loc)
print(' Teacher (code model)...', flush=True)
teacher = AutoModelForCausalLM.from_pretrained(
TCH, trust_remote_code=True, torch_dtype=torch.bfloat16, attn_implementation='sdpa').cuda()
teacher.eval()
for p in teacher.parameters(): p.requires_grad_(False)
print(f' T: {sum(p.numel() for p in teacher.parameters())/1e9:.2f}B', flush=True)
print(' Student...', flush=True)
student = AutoModelForCausalLM.from_pretrained(
STU, trust_remote_code=True, torch_dtype=torch.bfloat16, attn_implementation='sdpa').cuda()
student.train()
print(f' S: {sum(p.numel() for p in student.parameters())/1e9:.2f}B', flush=True)
print(f' VRAM: {torch.cuda.memory_allocated()/1e9:.1f}GB', flush=True)
# 3. Train (knowledge distillation)
print('[3/5] Train (KD)...', flush=True)
opt = torch.optim.AdamW(student.parameters(), LR, weight_decay=0.01)
steps_per_epoch = (len(data) // B)
total_steps = steps_per_epoch * E
global_step = 0
for ep in range(E):
print(f'\n===== Epoch {ep+1}/{E} =====', flush=True)
loader.dataset.d = data # reshuffle won't hurt
for batch in tqdm(loader, total=steps_per_epoch):
with torch.no_grad():
t_logits = teacher(**batch).logits
# Fix vocab mismatch: teacher(152064) vs student(151936)
t_logits = t_logits[:, :, :151936]
s_logits = student(**batch).logits
# SFT loss
sft_loss = F.cross_entropy(
s_logits.view(-1, s_logits.size(-1)),
batch['labels'].view(-1),
ignore_index=-100, reduction='mean')
# KL divergence (distillation loss) — use cached t_logits
T = TEMP
t_prob = F.log_softmax(s_logits[:, :-1] / T, dim=-1)
s_prob = F.softmax(t_logits[:, :-1] / T, dim=-1)
# Only on non-pad tokens
mask = (batch['input_ids'][:, 1:] != tok.pad_token_id).unsqueeze(-1)
kl_loss = (F.kl_div(t_prob, s_prob, reduction='none') * mask).sum() / mask.sum() * (T ** 2)
loss = sft_loss + ALPHA * kl_loss
loss.backward()
if (global_step + 1) % GA == 0:
torch.nn.utils.clip_grad_norm_(student.parameters(), 1.0)
opt.step()
opt.zero_grad()
if global_step % 50 == 0:
print(f' step={global_step} loss={loss.item():.4f} sft={sft_loss.item():.4f} kl={kl_loss.item():.4f}', flush=True)
# Periodic cache clear
if global_step % 500 == 0 and global_step > 0:
torch.cuda.empty_cache()
global_step += 1
# Save epoch checkpoint
ckpt = os.path.join(OUT, f'ep{ep+1}')
student.save_pretrained(ckpt)
tok.save_pretrained(ckpt)
torch.cuda.empty_cache()
print(f' Checkpoint: {ckpt}', flush=True)
# 4. Save final
print('[4/5] Save final...', flush=True)
fnl = os.path.join(OUT, 'final')
student.save_pretrained(fnl)
tok.save_pretrained(fnl)
print(f' Final: {fnl}', flush=True)
# 5. Upload to COS
print('[5/5] Upload to COS...', flush=True)
cfg = CosConfig(Region=RG, SecretId=CK, SecretKey=CS)
cl = CosS3Client(cfg)
for f in sorted(os.listdir(fnl)):
fp = os.path.join(fnl, f)
if os.path.isfile(fp):
mb = os.path.getsize(fp) / 1024 / 1024
print(f' {f} ({mb:.0f}MB)', flush=True)
cl.upload_file(Bucket=BKT, Key=f'models/qwen25-15b-coder-distill/{f}', LocalFilePath=fp)
print('ALL DONE', flush=True)