hugfaceguy0001 commited on
Commit
e10f35b
·
verified ·
1 Parent(s): eafce0f

upload model and train/infer codes

Browse files
Files changed (10) hide show
  1. dataset_loader.py +88 -0
  2. generate.py +98 -0
  3. model.py +245 -0
  4. model_sft.pt +3 -0
  5. pretrain.py +173 -0
  6. tokenizer.model +3 -0
  7. tokenizer.py +220 -0
  8. tokenizer.vocab +0 -0
  9. train_sft.py +312 -0
  10. 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()