upload model and train/infer codes
Browse files- dataset_loader.py +88 -0
- generate.py +98 -0
- model.py +245 -0
- model_sft.pt +3 -0
- pretrain.py +173 -0
- tokenizer.model +3 -0
- tokenizer.py +220 -0
- tokenizer.vocab +0 -0
- train_sft.py +312 -0
- train_tokenizer.py +41 -0
dataset_loader.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch.utils.data import IterableDataset
|
| 3 |
+
import random
|
| 4 |
+
import jsonlines
|
| 5 |
+
from tqdm import tqdm
|
| 6 |
+
from tokenizer import load_tokenizer
|
| 7 |
+
import concurrent.futures
|
| 8 |
+
|
| 9 |
+
class MultiSourceDataset(IterableDataset):
|
| 10 |
+
|
| 11 |
+
def __init__(self, source_files, probs, mini_batch=4):
|
| 12 |
+
"""
|
| 13 |
+
source_files example: [[file1.jsonl, file2.jsonl],[file3.jsonl]]
|
| 14 |
+
probs example: [0.3, 0.7]
|
| 15 |
+
"""
|
| 16 |
+
super().__init__()
|
| 17 |
+
self.sources = source_files
|
| 18 |
+
self.probs = torch.tensor(probs)
|
| 19 |
+
self.mini_batch = mini_batch
|
| 20 |
+
self.curr_count = 0
|
| 21 |
+
|
| 22 |
+
def __iter__(self):
|
| 23 |
+
while True:
|
| 24 |
+
if self.curr_count == 0:
|
| 25 |
+
idx = torch.multinomial(self.probs, 1).item()
|
| 26 |
+
filename = random.choice(self.sources[idx])
|
| 27 |
+
self.curr_data = []
|
| 28 |
+
with jsonlines.open(filename,'r') as f:
|
| 29 |
+
for obj in f:
|
| 30 |
+
self.curr_data.append(obj)
|
| 31 |
+
self.curr_count += 1
|
| 32 |
+
if self.curr_count == self.mini_batch:
|
| 33 |
+
self.curr_count = 0
|
| 34 |
+
yield random.choice(self.curr_data)
|
| 35 |
+
|
| 36 |
+
class MultiSourceDatasetV2(IterableDataset):
|
| 37 |
+
|
| 38 |
+
def __init__(self, source_files, probs, mini_batch=4, buffer_size_per_worker=12000, num_workers=10, tokenizer_path="tokenizer.model"):
|
| 39 |
+
"""
|
| 40 |
+
source_files example: [[file1.jsonl, file2.jsonl],[file3.jsonl]]
|
| 41 |
+
probs example: [0.3, 0.7]
|
| 42 |
+
"""
|
| 43 |
+
super().__init__()
|
| 44 |
+
self.sources = source_files
|
| 45 |
+
self.probs = torch.tensor(probs)
|
| 46 |
+
self.mini_batch = mini_batch
|
| 47 |
+
self.buffer_size_per_worker = buffer_size_per_worker
|
| 48 |
+
self.num_workers = num_workers
|
| 49 |
+
if tokenizer_path:
|
| 50 |
+
self.tokenizer = load_tokenizer(tokenizer_path)
|
| 51 |
+
else:
|
| 52 |
+
self.tokenizer = None
|
| 53 |
+
|
| 54 |
+
def _transform_ids(self, ids, block_size=1024, eos_id=50303):
|
| 55 |
+
ids = ids + [eos_id]
|
| 56 |
+
if len(ids) > block_size + 1:
|
| 57 |
+
start = random.randint(0,len(ids)-block_size-1)
|
| 58 |
+
ids = ids[start:start+block_size+1]
|
| 59 |
+
elif len(ids) < block_size + 1:
|
| 60 |
+
ids += [eos_id] * (block_size + 1 - len(ids))
|
| 61 |
+
ids = torch.tensor(ids,dtype=torch.int64)
|
| 62 |
+
return ids[:-1], ids[1:]
|
| 63 |
+
|
| 64 |
+
def _get_buffer(self):
|
| 65 |
+
print('\nGetting data buffer...')
|
| 66 |
+
buffer = []
|
| 67 |
+
for _ in tqdm(range(self.buffer_size_per_worker)):
|
| 68 |
+
idx = torch.multinomial(self.probs, 1).item()
|
| 69 |
+
filename = random.choice(self.sources[idx])
|
| 70 |
+
curr_data = []
|
| 71 |
+
with jsonlines.open(filename,'r') as f:
|
| 72 |
+
for obj in f:
|
| 73 |
+
curr_data.append(obj)
|
| 74 |
+
new_buffer = random.sample(curr_data, self.mini_batch)
|
| 75 |
+
if self.tokenizer is None:
|
| 76 |
+
buffer += new_buffer
|
| 77 |
+
else:
|
| 78 |
+
buffer += [self._transform_ids(self.tokenizer.encode(item['text'])) for item in new_buffer]
|
| 79 |
+
self.buffer += buffer
|
| 80 |
+
|
| 81 |
+
def __iter__(self):
|
| 82 |
+
while True:
|
| 83 |
+
self.buffer = []
|
| 84 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=self.num_workers) as executor:
|
| 85 |
+
for _ in range(self.num_workers):
|
| 86 |
+
executor.submit(self._get_buffer)
|
| 87 |
+
for item in self.buffer:
|
| 88 |
+
yield item
|
generate.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from model import TransformerConfig, TransformerLanguageModel
|
| 2 |
+
import torch
|
| 3 |
+
from tokenizer import load_tokenizer, SpecialToken
|
| 4 |
+
import argparse
|
| 5 |
+
|
| 6 |
+
# 模型参数(SFT版:词表扩展至50306,上下文2048)
|
| 7 |
+
config = TransformerConfig(
|
| 8 |
+
vocab_size=50306,
|
| 9 |
+
block_size=2048,
|
| 10 |
+
n_embed=768,
|
| 11 |
+
n_heads=12,
|
| 12 |
+
n_layers=12,
|
| 13 |
+
dropout=0.0,
|
| 14 |
+
bias=True
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
# 编码和解码
|
| 18 |
+
tokenizer = load_tokenizer("tokenizer.model")
|
| 19 |
+
|
| 20 |
+
# 建立模型
|
| 21 |
+
device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
|
| 22 |
+
model = TransformerLanguageModel(config)
|
| 23 |
+
model = model.to(device)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def build_prompt(user_text):
|
| 27 |
+
"""构建标准SFT格式的prompt"""
|
| 28 |
+
return [
|
| 29 |
+
SpecialToken("<|im_start|>"),
|
| 30 |
+
f"user\n{user_text}",
|
| 31 |
+
SpecialToken("<|im_end|>"),
|
| 32 |
+
"\n",
|
| 33 |
+
SpecialToken("<|im_start|>"),
|
| 34 |
+
"assistant\n",
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def decode_output(token_list):
|
| 39 |
+
"""将decode结果拼接为可读字符串"""
|
| 40 |
+
text = ""
|
| 41 |
+
for item in token_list:
|
| 42 |
+
if isinstance(item, str):
|
| 43 |
+
text += item
|
| 44 |
+
elif isinstance(item, SpecialToken):
|
| 45 |
+
text += f"<{item.name}>"
|
| 46 |
+
return text
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def generate(user_text, checkpoint_path='checkpoints/sft/sft_final.pt',
|
| 50 |
+
max_new_tokens=512, temperature=0.8, top_k=40):
|
| 51 |
+
"""加载模型并进行SFT推理"""
|
| 52 |
+
model.load_state_dict(torch.load(checkpoint_path, map_location=device))
|
| 53 |
+
model.eval()
|
| 54 |
+
|
| 55 |
+
# 构建输入token
|
| 56 |
+
prompt_tokens = tokenizer.encode_all(build_prompt(user_text))
|
| 57 |
+
context = torch.tensor(prompt_tokens, dtype=torch.int64).to(device).view(1, -1)
|
| 58 |
+
|
| 59 |
+
with torch.no_grad():
|
| 60 |
+
result = model.generate(
|
| 61 |
+
context,
|
| 62 |
+
max_new_tokens=max_new_tokens,
|
| 63 |
+
temperature=temperature,
|
| 64 |
+
top_k=top_k,
|
| 65 |
+
use_cache=True
|
| 66 |
+
)[0, :]
|
| 67 |
+
|
| 68 |
+
decoded = tokenizer.decode(result.tolist())
|
| 69 |
+
return decode_output(decoded)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
parser = argparse.ArgumentParser(description="SFT模型推理")
|
| 74 |
+
parser.add_argument("--checkpoint", type=str, default="model_sft.pt",
|
| 75 |
+
help="模型检查点路径")
|
| 76 |
+
parser.add_argument("--prompt", type=str, default="写一个恋爱喜剧轻小说,主角是能听到物品心声的高中生。",
|
| 77 |
+
help="用户输入prompt")
|
| 78 |
+
parser.add_argument("--max_tokens", type=int, default=512,
|
| 79 |
+
help="最大生成token数")
|
| 80 |
+
parser.add_argument("--temperature", type=float, default=0.8,
|
| 81 |
+
help="采样温度")
|
| 82 |
+
parser.add_argument("--top_k", type=int, default=40,
|
| 83 |
+
help="top_k采样")
|
| 84 |
+
args = parser.parse_args()
|
| 85 |
+
|
| 86 |
+
output = generate(
|
| 87 |
+
user_text=args.prompt,
|
| 88 |
+
checkpoint_path=args.checkpoint,
|
| 89 |
+
max_new_tokens=args.max_tokens,
|
| 90 |
+
temperature=args.temperature,
|
| 91 |
+
top_k=args.top_k
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
print("=" * 60)
|
| 95 |
+
print(f"Prompt: {args.prompt}")
|
| 96 |
+
print("=" * 60)
|
| 97 |
+
print(output)
|
| 98 |
+
print("=" * 60)
|
model.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
|
| 6 |
+
@dataclass
|
| 7 |
+
class TransformerConfig:
|
| 8 |
+
vocab_size: int
|
| 9 |
+
block_size: int
|
| 10 |
+
n_embed: int
|
| 11 |
+
n_heads: int
|
| 12 |
+
n_layers: int
|
| 13 |
+
dropout: float = 0.0
|
| 14 |
+
bias: bool = True
|
| 15 |
+
|
| 16 |
+
class MultiHeadAttention(nn.Module):
|
| 17 |
+
"""
|
| 18 |
+
多头注意力模块
|
| 19 |
+
"""
|
| 20 |
+
def __init__(self, config: TransformerConfig):
|
| 21 |
+
super().__init__()
|
| 22 |
+
assert config.n_embed % config.n_heads == 0
|
| 23 |
+
self.config = config
|
| 24 |
+
self.head_size = config.n_embed // config.n_heads
|
| 25 |
+
self.c_attn = nn.Linear(config.n_embed, config.n_embed * 3, bias = config.bias)
|
| 26 |
+
self.c_proj = nn.Linear(config.n_embed, config.n_embed)
|
| 27 |
+
self.attention_dropout = nn.Dropout(config.dropout)
|
| 28 |
+
self.residue_dropout = nn.Dropout(config.dropout)
|
| 29 |
+
# 是否支持flash attention
|
| 30 |
+
self.flash_att = hasattr(F, 'scaled_dot_product_attention')
|
| 31 |
+
if not self.flash_att:
|
| 32 |
+
print('警告:未使用Flash Attention, 这可能减慢模型计算速度。')
|
| 33 |
+
# casual mask需要使用的下三角矩阵
|
| 34 |
+
self.register_buffer('mask', torch.tril(torch.ones(config.block_size, config.block_size).view(1,1,config.block_size,config.block_size)))
|
| 35 |
+
|
| 36 |
+
def forward(self, x):
|
| 37 |
+
B,T,C = x.shape
|
| 38 |
+
q,k,v = self.c_attn(x).split(self.config.n_embed, dim=2)
|
| 39 |
+
q = q.view(B,T,self.config.n_heads,self.head_size).transpose(1,2)
|
| 40 |
+
k = k.view(B,T,self.config.n_heads,self.head_size).transpose(1,2)
|
| 41 |
+
v = v.view(B,T,self.config.n_heads,self.head_size).transpose(1,2)
|
| 42 |
+
if self.flash_att:
|
| 43 |
+
out = F.scaled_dot_product_attention(q, k, v, attn_mask=None, dropout_p=self.config.dropout if self.training else 0.0, is_causal=True)
|
| 44 |
+
else:
|
| 45 |
+
scale = self.head_size**(-0.5)
|
| 46 |
+
weight = q @ k.transpose(-2,-1) * scale
|
| 47 |
+
weight = weight.masked_fill(self.mask[:,:,:T,:T]==0, float('-inf'))
|
| 48 |
+
weight = F.softmax(weight, dim=-1)
|
| 49 |
+
weight = self.attention_dropout(weight)
|
| 50 |
+
out = weight @ v
|
| 51 |
+
out = out.transpose(1,2).contiguous().view(B,T,C)
|
| 52 |
+
out = self.residue_dropout(self.c_proj(out))
|
| 53 |
+
return out
|
| 54 |
+
|
| 55 |
+
def forward_with_cache(self, x, past_key_values=None, use_cache=False):
|
| 56 |
+
B,T,C = x.shape
|
| 57 |
+
q,k,v = self.c_attn(x).split(self.config.n_embed, dim=2)
|
| 58 |
+
q = q.view(B,T,self.config.n_heads,self.head_size).transpose(1,2)
|
| 59 |
+
k = k.view(B,T,self.config.n_heads,self.head_size).transpose(1,2)
|
| 60 |
+
v = v.view(B,T,self.config.n_heads,self.head_size).transpose(1,2)
|
| 61 |
+
if past_key_values is not None:
|
| 62 |
+
past_k, past_v = past_key_values
|
| 63 |
+
k = torch.cat([past_k, k], dim=2).contiguous()
|
| 64 |
+
v = torch.cat([past_v, v], dim=2).contiguous()
|
| 65 |
+
scale = self.head_size**(-0.5)
|
| 66 |
+
weight = q @ k.transpose(-2,-1) * scale
|
| 67 |
+
if past_key_values is None:
|
| 68 |
+
weight = weight.masked_fill(self.mask[:,:,:T,:T]==0, float('-inf'))
|
| 69 |
+
weight = F.softmax(weight, dim=-1)
|
| 70 |
+
weight = self.attention_dropout(weight)
|
| 71 |
+
out = weight @ v
|
| 72 |
+
out = out.transpose(1,2).contiguous().view(B,T,C)
|
| 73 |
+
out = self.residue_dropout(self.c_proj(out))
|
| 74 |
+
if use_cache:
|
| 75 |
+
kv_cache = (k,v)
|
| 76 |
+
else:
|
| 77 |
+
kv_cache = None
|
| 78 |
+
return (out, kv_cache)
|
| 79 |
+
|
| 80 |
+
class FeedForward(nn.Module):
|
| 81 |
+
"""
|
| 82 |
+
一个简单的前馈网络模块,包含两层线性层和中间的激活函数
|
| 83 |
+
"""
|
| 84 |
+
def __init__(self, config: TransformerConfig) -> None:
|
| 85 |
+
super().__init__()
|
| 86 |
+
self.config = config
|
| 87 |
+
self.layer_1 = nn.Linear(config.n_embed, 4 * config.n_embed, bias=config.bias)
|
| 88 |
+
self.gelu = nn.GELU()
|
| 89 |
+
self.layer_2 = nn.Linear(4 * config.n_embed, config.n_embed, bias=config.bias)
|
| 90 |
+
self.dropout = nn.Dropout(config.dropout)
|
| 91 |
+
|
| 92 |
+
def forward(self, x):
|
| 93 |
+
out = self.layer_1(x)
|
| 94 |
+
out = self.gelu(out)
|
| 95 |
+
out = self.layer_2(out)
|
| 96 |
+
out = self.dropout(out)
|
| 97 |
+
return out
|
| 98 |
+
|
| 99 |
+
class TransformerBlock(nn.Module):
|
| 100 |
+
"""
|
| 101 |
+
Transformer块
|
| 102 |
+
"""
|
| 103 |
+
def __init__(self, config: TransformerConfig):
|
| 104 |
+
super().__init__()
|
| 105 |
+
self.config = config
|
| 106 |
+
self.mha = MultiHeadAttention(config)
|
| 107 |
+
self.fwd = FeedForward(config)
|
| 108 |
+
self.ln1 = nn.LayerNorm(config.n_embed)
|
| 109 |
+
self.ln2 = nn.LayerNorm(config.n_embed)
|
| 110 |
+
|
| 111 |
+
def forward(self, x):
|
| 112 |
+
x = x + self.mha(self.ln1(x))
|
| 113 |
+
x = x + self.fwd(self.ln2(x))
|
| 114 |
+
return x
|
| 115 |
+
|
| 116 |
+
def forward_with_cache(self, x, kv_cache=None, use_cache=False):
|
| 117 |
+
y = self.ln1(x)
|
| 118 |
+
y = self.mha.forward_with_cache(y, kv_cache, use_cache)
|
| 119 |
+
x = x + y[0]
|
| 120 |
+
x = x + self.fwd(self.ln2(x))
|
| 121 |
+
return (x, y[1])
|
| 122 |
+
|
| 123 |
+
class TransformerLanguageModel(nn.Module):
|
| 124 |
+
"""
|
| 125 |
+
Transformer语言模型
|
| 126 |
+
"""
|
| 127 |
+
def __init__(self, config:TransformerConfig):
|
| 128 |
+
super().__init__()
|
| 129 |
+
self.config = config
|
| 130 |
+
# token嵌入层
|
| 131 |
+
self.token_embedding_table = nn.Embedding(config.vocab_size, config.n_embed)
|
| 132 |
+
# 位置编码层
|
| 133 |
+
self.position_embedding_table = nn.Embedding(config.block_size, config.n_embed)
|
| 134 |
+
# Transformer主体,由一系列堆叠的Transformer块组成
|
| 135 |
+
self.blocks = nn.ModuleList([TransformerBlock(config) for _ in range(config.n_layers)])
|
| 136 |
+
# 最后的LayerNorm层
|
| 137 |
+
self.ln_f = nn.LayerNorm(config.n_embed)
|
| 138 |
+
# 语言模型头,用于预测下一个token
|
| 139 |
+
self.lm_head = nn.Linear(config.n_embed, config.vocab_size)
|
| 140 |
+
|
| 141 |
+
def forward(self, idx, targets=None, device='cuda:0'):
|
| 142 |
+
B, T = idx.shape # batch size, context length
|
| 143 |
+
token_embed = self.token_embedding_table(idx) # token嵌入向量
|
| 144 |
+
pos_embed = self.position_embedding_table(torch.arange(T, device=device)) # 位置嵌入向量
|
| 145 |
+
x = token_embed + pos_embed # 两个向量相加输入到Transformer块中
|
| 146 |
+
for block in self.blocks:
|
| 147 |
+
x = block(x)
|
| 148 |
+
logits = self.lm_head(self.ln_f(x))
|
| 149 |
+
if targets is None:
|
| 150 |
+
loss = None
|
| 151 |
+
else:
|
| 152 |
+
# 计算交叉熵损失
|
| 153 |
+
logits = logits.view(B*T, self.config.vocab_size)
|
| 154 |
+
targets = targets.view(B*T)
|
| 155 |
+
loss = F.cross_entropy(logits, targets)
|
| 156 |
+
return logits, loss
|
| 157 |
+
|
| 158 |
+
def forward_with_cache(self, idx, kv_cache=None, use_cache=False, targets=None, device='cuda:0'):
|
| 159 |
+
B, T = idx.shape # batch size, context length
|
| 160 |
+
token_embed = self.token_embedding_table(idx) # token嵌入向量
|
| 161 |
+
if kv_cache is None:
|
| 162 |
+
pos_embed = self.position_embedding_table(torch.arange(T, device=device)) # 位置嵌入向量
|
| 163 |
+
else:
|
| 164 |
+
past_len = kv_cache[0][0].size(2)
|
| 165 |
+
pos_embed = self.position_embedding_table(torch.arange(past_len, T+past_len, device=device)) # 位置嵌入向量
|
| 166 |
+
x = token_embed + pos_embed # 两个向量相加输入到Transformer块中
|
| 167 |
+
|
| 168 |
+
# 张量顺次通过各个Transformer块
|
| 169 |
+
if kv_cache is not None:
|
| 170 |
+
new_cache = []
|
| 171 |
+
for i,block in enumerate(self.blocks):
|
| 172 |
+
x, curr_cache = block.forward_with_cache(x,kv_cache[i],use_cache)
|
| 173 |
+
new_cache.append(curr_cache)
|
| 174 |
+
# x = x1[0]
|
| 175 |
+
# kv_cache[i] = x1[1]
|
| 176 |
+
else:
|
| 177 |
+
if use_cache:
|
| 178 |
+
new_cache = [0] * self.config.n_layers
|
| 179 |
+
for i,block in enumerate(self.blocks):
|
| 180 |
+
x1 = block.forward_with_cache(x,None,use_cache)
|
| 181 |
+
x = x1[0]
|
| 182 |
+
if use_cache:
|
| 183 |
+
new_cache[i] = x1[1]
|
| 184 |
+
|
| 185 |
+
x = self.ln_f(x) # 张量通过最后的LayerNorm层
|
| 186 |
+
logits = self.lm_head(x) # 使用语言模型头得到logits
|
| 187 |
+
|
| 188 |
+
if not use_cache:
|
| 189 |
+
new_cache = None
|
| 190 |
+
if targets is None:
|
| 191 |
+
loss = None
|
| 192 |
+
else:
|
| 193 |
+
# 计算交叉熵损失
|
| 194 |
+
logits = logits.view(B*T, self.config.vocab_size)
|
| 195 |
+
targets = targets.view(B*T)
|
| 196 |
+
loss = F.cross_entropy(logits, targets)
|
| 197 |
+
|
| 198 |
+
return logits, new_cache, loss
|
| 199 |
+
|
| 200 |
+
@torch.no_grad()
|
| 201 |
+
def generate(self, idx, max_new_tokens=300, temperature=1.0, top_k=0, kv_cache = None, use_cache=False):
|
| 202 |
+
flag = False
|
| 203 |
+
curr_kv_cache = kv_cache
|
| 204 |
+
for _ in range(max_new_tokens):
|
| 205 |
+
if curr_kv_cache is None:
|
| 206 |
+
idx_cond = idx[:, -self.config.block_size:]
|
| 207 |
+
else:
|
| 208 |
+
if flag:
|
| 209 |
+
idx_cond = idx[:, -1:]
|
| 210 |
+
curr_kv_cache = [(item[0][:,:,-self.config.block_size:,:],item[1][:,:,-self.config.block_size:,:]) for item in curr_kv_cache]
|
| 211 |
+
else:
|
| 212 |
+
length0 = idx.shape[1]
|
| 213 |
+
length1 = kv_cache[0][0].shape[-2]
|
| 214 |
+
if length0 > self.config.block_size:
|
| 215 |
+
idx_cond = idx[:, -self.config.block_size:]
|
| 216 |
+
kv_cache = None
|
| 217 |
+
else:
|
| 218 |
+
idx_cond = idx[:, :]
|
| 219 |
+
if length0 + length1 > self.config.block_size:
|
| 220 |
+
length2 = self.config.block_size-length0
|
| 221 |
+
kv_cache = [(item[0][-length2:],item[1][-length2:]) for item in kv_cache]
|
| 222 |
+
logits, curr_kv_cache, _ = self.forward_with_cache(idx_cond, curr_kv_cache, use_cache)
|
| 223 |
+
# print(kv_cache[0][0].shape)
|
| 224 |
+
logits = logits[:,-1,:] / temperature
|
| 225 |
+
if top_k > 0:
|
| 226 |
+
logits_top, _ = torch.topk(logits, min(top_k, logits.shape[-1]))
|
| 227 |
+
logits[logits < logits_top[:,[-1]]] = -float('Inf')
|
| 228 |
+
probs = F.softmax(logits, dim=-1) # 计算各token出现的概率
|
| 229 |
+
next_idx = torch.multinomial(probs, num_samples=1) # 采样
|
| 230 |
+
idx = torch.cat((idx, next_idx), dim=1) # (B,T+1)
|
| 231 |
+
flag = True
|
| 232 |
+
return idx
|
| 233 |
+
|
| 234 |
+
@torch.no_grad()
|
| 235 |
+
def generate_normal(self, idx, max_new_tokens=300):
|
| 236 |
+
|
| 237 |
+
for _ in range(max_new_tokens):
|
| 238 |
+
idx_cond = idx[:,-self.config.block_size:]
|
| 239 |
+
logits, _ = self(idx_cond)
|
| 240 |
+
last_logits = logits[:,-1,:]
|
| 241 |
+
probs = F.softmax(last_logits, dim=-1) # 计算各token出现的概率
|
| 242 |
+
next_idx = torch.multinomial(probs, num_samples=1) # 采样
|
| 243 |
+
idx = torch.cat((idx, next_idx), dim=1) # (B,T+1)
|
| 244 |
+
|
| 245 |
+
return idx
|
model_sft.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:61e80ec6ced1d35bee1f8bc402e838707c26b13c9b4f6e7ef27cdeeea9d9682a
|
| 3 |
+
size 857181835
|
pretrain.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from model import TransformerConfig, TransformerLanguageModel
|
| 2 |
+
from tokenizer import load_tokenizer
|
| 3 |
+
import torch
|
| 4 |
+
from torch.utils.data import DataLoader
|
| 5 |
+
from dataset_loader import MultiSourceDatasetV2
|
| 6 |
+
import random
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
|
| 9 |
+
# 模型参数
|
| 10 |
+
config = TransformerConfig(
|
| 11 |
+
50304, # vocab_size
|
| 12 |
+
1024, # block_size
|
| 13 |
+
768, # n_embed
|
| 14 |
+
12, # n_heads
|
| 15 |
+
12, # n_layers
|
| 16 |
+
0.0, # dropout
|
| 17 |
+
True # bias
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# 训练参数
|
| 22 |
+
batch_size = 8
|
| 23 |
+
max_iters = 150000
|
| 24 |
+
gradient_accumulation_steps = 5
|
| 25 |
+
eval_interval = 100
|
| 26 |
+
save_interval = 500
|
| 27 |
+
learning_rate = 1e-4
|
| 28 |
+
device = 'cuda:0' # if torch.cuda.is_available() else 'cpu'
|
| 29 |
+
|
| 30 |
+
# 建立模型
|
| 31 |
+
model = TransformerLanguageModel(config)
|
| 32 |
+
model = model.to(device)
|
| 33 |
+
|
| 34 |
+
ckpt_id = 43000
|
| 35 |
+
checkpoint = f"checkpoints/new/{ckpt_id}.pt"
|
| 36 |
+
model.load_state_dict(torch.load(checkpoint))
|
| 37 |
+
|
| 38 |
+
# 加载分词器
|
| 39 |
+
tokenizer = load_tokenizer("tokenizer.model")
|
| 40 |
+
|
| 41 |
+
# 数据加载
|
| 42 |
+
recipe_files = [
|
| 43 |
+
[f"data/enwiki/enwiki-{page}.jsonl" for page in range(6400)],
|
| 44 |
+
[f"data/fineweb/fineweb-{page}.jsonl" for page in range(14850)],
|
| 45 |
+
[f"data/zhwiki/zhwiki-{page}.jsonl" for page in range(1350)],
|
| 46 |
+
[f"data/zhihu/zhihu-{page}.jsonl" for page in range(975)],
|
| 47 |
+
[f"data/allnovels-split/ans-{page}.jsonl" for page in range(1330)]
|
| 48 |
+
]
|
| 49 |
+
probs = [
|
| 50 |
+
0.2,
|
| 51 |
+
0.3,
|
| 52 |
+
0.2,
|
| 53 |
+
0.1,
|
| 54 |
+
0.2
|
| 55 |
+
]
|
| 56 |
+
|
| 57 |
+
# 建立数据加载器
|
| 58 |
+
ds = MultiSourceDatasetV2(recipe_files, probs)
|
| 59 |
+
loader = DataLoader(ds, batch_size)
|
| 60 |
+
|
| 61 |
+
# 建立数据处理函数
|
| 62 |
+
# 1. 使用tokenizer转化为整数id
|
| 63 |
+
# 2. 添加eos token
|
| 64 |
+
# 3. 对超出长度限制+1的数据进行随机截取
|
| 65 |
+
# 4. 计算最大长度
|
| 66 |
+
# 4. 对不足最大长度的数据用pad token(此处等于eos token)补足
|
| 67 |
+
# 5. 合成一个int64格式的tensor ids, 形状为(B,T+1), 使用ids[:,:-1]和ids[:,1:]作为x,y
|
| 68 |
+
def get_input_ids(text_batch, eos_token_id=50303, block_size=config.block_size):
|
| 69 |
+
texts = text_batch["text"]
|
| 70 |
+
ids = [tokenizer.encode(text) + [eos_token_id] for text in texts]
|
| 71 |
+
for i in range(len(ids)):
|
| 72 |
+
if len(ids[i]) > block_size+1:
|
| 73 |
+
start = random.randint(0,len(ids[i])-100)
|
| 74 |
+
ids[i] = ids[i][start:start+block_size+1]
|
| 75 |
+
max_len = max([len(item) for item in ids])
|
| 76 |
+
ids = [item + [eos_token_id] * (max_len - len(item)) for item in ids]
|
| 77 |
+
ids = torch.tensor(ids, dtype=torch.int64)
|
| 78 |
+
return ids[:,:-1],ids[:,1:]
|
| 79 |
+
|
| 80 |
+
# 建立优化器
|
| 81 |
+
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
|
| 82 |
+
scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, 1000, 2, 5e-7)
|
| 83 |
+
|
| 84 |
+
# 文本生成测试函数
|
| 85 |
+
@torch.no_grad()
|
| 86 |
+
def gen_text(text):
|
| 87 |
+
model.eval()
|
| 88 |
+
ids = torch.tensor(tokenizer.encode(text)).to(device).view(1,-1)
|
| 89 |
+
output_ids = model.generate(ids)[0,:]
|
| 90 |
+
model.train()
|
| 91 |
+
return tokenizer.decode(output_ids.tolist())[0]
|
| 92 |
+
|
| 93 |
+
# # 进行训练
|
| 94 |
+
# # 初始化进度条
|
| 95 |
+
# pbar = tqdm(total=max_iters+1)
|
| 96 |
+
# # 初始化每步的loss
|
| 97 |
+
# all_loss = 0.0
|
| 98 |
+
# # 初始化梯度累加
|
| 99 |
+
# grad_steps = 0
|
| 100 |
+
|
| 101 |
+
# # 使用数据加载器获取一个新的数据batch
|
| 102 |
+
# for iter_num, (x,y) in enumerate(loader):
|
| 103 |
+
# # x,y = get_input_ids(batch)
|
| 104 |
+
|
| 105 |
+
# # 每隔eval_interval轮检查模型生成效果,每隔save_interval保存一次
|
| 106 |
+
# steps = iter_num // gradient_accumulation_steps
|
| 107 |
+
# if iter_num % gradient_accumulation_steps == 0 and (steps % save_interval == 0 or steps == max_iters):
|
| 108 |
+
# print(gen_text("I love you, "))
|
| 109 |
+
# torch.save(model.state_dict(),f'checkpoints/mixed/mixed-{steps}.pt')
|
| 110 |
+
# print(f"Step {steps} saved.")
|
| 111 |
+
|
| 112 |
+
# # 调用模型计算logits和loss
|
| 113 |
+
# _, loss = model(x.to(device), targets = y.to(device), device=device)
|
| 114 |
+
# loss = loss / gradient_accumulation_steps
|
| 115 |
+
|
| 116 |
+
# # 反向传播计算梯度
|
| 117 |
+
# loss.backward()
|
| 118 |
+
# grad_steps += 1
|
| 119 |
+
# all_loss += loss.item()
|
| 120 |
+
|
| 121 |
+
# # 到达梯度累加步数以后更新参数
|
| 122 |
+
# if grad_steps >= gradient_accumulation_steps:
|
| 123 |
+
|
| 124 |
+
# # 更新参数
|
| 125 |
+
# optimizer.step()
|
| 126 |
+
|
| 127 |
+
# # 梯度归零
|
| 128 |
+
# optimizer.zero_grad(set_to_none=True)
|
| 129 |
+
|
| 130 |
+
# # 重置梯度累加步数
|
| 131 |
+
# grad_steps = 0
|
| 132 |
+
|
| 133 |
+
# # 更新进度条
|
| 134 |
+
# pbar.update()
|
| 135 |
+
|
| 136 |
+
# # 每轮输出一次loss
|
| 137 |
+
# print(f"\nLoss: {all_loss}")
|
| 138 |
+
|
| 139 |
+
# # 重置loss
|
| 140 |
+
# all_loss = 0.0
|
| 141 |
+
|
| 142 |
+
# # 达到步数以后结束训练
|
| 143 |
+
# if iter_num == max_iters * gradient_accumulation_steps:
|
| 144 |
+
# break
|
| 145 |
+
|
| 146 |
+
# 进行训练
|
| 147 |
+
ds_iter = iter(loader)
|
| 148 |
+
for iter in tqdm(range(max_iters+1)):
|
| 149 |
+
if iter < ckpt_id:
|
| 150 |
+
continue
|
| 151 |
+
all_loss = 0.0
|
| 152 |
+
# 梯度归零
|
| 153 |
+
optimizer.zero_grad(set_to_none=True)
|
| 154 |
+
for _ in range(gradient_accumulation_steps):
|
| 155 |
+
# 使用数据加载器获取一个新的数据batch
|
| 156 |
+
x, y = next(ds_iter)
|
| 157 |
+
# 调用模型计算logits和loss
|
| 158 |
+
logits,loss = model(x.to(device), y.to(device), device=device)
|
| 159 |
+
loss = loss / gradient_accumulation_steps
|
| 160 |
+
all_loss += loss.item()
|
| 161 |
+
# 反向传播计算梯度
|
| 162 |
+
loss.backward()
|
| 163 |
+
# 更新参数
|
| 164 |
+
optimizer.step()
|
| 165 |
+
scheduler.step()
|
| 166 |
+
# 每隔save_interval保存一次
|
| 167 |
+
if iter % save_interval == 0 or iter == max_iters:
|
| 168 |
+
torch.save(model.state_dict(),f'checkpoints/new/{iter}.pt')
|
| 169 |
+
print(f"Step {iter} saved.")
|
| 170 |
+
# 每隔eval_iter步评估一次
|
| 171 |
+
if iter % eval_interval == 0 or iter == max_iters:
|
| 172 |
+
print(f"Step: {iter}, Loss: {all_loss}")
|
| 173 |
+
print(gen_text("我喜欢你,"))
|
tokenizer.model
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0f5b5ff2605789f7b158cb60f911cdacd50d4ee943cf31a2b4d959d437c437b8
|
| 3 |
+
size 814176
|
tokenizer.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import regex
|
| 2 |
+
|
| 3 |
+
def get_stats(ids, counts=None):
|
| 4 |
+
"""
|
| 5 |
+
统计一列整数中相邻两个数组成的数对出现的频率
|
| 6 |
+
"""
|
| 7 |
+
if len(ids) <= 1:
|
| 8 |
+
return counts
|
| 9 |
+
counts = {} if counts is None else counts
|
| 10 |
+
for pair in zip(ids[:-1],ids[1:]):
|
| 11 |
+
counts[pair] = counts.get(pair,0)+1
|
| 12 |
+
return counts
|
| 13 |
+
|
| 14 |
+
def merge_once(ids, pair, idx):
|
| 15 |
+
"""
|
| 16 |
+
把ids中的每个形如pair的id对变成idx
|
| 17 |
+
"""
|
| 18 |
+
new_ids = []
|
| 19 |
+
i = 0
|
| 20 |
+
while i < len(ids):
|
| 21 |
+
if i == len(ids)-1:
|
| 22 |
+
new_ids.append(ids[i])
|
| 23 |
+
i += 1
|
| 24 |
+
else:
|
| 25 |
+
p1, p2 = ids[i], ids[i+1]
|
| 26 |
+
if (p1,p2) == pair:
|
| 27 |
+
new_ids.append(idx)
|
| 28 |
+
i += 2
|
| 29 |
+
else:
|
| 30 |
+
new_ids.append(p1)
|
| 31 |
+
i += 1
|
| 32 |
+
return new_ids
|
| 33 |
+
|
| 34 |
+
def do_merge(ids, merges):
|
| 35 |
+
"""
|
| 36 |
+
使用merges字典把ids合并为简化的列表
|
| 37 |
+
例如,[1,2,3,1,2,3,4,1,2],{(1,2):5,(5,3):6}-->[6,6,4,5]
|
| 38 |
+
"""
|
| 39 |
+
new_ids = ids
|
| 40 |
+
while len(new_ids) >= 2:
|
| 41 |
+
# 统计id列表的id对
|
| 42 |
+
counts = get_stats(new_ids)
|
| 43 |
+
# 选择id最小的merge对,找不到可以合并的pair时跳出
|
| 44 |
+
counts_in_merges = {k:v for k,v in counts.items() if k in merges.keys()}
|
| 45 |
+
if len(counts_in_merges.keys())==0:
|
| 46 |
+
break
|
| 47 |
+
pair = min(counts_in_merges, key=lambda p: merges[p])
|
| 48 |
+
# 进行一次合并,把pair变成merges[pair]
|
| 49 |
+
new_ids = merge_once(new_ids, pair, merges[pair])
|
| 50 |
+
return new_ids
|
| 51 |
+
|
| 52 |
+
class SpecialToken:
|
| 53 |
+
|
| 54 |
+
def __init__(self, name):
|
| 55 |
+
self.name = name
|
| 56 |
+
|
| 57 |
+
def __str__(self):
|
| 58 |
+
return self.name
|
| 59 |
+
|
| 60 |
+
def __repr__(self):
|
| 61 |
+
return f"SpecialToken({self.name})"
|
| 62 |
+
|
| 63 |
+
def __eq__(self, other):
|
| 64 |
+
if isinstance(other, SpecialToken):
|
| 65 |
+
return self.name == other.name
|
| 66 |
+
return False
|
| 67 |
+
|
| 68 |
+
def __hash__(self):
|
| 69 |
+
return hash(self.name)
|
| 70 |
+
|
| 71 |
+
class Tokenizer:
|
| 72 |
+
|
| 73 |
+
def __init__(self, pattern):
|
| 74 |
+
self.merges = {}
|
| 75 |
+
self.pattern_string = pattern
|
| 76 |
+
self.pattern = regex.compile(pattern)
|
| 77 |
+
self.vocab = {idx:bytes([idx]) for idx in range(256)}
|
| 78 |
+
self.vocab_size = 256
|
| 79 |
+
self.special_tokens = {}
|
| 80 |
+
self.special_tokens_inv = {}
|
| 81 |
+
|
| 82 |
+
def train(self, vocab_size, dataloader, merge_increase_per_loop=1):
|
| 83 |
+
"""
|
| 84 |
+
训练Tokenizer,使得token数量最终达到vocab_size
|
| 85 |
+
每次使用dataloader加载一组新的文本,使用已有的merges进行合并后,
|
| 86 |
+
使用bpe算法找到merge_increase_per_loop个高频token对,加到self.merges中
|
| 87 |
+
直到token总量达标为止
|
| 88 |
+
"""
|
| 89 |
+
assert vocab_size >= self.vocab_size
|
| 90 |
+
|
| 91 |
+
# 循环获取批量的文本
|
| 92 |
+
for text_batch in dataloader:
|
| 93 |
+
# 处理文本列表,把每条文本划分为文本块,把全部文本块合并为一个List
|
| 94 |
+
text_chunks = []
|
| 95 |
+
for text in text_batch:
|
| 96 |
+
text_chunks += regex.findall(self.pattern, text)
|
| 97 |
+
# 把文本块预处理为字节形式(即0~255的整数的列表)
|
| 98 |
+
ids = [list(ch.encode('utf-8')) for ch in text_chunks]
|
| 99 |
+
# 使用已有的merge更新ids
|
| 100 |
+
ids = [do_merge(idlist,self.merges) for idlist in ids]
|
| 101 |
+
for i in range(merge_increase_per_loop):
|
| 102 |
+
# 统计数据中的id对
|
| 103 |
+
counts = None
|
| 104 |
+
for idlist in ids:
|
| 105 |
+
counts = get_stats(idlist, counts)
|
| 106 |
+
# 找到频率最高的对
|
| 107 |
+
pair = max(counts, key=lambda p: counts[p])
|
| 108 |
+
if counts[pair] == 0:
|
| 109 |
+
break
|
| 110 |
+
# 这对id对应的编号
|
| 111 |
+
idx = self.vocab_size
|
| 112 |
+
# 添加新token
|
| 113 |
+
self.merges[pair] = idx
|
| 114 |
+
self.vocab[idx] = self.vocab[pair[0]] + self.vocab[pair[1]]
|
| 115 |
+
self.vocab_size += 1
|
| 116 |
+
print(f"New merge: {pair}->{idx}")
|
| 117 |
+
if self.vocab_size >= vocab_size:
|
| 118 |
+
return 0
|
| 119 |
+
# 更新ids
|
| 120 |
+
ids = [merge_once(idlist, pair, idx) for idlist in ids]
|
| 121 |
+
if self.vocab_size % 1000 == 0:
|
| 122 |
+
self.save(f"tokenizer-{self.vocab_size}")
|
| 123 |
+
|
| 124 |
+
def add_special_tokens(self, special_tokens):
|
| 125 |
+
self.special_tokens = special_tokens
|
| 126 |
+
self.special_tokens_inv = {v:k for k,v in special_tokens.items()}
|
| 127 |
+
|
| 128 |
+
def build_vocab(self):
|
| 129 |
+
self.vocab = {idx:bytes([idx]) for idx in range(256)}
|
| 130 |
+
self.vocab_size = 256
|
| 131 |
+
for (p1,p2),idx in self.merges.items():
|
| 132 |
+
self.vocab[idx] = self.vocab[p1] + self.vocab[p2]
|
| 133 |
+
self.vocab_size += 1
|
| 134 |
+
|
| 135 |
+
def encode(self, text):
|
| 136 |
+
text_chunks = regex.findall(self.pattern, text)
|
| 137 |
+
all_ids = [do_merge(list(ch.encode('utf-8')),self.merges) for ch in text_chunks]
|
| 138 |
+
ids = []
|
| 139 |
+
for new_ids in all_ids:
|
| 140 |
+
ids += new_ids
|
| 141 |
+
return ids
|
| 142 |
+
|
| 143 |
+
def encode_all(self, text_special_mix):
|
| 144 |
+
ids = []
|
| 145 |
+
for s in text_special_mix:
|
| 146 |
+
if isinstance(s,str):
|
| 147 |
+
ids += self.encode(s)
|
| 148 |
+
elif isinstance(s,SpecialToken):
|
| 149 |
+
ids.append(self.special_tokens[s])
|
| 150 |
+
else:
|
| 151 |
+
raise TypeError
|
| 152 |
+
return ids
|
| 153 |
+
|
| 154 |
+
def decode(self, ids):
|
| 155 |
+
decoded = []
|
| 156 |
+
curr_text_bytes = b""
|
| 157 |
+
for i in range(len(ids)):
|
| 158 |
+
if ids[i] in self.special_tokens_inv.keys():
|
| 159 |
+
if curr_text_bytes:
|
| 160 |
+
decoded.append(curr_text_bytes.decode("utf-8",errors="replace"))
|
| 161 |
+
curr_text_bytes = b""
|
| 162 |
+
decoded.append(SpecialToken(self.special_tokens_inv[ids[i]]))
|
| 163 |
+
elif ids[i] in self.vocab.keys():
|
| 164 |
+
curr_text_bytes += self.vocab[ids[i]]
|
| 165 |
+
if i == len(ids) - 1:
|
| 166 |
+
decoded.append(curr_text_bytes.decode("utf-8",errors="replace"))
|
| 167 |
+
curr_text_bytes = b""
|
| 168 |
+
else:
|
| 169 |
+
print(f"{ids[i]}: Error token id.")
|
| 170 |
+
return decoded
|
| 171 |
+
|
| 172 |
+
def save(self, filename="tokenizer"):
|
| 173 |
+
with open(f"{filename}.model","w") as f:
|
| 174 |
+
f.write("Tokenizer V1\n")
|
| 175 |
+
f.write(self.pattern_string+"\n")
|
| 176 |
+
f.write(f"{len(self.special_tokens)}\n")
|
| 177 |
+
for st, sid in self.special_tokens.items():
|
| 178 |
+
f.write(f"{st.name} {sid}\n")
|
| 179 |
+
for (p1,p2),idx in self.merges.items():
|
| 180 |
+
f.write(f"{p1} {p2} {idx}\n")
|
| 181 |
+
|
| 182 |
+
with open(f"{filename}.vocab","w") as f:
|
| 183 |
+
f.write('Common Tokens:\n')
|
| 184 |
+
for idx,bstr in self.vocab.items():
|
| 185 |
+
f.write(f"{idx} {str(bstr)[2:-1]}\n")
|
| 186 |
+
f.write('Special Tokens:\n')
|
| 187 |
+
for idx,spt in self.special_tokens_inv.items():
|
| 188 |
+
f.write(f"{idx} {spt.name}\n")
|
| 189 |
+
|
| 190 |
+
def load_tokenizer(filename="tokenizer.model"):
|
| 191 |
+
with open(filename,"r",encoding="utf-8") as f:
|
| 192 |
+
version = f.readline().strip()
|
| 193 |
+
assert version == "Tokenizer V1"
|
| 194 |
+
pat = f.readline().strip()
|
| 195 |
+
num_spt = int(f.readline().strip())
|
| 196 |
+
spts = {}
|
| 197 |
+
for _ in range(num_spt):
|
| 198 |
+
line = f.readline().strip()
|
| 199 |
+
spt_name, spt_idx = line.split()
|
| 200 |
+
spts[SpecialToken(spt_name)] = int(spt_idx)
|
| 201 |
+
merges = {}
|
| 202 |
+
line = f.readline().strip()
|
| 203 |
+
while len(line)>=5:
|
| 204 |
+
p1,p2,idx = line.split()
|
| 205 |
+
merges[(int(p1),int(p2))] = int(idx)
|
| 206 |
+
line = f.readline().strip()
|
| 207 |
+
tokenizer = Tokenizer(pat)
|
| 208 |
+
tokenizer.merges = merges
|
| 209 |
+
tokenizer.build_vocab()
|
| 210 |
+
tokenizer.add_special_tokens(spts)
|
| 211 |
+
# 确保vocab包含所有特殊token,并正确设置vocab_size
|
| 212 |
+
max_id = max(tokenizer.vocab.keys()) if tokenizer.vocab else 255
|
| 213 |
+
for st, sid in spts.items():
|
| 214 |
+
tokenizer.vocab[sid] = f"<{st.name}>".encode("utf-8", errors="replace")
|
| 215 |
+
if sid > max_id:
|
| 216 |
+
max_id = sid
|
| 217 |
+
tokenizer.vocab_size = max_id + 1
|
| 218 |
+
return tokenizer
|
| 219 |
+
|
| 220 |
+
|
tokenizer.vocab
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
train_sft.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
from torch.utils.data import DataLoader, IterableDataset
|
| 4 |
+
from torch.amp import autocast, GradScaler
|
| 5 |
+
from model import TransformerConfig, TransformerLanguageModel
|
| 6 |
+
from tokenizer import load_tokenizer, SpecialToken
|
| 7 |
+
import json
|
| 8 |
+
import random
|
| 9 |
+
import os
|
| 10 |
+
from tqdm import tqdm
|
| 11 |
+
|
| 12 |
+
# ============== 1. 准备Tokenizer ==============
|
| 13 |
+
def prepare_tokenizer():
|
| 14 |
+
tok = load_tokenizer("tokenizer.model")
|
| 15 |
+
# 添加新token
|
| 16 |
+
new_tokens = {
|
| 17 |
+
SpecialToken("<|im_start|>"): 50304,
|
| 18 |
+
SpecialToken("<|im_end|>"): 50305,
|
| 19 |
+
}
|
| 20 |
+
tok.special_tokens.update(new_tokens)
|
| 21 |
+
tok.special_tokens_inv = {v: k for k, v in tok.special_tokens.items()}
|
| 22 |
+
# vocab_size应该是max id + 1
|
| 23 |
+
tok.vocab_size = max(tok.vocab.keys()) + 1
|
| 24 |
+
# 确保新id在vocab中有占位
|
| 25 |
+
for st, sid in new_tokens.items():
|
| 26 |
+
if sid not in tok.vocab:
|
| 27 |
+
tok.vocab[sid] = f"<{st.name}>".encode("utf-8", errors="replace")
|
| 28 |
+
tok.save("tokenizer_sft")
|
| 29 |
+
print(f"Saved tokenizer_sft.model with vocab_size={tok.vocab_size}")
|
| 30 |
+
return tok
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# ============== 2. 扩展模型词表 ==============
|
| 34 |
+
def expand_model_for_new_tokens(old_ckpt_path, new_vocab_size, config):
|
| 35 |
+
old_config = TransformerConfig(
|
| 36 |
+
vocab_size=50304,
|
| 37 |
+
block_size=1024, # 旧模型使用1024上下文
|
| 38 |
+
n_embed=config.n_embed,
|
| 39 |
+
n_heads=config.n_heads,
|
| 40 |
+
n_layers=config.n_layers,
|
| 41 |
+
dropout=config.dropout,
|
| 42 |
+
bias=config.bias,
|
| 43 |
+
)
|
| 44 |
+
old_model = TransformerLanguageModel(old_config)
|
| 45 |
+
old_model.load_state_dict(torch.load(old_ckpt_path, map_location="cpu"))
|
| 46 |
+
|
| 47 |
+
new_config = TransformerConfig(
|
| 48 |
+
vocab_size=new_vocab_size,
|
| 49 |
+
block_size=config.block_size,
|
| 50 |
+
n_embed=config.n_embed,
|
| 51 |
+
n_heads=config.n_heads,
|
| 52 |
+
n_layers=config.n_layers,
|
| 53 |
+
dropout=config.dropout,
|
| 54 |
+
bias=config.bias,
|
| 55 |
+
)
|
| 56 |
+
new_model = TransformerLanguageModel(new_config)
|
| 57 |
+
|
| 58 |
+
new_state = new_model.state_dict()
|
| 59 |
+
old_state = old_model.state_dict()
|
| 60 |
+
|
| 61 |
+
for key in new_state:
|
| 62 |
+
if key in old_state:
|
| 63 |
+
if new_state[key].shape == old_state[key].shape:
|
| 64 |
+
new_state[key].copy_(old_state[key])
|
| 65 |
+
else:
|
| 66 |
+
print(f"Expanding {key}: {old_state[key].shape} -> {new_state[key].shape}")
|
| 67 |
+
if "token_embedding_table" in key:
|
| 68 |
+
new_state[key][: old_state[key].size(0)].copy_(old_state[key])
|
| 69 |
+
elif "lm_head" in key:
|
| 70 |
+
new_state[key][: old_state[key].size(0)].copy_(old_state[key])
|
| 71 |
+
elif "position_embedding_table" in key:
|
| 72 |
+
# 复制旧的位置编码,新的用随机初始化
|
| 73 |
+
new_state[key][: old_state[key].size(0)].copy_(old_state[key])
|
| 74 |
+
elif "mask" in key:
|
| 75 |
+
# mask是buffer,新模型已经初始化为正确大小
|
| 76 |
+
pass
|
| 77 |
+
else:
|
| 78 |
+
print(f"Warning: unexpected shape mismatch for {key}")
|
| 79 |
+
else:
|
| 80 |
+
print(f"Key {key} not in old model, initialized randomly.")
|
| 81 |
+
|
| 82 |
+
new_model.load_state_dict(new_state)
|
| 83 |
+
return new_model
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# ============== 3. SFT数据集 ==============
|
| 87 |
+
class SFTDataset(IterableDataset):
|
| 88 |
+
def __init__(self, data_file, tokenizer, block_size=2048, mask_prob=0.8):
|
| 89 |
+
self.tokenizer = tokenizer
|
| 90 |
+
self.block_size = block_size
|
| 91 |
+
self.mask_prob = mask_prob # 80%概率只计算assistant loss
|
| 92 |
+
self.eos_id = tokenizer.special_tokens[SpecialToken("<|endoftext|>")]
|
| 93 |
+
|
| 94 |
+
# 预加载所有数据并编码
|
| 95 |
+
self.samples = []
|
| 96 |
+
with open(data_file, "r", encoding="utf-8") as f:
|
| 97 |
+
for line in f:
|
| 98 |
+
line = line.strip()
|
| 99 |
+
if not line:
|
| 100 |
+
continue
|
| 101 |
+
item = json.loads(line)
|
| 102 |
+
tokens, mask = self._encode_messages(item["messages"])
|
| 103 |
+
# 过滤掉没有任何assistant内容的样本
|
| 104 |
+
if len(tokens) > 0 and sum(mask) > 0:
|
| 105 |
+
self.samples.append((tokens, mask))
|
| 106 |
+
|
| 107 |
+
print(f"Loaded {len(self.samples)} valid SFT samples.")
|
| 108 |
+
|
| 109 |
+
def _encode_messages(self, messages):
|
| 110 |
+
token_ids = []
|
| 111 |
+
loss_mask = []
|
| 112 |
+
|
| 113 |
+
for msg in messages:
|
| 114 |
+
role = msg["role"]
|
| 115 |
+
content = msg["content"]
|
| 116 |
+
|
| 117 |
+
prefix = self.tokenizer.encode_all([
|
| 118 |
+
SpecialToken("<|im_start|>"),
|
| 119 |
+
f"{role}\n",
|
| 120 |
+
])
|
| 121 |
+
content_ids = self.tokenizer.encode(content)
|
| 122 |
+
suffix = self.tokenizer.encode_all([
|
| 123 |
+
SpecialToken("<|im_end|>"),
|
| 124 |
+
"\n",
|
| 125 |
+
])
|
| 126 |
+
|
| 127 |
+
msg_tokens = prefix + content_ids + suffix
|
| 128 |
+
msg_mask = [1 if role == "assistant" else 0] * len(msg_tokens)
|
| 129 |
+
|
| 130 |
+
token_ids.extend(msg_tokens)
|
| 131 |
+
loss_mask.extend(msg_mask)
|
| 132 |
+
|
| 133 |
+
# 添加eos
|
| 134 |
+
token_ids.append(self.eos_id)
|
| 135 |
+
loss_mask.append(1)
|
| 136 |
+
|
| 137 |
+
return token_ids, loss_mask
|
| 138 |
+
|
| 139 |
+
def __iter__(self):
|
| 140 |
+
while True:
|
| 141 |
+
idx = random.randint(0, len(self.samples) - 1)
|
| 142 |
+
tokens, assistant_mask = self.samples[idx]
|
| 143 |
+
|
| 144 |
+
# 截断到 block_size+1(为x,y留出空间)
|
| 145 |
+
max_len = self.block_size + 1
|
| 146 |
+
if len(tokens) > max_len:
|
| 147 |
+
tokens = tokens[:max_len]
|
| 148 |
+
assistant_mask = assistant_mask[:max_len]
|
| 149 |
+
|
| 150 |
+
x = tokens[:-1]
|
| 151 |
+
y = tokens[1:]
|
| 152 |
+
mask = assistant_mask[:-1]
|
| 153 |
+
|
| 154 |
+
# pad到block_size
|
| 155 |
+
pad_len = self.block_size - len(x)
|
| 156 |
+
if pad_len > 0:
|
| 157 |
+
x = x + [self.eos_id] * pad_len
|
| 158 |
+
y = y + [self.eos_id] * pad_len
|
| 159 |
+
mask = mask + [0] * pad_len
|
| 160 |
+
|
| 161 |
+
# 80% / 20% 策略
|
| 162 |
+
if random.random() < self.mask_prob:
|
| 163 |
+
final_mask = mask
|
| 164 |
+
else:
|
| 165 |
+
final_mask = [1] * self.block_size
|
| 166 |
+
|
| 167 |
+
yield (
|
| 168 |
+
torch.tensor(x, dtype=torch.int64),
|
| 169 |
+
torch.tensor(y, dtype=torch.int64),
|
| 170 |
+
torch.tensor(final_mask, dtype=torch.float32),
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
# ============== 4. 文本生成测试 ==============
|
| 175 |
+
@torch.no_grad()
|
| 176 |
+
def gen_text(model, tokenizer, text, device="cuda:0", max_new_tokens=200):
|
| 177 |
+
model.eval()
|
| 178 |
+
ids = torch.tensor(tokenizer.encode_all([
|
| 179 |
+
SpecialToken("<|im_start|>"),
|
| 180 |
+
"user\n",
|
| 181 |
+
text,
|
| 182 |
+
SpecialToken("<|im_end|>"),
|
| 183 |
+
"\n",
|
| 184 |
+
SpecialToken("<|im_start|>"),
|
| 185 |
+
"assistant\n",
|
| 186 |
+
]), dtype=torch.int64).to(device).view(1, -1)
|
| 187 |
+
|
| 188 |
+
output_ids = model.generate(ids, max_new_tokens=max_new_tokens)[0, :]
|
| 189 |
+
decoded = tokenizer.decode(output_ids.tolist())
|
| 190 |
+
model.train()
|
| 191 |
+
return decoded
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
# ============== 5. 训练 ==============
|
| 195 |
+
def train():
|
| 196 |
+
device = "cuda:0"
|
| 197 |
+
block_size = 2048
|
| 198 |
+
new_vocab_size = 50306
|
| 199 |
+
batch_size = 4
|
| 200 |
+
gradient_accumulation_steps = 4
|
| 201 |
+
learning_rate = 1e-5
|
| 202 |
+
max_iters = 2000
|
| 203 |
+
save_interval = 200
|
| 204 |
+
eval_interval = 50
|
| 205 |
+
|
| 206 |
+
# 准备tokenizer
|
| 207 |
+
if not os.path.exists("tokenizer_sft.model"):
|
| 208 |
+
tokenizer = prepare_tokenizer()
|
| 209 |
+
else:
|
| 210 |
+
tokenizer = load_tokenizer("tokenizer_sft.model")
|
| 211 |
+
print(f"Loaded tokenizer_sft.model with vocab_size={tokenizer.vocab_size}")
|
| 212 |
+
|
| 213 |
+
# 模型配置
|
| 214 |
+
config = TransformerConfig(
|
| 215 |
+
vocab_size=new_vocab_size,
|
| 216 |
+
block_size=block_size,
|
| 217 |
+
n_embed=768,
|
| 218 |
+
n_heads=12,
|
| 219 |
+
n_layers=12,
|
| 220 |
+
dropout=0.0,
|
| 221 |
+
bias=True,
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
# 扩展并加载模型
|
| 225 |
+
print("Expanding model vocab and loading checkpoint 150000.pt...")
|
| 226 |
+
model = expand_model_for_new_tokens("checkpoints/new/150000.pt", new_vocab_size, config)
|
| 227 |
+
model = model.to(device)
|
| 228 |
+
total_params = sum(p.numel() for p in model.parameters())
|
| 229 |
+
print(f"Model loaded. Total parameters: {total_params / 1e6:.2f}M")
|
| 230 |
+
|
| 231 |
+
# 数据集
|
| 232 |
+
dataset = SFTDataset(
|
| 233 |
+
"data/novels_sft_dataset.jsonl",
|
| 234 |
+
tokenizer,
|
| 235 |
+
block_size=block_size,
|
| 236 |
+
mask_prob=0.8,
|
| 237 |
+
)
|
| 238 |
+
loader = DataLoader(dataset, batch_size=batch_size)
|
| 239 |
+
data_iter = iter(loader)
|
| 240 |
+
|
| 241 |
+
# 优化器
|
| 242 |
+
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
|
| 243 |
+
|
| 244 |
+
# AMP混合精度
|
| 245 |
+
scaler = GradScaler("cuda")
|
| 246 |
+
autocast_ctx = lambda: autocast("cuda", dtype=torch.float16)
|
| 247 |
+
|
| 248 |
+
os.makedirs("checkpoints/sft", exist_ok=True)
|
| 249 |
+
|
| 250 |
+
model.train()
|
| 251 |
+
pbar = tqdm(total=max_iters, desc="SFT Training")
|
| 252 |
+
all_loss = 0.0
|
| 253 |
+
|
| 254 |
+
for iter_num in range(max_iters + 1):
|
| 255 |
+
optimizer.zero_grad(set_to_none=True)
|
| 256 |
+
accum_loss = 0.0
|
| 257 |
+
|
| 258 |
+
for _ in range(gradient_accumulation_steps):
|
| 259 |
+
x, y, mask = next(data_iter)
|
| 260 |
+
x = x.to(device)
|
| 261 |
+
y = y.to(device)
|
| 262 |
+
mask = mask.to(device)
|
| 263 |
+
|
| 264 |
+
with autocast_ctx():
|
| 265 |
+
logits, _ = model(x, device=device)
|
| 266 |
+
logits = logits.view(-1, config.vocab_size)
|
| 267 |
+
y_flat = y.view(-1)
|
| 268 |
+
mask_flat = mask.view(-1)
|
| 269 |
+
|
| 270 |
+
loss = F.cross_entropy(logits, y_flat, reduction="none")
|
| 271 |
+
loss = (loss * mask_flat).sum() / (mask_flat.sum() + 1e-8)
|
| 272 |
+
loss = loss / gradient_accumulation_steps
|
| 273 |
+
|
| 274 |
+
scaler.scale(loss).backward()
|
| 275 |
+
accum_loss += loss.item()
|
| 276 |
+
|
| 277 |
+
# 梯度裁剪
|
| 278 |
+
scaler.unscale_(optimizer)
|
| 279 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
|
| 280 |
+
scaler.step(optimizer)
|
| 281 |
+
scaler.update()
|
| 282 |
+
|
| 283 |
+
all_loss += accum_loss
|
| 284 |
+
pbar.update(1)
|
| 285 |
+
pbar.set_postfix(loss=f"{accum_loss:.4f}")
|
| 286 |
+
|
| 287 |
+
if iter_num % eval_interval == 0:
|
| 288 |
+
print(f"\n[Step {iter_num}] Loss: {accum_loss:.4f}")
|
| 289 |
+
try:
|
| 290 |
+
decoded = gen_text(model, tokenizer, "写一个恋爱喜剧轻小说,主角是能听到物品心声的高中生。", device=device)
|
| 291 |
+
# 找到assistant回复部分打印
|
| 292 |
+
text_out = ""
|
| 293 |
+
for tok in decoded:
|
| 294 |
+
if isinstance(tok, str):
|
| 295 |
+
text_out += tok
|
| 296 |
+
print(f"Sample output: {text_out[:200]}...")
|
| 297 |
+
except Exception as e:
|
| 298 |
+
print(f"Generation error: {e}")
|
| 299 |
+
|
| 300 |
+
if iter_num > 0 and (iter_num % save_interval == 0 or iter_num == max_iters):
|
| 301 |
+
ckpt_path = f"checkpoints/sft/sft_{iter_num}.pt"
|
| 302 |
+
torch.save(model.state_dict(), ckpt_path)
|
| 303 |
+
print(f"\nSaved checkpoint: {ckpt_path}")
|
| 304 |
+
|
| 305 |
+
pbar.close()
|
| 306 |
+
final_path = "checkpoints/sft/sft_final.pt"
|
| 307 |
+
torch.save(model.state_dict(), final_path)
|
| 308 |
+
print(f"Training complete. Final model saved to {final_path}")
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
if __name__ == "__main__":
|
| 312 |
+
train()
|
train_tokenizer.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import jsonlines
|
| 2 |
+
from torch.utils.data import Dataset, DataLoader
|
| 3 |
+
from tokenizer import SpecialToken, Tokenizer, load_tokenizer
|
| 4 |
+
|
| 5 |
+
class MyDataset(Dataset):
|
| 6 |
+
def __init__(self, file_path):
|
| 7 |
+
data = []
|
| 8 |
+
with jsonlines.open(file_path, 'r') as f:
|
| 9 |
+
for obj in f:
|
| 10 |
+
data.append(obj['text'])
|
| 11 |
+
self.data = data
|
| 12 |
+
|
| 13 |
+
def __len__(self):
|
| 14 |
+
return len(self.data)
|
| 15 |
+
|
| 16 |
+
def __getitem__(self, idx):
|
| 17 |
+
return self.data[idx]
|
| 18 |
+
|
| 19 |
+
# 创建数据集
|
| 20 |
+
file_path = 'data/zhwiki.jsonl'
|
| 21 |
+
dataset = MyDataset(file_path)
|
| 22 |
+
|
| 23 |
+
# 创建DataLoader
|
| 24 |
+
batch_size = 1024
|
| 25 |
+
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
|
| 26 |
+
|
| 27 |
+
# 创建Tokenizer
|
| 28 |
+
# GPT2pattern = r"""'(?:[sdmt]|ll|ve|re)| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"""
|
| 29 |
+
# tokenizer = Tokenizer(GPT2pattern)
|
| 30 |
+
|
| 31 |
+
# 添加结束符号
|
| 32 |
+
# tokenizer.add_special_tokens({SpecialToken("<|endoftext|>"):50303})
|
| 33 |
+
|
| 34 |
+
# 加载tokenizer
|
| 35 |
+
tokenizer = load_tokenizer("tokenizer.model")
|
| 36 |
+
|
| 37 |
+
# 训练
|
| 38 |
+
tokenizer.train(50303, dataloader, merge_increase_per_loop=20)
|
| 39 |
+
|
| 40 |
+
# 保存
|
| 41 |
+
tokenizer.save()
|