thongbuind commited on
Commit
be8ee5f
·
verified ·
1 Parent(s): d59b334

Keep a single inference source tree with original filenames

Browse files
src/model/DecoderBlock.py → DecoderBlock.py RENAMED
@@ -1,6 +1,6 @@
1
  import torch.nn as nn
2
- from src.model.SwiGLU import SwiGLU
3
- from src.model.GroupedQueryAttention import GroupedQueryAttention
4
 
5
  class DecoderBlock(nn.Module):
6
  def __init__(self, d_model: int, num_heads: int, num_kv_heads: int, ff_dim: int, dropout: float):
 
1
  import torch.nn as nn
2
+ from .SwiGLU import SwiGLU
3
+ from .GroupedQueryAttention import GroupedQueryAttention
4
 
5
  class DecoderBlock(nn.Module):
6
  def __init__(self, d_model: int, num_heads: int, num_kv_heads: int, ff_dim: int, dropout: float):
src/model/GroupedQueryAttention.py → GroupedQueryAttention.py RENAMED
@@ -2,7 +2,7 @@ import torch
2
  import torch.nn as nn
3
  import torch.nn.functional as F
4
  from torch.nn.attention import sdpa_kernel, SDPBackend
5
- from src.model.RotaryPositionalEmbedding import RotaryPositionalEmbedding
6
 
7
  _SDPA_BACKENDS = [
8
  SDPBackend.CUDNN_ATTENTION,
 
2
  import torch.nn as nn
3
  import torch.nn.functional as F
4
  from torch.nn.attention import sdpa_kernel, SDPBackend
5
+ from .RotaryPositionalEmbedding import RotaryPositionalEmbedding
6
 
7
  _SDPA_BACKENDS = [
8
  SDPBackend.CUDNN_ATTENTION,
README.md CHANGED
@@ -68,7 +68,7 @@ Mặc định, mô hình sinh tối đa 200 token bằng beam search với 5 bea
68
 
69
  ### Source và adapter Hugging Face
70
 
71
- Thư mục `src/` chứa nguyên vẹn các file Python từ project huấn luyện. `generation_sai.py` là bản sao byte-for-byte của `src/inference/generate.py`. Các module `runtime_*.py` lấy trực tiếp từ source model; thay đổi duy nhất ở các file cần thiết là chuyển import `src.*` thành relative import để cơ chế `trust_remote_code` có thể tải chúng.
72
 
73
  `modeling_sai.py` chỉ là adapter mỏng: đọc `SAIConfig`, giữ nguyên tên tensor trong state dict, chuyển lời gọi sang `TransformerModel` gốc và materialize RoPE/causal-mask buffer sau cơ chế meta-device của `from_pretrained()`. Adapter không cài đặt lại attention, decoder, KV cache hay beam search.
74
 
@@ -115,7 +115,7 @@ answer = model.generate(
115
  print(answer)
116
  ```
117
 
118
- `AutoModelForCausalLM` và `AutoTokenizer` chỉ được dùng làm lớp tương thích để tải config, trọng số và SentencePiece tokenizer. Thuật toán inference không gọi `GenerationMixin.generate()`, `DynamicCache`, `GenerationConfig`, `apply_chat_template()` hay beam search mặc định của Transformers. Phương thức `SAIForCausalLM.generate()` gọi trực tiếp beam search trong `generation_sai.py`.
119
 
120
  Model cần nằm trọn trên một device vì KV buffer của thuật toán gốc được cấp phát trên device chứa `lm_head`. Không dùng `device_map="auto"` để chia model qua nhiều device.
121
 
 
68
 
69
  ### Source và adapter Hugging Face
70
 
71
+ Repo chỉ giữ một bộ code inference với tên file nguyên bản: `TransformerModel.py`, `DecoderBlock.py`, `GroupedQueryAttention.py`, `RotaryPositionalEmbedding.py`, `SwiGLU.py` và `generate.py`. Nội dung thuật toán lấy trực tiếp từ project gốc; thay đổi duy nhất là chuyển các import `src.*` thành relative import để cơ chế `trust_remote_code` tải được chúng.
72
 
73
  `modeling_sai.py` chỉ là adapter mỏng: đọc `SAIConfig`, giữ nguyên tên tensor trong state dict, chuyển lời gọi sang `TransformerModel` gốc và materialize RoPE/causal-mask buffer sau cơ chế meta-device của `from_pretrained()`. Adapter không cài đặt lại attention, decoder, KV cache hay beam search.
74
 
 
115
  print(answer)
116
  ```
117
 
118
+ `AutoModelForCausalLM` và `AutoTokenizer` chỉ được dùng làm lớp tương thích để tải config, trọng số và SentencePiece tokenizer. Thuật toán inference không gọi `GenerationMixin.generate()`, `DynamicCache`, `GenerationConfig`, `apply_chat_template()` hay beam search mặc định của Transformers. Phương thức `SAIForCausalLM.generate()` gọi trực tiếp beam search trong `generate.py`.
119
 
120
  Model cần nằm trọn trên một device vì KV buffer của thuật toán gốc được cấp phát trên device chứa `lm_head`. Không dùng `device_map="auto"` để chia model qua nhiều device.
121
 
src/model/RotaryPositionalEmbedding.py → RotaryPositionalEmbedding.py RENAMED
File without changes
src/model/SwiGLU.py → SwiGLU.py RENAMED
File without changes
src/model/TransformerModel.py → TransformerModel.py RENAMED
@@ -1,8 +1,8 @@
1
  import torch
2
  import torch.nn as nn
3
- from src.inference.generate import generate
4
- from src.model.DecoderBlock import DecoderBlock
5
- from src.model.RotaryPositionalEmbedding import RotaryPositionalEmbedding
6
 
7
  class TransformerModel(nn.Module):
8
  def __init__(self, vocab_size: int, d_model: int, num_heads: int, num_kv_heads: int, num_layers: int, ff_dim: int, max_seq_len: int, dropout: float, pad_token_id: int = 0):
 
1
  import torch
2
  import torch.nn as nn
3
+ from .generate import generate
4
+ from .DecoderBlock import DecoderBlock
5
+ from .RotaryPositionalEmbedding import RotaryPositionalEmbedding
6
 
7
  class TransformerModel(nn.Module):
8
  def __init__(self, vocab_size: int, d_model: int, num_heads: int, num_kv_heads: int, num_layers: int, ff_dim: int, max_seq_len: int, dropout: float, pad_token_id: int = 0):
src/inference/generate.py → generate.py RENAMED
File without changes
generation_sai.py DELETED
@@ -1,196 +0,0 @@
1
- import torch
2
- from pathlib import Path
3
-
4
- def build_input(user_input, tokenizer):
5
- BOS = tokenizer.piece_to_id("[BOS]")
6
- EOS = tokenizer.piece_to_id("[EOS]")
7
- IM_START = tokenizer.piece_to_id("<|im_start|>")
8
- IM_END = tokenizer.piece_to_id("<|im_end|>")
9
-
10
- user_ids = (
11
- [IM_START]
12
- + tokenizer.encode("user\n" + user_input.strip().lower(), out_type=int)
13
- + [IM_END]
14
- )
15
- assistant_prefix = [IM_START] + tokenizer.encode("model\n", out_type=int)
16
- input_ids = [BOS] + user_ids + assistant_prefix
17
-
18
- return input_ids, len(input_ids), EOS, IM_END
19
-
20
- def decode_output(best, start, EOS, IM_END, tokenizer):
21
- output_tokens = best["seq"][start:]
22
- while output_tokens and output_tokens[-1] in (EOS, IM_END):
23
- output_tokens.pop()
24
- return tokenizer.decode(output_tokens)
25
-
26
- def score(seq, log_prob, start):
27
- out_len = max(len(seq) - start, 1)
28
- return log_prob / (out_len ** 1.0)
29
-
30
- def apply_penalty(logits, seq, penalty):
31
- if penalty == 1.0:
32
- return logits
33
- logits = logits.clone()
34
- for tid in set(seq):
35
- if logits[tid] < 0:
36
- logits[tid] *= penalty
37
- else:
38
- logits[tid] /= penalty
39
- return logits
40
-
41
- def get_banned_tokens(seq, n):
42
- banned = set()
43
- if n > 0 and len(seq) >= n:
44
- prefix = tuple(seq[-(n - 1):])
45
- for i in range(len(seq) - n + 1):
46
- if tuple(seq[i:i + n - 1]) == prefix:
47
- banned.add(seq[i + n - 1])
48
- return banned
49
-
50
- def forward_init(model, input_ids, max_beam_size, max_new_tokens, device):
51
- prompt_len = len(input_ids)
52
- max_total = prompt_len + max_new_tokens
53
-
54
- kv_buffers = model.init_cache(max_beam_size, max_total, device)
55
-
56
- prompt_tensor = torch.tensor([input_ids], dtype=torch.long, device=device)
57
-
58
- with torch.inference_mode():
59
- first_logits, present_cache = model.prefill(prompt_tensor, kv_cache=None)
60
- for i, (k, v) in enumerate(present_cache):
61
- kv_buffers[i][0][:, :, :prompt_len, :].copy_(k.expand(max_beam_size, -1, -1, -1))
62
- kv_buffers[i][1][:, :, :prompt_len, :].copy_(v.expand(max_beam_size, -1, -1, -1))
63
-
64
- return first_logits[0], kv_buffers, prompt_len
65
-
66
- def forward_step(model, last_tokens, kv_buffers, cache_len):
67
- with torch.inference_mode():
68
- return model.decode_step(last_tokens, kv_buffers, cache_len)
69
-
70
- def beam_core(model, input_ids, start, score_fn, penalty_fn, max_new_tokens, beam_size, no_repeat_ngram, penalty, device, EOS, IM_END, max_seq_len, early_stop=True, patience=10):
71
-
72
- first_logits, kv_buffers, cache_len = forward_init(
73
- model, input_ids, beam_size, max_new_tokens, device
74
- )
75
-
76
- first_logits = penalty_fn(first_logits, input_ids, penalty)
77
- first_lp = torch.clamp(torch.log_softmax(first_logits, -1), -1e9, 0.0)
78
- topk_lp, topk_tok = torch.topk(first_lp, beam_size)
79
-
80
- seqs = [input_ids + [int(t)] for t in topk_tok.tolist()]
81
- log_probs = topk_lp.tolist()
82
- dones = [int(t) in (EOS, IM_END) for t in topk_tok.tolist()]
83
- unique_sets = [set(input_ids) | {int(t)} for t in topk_tok.tolist()]
84
- completed = []
85
- K = beam_size * 3
86
- patience_counter = 0
87
-
88
- for _ in range(max_new_tokens - 1):
89
- if all(dones):
90
- break
91
-
92
- last = torch.tensor([[seqs[i][-1]] for i in range(len(seqs))], dtype=torch.long, device=device)
93
- logits_batch = forward_step(model, last, kv_buffers, cache_len)
94
- cache_len += 1
95
-
96
- n_beams = len(seqs)
97
- if penalty != 1.0:
98
- pen_mask = torch.ones(n_beams, logits_batch.shape[-1], device=device)
99
- for i, uid in enumerate(unique_sets):
100
- if dones[i]:
101
- continue
102
- idx = torch.tensor(list(uid), dtype=torch.long, device=device)
103
- pen_mask[i, idx] = penalty
104
- neg = logits_batch < 0
105
- logits_batch = torch.where(neg, logits_batch * pen_mask, logits_batch / pen_mask)
106
-
107
- lp_batch = torch.clamp(torch.log_softmax(logits_batch, -1), -1e9, 0.0)
108
- topk_lp_b, topk_tok_b = torch.topk(lp_batch, K, dim=-1)
109
- topk_lp_b = topk_lp_b.tolist()
110
- topk_tok_b = topk_tok_b.tolist()
111
-
112
- candidates = []
113
- for beam_i in range(n_beams):
114
- if dones[beam_i]:
115
- continue
116
- banned = get_banned_tokens(seqs[beam_i], no_repeat_ngram)
117
- count = 0
118
- for l, t in zip(topk_lp_b[beam_i], topk_tok_b[beam_i]):
119
- if count >= beam_size:
120
- break
121
- if t in banned:
122
- continue
123
- new_seq = seqs[beam_i] + [t]
124
- done = t in (EOS, IM_END) or len(new_seq) >= max_seq_len
125
- candidates.append({
126
- "beam_src": beam_i,
127
- "seq": new_seq,
128
- "log_prob": log_probs[beam_i] + l,
129
- "done": done
130
- })
131
- count += 1
132
-
133
- if not candidates:
134
- break
135
-
136
- candidates.sort(key=lambda x: score_fn(x["seq"], x["log_prob"], start), reverse=True)
137
-
138
- kept, src = [], []
139
- for c in candidates:
140
- if c["done"]:
141
- completed.append(c)
142
- elif len(kept) < beam_size:
143
- kept.append(c)
144
- src.append(c["beam_src"])
145
-
146
- if not kept:
147
- break
148
-
149
- if len(completed) >= beam_size:
150
- best_done = max(completed, key=lambda x: score_fn(x["seq"], x["log_prob"], start))
151
- best_alive = max(kept, key=lambda x: score_fn(x["seq"], x["log_prob"], start))
152
- if score_fn(best_done["seq"], best_done["log_prob"], start) >= \
153
- score_fn(best_alive["seq"], best_alive["log_prob"], start):
154
- if early_stop:
155
- break
156
- else:
157
- patience_counter += 1
158
- if patience_counter >= patience:
159
- break
160
- else:
161
- patience_counter = 0
162
-
163
- if src != list(range(len(src))):
164
- src_t = torch.tensor(src, dtype=torch.long, device=device)
165
- for k_buf, v_buf in kv_buffers:
166
- k_tmp = k_buf[src_t].clone()
167
- v_tmp = v_buf[src_t].clone()
168
- k_buf[:len(src)].copy_(k_tmp)
169
- v_buf[:len(src)].copy_(v_tmp)
170
-
171
- unique_sets = [unique_sets[src[i]] | {c["seq"][-1]} for i, c in enumerate(kept)]
172
- seqs = [c["seq"] for c in kept]
173
- log_probs = [c["log_prob"] for c in kept]
174
- dones = [False] * len(kept)
175
-
176
- pool = completed if completed else [{"seq": seqs[i], "log_prob": log_probs[i]} for i in range(len(seqs))]
177
- return max(pool, key=lambda x: score_fn(x["seq"], x["log_prob"], start))
178
-
179
- # ================= PUBLIC API =================
180
- def generate(model, user_input, tokenizer,
181
- max_new_tokens=200, beam_size=5,
182
- no_repeat_ngram=3, penalty=1.2,
183
- early_stop=False, patience=30):
184
-
185
- device = model.lm_head.weight.device
186
- ids, start, EOS, IM_END = build_input(user_input, tokenizer)
187
-
188
- best = beam_core(
189
- model, ids, start,
190
- score, apply_penalty,
191
- max_new_tokens, beam_size, no_repeat_ngram, penalty,
192
- device, EOS, IM_END, model.max_seq_len,
193
- early_stop=early_stop, patience=patience
194
- )
195
-
196
- return decode_output(best, start, EOS, IM_END, tokenizer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modeling_sai.py CHANGED
@@ -3,12 +3,12 @@ import torch.nn as nn
3
  from transformers import PreTrainedModel
4
 
5
  from .configuration_sai import SAIConfig
6
- from .generation_sai import generate as _source_generate
7
- from .runtime_decoder import DecoderBlock as _SourceDecoderBlock
8
- from .runtime_gqa import GroupedQueryAttention as _SourceGroupedQueryAttention
9
- from .runtime_rope import RotaryPositionalEmbedding as _SourceRoPE
10
- from .runtime_swiglu import SwiGLU as _SourceSwiGLU
11
- from .runtime_transformer import TransformerModel
12
 
13
 
14
  class SAIForCausalLM(PreTrainedModel):
 
3
  from transformers import PreTrainedModel
4
 
5
  from .configuration_sai import SAIConfig
6
+ from .generate import generate as _source_generate
7
+ from .DecoderBlock import DecoderBlock as _SourceDecoderBlock
8
+ from .GroupedQueryAttention import GroupedQueryAttention as _SourceGroupedQueryAttention
9
+ from .RotaryPositionalEmbedding import RotaryPositionalEmbedding as _SourceRoPE
10
+ from .SwiGLU import SwiGLU as _SourceSwiGLU
11
+ from .TransformerModel import TransformerModel
12
 
13
 
14
  class SAIForCausalLM(PreTrainedModel):
runtime_decoder.py DELETED
@@ -1,28 +0,0 @@
1
- import torch.nn as nn
2
- from .runtime_swiglu import SwiGLU
3
- from .runtime_gqa import GroupedQueryAttention
4
-
5
- class DecoderBlock(nn.Module):
6
- def __init__(self, d_model: int, num_heads: int, num_kv_heads: int, ff_dim: int, dropout: float):
7
- super().__init__()
8
- self.gqa = GroupedQueryAttention(d_model, num_heads, num_kv_heads, dropout)
9
- self.ffn = SwiGLU(d_model, ff_dim)
10
- self.norm1 = nn.RMSNorm(d_model, eps=1e-6)
11
- self.norm2 = nn.RMSNorm(d_model, eps=1e-6)
12
- self.drop = nn.Dropout(dropout)
13
-
14
- def forward(self, x, cos, sin, attn_mask=None):
15
- x = x + self.drop(self.gqa(self.norm1(x), cos, sin, attn_mask))
16
- x = x + self.drop(self.ffn(self.norm2(x)))
17
- return x
18
-
19
- def prefill(self, x, cos, sin):
20
- attn, kv = self.gqa.prefill(self.norm1(x), cos, sin)
21
- x = x + self.drop(attn)
22
- x = x + self.drop(self.ffn(self.norm2(x)))
23
- return x, list(kv)
24
-
25
- def forward_with_cache(self, x, kv, cache_len: int, cos, sin):
26
- x = x + self.drop(self.gqa.forward_with_cache(self.norm1(x), kv, cache_len, cos, sin))
27
- x = x + self.drop(self.ffn(self.norm2(x)))
28
- return x
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
runtime_gqa.py DELETED
@@ -1,96 +0,0 @@
1
- import torch
2
- import torch.nn as nn
3
- import torch.nn.functional as F
4
- from torch.nn.attention import sdpa_kernel, SDPBackend
5
- from .runtime_rope import RotaryPositionalEmbedding
6
-
7
- _SDPA_BACKENDS = [
8
- SDPBackend.CUDNN_ATTENTION,
9
- SDPBackend.FLASH_ATTENTION,
10
- SDPBackend.EFFICIENT_ATTENTION,
11
- SDPBackend.MATH,
12
- ]
13
-
14
- class GroupedQueryAttention(nn.Module):
15
- def __init__(self, d_model: int, num_heads: int, num_kv_heads: int, dropout: float):
16
- super().__init__()
17
- assert d_model % num_heads == 0
18
- assert num_heads % num_kv_heads == 0, \
19
- "num_heads phải chia hết cho num_kv_heads"
20
-
21
- self.num_heads = num_heads
22
- self.num_kv_heads = num_kv_heads
23
- self.num_groups = num_heads // num_kv_heads
24
- self.d_k = d_model // num_heads
25
- self.dropout_rate = dropout
26
- self.q_dim = num_heads * self.d_k
27
- self.kv_dim = num_kv_heads * self.d_k
28
- self.wqkv = nn.Linear(d_model, self.q_dim + 2 * self.kv_dim, bias=False)
29
- self.wo = nn.Linear(self.q_dim, d_model, bias=False)
30
-
31
- def _project_qkv(self, x: torch.Tensor):
32
- B, T, _ = x.shape
33
- qkv = self.wqkv(x)
34
- q, k, v = qkv.split([self.q_dim, self.kv_dim, self.kv_dim], dim=-1)
35
- q = q.view(B, T, self.num_heads, self.d_k)
36
- k = k.view(B, T, self.num_kv_heads, self.d_k)
37
- v = v.view(B, T, self.num_kv_heads, self.d_k)
38
- return q, k, v
39
-
40
- def _merge(self, out: torch.Tensor) -> torch.Tensor:
41
- B, _, T, _ = out.shape
42
- return self.wo(out.transpose(1, 2).contiguous().view(B, T, self.num_heads * self.d_k))
43
-
44
- def forward(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, attn_mask=None) -> torch.Tensor:
45
- q, k, v = self._project_qkv(x)
46
-
47
- q = RotaryPositionalEmbedding.apply_rope(q, cos, sin)
48
- k = RotaryPositionalEmbedding.apply_rope(k, cos, sin)
49
-
50
- q = q.transpose(1, 2)
51
- k = k.transpose(1, 2)
52
- v = v.transpose(1, 2)
53
-
54
- dropout_p = self.dropout_rate if self.training else 0.0
55
-
56
- with sdpa_kernel(_SDPA_BACKENDS):
57
- out = F.scaled_dot_product_attention(
58
- q, k, v, attn_mask=attn_mask, is_causal=(attn_mask is None), dropout_p=dropout_p,
59
- enable_gqa=True,
60
- )
61
- return self._merge(out)
62
-
63
- def prefill(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor):
64
- q, k, v = self._project_qkv(x)
65
-
66
- q = RotaryPositionalEmbedding.apply_rope(q, cos, sin)
67
- k = RotaryPositionalEmbedding.apply_rope(k, cos, sin)
68
-
69
- q = q.transpose(1, 2)
70
- k = k.transpose(1, 2)
71
- v = v.transpose(1, 2)
72
-
73
- with sdpa_kernel(_SDPA_BACKENDS):
74
- out = F.scaled_dot_product_attention(q, k, v, is_causal=True, enable_gqa=True)
75
- return self._merge(out), (k, v)
76
-
77
- def forward_with_cache(self, x: torch.Tensor, past_kv, cache_len: int, cos: torch.Tensor, sin: torch.Tensor):
78
- B, T, _ = x.shape
79
- q, k, v = self._project_qkv(x)
80
-
81
- q = RotaryPositionalEmbedding.apply_rope(q, cos, sin)
82
- k = RotaryPositionalEmbedding.apply_rope(k, cos, sin)
83
-
84
- q = q.transpose(1, 2)
85
- k = k.transpose(1, 2)
86
- v = v.transpose(1, 2)
87
-
88
- past_kv[0][:B, :, cache_len:cache_len + T, :] = k
89
- past_kv[1][:B, :, cache_len:cache_len + T, :] = v
90
-
91
- k_full = past_kv[0][:B, :, :cache_len + T, :]
92
- v_full = past_kv[1][:B, :, :cache_len + T, :]
93
-
94
- with sdpa_kernel(_SDPA_BACKENDS):
95
- out = F.scaled_dot_product_attention(q, k_full, v_full, is_causal=False, enable_gqa=True)
96
- return self._merge(out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
runtime_rope.py DELETED
@@ -1,33 +0,0 @@
1
- import torch
2
- import torch.nn as nn
3
-
4
- class RotaryPositionalEmbedding(nn.Module):
5
- def __init__(self, head_dim: int, max_seq_len: int, base: int = 10_000):
6
- super().__init__()
7
- assert head_dim % 2 == 0, "head_dim phải chẵn"
8
- inv_freq = 1.0 / (
9
- base ** (torch.arange(0, head_dim, 2, dtype=torch.float32) / head_dim)
10
- )
11
- self.register_buffer("inv_freq", inv_freq, persistent=False)
12
- self._build_cache(max_seq_len)
13
-
14
- def _build_cache(self, seq_len: int):
15
- pos = torch.arange(seq_len, dtype=torch.float32)
16
- freqs = torch.outer(pos, self.inv_freq) # (seq_len, head_dim/2)
17
- emb = torch.cat([freqs, freqs], dim=-1) # (seq_len, head_dim)
18
- self.register_buffer("cos_cached", emb.cos(), persistent=False)
19
- self.register_buffer("sin_cached", emb.sin(), persistent=False)
20
-
21
- def get_cos_sin(self, positions: torch.Tensor):
22
- cos = self.cos_cached[positions][None, :, None, :]
23
- sin = self.sin_cached[positions][None, :, None, :]
24
- return cos, sin
25
-
26
- @staticmethod
27
- def _rotate_half(x: torch.Tensor) -> torch.Tensor:
28
- x1, x2 = x.chunk(2, dim=-1)
29
- return torch.cat((-x2, x1), dim=-1)
30
-
31
- @staticmethod
32
- def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
33
- return x * cos + RotaryPositionalEmbedding._rotate_half(x) * sin
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
runtime_swiglu.py DELETED
@@ -1,14 +0,0 @@
1
- import torch
2
- import torch.nn as nn
3
- import torch.nn.functional as F
4
-
5
- class SwiGLU(nn.Module):
6
- def __init__(self, d_model: int, ff_dim: int):
7
- super().__init__()
8
- self.ff_dim = ff_dim
9
- self.gate_up = nn.Linear(d_model, 2 * ff_dim, bias=False)
10
- self.down = nn.Linear(ff_dim, d_model, bias=False)
11
-
12
- def forward(self, x: torch.Tensor) -> torch.Tensor:
13
- gate, up = self.gate_up(x).chunk(2, dim=-1)
14
- return self.down(up * F.silu(gate))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
runtime_transformer.py DELETED
@@ -1,114 +0,0 @@
1
- import torch
2
- import torch.nn as nn
3
- from .generation_sai import generate
4
- from .runtime_decoder import DecoderBlock
5
- from .runtime_rope import RotaryPositionalEmbedding
6
-
7
- class TransformerModel(nn.Module):
8
- def __init__(self, vocab_size: int, d_model: int, num_heads: int, num_kv_heads: int, num_layers: int, ff_dim: int, max_seq_len: int, dropout: float, pad_token_id: int = 0):
9
- super().__init__()
10
- self.d_model = d_model
11
- self.num_heads = num_heads
12
- self.num_kv_heads = num_kv_heads
13
- self.num_layers = num_layers
14
- self.pad_token_id = pad_token_id
15
- self.max_seq_len = max_seq_len
16
- self.embed = nn.Embedding(vocab_size, d_model)
17
- d_k = d_model // num_heads
18
- self.rope = RotaryPositionalEmbedding(d_k, max_seq_len)
19
- self.blocks = nn.ModuleList([
20
- DecoderBlock(d_model, num_heads, num_kv_heads, ff_dim, dropout)
21
- for _ in range(num_layers)
22
- ])
23
- self.norm = nn.RMSNorm(d_model, eps=1e-6)
24
- self.lm_head = nn.Linear(d_model, vocab_size, bias=False)
25
- self.lm_head.weight = self.embed.weight
26
-
27
- causal = torch.triu(
28
- torch.full((max_seq_len, max_seq_len), float('-inf')), diagonal=1
29
- )
30
- self.register_buffer("causal_mask", causal, persistent=False)
31
-
32
- self._init_weights()
33
-
34
- def _init_weights(self):
35
- std = self.d_model ** -0.5
36
- nn.init.normal_(self.embed.weight, mean=0.0, std=std)
37
- for module in self.modules():
38
- if isinstance(module, nn.Linear):
39
- nn.init.normal_(module.weight, mean=0.0, std=std)
40
- if module.bias is not None:
41
- nn.init.zeros_(module.bias)
42
-
43
- def _build_attn_mask(self, T: int, pad_mask, device):
44
- attn_mask = self.causal_mask[:T, :T][None, None, :, :]
45
- if pad_mask is not None:
46
- pad = torch.zeros(pad_mask.shape[0], 1, 1, T, device=device)
47
- pad.masked_fill_(~pad_mask[:, None, None, :], float('-inf'))
48
- attn_mask = attn_mask + pad
49
- return attn_mask
50
-
51
- def forward_features(self, input_ids: torch.Tensor, attention_mask=None, has_padding: bool = True) -> torch.Tensor:
52
- """Giống forward() nhưng DỪNG TRƯỚC lm_head — dùng cho training loss chunked
53
- (tránh vật lý hóa logits full (B*T, vocab_size))."""
54
- pad_mask = attention_mask.bool() if attention_mask is not None \
55
- else (input_ids != self.pad_token_id)
56
- B, T = input_ids.shape
57
- x = self.embed(input_ids)
58
- pos = torch.arange(T, device=input_ids.device)
59
- cos, sin = self.rope.get_cos_sin(pos)
60
-
61
- # Chỉ build mask (và cộng pad-bias) khi batch này thực sự có token PAD.
62
- # Nếu không có PAD, causal mask thuần == causal+pad mask về mặt toán học
63
- # (pad-bias toàn số 0) -> bỏ qua an toàn, không đổi kết quả.
64
- # has_padding được tính sẵn trên CPU trong collate_fn nên không tốn sync GPU ở đây.
65
- attn_mask = self._build_attn_mask(T, pad_mask, x.device) if has_padding else None
66
-
67
- for block in self.blocks:
68
- x = block(x, cos, sin, attn_mask)
69
- return self.norm(x) # (B, T, d_model) — CHƯA qua lm_head
70
-
71
- def forward(self, input_ids: torch.Tensor, attention_mask=None, has_padding: bool = True) -> torch.Tensor:
72
- x = self.forward_features(input_ids, attention_mask, has_padding)
73
- return self.lm_head(x)
74
-
75
- def init_cache(self, batch_size: int, max_gen_len: int, device: torch.device):
76
- d_k = self.d_model // self.num_heads
77
- return [
78
- [
79
- torch.empty(batch_size, self.num_kv_heads, max_gen_len, d_k, device=device),
80
- torch.empty(batch_size, self.num_kv_heads, max_gen_len, d_k, device=device),
81
- ]
82
- for _ in self.blocks
83
- ]
84
-
85
- def prefill(self, input_ids: torch.Tensor, kv_cache=None):
86
- B, T = input_ids.shape
87
- x = self.embed(input_ids)
88
- pos = torch.arange(T, device=input_ids.device)
89
- cos, sin = self.rope.get_cos_sin(pos)
90
- new_cache = []
91
-
92
- for i, block in enumerate(self.blocks):
93
- x, kv = block.prefill(x, cos, sin)
94
- if kv_cache is not None:
95
- kv_cache[i][0][:B, :, :T, :] = kv[0]
96
- kv_cache[i][1][:B, :, :T, :] = kv[1]
97
- new_cache.append(kv_cache[i])
98
- else:
99
- new_cache.append(kv)
100
- logits = self.lm_head(self.norm(x))[:, -1, :]
101
- return logits, new_cache
102
-
103
- def decode_step(self, token_ids: torch.Tensor, kv_cache, cache_len: int):
104
- token_ids = token_ids.view(-1, 1)
105
- x = self.embed(token_ids)
106
- pos = torch.arange(cache_len, cache_len + 1, device=token_ids.device)
107
- cos, sin = self.rope.get_cos_sin(pos)
108
-
109
- for block, kv in zip(self.blocks, kv_cache):
110
- x = block.forward_with_cache(x, kv, cache_len, cos, sin)
111
- return self.lm_head(self.norm(x))[:, 0, :]
112
-
113
- def generate_response(self, user_input, tokenizer, **kwargs):
114
- return generate(self, user_input, tokenizer, **kwargs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/__init__.py DELETED
File without changes
src/data/Dataset.py DELETED
@@ -1,260 +0,0 @@
1
- import torch
2
- import numpy as np
3
- import gc
4
- import json
5
- from pathlib import Path
6
- from src.utils.utils import log_progress
7
-
8
- class Dataset(torch.utils.data.Dataset):
9
- def __init__(self, X, Y, lengths, indices, loss_masks=None):
10
- self.X = X
11
- self.Y = Y
12
- self.lengths = lengths
13
- self.indices = indices
14
- self.loss_masks = loss_masks
15
-
16
- def __len__(self):
17
- return len(self.indices)
18
-
19
- def __getitem__(self, idx):
20
- real_idx = self.indices[idx]
21
- return (
22
- self.X[real_idx],
23
- self.Y[real_idx],
24
- self.lengths[real_idx],
25
- self.loss_masks[real_idx] if self.loss_masks is not None else None
26
- )
27
-
28
- @classmethod
29
- def create_dataloader(cls, X, Y, lengths, batch_size, max_seq_len, num_workers, shuffle, loss_masks=None):
30
- log_progress(f"Đang tạo dataset từ {len(X)} samples...")
31
- indices = np.arange(len(X))
32
- dataset = cls(X, Y, lengths, indices, loss_masks)
33
- bucket_size = 128
34
- PAD_ID = 0
35
-
36
- def collate_fn(batch):
37
- X_batch = [item[0] for item in batch]
38
- Y_batch = [item[1] for item in batch]
39
- lm_batch = [item[3] for item in batch]
40
-
41
- max_len = max(len(x) for x in X_batch)
42
- if max_len > (max_seq_len - (max_seq_len%bucket_size)):
43
- bucket = max_seq_len
44
- else:
45
- bucket = int(np.ceil(max_len / bucket_size) * bucket_size)
46
-
47
- bsz = len(X_batch)
48
- X_padded = torch.zeros((bsz, bucket), dtype=torch.long)
49
- Y_padded = torch.zeros((bsz, bucket), dtype=torch.long)
50
- has_padding = any(len(x) < bucket for x in X_batch)
51
-
52
- if loss_masks is not None:
53
- loss_mask_padded = torch.zeros((bsz, bucket), dtype=torch.float)
54
-
55
- for i, (x, y) in enumerate(zip(X_batch, Y_batch)):
56
- x_len = len(x)
57
- y_len = len(y)
58
-
59
- X_padded[i, :x_len] = torch.as_tensor(x, dtype=torch.long)
60
- Y_padded[i, :y_len] = torch.as_tensor(y, dtype=torch.long)
61
-
62
- if loss_masks is not None:
63
- lm = lm_batch[i]
64
- lm_len = len(lm)
65
- loss_mask_padded[i, :lm_len] = torch.as_tensor(lm, dtype=torch.float)
66
-
67
- attention_mask = (X_padded != PAD_ID).float()
68
-
69
- if loss_masks is None:
70
- sample_weight = (Y_padded != PAD_ID).float()
71
- else:
72
- sample_weight = loss_mask_padded
73
-
74
- return X_padded, Y_padded, sample_weight, attention_mask, has_padding
75
-
76
- dataloader = torch.utils.data.DataLoader(
77
- dataset,
78
- batch_size=batch_size,
79
- shuffle=shuffle,
80
- collate_fn=collate_fn,
81
- num_workers=num_workers,
82
- pin_memory=torch.cuda.is_available(),
83
- persistent_workers = True,
84
- drop_last=False
85
- )
86
-
87
- log_progress(f"Dataset được tạo với batch_size={batch_size}")
88
- return dataloader
89
-
90
- def split_train_val_test(X, Y, loss_masks, lengths, train_ratio, val_ratio, seed=54):
91
- total_sample = len(X)
92
- rng = np.random.default_rng(seed)
93
- indices = rng.permutation(total_sample)
94
-
95
- train_end = int(total_sample * train_ratio)
96
- val_end = int(total_sample * (train_ratio + val_ratio))
97
-
98
- train_idx = indices[:train_end]
99
- val_idx = indices[train_end:val_end]
100
- test_idx = indices[val_end:]
101
-
102
- X_train, Y_train, lengths_train = X[train_idx], Y[train_idx], lengths[train_idx]
103
- X_val, Y_val, lengths_val = X[val_idx], Y[val_idx], lengths[val_idx]
104
- X_test, Y_test, lengths_test = X[test_idx], Y[test_idx], lengths[test_idx]
105
-
106
- if loss_masks is not None:
107
- mask_train = loss_masks[train_idx]
108
- mask_val = loss_masks[val_idx]
109
- mask_test = loss_masks[test_idx]
110
-
111
- return (X_train, Y_train, mask_train, lengths_train,
112
- X_val, Y_val, mask_val, lengths_val,
113
- X_test, Y_test, mask_test, lengths_test)
114
- else:
115
- return (X_train, Y_train, None, lengths_train,
116
- X_val, Y_val, None, lengths_val,
117
- X_test, Y_test, None, lengths_test)
118
-
119
- def _load_single_npz(path, load_mask=False):
120
- """Bản gốc: đọc 1 file .npz duy nhất."""
121
- f = np.load(path)
122
- def reconstruct(name):
123
- flat, offsets = f[f"{name}_flat"], f[f"{name}_offsets"]
124
- return np.array([flat[offsets[i]:offsets[i+1]] for i in range(len(offsets)-1)], dtype=object)
125
- X = reconstruct("X")
126
- Y = reconstruct("Y")
127
- lengths = f["lengths"]
128
- if load_mask:
129
- M = reconstruct("M")
130
- f.close()
131
- return X, Y, M, lengths
132
- f.close()
133
- return X, Y, lengths
134
-
135
- def _load_manifest_shards(manifest_path, load_mask=False):
136
- """Đọc manifest.json rồi ghép nhiều shard .npz lại thành 1 dataset logic."""
137
- with open(manifest_path, "r", encoding="utf-8") as f:
138
- manifest = json.load(f)
139
-
140
- manifest_dir = Path(manifest_path).parent
141
- shards = manifest["shards"]
142
-
143
- X_list, Y_list, M_list, L_list = [], [], [], []
144
- for shard in shards:
145
- shard_path = manifest_dir / shard["file"]
146
- if load_mask:
147
- X_s, Y_s, M_s, L_s = _load_single_npz(shard_path, load_mask=True)
148
- M_list.append(M_s)
149
- else:
150
- X_s, Y_s, L_s = _load_single_npz(shard_path, load_mask=False)
151
- X_list.append(X_s)
152
- Y_list.append(Y_s)
153
- L_list.append(L_s)
154
-
155
- X = np.concatenate(X_list)
156
- Y = np.concatenate(Y_list)
157
- lengths = np.concatenate(L_list)
158
-
159
- if load_mask:
160
- M = np.concatenate(M_list)
161
- return X, Y, M, lengths
162
- return X, Y, lengths
163
-
164
- def load_npz(path, load_mask=False):
165
- """
166
- - path là file .json -> manifest nhiều shard, tự đọc & ghép tất cả.
167
- - path là file .npz -> đọc 1 file như bản gốc.
168
- """
169
- path = Path(path)
170
- if path.suffix == ".json":
171
- return _load_manifest_shards(path, load_mask=load_mask)
172
- return _load_single_npz(path, load_mask=load_mask)
173
-
174
- def load_data(data_type, main_data, sub_data=None, seed=54):
175
- if data_type == "pretrain":
176
- X, Y, lengths = load_npz(main_data)
177
- return X, Y, lengths
178
-
179
- elif data_type == "continued_pretrain":
180
- X_main, Y_main, L_main = load_npz(main_data)
181
-
182
- if sub_data is not None:
183
- X_sub, Y_sub, L_sub = load_npz(sub_data)
184
-
185
- n_continued = len(X_main)
186
- n_pretrain_needed = 3 * n_continued
187
- total_pretrain = len(X_sub)
188
-
189
- rng = np.random.default_rng(seed)
190
- shuffled_indices = rng.permutation(total_pretrain)
191
-
192
- n_samples = min(n_pretrain_needed, total_pretrain)
193
- selected_indices = shuffled_indices[:n_samples]
194
-
195
- X_sub_sampled = X_sub[selected_indices]
196
- Y_sub_sampled = Y_sub[selected_indices]
197
- L_sub_sampled = L_sub[selected_indices]
198
-
199
- del X_sub, Y_sub, L_sub, shuffled_indices, selected_indices
200
- gc.collect()
201
-
202
- X_combined = np.concatenate([X_main, X_sub_sampled])
203
- Y_combined = np.concatenate([Y_main, Y_sub_sampled])
204
- L_combined = np.concatenate([L_main, L_sub_sampled])
205
-
206
- combined_indices = rng.permutation(len(X_combined))
207
- X = X_combined[combined_indices]
208
- Y = Y_combined[combined_indices]
209
- lengths = L_combined[combined_indices]
210
-
211
- else:
212
- X, Y, lengths = X_main, Y_main, L_main
213
-
214
- return X, Y, lengths
215
-
216
- elif data_type == "sft1":
217
- X, Y, loss_mask, lengths = load_npz(main_data, load_mask=True)
218
- return X, Y, loss_mask, lengths
219
-
220
- elif data_type == "sft2":
221
- X_main, Y_main, M_main, L_main = load_npz(main_data, load_mask=True)
222
-
223
- if sub_data is not None:
224
- X_sub, Y_sub, M_sub, L_sub = load_npz(sub_data, load_mask=True)
225
-
226
- n_sub = len(X_main) * 3
227
- total_sub = len(X_sub)
228
-
229
- rng = np.random.default_rng(seed)
230
- shuffled_indices = rng.permutation(total_sub)
231
-
232
- n_samples = min(n_sub, total_sub)
233
- selected_indices = shuffled_indices[:n_samples]
234
-
235
- X_sub_sampled = X_sub[selected_indices]
236
- Y_sub_sampled = Y_sub[selected_indices]
237
- M_sub_sampled = M_sub[selected_indices]
238
- L_sub_sampled = L_sub[selected_indices]
239
-
240
- del X_sub, Y_sub, M_sub, L_sub, shuffled_indices, selected_indices
241
- gc.collect()
242
-
243
- X_combined = np.concatenate([X_main, X_sub_sampled])
244
- Y_combined = np.concatenate([Y_main, Y_sub_sampled])
245
- M_combined = np.concatenate([M_main, M_sub_sampled])
246
- L_combined = np.concatenate([L_main, L_sub_sampled])
247
-
248
- combined_indices = rng.permutation(len(X_combined))
249
- X = X_combined[combined_indices]
250
- Y = Y_combined[combined_indices]
251
- loss_mask = M_combined[combined_indices]
252
- lengths = L_combined[combined_indices]
253
-
254
- else:
255
- X, Y, loss_mask, lengths = X_main, Y_main, M_main, L_main
256
-
257
- return X, Y, loss_mask, lengths
258
-
259
- else:
260
- raise ValueError(f"Unknown data_type: {data_type}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/data/__init__.py DELETED
File without changes
src/inference/__init__.py DELETED
File without changes
src/model/__init__.py DELETED
File without changes
src/tokenizer/__init__.py DELETED
File without changes
src/tokenizer/encoders.py DELETED
@@ -1,224 +0,0 @@
1
- import json
2
- from pathlib import Path
3
- import numpy as np
4
- import sentencepiece as spm
5
-
6
- current_file = Path(__file__).resolve()
7
- project_root = current_file.parent.parent.parent
8
- config_file = project_root / "config" / "base.json"
9
- tokenizer_dir = project_root / "src" / "tokenizer"
10
- data_dir = project_root / "data"
11
- raw_dir = data_dir / "raw"
12
- processed_dir = data_dir / "processed"
13
- processed_dir.mkdir(parents=True, exist_ok=True)
14
-
15
- with open(config_file, 'r') as f:
16
- config = json.load(f)
17
- max_seq_len = config['max_seq_len']
18
-
19
- tokenizer_file = tokenizer_dir / "tokenizer.model"
20
- sp = spm.SentencePieceProcessor()
21
- sp.load(str(tokenizer_file))
22
-
23
- BOS = sp.piece_to_id("[BOS]")
24
- EOS = sp.piece_to_id("[EOS]")
25
- IM_START = sp.piece_to_id("<|im_start|>")
26
- IM_END = sp.piece_to_id("<|im_end|>")
27
-
28
- def iter_text_jsonl(path, key="text"):
29
- with open(path, "r", encoding="utf-8") as f:
30
- for line in f:
31
- line = line.strip()
32
- if not line:
33
- continue
34
- try:
35
- obj = json.loads(line)
36
- except json.JSONDecodeError:
37
- continue
38
- if isinstance(obj, dict) and key in obj:
39
- text = obj[key].strip()
40
- if text:
41
- yield text
42
-
43
- def iter_sft_conversations(path):
44
- """Đọc file .json chứa 1 mảng các object {instruction, history: [{user, model}, ...]}."""
45
- with open(raw_dir / path, "r", encoding="utf-8") as f:
46
- data = json.load(f)
47
- if not isinstance(data, list):
48
- data = [data]
49
- for obj in data:
50
- if not isinstance(obj, dict):
51
- continue
52
- history = obj.get("history", [])
53
- if not history:
54
- continue
55
- yield obj
56
-
57
- def _encode_pretrain(text):
58
- tokens = sp.encode(text.lower(), out_type=int)
59
- if len(tokens) < 2 or len(tokens) + 2 > max_seq_len:
60
- return None
61
- x = [BOS] + tokens
62
- y = tokens + [EOS]
63
- return x, y
64
-
65
- def _encode_sft(sample):
66
- instruction = sample.get("instruction", "").strip()
67
- history = sample.get("history", [])
68
- if not history:
69
- return None
70
-
71
- all_tokens = []
72
- mask = []
73
-
74
- if instruction:
75
- instr_ids = (
76
- [IM_START]
77
- + sp.encode("system\n" + instruction.lower(), out_type=int)
78
- + [IM_END]
79
- )
80
- all_tokens += instr_ids
81
- mask += [0] * len(instr_ids)
82
-
83
- for turn in history:
84
- user_text = turn.get("user", "").strip()
85
- model_text = turn.get("model", "").strip()
86
- if not model_text:
87
- continue
88
-
89
- user_ids = (
90
- [IM_START]
91
- + sp.encode("user\n" + user_text.lower(), out_type=int)
92
- + [IM_END]
93
- )
94
- model_ids = (
95
- [IM_START]
96
- + sp.encode("model\n" + model_text.lower(), out_type=int)
97
- + [IM_END]
98
- )
99
-
100
- all_tokens += user_ids + model_ids
101
- mask += [0] * len(user_ids) + [1] * len(model_ids)
102
-
103
- if not all_tokens:
104
- return None
105
-
106
- input_ids = [BOS] + all_tokens + [EOS]
107
- if len(input_ids) > max_seq_len:
108
- return None
109
-
110
- target_ids = input_ids[1:]
111
- mask = mask + [1]
112
-
113
- assert len(mask) == len(target_ids), (
114
- f"Độ dài mask ({len(mask)}) khác target_ids ({len(target_ids)})"
115
- )
116
- return input_ids, target_ids, mask
117
-
118
- def _two_pass_write(path, encoded_iter_fn, has_mask=False):
119
- """Logic 2-pass gốc, ghi ra .npz (giống bản đầu tiên)."""
120
- x_offsets = [0]
121
- y_offsets = [0]
122
- m_offsets = [0]
123
- lengths = []
124
-
125
- for idx, item in enumerate(encoded_iter_fn()):
126
- if idx % 100_000 == 0:
127
- print(f" [pass 1] {idx:,} dòng...")
128
- if has_mask:
129
- x, y, m = item
130
- else:
131
- x, y = item
132
- x_offsets.append(x_offsets[-1] + len(x))
133
- y_offsets.append(y_offsets[-1] + len(y))
134
- if has_mask:
135
- m_offsets.append(m_offsets[-1] + len(m))
136
- lengths.append(len(x))
137
-
138
- n = len(lengths)
139
- if n == 0:
140
- return 0, 0
141
-
142
- total_x = x_offsets[-1]
143
- total_y = y_offsets[-1]
144
-
145
- x_flat = np.empty(total_x, dtype=np.int32)
146
- y_flat = np.empty(total_y, dtype=np.int32)
147
- if has_mask:
148
- m_flat = np.empty(m_offsets[-1], dtype=np.int8)
149
-
150
- x_off_arr = np.array(x_offsets, dtype=np.int64)
151
- y_off_arr = np.array(y_offsets, dtype=np.int64)
152
- len_arr = np.array(lengths, dtype=np.int32)
153
- if has_mask:
154
- m_off_arr = np.array(m_offsets, dtype=np.int64)
155
-
156
- for idx, item in enumerate(encoded_iter_fn()):
157
- if idx % 100_000 == 0:
158
- print(f" [pass 2] {idx:,}/{n:,} dòng...")
159
- if has_mask:
160
- x, y, m = item
161
- m_flat[m_offsets[idx]: m_offsets[idx + 1]] = m
162
- else:
163
- x, y = item
164
- x_flat[x_offsets[idx]: x_offsets[idx + 1]] = x
165
- y_flat[y_offsets[idx]: y_offsets[idx + 1]] = y
166
-
167
- out = {
168
- "X_flat": x_flat, "X_offsets": x_off_arr,
169
- "Y_flat": y_flat, "Y_offsets": y_off_arr,
170
- "lengths": len_arr,
171
- }
172
- if has_mask:
173
- out["M_flat"] = m_flat
174
- out["M_offsets"] = m_off_arr
175
-
176
- np.savez_compressed(path, **out)
177
- return n, int(len_arr.sum())
178
-
179
- def _write_manifest(manifest_path, shards):
180
- """Sổ mục lục các shard .npz để lúc load ghép lại thành 1 dataset logic."""
181
- manifest = {
182
- "shards": shards,
183
- "total_samples": sum(s["n_samples"] for s in shards),
184
- "total_tokens": sum(s["n_tokens"] for s in shards),
185
- }
186
- with open(manifest_path, "w", encoding="utf-8") as f:
187
- json.dump(manifest, f, ensure_ascii=False, indent=2)
188
- return manifest
189
-
190
- # ---- Pretrain: mỗi file pretrain_data_00X.jsonl -> 1 shard .npz riêng ----
191
- pretrain_paths = sorted(raw_dir.glob("pretrain_data_*.jsonl"))
192
- print(f"Pretrain - tìm thấy {len(pretrain_paths)} file shard...")
193
-
194
- pretrain_shards = []
195
- for p in pretrain_paths:
196
- shard_name = p.stem # ví dụ: pretrain_data_001
197
- print(f"Pretrain shard {shard_name} - pass 1 & 2...")
198
- n, total = _two_pass_write(
199
- processed_dir / f"{shard_name}_ids.npz",
200
- lambda p=p: filter(None, (_encode_pretrain(t) for t in iter_text_jsonl(p))),
201
- )
202
- print(f"✅ {shard_name}: {n} samples | Tổng số token: {total:,}")
203
- pretrain_shards.append({
204
- "name": shard_name,
205
- "file": f"{shard_name}_ids.npz",
206
- "n_samples": n,
207
- "n_tokens": total,
208
- })
209
-
210
- manifest = _write_manifest(processed_dir / "pretrain_manifest.json", pretrain_shards)
211
- print(f"✅ Pretrain tổng: {manifest['total_samples']} samples | {manifest['total_tokens']:,} token | manifest: pretrain_manifest.json")
212
-
213
- # cont_path = raw_dir / "continued_pretrain_data.jsonl"
214
- # print("Continued Pretrain - pass 1 & 2...")
215
- # n, total = _two_pass_write(processed_dir / "continued_pretrain_data_ids.npz", lambda: filter(None, (_encode_pretrain(t) for t in iter_text_jsonl(cont_path))))
216
- # print(f"✅ Continued Pretrain: {n} samples | Tổng số token: {total:,}")
217
-
218
- print("SFT1 - pass 1 & 2...")
219
- n, total = _two_pass_write(processed_dir / "SFT1_data_ids.npz", lambda: filter(None, (_encode_sft(s) for s in iter_sft_conversations("SFT_1.json"))), has_mask=True)
220
- print(f"✅ Đã lưu SFT1: {n} samples | Tổng số token: {total:,}")
221
-
222
- # print("SFT2 - pass 1 & 2...")
223
- # n, total = _two_pass_write(processed_dir / "SFT2_data_ids.npz", lambda: filter(None, (_encode_sft(s) for s in iter_sft_conversations("SFT_2.json"))), has_mask=True)
224
- # print(f"✅ Đã lưu SFT2: {n} samples | Tổng số token: {total:,}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/tokenizer/tokenizer.model DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:3eb2e8f55e0472fbe6c5f597d601746934e69542b5238a18852132761c03a9ac
3
- size 156890
 
 
 
 
src/tokenizer/tokenizer.vocab DELETED
The diff for this file is too large to render. See raw diff
 
src/tokenizer/train_tokenizer.py DELETED
@@ -1,103 +0,0 @@
1
- import json
2
- import time
3
- from pathlib import Path
4
- import sentencepiece as spm
5
-
6
- # Khởi động đồng hồ đo thời gian toàn bộ tiến trình
7
- start_total_time = time.time()
8
-
9
- # 1. Giữ nguyên logic tìm thư mục gốc tự động
10
- current_file = Path(__file__).resolve()
11
- project_root = current_file.parent
12
-
13
- RAW_DATA_ABSOLUTE_PATH = Path("/Users/thongbui.nd/Documents/Thong Bui/dev_2/data/raw/pretrain_data.jsonl")
14
-
15
- vocab_size = 10000
16
- input_text_path = project_root / "spm_input.txt"
17
-
18
- # =========================================================
19
- # BƯỚC 1+2: ĐỌC, SÀN LỌC VÀ GHI THẲNG RA FILE TẠM
20
- # =========================================================
21
- print(f"🚀 Bước 1+2: Đang xử lý dữ liệu và ghi ra file tạm: {input_text_path.name}...")
22
- start_step1 = time.time()
23
-
24
- total_lines_read = 0
25
- malformed_lines = 0
26
- empty_lines = 0
27
- valid_count = 0
28
-
29
- # Đọc từ file hardcode tuyệt đối, ghi vào file tạm ở project_root
30
- with open(RAW_DATA_ABSOLUTE_PATH, "r", encoding="utf-8") as fin, \
31
- open(input_text_path, "w", encoding="utf-8") as fout:
32
-
33
- for line in fin:
34
- total_lines_read += 1
35
-
36
- if total_lines_read % 100000 == 0:
37
- print(f" [Tiến trình] Đã quét qua {total_lines_read:,} dòng dữ liệu thô...")
38
-
39
- line = line.strip()
40
- if not line:
41
- empty_lines += 1
42
- continue
43
-
44
- try:
45
- obj = json.loads(line)
46
- except json.JSONDecodeError:
47
- malformed_lines += 1
48
- continue
49
-
50
- if isinstance(obj, dict) and "text" in obj:
51
- text = obj["text"].strip()
52
- if text:
53
- fout.write(text.lower() + "\n")
54
- valid_count += 1
55
- else:
56
- empty_lines += 1
57
-
58
- print(f"📊 Thống kê dữ liệu đã đọc:")
59
- print(f" - Tổng số dòng quét qua: {total_lines_read:,}")
60
- print(f" - Số dòng trống/không có text: {empty_lines:,}")
61
- print(f" - Số dòng lỗi định dạng JSON: {malformed_lines:,}")
62
- print(f" - Số dòng hợp lệ đưa vào huấn luyện: {valid_count:,}")
63
- print(f"⏱️ Thời gian xử lý Bước 1+2: {time.time() - start_step1:.2f} giây\n")
64
-
65
- # =========================================================
66
- # BƯỚC 3: HUẤN LUYỆN SENTENCEPIECE
67
- # =========================================================
68
- print("🧠 Bước 3: Đang huấn luyện SentencePiece (Unigram)...")
69
- print(" * Lưu ý: Thư viện SentencePiece có bộ log C++ nội bộ riêng, chuẩn bị hiển thị bên dưới... *")
70
- start_step3 = time.time()
71
-
72
- spm.SentencePieceTrainer.train(
73
- input=str(input_text_path),
74
- model_prefix=str(project_root / "tokenizer"),
75
- vocab_size=vocab_size,
76
- model_type="unigram",
77
- character_coverage=0.9999,
78
- pad_piece="[PAD]",
79
- unk_piece="[UNK]",
80
- bos_piece="[BOS]",
81
- eos_piece="[EOS]",
82
- user_defined_symbols=["<|im_start|>", "<|im_end|>"],
83
- normalization_rule_name='identity',
84
- # split_by_whitespace=False,
85
- treat_whitespace_as_suffix=False,
86
- input_sentence_size=1000000,
87
- shuffle_input_sentence=True,
88
- remove_extra_whitespaces=False,
89
- split_digits=True,
90
- byte_fallback=True,
91
- max_sentencepiece_length=8
92
- )
93
-
94
- print(f"\nThời gian huấn luyện SentencePiece: {time.time() - start_step3:.2f} giây\n")
95
-
96
- # =========================================================
97
- # HOÀN TẤT TIẾN TRÌNH
98
- # =========================================================
99
- print("🎉 Đã huấn luyện xong!")
100
- print(f"📁 Xuất file thành công tại:")
101
- print(f" - Model: {project_root / 'tokenizer.model'}")
102
- print(f" - Vocab: {project_root / 'tokenizer.vocab'}")
103
- print(f"🚀 Tổng thời gian thực hiện toàn bộ script: {time.time() - start_total_time:.2f} giây.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/training/PenaltyEngine.py DELETED
@@ -1,188 +0,0 @@
1
- import torch
2
- import torch.nn as nn
3
- import torch.nn.functional as F
4
- from abc import ABC, abstractmethod
5
- from typing import Optional, NamedTuple
6
-
7
- """
8
- WrongTokenMarginPenalty: Khi mô hình dự đoán sai
9
- - Ép mô hình tăng logit của token đúng (nếu detach_max = False thì trực tiếp giảm thêm logit của token sai nhưng không nên dùng)
10
-
11
- WrongTokenEntropyPenalty: Khi mô hình đã dự đoán sai, mà lại còn tự tin vào cái token sai đấy
12
- - Dấu hiệu: entropy thấp
13
- - Công thức: penalty = max(0, min_entropy - H) * WrongMask
14
- - Không quan tâm đến token đúng, chỉ cần gradient kéo toàn bộ phân phối xác suất phẳng hơn là được.
15
-
16
- FocalOverconfidencePenalty: Khi mô hình dự đoán sai
17
- - Khuếch đại cái sai theo hệ số gamma, phạt cho xác suất của token sai giảm xuống
18
- - Xác suất mô hình dành cho token sai càng lơn, phạt càng mạnh
19
-
20
- Các trường hợp:
21
- - tất cả z đều thấp, và model dự đoán đúng: chưa xử lý
22
- - tất cả z đều thấp, và model dự đoán sai: dùng WrongTokenMarginPenalty để kéo logit của token đúng lên
23
- - tất cả z đều cao, và model dự đoán sai: dùng WrongTokenMarginPenalty để kéo logit của token đúng lên và FocalOverconfidencePenalty để giảm xác suất của token sai
24
- - tất cả z đều cao, và model dự đoán đúng: chưa xử lý
25
- - entropy thấp, model dự đoán đúng: chưa xử lý
26
- - entropy thấp, model dự đoán sai: dùng WrongTokenMarginPenalty để kéo logit của token đúng lên và WrongTokenEntropyPenalty để kéo phẳng phân phối và FocalOverconfidencePenalty để giảm xác suất của token sai
27
- """
28
-
29
- class PenaltyContext(NamedTuple):
30
- logits: torch.Tensor
31
- inputs: torch.Tensor
32
- targets: torch.Tensor
33
- loss_mask: Optional[torch.Tensor]
34
- max_logits: torch.Tensor
35
- predicted: torch.Tensor
36
- target_logits: torch.Tensor
37
- wrong_mask: torch.Tensor
38
- log_probs: Optional[torch.Tensor]
39
-
40
- def _safe_masked_mean(tensor: torch.Tensor, mask: Optional[torch.Tensor]) -> torch.Tensor:
41
- if mask is not None:
42
- tensor = tensor * mask
43
- denom = mask.sum().clamp(min=1e-6)
44
- else:
45
- denom = torch.tensor(tensor.numel(), dtype=tensor.dtype, device=tensor.device).clamp(min=1e-6)
46
- return tensor.sum() / denom
47
-
48
- class PenaltyRule(nn.Module, ABC):
49
- needs_log_probs: bool = False
50
-
51
- @abstractmethod
52
- def forward(self, ctx: PenaltyContext) -> torch.Tensor:
53
- """Trả về scalar penalty."""
54
-
55
- class WrongTokenMarginPenalty(PenaltyRule):
56
- needs_log_probs = False
57
- def __init__(self, weight: float = 1.0, detach_max: bool = True):
58
- super().__init__()
59
- self.weight = weight
60
- self.detach_max = detach_max
61
-
62
- def forward(self, ctx: PenaltyContext) -> torch.Tensor:
63
- max_l = ctx.max_logits.detach() if self.detach_max else ctx.max_logits
64
- margin = F.relu(max_l - ctx.target_logits)
65
- penalty = margin * ctx.wrong_mask
66
-
67
- return self.weight * _safe_masked_mean(penalty, ctx.loss_mask)
68
-
69
- class WrongTokenEntropyPenalty(PenaltyRule):
70
- needs_log_probs = True
71
- def __init__(self, weight: float = 0.01, min_entropy: float = 0.5):
72
- super().__init__()
73
- self.weight = weight
74
- self.min_entropy = min_entropy
75
-
76
- def forward(self, ctx: PenaltyContext) -> torch.Tensor:
77
- assert ctx.log_probs is not None, \
78
- "WrongTokenEntropyPenalty cần log_probs; thêm needs_log_probs=True"
79
-
80
- probs = ctx.log_probs.exp()
81
- entropy = -(probs * ctx.log_probs).sum(dim=-1)
82
-
83
- low_entropy = F.relu(self.min_entropy - entropy)
84
-
85
- combined_mask = ctx.wrong_mask * (ctx.loss_mask if ctx.loss_mask is not None else 1.0)
86
- penalty = low_entropy * combined_mask
87
-
88
- return self.weight * _safe_masked_mean(penalty, ctx.loss_mask)
89
-
90
- class FocalOverconfidencePenalty(PenaltyRule):
91
- """
92
- Lấy cảm hứng từ Focal Loss nhưng đảo chiều:
93
- phạt nặng khi model quá chắc vào token SAI.
94
-
95
- penalty = (p_wrong)^gamma * log(p_wrong + ε) khi sai
96
-
97
- Với gamma > 1: gradient TĂNG khi overconfident
98
- (ngược lại với p(1-p) trong softmax thông thường).
99
-
100
- Cần log_probs để lấy p_max một cách hiệu quả.
101
- """
102
- needs_log_probs = True
103
-
104
- def __init__(self, weight: float = 1.0, gamma: float = 2.0):
105
- super().__init__()
106
- self.weight = weight
107
- self.gamma = gamma
108
-
109
- def forward(self, ctx: PenaltyContext) -> torch.Tensor:
110
- assert ctx.log_probs is not None
111
-
112
- # xác suất của token được predict (token sai)
113
- log_p_wrong = ctx.log_probs.gather(
114
- dim=-1,
115
- index=ctx.predicted.unsqueeze(-1)
116
- ).squeeze(-1)
117
- p_wrong = log_p_wrong.exp()
118
-
119
- # focal penalty: nặng khi p cao, nhẹ khi p thấp
120
- penalty = (p_wrong ** self.gamma) * (-log_p_wrong)
121
- penalty = penalty * ctx.wrong_mask
122
-
123
- return self.weight * _safe_masked_mean(penalty, ctx.loss_mask)
124
-
125
- class PenaltyEngine(nn.Module):
126
- def __init__(self):
127
- super().__init__()
128
- self.rules = nn.ModuleList()
129
-
130
- @classmethod
131
- def build_default(cls) -> "PenaltyEngine":
132
- return (
133
- cls()
134
- .add_rule(WrongTokenMarginPenalty(weight=0.1, detach_max=True))
135
- .add_rule(WrongTokenEntropyPenalty(weight=0.001, min_entropy=0.5))
136
- .add_rule(FocalOverconfidencePenalty(weight=0.05, gamma=2.0))
137
- )
138
-
139
- def add_rule(self, rule: PenaltyRule) -> "PenaltyEngine":
140
- self.rules.append(rule)
141
- return self
142
-
143
- def _build_context(
144
- self,
145
- logits: torch.Tensor,
146
- inputs: torch.Tensor,
147
- targets: torch.Tensor,
148
- loss_mask: Optional[torch.Tensor],
149
- ) -> PenaltyContext:
150
-
151
- max_logits, predicted = logits.max(dim=-1)
152
- target_logits = logits.gather(
153
- dim=-1, index=targets.unsqueeze(-1)
154
- ).squeeze(-1)
155
-
156
- wrong_mask = (predicted != targets).float()
157
-
158
- needs_log = any(getattr(r, "needs_log_probs", False) for r in self.rules)
159
- log_probs = F.log_softmax(logits, dim=-1) if needs_log else None
160
-
161
- return PenaltyContext(
162
- logits=logits,
163
- inputs=inputs,
164
- targets=targets,
165
- loss_mask=loss_mask,
166
- max_logits=max_logits,
167
- predicted=predicted,
168
- target_logits=target_logits,
169
- wrong_mask=wrong_mask,
170
- log_probs=log_probs,
171
- )
172
-
173
- def forward(
174
- self,
175
- logits: torch.Tensor,
176
- inputs: torch.Tensor,
177
- targets: torch.Tensor,
178
- loss_mask: Optional[torch.Tensor] = None,
179
- ) -> torch.Tensor:
180
- if not self.rules:
181
- return torch.tensor(0.0, device=logits.device)
182
-
183
- ctx = self._build_context(logits, inputs, targets, loss_mask)
184
-
185
- total = torch.tensor(0.0, device=logits.device)
186
- for rule in self.rules:
187
- total = total + rule(ctx)
188
- return total
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/training/__init__.py DELETED
File without changes
src/training/finetune.py DELETED
@@ -1,581 +0,0 @@
1
- from pathlib import Path
2
- import torch
3
- import torch.optim as optim
4
- from torch.amp import GradScaler, autocast
5
- import torch._dynamo
6
- import json
7
- import gc
8
- import argparse
9
- import logging
10
- import math
11
- from src.utils.utils import (get_step_lr_lambda, freeze_layers, unfreeze_all_layers, log_progress, load_checkpoint, save_checkpoint, estimate_training_flops, print_training_flops_summary, TflopsBenchmarker, KernelLogger)
12
- from src.utils.chunked_loss import chunked_lm_loss, chunked_sft_loss
13
- from src.data.Dataset import Dataset, split_train_val_test, load_data
14
- from src.training.PenaltyEngine import PenaltyEngine, WrongTokenMarginPenalty, WrongTokenEntropyPenalty, FocalOverconfidencePenalty
15
- from src.model.TransformerModel import TransformerModel
16
-
17
- parser = argparse.ArgumentParser()
18
- parser.add_argument("--model", type=str, required=True, help="Model size: 35M, 100M or 500M")
19
- parser.add_argument(
20
- "--phase", type=str, required=True,
21
- choices=["sft1", "sft2", "sft1_resume", "sft2_resume", "full"],
22
- help="Training phase: sft1 | sft2 | sft1_resume | sft2_resume | full"
23
- )
24
- parser.add_argument(
25
- "--profile-kernels", action="store_true",
26
- help="Profile kernel tại step 500 và lưu compact JSON"
27
- )
28
- parser.add_argument(
29
- "--limited-max-autotune", action="store_true",
30
- help="Bật max-autotune giới hạn cho forward_features (chỉ ATEN/TRITON)"
31
- )
32
- args = parser.parse_args()
33
- torch._dynamo.config.cache_size_limit = 24
34
- logging.getLogger("torch._inductor.select_algorithm").setLevel(logging.ERROR)
35
- model_size = args.model
36
-
37
- current_file = Path(__file__).resolve()
38
- project_root = current_file.parent.parent.parent
39
- config_dir = project_root / "config"
40
- data_dir = project_root / "data"
41
- model_dir = project_root / "model"
42
- src_dir = project_root / "src"
43
- base_config_file = config_dir / "base.json"
44
- model_config_file = config_dir / f"{args.model}.json"
45
- model_dir.mkdir(parents=True, exist_ok=True)
46
- data_processed_dir = project_root / "data" / "processed"
47
- SFT1_data_ids_file = data_processed_dir / "SFT1_data_ids.npz"
48
- SFT2_data_ids_file = data_processed_dir / "SFT2_data_ids.npz"
49
-
50
- pretrained_save_path = model_dir / f"pretrained_{model_size}.pt"
51
- sft1_save_path = model_dir / f"sft1_{model_size}.pt"
52
- sft1_ckpt_path = model_dir / f"sft1_{model_size}.ckpt.pt"
53
- sft2_save_path = model_dir / f"sft2_{model_size}.pt"
54
- sft2_ckpt_path = model_dir / f"sft2_{model_size}.ckpt.pt"
55
-
56
- # Peak logits mỗi chunk ~ LM_LOSS_CHUNK_SIZE * vocab_size * 4 bytes.
57
- LM_LOSS_CHUNK_SIZE = 4096
58
-
59
- def _build_val_test_loaders(phase_name, main_data, sub_data, train_ratio, val_ratio, batch_size, num_workers):
60
- X, Y, loss_mask, lengths = load_data(phase_name, main_data, sub_data)
61
-
62
- X_train, Y_train, mask_train, len_train, \
63
- X_val, Y_val, mask_val, len_val, \
64
- X_test, Y_test, mask_test, len_test = split_train_val_test(
65
- X, Y, loss_mask, lengths, train_ratio, val_ratio
66
- )
67
-
68
- val_ds = Dataset.create_dataloader(X_val, Y_val, len_val, batch_size, max_seq_len, num_workers, shuffle=False, loss_masks=mask_val)
69
- test_ds = Dataset.create_dataloader(X_test, Y_test, len_test, batch_size, max_seq_len, num_workers, shuffle=False, loss_masks=mask_test)
70
-
71
- train_size = len(X_train)
72
- val_size = len(X_val)
73
- test_size = len(X_test)
74
-
75
- del X_train, Y_train, mask_train, len_train
76
- del X_val, Y_val, mask_val, len_val
77
- del X_test, Y_test, mask_test, len_test
78
- del X, Y, loss_mask, lengths
79
- gc.collect()
80
- if torch.cuda.is_available():
81
- torch.cuda.empty_cache()
82
-
83
- return val_ds, test_ds, train_size, val_size, test_size
84
-
85
- def _build_train_loader_epoch(phase_name, main_data, sub_data, train_ratio, val_ratio, batch_size, num_workers, epoch=0):
86
- X, Y, loss_mask, lengths = load_data(phase_name, main_data, sub_data, seed=epoch)
87
-
88
- X_train, Y_train, mask_train, len_train, \
89
- X_val, Y_val, mask_val, len_val, \
90
- X_test, Y_test, mask_test, len_test = split_train_val_test(
91
- X, Y, loss_mask, lengths, train_ratio, val_ratio
92
- )
93
-
94
- train_ds = Dataset.create_dataloader(X_train, Y_train, len_train, batch_size, max_seq_len, num_workers, shuffle=True, loss_masks=mask_train)
95
-
96
- del X_val, Y_val, mask_val, len_val
97
- del X_test, Y_test, mask_test, len_test
98
- del X_train, Y_train, mask_train, len_train
99
- del X, Y, loss_mask, lengths
100
- gc.collect()
101
- if torch.cuda.is_available():
102
- torch.cuda.empty_cache()
103
-
104
- return train_ds
105
-
106
- def finetune(model_raw, optimizer, device, main_data, sub_data, num_epochs, model_save_path, train_ratio, val_ratio, batch_size, num_workers, phase_name, penalty_engine, resample_per_epoch=False, resume_checkpoint_path: Path = None, profile_kernels=False):
107
- print(f"╠════════════════════════════════════════════════════════════════════════════════════╣")
108
- print(f"║ BẮT ĐẦU LOAD {phase_name.upper():<4} DATA ║")
109
- print(f"╠════════════════════════════════════════════════════════════════════════════════════╣")
110
-
111
- if not resample_per_epoch:
112
- X, Y, loss_mask, lengths = load_data(phase_name, main_data, sub_data)
113
-
114
- X_train, Y_train, mask_train, len_train, \
115
- X_val, Y_val, mask_val, len_val, \
116
- X_test, Y_test, mask_test, len_test = split_train_val_test(
117
- X, Y, loss_mask, lengths, train_ratio, val_ratio
118
- )
119
-
120
- log_progress(f"[{phase_name}] Train: {len(X_train)}, Val: {len(X_val)}, Test: {len(X_test)}")
121
-
122
- train_ds = Dataset.create_dataloader(X_train, Y_train, len_train, batch_size, max_seq_len, num_workers, shuffle=True, loss_masks=mask_train)
123
- val_ds = Dataset.create_dataloader(X_val, Y_val, len_val, batch_size, max_seq_len, num_workers, shuffle=False, loss_masks=mask_val)
124
- test_ds = Dataset.create_dataloader(X_test, Y_test, len_test, batch_size, max_seq_len, num_workers, shuffle=False, loss_masks=mask_test)
125
-
126
- del X_train, Y_train, mask_train, len_train
127
- del X_val, Y_val, mask_val, len_val
128
- del X_test, Y_test, mask_test, len_test
129
- gc.collect()
130
- if torch.cuda.is_available():
131
- torch.cuda.empty_cache()
132
-
133
- total_steps = math.ceil(len(train_ds) / accumulation_steps) * num_epochs
134
-
135
- else:
136
- val_ds, test_ds, train_size, val_size, test_size = _build_val_test_loaders(
137
- phase_name, main_data, sub_data, train_ratio, val_ratio, batch_size, num_workers
138
- )
139
- log_progress(f"[{phase_name}] Train: ~{train_size}, Val: {val_size}, Test: {test_size}")
140
-
141
- train_ds = _build_train_loader_epoch(phase_name, main_data, sub_data, train_ratio, val_ratio, batch_size, num_workers, epoch=0)
142
- steps_per_epoch = len(train_ds)
143
- total_steps = math.ceil(steps_per_epoch / accumulation_steps) * num_epochs
144
-
145
- warmup_steps = int(total_steps * 0.15)
146
- lr_lambda = get_step_lr_lambda(warmup_steps, total_steps)
147
- scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)
148
- log_progress(f"Step-based LR: warmup={warmup_steps} steps, total={total_steps} steps")
149
-
150
- best_val_loss = float("inf")
151
- global_step = 0
152
- start_epoch = 0
153
- use_penalty = penalty_engine is not None and len(penalty_engine.rules) > 0
154
- checkpoint_path = model_save_path.with_suffix(".ckpt.pt")
155
- if resume_checkpoint_path is not None:
156
- if resume_checkpoint_path.exists():
157
- start_epoch, global_step, best_val_loss = load_checkpoint(
158
- resume_checkpoint_path, model_raw, optimizer, scheduler, device
159
- )
160
- else:
161
- log_progress(f"[WARNING] Checkpoint not found at {resume_checkpoint_path}. Starting from scratch.")
162
-
163
- flops_info = estimate_training_flops(
164
- model=model_raw,
165
- num_layers=num_layers,
166
- max_seq_len=max_seq_len,
167
- d_model=d_model,
168
- epochs=num_epochs,
169
- batches_per_epoch=len(train_ds),
170
- batch_size=batch_size,
171
- )
172
- print_training_flops_summary(flops_info, num_epochs)
173
- bench = TflopsBenchmarker(
174
- flops_per_token=flops_info["flops_per_token"],
175
- total_flops_needed=flops_info["total_flops"],
176
- total_batches_per_epoch=flops_info["total_batches_per_epoch"],
177
- epochs=num_epochs,
178
- device=device,
179
- warmup_steps=100,
180
- target_steps=500,
181
- )
182
- kernel_logger = KernelLogger(enabled=profile_kernels, log_step=500)
183
-
184
- for epoch in range(start_epoch, num_epochs):
185
-
186
- if resample_per_epoch and epoch > 0:
187
- log_progress(f"[{phase_name}] Epoch {epoch+1}: Re-sampling train data...")
188
- train_ds = _build_train_loader_epoch(
189
- phase_name, main_data, sub_data, train_ratio, val_ratio, batch_size, num_workers, epoch
190
- )
191
-
192
- model_raw.train()
193
- train_loss = torch.zeros((), device=device)
194
- batch_count = 0
195
- total_batches = len(train_ds)
196
- penalty = torch.zeros((), device=device)
197
-
198
- optimizer.zero_grad(set_to_none=True)
199
- for batch_idx, (X_batch, Y_batch, sample_weight, attention_mask, has_padding) in enumerate(train_ds):
200
- X_batch = X_batch.to(device, non_blocking=True)
201
- Y_batch = Y_batch.to(device, non_blocking=True)
202
- sample_weight = sample_weight.to(device, non_blocking=True)
203
- attention_mask = attention_mask.to(device, non_blocking=True)
204
-
205
- _is_bench_step = bench.is_bench_step(epoch, start_epoch, batch_idx)
206
- if _is_bench_step:
207
- bench.on_step_begin(batch_idx, X_batch.shape[0] * X_batch.shape[1])
208
-
209
- _is_kernel_log = kernel_logger.should_log(epoch, start_epoch, batch_idx)
210
- if _is_kernel_log:
211
- kernel_logger.start()
212
-
213
- with autocast(device_type=device.type, dtype=amp_dtype, enabled=amp_enabled):
214
- hidden = model_features(
215
- X_batch,
216
- attention_mask=attention_mask,
217
- has_padding=has_padding,
218
- )
219
- hidden_flat = hidden.reshape(-1, hidden.size(-1))
220
- inputs_flat = X_batch.reshape(-1)
221
- targets_flat = Y_batch.reshape(-1)
222
- weight_flat = sample_weight.reshape(-1)
223
- num_valid_tokens = weight_flat.sum()
224
-
225
- ce_sum, penalty_sum = chunked_sft_loss(
226
- hidden_flat,
227
- model_raw.lm_head.weight,
228
- inputs_flat,
229
- targets_flat,
230
- weight_flat,
231
- penalty_engine=penalty_engine if use_penalty else None,
232
- chunk_size=LM_LOSS_CHUNK_SIZE,
233
- )
234
- ce_loss = ce_sum / (num_valid_tokens + 1e-8)
235
- penalty = penalty_sum / (num_valid_tokens + 1e-8)
236
- loss = ce_loss + penalty
237
-
238
- scaled_loss = loss / accumulation_steps
239
- scaler.scale(scaled_loss).backward()
240
- penalty_for_log = penalty.detach()
241
-
242
- if _is_kernel_log:
243
- kernel_logger.stop_and_report(batch_idx, epoch, device)
244
-
245
- if (batch_idx + 1) % accumulation_steps == 0 or (batch_idx + 1) == total_batches:
246
- scaler.unscale_(optimizer)
247
- torch.nn.utils.clip_grad_norm_(model_raw.parameters(), max_norm=1.0)
248
- scaler.step(optimizer)
249
- scaler.update()
250
- optimizer.zero_grad(set_to_none=True)
251
- scheduler.step()
252
- global_step += 1
253
-
254
- train_loss += loss.detach()
255
- batch_count += 1
256
- current_lr = optimizer.param_groups[0]["lr"]
257
-
258
- if (batch_idx + 1) % 10 == 0 or (batch_idx + 1) == total_batches:
259
- avg_loss = (train_loss / batch_count).item()
260
- penalty_str = f" | penalty: {penalty_for_log.item():.4f}" if use_penalty else ""
261
- print(
262
- f"\r{phase_name} | Epoch {epoch+1}/{num_epochs} "
263
- f"- Step {global_step}/{total_steps} "
264
- f"- loss: {avg_loss:.4f}{penalty_str} "
265
- f"- lr: {current_lr:.2e}",
266
- end=""
267
- )
268
-
269
- if _is_bench_step:
270
- bench.on_step_end(batch_idx)
271
-
272
- del X_batch, Y_batch, hidden, hidden_flat, inputs_flat, targets_flat
273
- del weight_flat, num_valid_tokens, loss, scaled_loss, ce_loss, penalty
274
- del penalty_for_log, ce_sum, penalty_sum, sample_weight, attention_mask
275
-
276
- print()
277
- train_loss = (train_loss / len(train_ds)).item()
278
-
279
- model_raw.eval()
280
- val_loss = torch.zeros((), device=device)
281
- with torch.no_grad():
282
- for X_batch, Y_batch, sample_weight, attention_mask, has_padding in val_ds:
283
- X_batch = X_batch.to(device, non_blocking=True)
284
- Y_batch = Y_batch.to(device, non_blocking=True)
285
- sample_weight = sample_weight.to(device, non_blocking=True)
286
- attention_mask = attention_mask.to(device, non_blocking=True)
287
-
288
- with autocast(device_type=device.type, dtype=amp_dtype, enabled=amp_enabled):
289
- hidden = model_features(
290
- X_batch,
291
- attention_mask=attention_mask,
292
- has_padding=has_padding,
293
- )
294
- hidden_flat = hidden.reshape(-1, hidden.size(-1))
295
- targets_flat = Y_batch.reshape(-1)
296
- weight_flat = sample_weight.reshape(-1)
297
- num_valid_tokens = weight_flat.sum()
298
- loss_sum = chunked_lm_loss(
299
- hidden_flat,
300
- model_raw.lm_head.weight,
301
- targets_flat,
302
- weight_flat,
303
- chunk_size=LM_LOSS_CHUNK_SIZE,
304
- )
305
- loss = loss_sum / (num_valid_tokens + 1e-8)
306
- val_loss += loss.detach()
307
-
308
- val_loss = (val_loss / len(val_ds)).item()
309
- log_progress(f"{phase_name} Epoch {epoch+1}/{num_epochs} | Train Loss: {train_loss:.4f} | Val Loss (CE only): {val_loss:.4f}")
310
-
311
- if val_loss < best_val_loss:
312
- best_val_loss = val_loss
313
- torch.save(model_raw.state_dict(), model_save_path)
314
- log_progress(f"Epoch {epoch+1}: Saving model and checkpoint")
315
-
316
- save_checkpoint(
317
- checkpoint_path, epoch, global_step,
318
- model_raw, optimizer, scheduler, best_val_loss
319
- )
320
-
321
- print(f"╠════════════════════════════════════════════════════════════════════════════════════╣")
322
- print(f"║ ĐÁNH GIÁ {phase_name.upper():<4} TRÊN TEST SET ║")
323
- print(f"╠════════════════════════════════════════════════════════════════════════════════════╣")
324
-
325
- model_raw.load_state_dict(torch.load(model_save_path, map_location=device))
326
- model_raw.eval()
327
- test_loss = torch.zeros((), device=device)
328
- with torch.no_grad():
329
- for X_batch, Y_batch, sample_weight, attention_mask, has_padding in test_ds:
330
- X_batch = X_batch.to(device, non_blocking=True)
331
- Y_batch = Y_batch.to(device, non_blocking=True)
332
- sample_weight = sample_weight.to(device, non_blocking=True)
333
- attention_mask = attention_mask.to(device, non_blocking=True)
334
-
335
- with autocast(device_type=device.type, dtype=amp_dtype, enabled=amp_enabled):
336
- hidden = model_features(
337
- X_batch,
338
- attention_mask=attention_mask,
339
- has_padding=has_padding,
340
- )
341
- hidden_flat = hidden.reshape(-1, hidden.size(-1))
342
- targets_flat = Y_batch.reshape(-1)
343
- weight_flat = sample_weight.reshape(-1)
344
- num_valid_tokens = weight_flat.sum()
345
- loss_sum = chunked_lm_loss(
346
- hidden_flat,
347
- model_raw.lm_head.weight,
348
- targets_flat,
349
- weight_flat,
350
- chunk_size=LM_LOSS_CHUNK_SIZE,
351
- )
352
- loss = loss_sum / (num_valid_tokens + 1e-8)
353
- test_loss += loss.detach()
354
-
355
- test_loss = (test_loss / len(test_ds)).item()
356
- log_progress(f"{phase_name} Test Loss (CE only): {test_loss:.4f}")
357
- print(f"╠════════════════════════════════════════════════════════════════════════════════════╣")
358
-
359
- return test_loss
360
-
361
- with open(base_config_file, 'r') as f:
362
- config = json.load(f)
363
- with open(model_config_file, 'r') as f:
364
- config.update(json.load(f))
365
-
366
- vocab_size = config["vocab_size"]
367
- max_seq_len = config["max_seq_len"]
368
- d_model = config["d_model"]
369
- num_heads = config["num_heads"]
370
- num_kv_heads = config['num_kv_heads']
371
- num_layers = config["num_layers"]
372
- ff_dim = config["ff_dim"]
373
- dropout = config["dropout"]
374
- sft1_epochs = config["sft1_epochs"]
375
- sft2_epochs = config["sft2_epochs"]
376
- batch_size = config["batch_size"]
377
- train_ratio = config["train_ratio"]
378
- val_ratio = config["val_ratio"]
379
- sft1_learning_rate = config["sft1_learning_rate"]
380
- sft1_learning_weight_decay = config["sft1_learning_weight_decay"]
381
- sft2_learning_rate = config["sft2_learning_rate"]
382
- accumulation_steps = config["accumulation_steps"]
383
- sft2_learning_weight_decay = config["sft2_learning_weight_decay"]
384
- freeze = config["freeze"]
385
- num_workers = config["num_workers"]
386
- penalty_engine = (PenaltyEngine()
387
- .add_rule(WrongTokenMarginPenalty(weight=config["penalty_margin_weight"], detach_max=config["penalty_margin_detach_max"]))
388
- .add_rule(WrongTokenEntropyPenalty(weight=config["penalty_entropy_weight"], min_entropy=config["penalty_entropy_min_entropy"]))
389
- .add_rule(FocalOverconfidencePenalty(weight=config["penalty_focal_weight"], gamma=config["penalty_focal_gamma"]))
390
- )
391
-
392
- if torch.cuda.is_available():
393
- device = torch.device("cuda")
394
- torch.backends.cudnn.benchmark = True
395
- elif torch.backends.mps.is_available():
396
- device = torch.device("mps")
397
- else:
398
- device = torch.device("cpu")
399
-
400
- amp_enabled = device.type == "cuda"
401
- amp_dtype = torch.bfloat16 if amp_enabled and torch.cuda.is_bf16_supported() else torch.float16
402
- scaler = GradScaler("cuda", enabled=amp_enabled and amp_dtype == torch.float16)
403
- log_progress(f"Sử dụng device: {device}")
404
- if amp_enabled:
405
- precision_name = "BF16" if amp_dtype == torch.bfloat16 else "FP16 + GradScaler"
406
- log_progress(f"CUDA mixed precision: {precision_name}")
407
-
408
- model_raw = TransformerModel(vocab_size, d_model, num_heads, num_kv_heads, num_layers, ff_dim, max_seq_len, dropout).to(device)
409
- # Giữ alias `model` để không thay khung dispatch phase hiện tại.
410
- model = model_raw
411
- if args.limited_max_autotune:
412
- compile_options = {
413
- "max_autotune": True,
414
- "max_autotune_gemm_backends": "ATEN,TRITON",
415
- "max_autotune_gemm_search_space": "DEFAULT",
416
- "epilogue_fusion": True,
417
- "triton.cudagraphs": False,
418
- }
419
- model_features = torch.compile(
420
- model_raw.forward_features,
421
- dynamic=True,
422
- options=compile_options,
423
- )
424
- log_progress("torch.compile: limited max-autotune (ATEN,TRITON; DEFAULT; dynamic=True; CUDA Graph off)")
425
- else:
426
- model_features = torch.compile(model_raw.forward_features, dynamic=True)
427
-
428
- phase = args.phase
429
- if phase == "sft1":
430
- log_progress("Load model từ pretrain...")
431
- model.load_state_dict(torch.load(pretrained_save_path, map_location=device))
432
- freeze_layers(model, freeze)
433
-
434
- optimizer_sft1 = optim.AdamW(
435
- filter(lambda p: p.requires_grad, model.parameters()),
436
- lr=sft1_learning_rate, weight_decay=sft1_learning_weight_decay,
437
- fused=amp_enabled,
438
- )
439
-
440
- test_loss = finetune(
441
- model, optimizer_sft1, device, SFT1_data_ids_file, sub_data=None,
442
- num_epochs=sft1_epochs,
443
- model_save_path=sft1_save_path,
444
- train_ratio=train_ratio, val_ratio=val_ratio,
445
- batch_size=batch_size, phase_name="sft1",
446
- num_workers=num_workers,
447
- penalty_engine=penalty_engine,
448
- resample_per_epoch=False,
449
- resume_checkpoint_path=None,
450
- profile_kernels=args.profile_kernels,
451
- )
452
- log_progress(f"SFT1 Test Loss: {test_loss:.4f}")
453
-
454
- elif phase == "sft1_resume":
455
- log_progress("SFT1 resume: khởi tạo model skeleton trước khi load checkpoint...")
456
- freeze_layers(model, freeze)
457
-
458
- optimizer_sft1 = optim.AdamW(
459
- filter(lambda p: p.requires_grad, model.parameters()),
460
- lr=sft1_learning_rate, weight_decay=sft1_learning_weight_decay,
461
- fused=amp_enabled,
462
- )
463
-
464
- test_loss = finetune(
465
- model, optimizer_sft1, device, SFT1_data_ids_file, sub_data=None,
466
- num_epochs=sft1_epochs,
467
- model_save_path=sft1_save_path,
468
- train_ratio=train_ratio, val_ratio=val_ratio,
469
- batch_size=batch_size, phase_name="sft1",
470
- num_workers=num_workers,
471
- penalty_engine=penalty_engine,
472
- resample_per_epoch=False,
473
- resume_checkpoint_path=sft1_ckpt_path,
474
- profile_kernels=args.profile_kernels,
475
- )
476
- log_progress(f"SFT1 Test Loss: {test_loss:.4f}")
477
-
478
- elif phase == "sft2":
479
- log_progress("Load model từ sft1...")
480
- model.load_state_dict(torch.load(sft1_save_path, map_location=device))
481
- unfreeze_all_layers(model)
482
- freeze_layers(model, freeze)
483
-
484
- optimizer_sft2 = optim.AdamW(
485
- filter(lambda p: p.requires_grad, model.parameters()),
486
- lr=sft2_learning_rate, weight_decay=sft2_learning_weight_decay,
487
- fused=amp_enabled,
488
- )
489
-
490
- test_loss = finetune(
491
- model, optimizer_sft2, device, SFT2_data_ids_file, SFT1_data_ids_file,
492
- num_epochs=sft2_epochs,
493
- model_save_path=sft2_save_path,
494
- train_ratio=train_ratio, val_ratio=val_ratio,
495
- batch_size=batch_size, phase_name="sft2",
496
- num_workers=num_workers,
497
- penalty_engine=penalty_engine,
498
- resample_per_epoch=True,
499
- resume_checkpoint_path=None,
500
- profile_kernels=args.profile_kernels,
501
- )
502
- log_progress(f"SFT2 Test Loss: {test_loss:.4f}")
503
-
504
- elif phase == "sft2_resume":
505
- log_progress("SFT2 resume: khởi tạo model skeleton trước khi load checkpoint...")
506
- unfreeze_all_layers(model)
507
- freeze_layers(model, freeze)
508
-
509
- optimizer_sft2 = optim.AdamW(
510
- filter(lambda p: p.requires_grad, model.parameters()),
511
- lr=sft2_learning_rate, weight_decay=sft2_learning_weight_decay,
512
- fused=amp_enabled,
513
- )
514
-
515
- test_loss = finetune(
516
- model, optimizer_sft2, device, SFT2_data_ids_file, SFT1_data_ids_file,
517
- num_epochs=sft2_epochs,
518
- model_save_path=sft2_save_path,
519
- train_ratio=train_ratio, val_ratio=val_ratio,
520
- batch_size=batch_size, phase_name="sft2",
521
- num_workers=num_workers,
522
- penalty_engine=penalty_engine,
523
- resample_per_epoch=True,
524
- resume_checkpoint_path=sft2_ckpt_path,
525
- profile_kernels=args.profile_kernels,
526
- )
527
- log_progress(f"SFT2 Test Loss: {test_loss:.4f}")
528
-
529
- elif phase == "full":
530
- # ── SFT1 ──
531
- log_progress("Load model từ pretrain...")
532
- model.load_state_dict(torch.load(pretrained_save_path, map_location=device))
533
- freeze_layers(model, freeze)
534
-
535
- optimizer_sft1 = optim.AdamW(
536
- filter(lambda p: p.requires_grad, model.parameters()),
537
- lr=sft1_learning_rate, weight_decay=sft1_learning_weight_decay,
538
- fused=amp_enabled,
539
- )
540
-
541
- test_loss = finetune(
542
- model, optimizer_sft1, device, SFT1_data_ids_file, sub_data=None,
543
- num_epochs=sft1_epochs,
544
- model_save_path=sft1_save_path,
545
- train_ratio=train_ratio, val_ratio=val_ratio,
546
- batch_size=batch_size, phase_name="sft1",
547
- num_workers=num_workers,
548
- penalty_engine=penalty_engine,
549
- resample_per_epoch=False,
550
- resume_checkpoint_path=None,
551
- profile_kernels=args.profile_kernels,
552
- )
553
- log_progress(f"SFT1 Test Loss: {test_loss:.4f}")
554
-
555
- # ── SFT2 ──
556
- log_progress("Load model từ sft1...")
557
- model.load_state_dict(torch.load(sft1_save_path, map_location=device))
558
- unfreeze_all_layers(model)
559
- freeze_layers(model, freeze)
560
-
561
- optimizer_sft2 = optim.AdamW(
562
- filter(lambda p: p.requires_grad, model.parameters()),
563
- lr=sft2_learning_rate, weight_decay=sft2_learning_weight_decay,
564
- fused=amp_enabled,
565
- )
566
-
567
- test_loss = finetune(
568
- model, optimizer_sft2, device, SFT2_data_ids_file, SFT1_data_ids_file,
569
- num_epochs=sft2_epochs,
570
- model_save_path=sft2_save_path,
571
- train_ratio=train_ratio, val_ratio=val_ratio,
572
- batch_size=batch_size, phase_name="sft2",
573
- num_workers=num_workers,
574
- penalty_engine=penalty_engine,
575
- resample_per_epoch=True,
576
- resume_checkpoint_path=None,
577
- profile_kernels=args.profile_kernels,
578
- )
579
- log_progress(f"SFT2 Test Loss: {test_loss:.4f}")
580
-
581
- log_progress(f"Model cuối cùng lưu tại: {model_dir / (phase.split('_')[0] + f'_{model_size}.pt')}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/training/pretrain.py DELETED
@@ -1,421 +0,0 @@
1
- from pathlib import Path
2
- import torch
3
- import torch.nn as nn
4
- import torch.optim as optim
5
- from torch.amp import GradScaler, autocast
6
- import torch._dynamo
7
- import logging
8
- import json
9
- import gc
10
- import argparse
11
- import math
12
- from src.utils.utils import (get_step_lr_lambda, log_progress, load_checkpoint, save_checkpoint, estimate_training_flops, print_training_flops_summary, TflopsBenchmarker, KernelLogger)
13
- from src.utils.chunked_loss import chunked_lm_loss
14
- from src.data.Dataset import Dataset, split_train_val_test, load_data
15
- from src.model.TransformerModel import TransformerModel
16
-
17
- parser = argparse.ArgumentParser()
18
- parser.add_argument("--model", type=str, required=True, help="Model size: 100M or 500M")
19
- parser.add_argument(
20
- "--phase", type=str, required=True,
21
- choices=["pretrain", "pretrain_resume", "continued_pretrain", "continued_pretrain_resume", "full"],
22
- help="Training phase: pretrain | pretrain_resume | continued_pretrain | continued_pretrain_resume | full"
23
- )
24
- parser.add_argument(
25
- "--profile-kernels", action="store_true",
26
- help="Chỉ chạy 1 lần duy nhất, log tên kernel CPU/CUDA tại 1 step rồi thôi (dùng để debug performance)"
27
- )
28
- parser.add_argument(
29
- "--limited-max-autotune", action="store_true",
30
- help="Bật max-autotune giới hạn cho forward_features (chỉ ATEN/TRITON, không exhaustive/CUDA Graph)"
31
- )
32
- args = parser.parse_args()
33
- torch._dynamo.config.cache_size_limit = 24
34
- logging.getLogger("torch._inductor.select_algorithm").setLevel(logging.ERROR)
35
- model_size = args.model
36
-
37
- current_file = Path(__file__).resolve()
38
- project_root = current_file.parent.parent.parent
39
- config_dir = project_root / "config"
40
- data_dir = project_root / "data"
41
- model_dir = project_root / "model"
42
- src_dir = project_root / "src"
43
- base_config_file = config_dir / "base.json"
44
- model_config_file = config_dir / f"{args.model}.json"
45
- model_dir.mkdir(parents=True, exist_ok=True)
46
- data_processed_dir = project_root / "data" / "processed"
47
- pretrain_tokenized_file = data_processed_dir / "pretrain_manifest.json"
48
- continued_pretrain_tokenized_file = data_processed_dir / "continued_pretrain_data_ids.npz"
49
-
50
- pretrained_save_path = model_dir / f"pretrained_{model_size}.pt"
51
- pretrained_ckpt_path = model_dir / f"pretrained_{model_size}.ckpt.pt"
52
- continued_pretrained_save_path = model_dir / f"continued_pretrained_{model_size}.pt"
53
- continued_pretrained_ckpt_path = model_dir / f"continued_pretrained_{model_size}.ckpt.pt"
54
-
55
- LM_LOSS_CHUNK_SIZE = 4096
56
-
57
- def train_loop(data_type, tokenized_file, epochs, learning_rate, weight_decay, num_workers, extra_file=None, model_save_path=None, resume_checkpoint_path=None, profile_kernels=False):
58
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
59
- print("║ BAT ĐAU LOAD DATA ║")
60
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
61
-
62
- if extra_file is None:
63
- X, Y, lengths = load_data(data_type, tokenized_file)
64
- else:
65
- X, Y, lengths = load_data(data_type, tokenized_file, extra_file)
66
-
67
- X_train, Y_train, _, lengths_train, X_val, Y_val, _, lengths_val, X_test, Y_test, _, lengths_test = split_train_val_test(X, Y, None, lengths, train_ratio, val_ratio)
68
- log_progress(f"Train: {len(X_train)}, Val: {len(X_val)}, Test: {len(X_test)}")
69
-
70
- train_ds = Dataset.create_dataloader(X_train, Y_train, lengths_train, batch_size, max_seq_len, num_workers, shuffle=True)
71
- val_ds = Dataset.create_dataloader(X_val, Y_val, lengths_val, batch_size, max_seq_len, num_workers, shuffle=False)
72
- test_ds = Dataset.create_dataloader(X_test, Y_test, lengths_test, batch_size, max_seq_len, num_workers, shuffle=False)
73
-
74
- del X_train, Y_train, lengths_train, X_val, Y_val, lengths_val, X_test, Y_test, lengths_test
75
- gc.collect()
76
-
77
- global optimizer, scaler
78
- optimizer = optim.AdamW(model_raw.parameters(), lr=learning_rate, weight_decay=weight_decay, fused=True)
79
-
80
- total_steps = math.ceil(len(train_ds) / accumulation_steps) * epochs
81
- warmup_steps = total_steps // 10
82
-
83
- lr_lambda = get_step_lr_lambda(warmup_steps, total_steps)
84
- scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)
85
-
86
- best_val_loss = float('inf')
87
- global_step = 0
88
- start_epoch = 0
89
-
90
- checkpoint_path = model_save_path.with_suffix(".ckpt.pt") if model_save_path is not None else None
91
- if resume_checkpoint_path is not None:
92
- if resume_checkpoint_path.exists():
93
- start_epoch, global_step, best_val_loss = load_checkpoint(
94
- resume_checkpoint_path, model_raw, optimizer, scheduler, device
95
- )
96
- else:
97
- log_progress(f"[WARNING] Checkpoint not found at {resume_checkpoint_path}. Starting from scratch.")
98
-
99
- flops_info = estimate_training_flops(model=model_raw, num_layers=num_layers, max_seq_len=max_seq_len, d_model=d_model, epochs=epochs, batches_per_epoch=len(train_ds), batch_size=batch_size)
100
- print_training_flops_summary(flops_info, epochs)
101
- bench = TflopsBenchmarker(flops_per_token=flops_info["flops_per_token"], total_flops_needed=flops_info["total_flops"], total_batches_per_epoch=flops_info["total_batches_per_epoch"], epochs=epochs, device=device, warmup_steps=100, target_steps=500)
102
- kernel_logger = KernelLogger(enabled=profile_kernels, log_step=500)
103
-
104
- for epoch in range(start_epoch, epochs):
105
- model_raw.train()
106
- # Gom loss trên GPU (tensor), chỉ .item() khi thực sự cần in/log ->
107
- # tránh sync CPU-GPU mỗi micro-batch, kết quả loss cuối cùng không đổi.
108
- train_loss = torch.zeros((), device=device)
109
- batch_count = 0
110
- total_batches = len(train_ds)
111
- optimizer.zero_grad()
112
-
113
- for batch_idx, (X_batch, Y_batch, sample_weight, attention_mask, has_padding) in enumerate(train_ds):
114
- X_batch = X_batch.to(device, non_blocking=True)
115
- Y_batch = Y_batch.to(device, non_blocking=True)
116
- sample_weight = sample_weight.to(device, non_blocking=True)
117
- attention_mask = attention_mask.to(device, non_blocking=True)
118
-
119
- _is_bench_step = bench.is_bench_step(epoch, start_epoch, batch_idx)
120
- if _is_bench_step:
121
- bench.on_step_begin(batch_idx, X_batch.shape[0] * X_batch.shape[1])
122
-
123
- _is_kernel_log = kernel_logger.should_log(epoch, start_epoch, batch_idx)
124
- if _is_kernel_log:
125
- kernel_logger.start()
126
-
127
- with autocast(device_type=device.type, dtype=amp_dtype, enabled=amp_enabled):
128
- hidden = model_features(X_batch, attention_mask=attention_mask, has_padding=has_padding)
129
- hidden_flat = hidden.reshape(-1, hidden.size(-1))
130
- targets_flat = Y_batch.reshape(-1)
131
- weight_flat = sample_weight.reshape(-1)
132
- num_valid_tokens = weight_flat.sum()
133
- loss_sum = chunked_lm_loss(
134
- hidden_flat, model_raw.lm_head.weight, targets_flat, weight_flat,
135
- chunk_size=LM_LOSS_CHUNK_SIZE,
136
- )
137
- loss = loss_sum / (num_valid_tokens + 1e-8)
138
-
139
- scaled_loss = loss / accumulation_steps
140
- scaler.scale(scaled_loss).backward()
141
-
142
- if _is_kernel_log:
143
- kernel_logger.stop_and_report(batch_idx, epoch, device)
144
-
145
- if (batch_idx + 1) % accumulation_steps == 0 or (batch_idx + 1) == total_batches:
146
- scaler.unscale_(optimizer)
147
- torch.nn.utils.clip_grad_norm_(model_raw.parameters(), max_norm=1.0)
148
- scaler.step(optimizer)
149
- scaler.update()
150
-
151
- optimizer.zero_grad()
152
- scheduler.step()
153
- global_step += 1
154
-
155
- train_loss += loss.detach()
156
- batch_count += 1
157
- current_lr = optimizer.param_groups[0]['lr']
158
-
159
- if (batch_idx + 1) % 10 == 0 or (batch_idx + 1) == total_batches:
160
- avg_loss = (train_loss / batch_count).item()
161
- print(f"\rEpoch {epoch+1}/{epochs} - Step {global_step}/{total_steps} - loss: {avg_loss:.4f} - lr: {current_lr:.2e}", end='')
162
-
163
- if _is_bench_step:
164
- bench.on_step_end(batch_idx)
165
-
166
- del X_batch, Y_batch, hidden, hidden_flat, loss, loss_sum, sample_weight, attention_mask
167
-
168
- print()
169
-
170
- train_loss = (train_loss / len(train_ds)).item()
171
- model_raw.eval()
172
- val_loss = torch.zeros((), device=device)
173
-
174
- with torch.no_grad():
175
- for X_batch, Y_batch, sample_weight, attention_mask, has_padding in val_ds:
176
- X_batch = X_batch.to(device, non_blocking=True)
177
- Y_batch = Y_batch.to(device, non_blocking=True)
178
- sample_weight = sample_weight.to(device, non_blocking=True)
179
- attention_mask = attention_mask.to(device, non_blocking=True)
180
-
181
- with autocast(device_type=device.type, dtype=amp_dtype, enabled=amp_enabled):
182
- hidden = model_features(X_batch, attention_mask=attention_mask, has_padding=has_padding)
183
- hidden_flat = hidden.reshape(-1, hidden.size(-1))
184
- targets_flat = Y_batch.reshape(-1)
185
- weight_flat = sample_weight.reshape(-1)
186
- num_valid_tokens = weight_flat.sum()
187
- loss_sum = chunked_lm_loss(
188
- hidden_flat, model_raw.lm_head.weight, targets_flat, weight_flat,
189
- chunk_size=LM_LOSS_CHUNK_SIZE,
190
- )
191
-
192
- loss = loss_sum / (num_valid_tokens + 1e-8)
193
- val_loss += loss.detach()
194
-
195
- del X_batch, Y_batch, hidden, hidden_flat, loss, loss_sum, sample_weight, attention_mask
196
-
197
- val_loss = (val_loss / len(val_ds)).item()
198
- log_progress(f"Epoch {epoch+1}/{epochs} Train Loss: {train_loss:.4f} Val Loss: {val_loss:.4f}")
199
-
200
- if val_loss < best_val_loss:
201
- best_val_loss = val_loss
202
- if model_save_path is not None:
203
- torch.save(model_raw.state_dict(), model_save_path)
204
- print(f"Epoch {epoch+1}: val_loss improved to {val_loss:.5f}, saving model to {model_save_path}")
205
- else:
206
- torch.save(model_raw.state_dict(), pretrained_save_path)
207
- print(f"Epoch {epoch+1}: val_loss improved to {val_loss:.5f}, saving model to default path")
208
-
209
- if checkpoint_path is not None:
210
- save_checkpoint(
211
- checkpoint_path, epoch, global_step,
212
- model_raw, optimizer, scheduler, best_val_loss
213
- )
214
- log_progress(f"Checkpoint saved → {checkpoint_path}")
215
-
216
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
217
- print("║ DANH GIA TREN TEST SET ║")
218
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
219
-
220
- model_raw.eval()
221
- test_loss = torch.zeros((), device=device)
222
-
223
- with torch.no_grad():
224
- for X_batch, Y_batch, sample_weight, attention_mask, has_padding in test_ds:
225
- X_batch = X_batch.to(device, non_blocking=True)
226
- Y_batch = Y_batch.to(device, non_blocking=True)
227
- sample_weight = sample_weight.to(device, non_blocking=True)
228
- attention_mask = attention_mask.to(device, non_blocking=True)
229
-
230
- with autocast(device_type=device.type, dtype=amp_dtype, enabled=amp_enabled):
231
- hidden = model_features(X_batch, attention_mask=attention_mask, has_padding=has_padding)
232
- hidden_flat = hidden.reshape(-1, hidden.size(-1))
233
- targets_flat = Y_batch.reshape(-1)
234
- weight_flat = sample_weight.reshape(-1)
235
- num_valid_tokens = weight_flat.sum()
236
- loss_sum = chunked_lm_loss(
237
- hidden_flat, model_raw.lm_head.weight, targets_flat, weight_flat,
238
- chunk_size=LM_LOSS_CHUNK_SIZE,
239
- )
240
-
241
- loss = loss_sum / (num_valid_tokens + 1e-8)
242
- test_loss += loss.detach()
243
-
244
- del X_batch, Y_batch, hidden, hidden_flat, loss, loss_sum, sample_weight, attention_mask
245
-
246
- test_loss = (test_loss / len(test_ds)).item()
247
- log_progress(f"Test Loss: {test_loss:.4f}")
248
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
249
-
250
- return test_loss
251
-
252
- with open(base_config_file, 'r') as f:
253
- config = json.load(f)
254
- with open(model_config_file, 'r') as f:
255
- config.update(json.load(f))
256
-
257
- vocab_size = config['vocab_size']
258
- max_seq_len = config['max_seq_len']
259
- d_model = config['d_model']
260
- num_heads = config['num_heads']
261
- num_kv_heads = config['num_kv_heads']
262
- num_layers = config['num_layers']
263
- ff_dim = config['ff_dim']
264
- dropout = config['dropout']
265
- pretrain_epochs = config['pretrain_epochs']
266
- continued_pretrain_epochs = config['continued_pretrain_epochs']
267
- batch_size = config['batch_size']
268
- train_ratio = config['train_ratio']
269
- val_ratio = config['val_ratio']
270
- pretrain_learning_rate = config['pretrain_learning_rate']
271
- continued_pretrain_learning_rate = config['continued_pretrain_learning_rate']
272
- accumulation_steps = config['accumulation_steps']
273
- pretrain_weight_decay = config['pretrain_weight_decay']
274
- continued_pretrain_weight_decay = config['continued_pretrain_weight_decay']
275
- num_workers = config['num_workers']
276
-
277
- if torch.cuda.is_available():
278
- device = torch.device("cuda")
279
- torch.backends.cudnn.benchmark = True
280
- elif torch.backends.mps.is_available():
281
- device = torch.device("mps")
282
- else:
283
- device = torch.device("cpu")
284
-
285
- amp_enabled = device.type == "cuda"
286
- amp_dtype = torch.bfloat16 if amp_enabled and torch.cuda.is_bf16_supported() else torch.float16
287
- scaler = GradScaler("cuda", enabled=amp_enabled and amp_dtype == torch.float16)
288
-
289
- if amp_enabled:
290
- precision_name = "BF16" if amp_dtype == torch.bfloat16 else "FP16 + GradScaler"
291
- log_progress(f"CUDA mixed precision: {precision_name}")
292
-
293
- model_raw = TransformerModel(vocab_size, d_model, num_heads, num_kv_heads, num_layers, ff_dim, max_seq_len, dropout).to(device)
294
- model = torch.compile(model_raw, dynamic=True)
295
- if args.limited_max_autotune:
296
- compile_options = {
297
- "max_autotune": True,
298
- "max_autotune_gemm_backends": "ATEN,TRITON",
299
- "max_autotune_gemm_search_space": "DEFAULT",
300
- "epilogue_fusion": True,
301
- "triton.cudagraphs": False,
302
- }
303
- model_features = torch.compile(
304
- model_raw.forward_features,
305
- dynamic=True,
306
- options=compile_options,
307
- )
308
- log_progress("torch.compile: limited max-autotune (ATEN,TRITON; DEFAULT; dynamic=True; CUDA Graph off)")
309
- else:
310
- model_features = torch.compile(model_raw.forward_features, dynamic=True)
311
- optimizer = optim.AdamW(model_raw.parameters(), lr=pretrain_learning_rate, weight_decay=pretrain_weight_decay, fused=True)
312
-
313
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
314
- print("║ BAT ĐAU TRAINING ║")
315
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
316
-
317
- phase = args.phase
318
-
319
- if phase == "pretrain":
320
- pretrain_test_loss = train_loop(
321
- data_type="pretrain",
322
- tokenized_file=pretrain_tokenized_file,
323
- epochs=pretrain_epochs,
324
- learning_rate=pretrain_learning_rate,
325
- weight_decay=pretrain_weight_decay,
326
- num_workers=num_workers,
327
- model_save_path=pretrained_save_path,
328
- resume_checkpoint_path=None,
329
- profile_kernels=args.profile_kernels,
330
- )
331
- log_progress(f"Pretrain Test Loss: {pretrain_test_loss:.4f}")
332
-
333
- elif phase == "pretrain_resume":
334
- log_progress("Pretrain resume: khởi tạo model skeleton trước khi load checkpoint...")
335
- pretrain_test_loss = train_loop(
336
- data_type="pretrain",
337
- tokenized_file=pretrain_tokenized_file,
338
- epochs=pretrain_epochs,
339
- learning_rate=pretrain_learning_rate,
340
- weight_decay=pretrain_weight_decay,
341
- num_workers=num_workers,
342
- model_save_path=pretrained_save_path,
343
- resume_checkpoint_path=pretrained_ckpt_path,
344
- profile_kernels=args.profile_kernels,
345
- )
346
- log_progress(f"Pretrain Test Loss: {pretrain_test_loss:.4f}")
347
-
348
- elif phase == "continued_pretrain":
349
- log_progress("Load best model từ pretrain để tiếp tục training...")
350
- model_raw.load_state_dict(torch.load(pretrained_save_path, map_location=device))
351
- model_raw.to(device)
352
- optimizer = optim.AdamW(model_raw.parameters(), lr=continued_pretrain_learning_rate, weight_decay=continued_pretrain_weight_decay, fused=True)
353
- log_progress(f"Reset optimizer với learning rate: {continued_pretrain_learning_rate}")
354
-
355
- continued_pretrain_test_loss = train_loop(
356
- data_type="continued_pretrain",
357
- tokenized_file=continued_pretrain_tokenized_file,
358
- epochs=continued_pretrain_epochs,
359
- learning_rate=continued_pretrain_learning_rate,
360
- weight_decay=continued_pretrain_weight_decay,
361
- num_workers=num_workers,
362
- extra_file=pretrain_tokenized_file,
363
- model_save_path=continued_pretrained_save_path,
364
- resume_checkpoint_path=None,
365
- profile_kernels=args.profile_kernels,
366
- )
367
- log_progress(f"Continued Pretrain Test Loss: {continued_pretrain_test_loss:.4f}")
368
-
369
- elif phase == "continued_pretrain_resume":
370
- log_progress("Continued pretrain resume: khởi tạo model skeleton trước khi load checkpoint...")
371
- optimizer = optim.AdamW(model_raw.parameters(), lr=continued_pretrain_learning_rate, weight_decay=continued_pretrain_weight_decay, fused=True)
372
-
373
- continued_pretrain_test_loss = train_loop(
374
- data_type="continued_pretrain",
375
- tokenized_file=continued_pretrain_tokenized_file,
376
- epochs=continued_pretrain_epochs,
377
- learning_rate=continued_pretrain_learning_rate,
378
- weight_decay=continued_pretrain_weight_decay,
379
- num_workers=num_workers,
380
- extra_file=pretrain_tokenized_file,
381
- model_save_path=continued_pretrained_save_path,
382
- resume_checkpoint_path=continued_pretrained_ckpt_path,
383
- profile_kernels=args.profile_kernels,
384
- )
385
- log_progress(f"Continued Pretrain Test Loss: {continued_pretrain_test_loss:.4f}")
386
-
387
- elif phase == "full":
388
- pretrain_test_loss = train_loop(
389
- data_type="pretrain",
390
- tokenized_file=pretrain_tokenized_file,
391
- epochs=pretrain_epochs,
392
- learning_rate=pretrain_learning_rate,
393
- weight_decay=pretrain_weight_decay,
394
- num_workers=num_workers,
395
- model_save_path=pretrained_save_path,
396
- resume_checkpoint_path=None,
397
- profile_kernels=args.profile_kernels,
398
- )
399
-
400
- log_progress("Đang load best model từ pretrain để tiếp tục training...")
401
- model_raw.load_state_dict(torch.load(pretrained_save_path, map_location=device))
402
- model_raw.to(device)
403
- optimizer = optim.AdamW(model_raw.parameters(), lr=continued_pretrain_learning_rate, weight_decay=continued_pretrain_weight_decay, fused=True)
404
- log_progress(f"Reset optimizer với learning rate: {continued_pretrain_learning_rate}")
405
-
406
- continued_pretrain_test_loss = train_loop(
407
- data_type="continued_pretrain",
408
- tokenized_file=continued_pretrain_tokenized_file,
409
- epochs=continued_pretrain_epochs,
410
- learning_rate=continued_pretrain_learning_rate,
411
- weight_decay=continued_pretrain_weight_decay,
412
- num_workers=num_workers,
413
- extra_file=pretrain_tokenized_file,
414
- model_save_path=continued_pretrained_save_path,
415
- resume_checkpoint_path=None,
416
- profile_kernels=args.profile_kernels,
417
- )
418
-
419
- log_progress(f"Hoàn thành training!")
420
- log_progress(f"Pretrain Test Loss: {pretrain_test_loss:.4f}")
421
- log_progress(f"Continued Pretrain Test Loss: {continued_pretrain_test_loss:.4f}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/__init__.py DELETED
File without changes
src/utils/chunked_loss.py DELETED
@@ -1,90 +0,0 @@
1
- import torch
2
- import torch.nn.functional as F
3
- from torch.utils.checkpoint import checkpoint
4
-
5
- def chunked_lm_loss(hidden: torch.Tensor, weight: torch.Tensor, targets: torch.Tensor,
6
- token_weight: torch.Tensor, chunk_size: int = 4096) -> torch.Tensor:
7
- N = hidden.shape[0]
8
- total_loss = hidden.new_zeros((), dtype=torch.float32)
9
-
10
- def _chunk_fn(h_chunk, t_chunk, w_chunk):
11
- logits_chunk = F.linear(h_chunk, weight).float()
12
- loss_per_token = F.cross_entropy(logits_chunk, t_chunk, reduction='none')
13
- return (loss_per_token * w_chunk).sum()
14
-
15
- for start in range(0, N, chunk_size):
16
- end = min(start + chunk_size, N)
17
- chunk_args = (
18
- hidden[start:end],
19
- targets[start:end],
20
- token_weight[start:end],
21
- )
22
- if torch.is_grad_enabled():
23
- chunk_loss = checkpoint(
24
- _chunk_fn, *chunk_args, use_reentrant=False,
25
- )
26
- else:
27
- chunk_loss = _chunk_fn(*chunk_args)
28
- total_loss = total_loss + chunk_loss
29
-
30
- return total_loss
31
-
32
-
33
- def chunked_sft_loss(
34
- hidden: torch.Tensor,
35
- weight: torch.Tensor,
36
- inputs: torch.Tensor,
37
- targets: torch.Tensor,
38
- token_weight: torch.Tensor,
39
- penalty_engine=None,
40
- chunk_size: int = 4096,
41
- ):
42
- """Tính CE + penalty theo chunk mà không vật lý hóa full logits.
43
-
44
- PenaltyEngine trả về mean theo loss mask. Nhân lại với số token
45
- hợp lệ trong từng chunk rồi chia một lần ở caller giữ nguyên
46
- công thức mean trên toàn batch.
47
- """
48
- N = hidden.shape[0]
49
- total_ce = hidden.new_zeros((), dtype=torch.float32)
50
- total_penalty = hidden.new_zeros((), dtype=torch.float32)
51
-
52
- def _chunk_fn(h_chunk, i_chunk, t_chunk, w_chunk):
53
- logits_chunk = F.linear(h_chunk, weight).float()
54
- loss_per_token = F.cross_entropy(
55
- logits_chunk, t_chunk, reduction="none"
56
- )
57
- ce_sum = (loss_per_token * w_chunk).sum()
58
-
59
- if penalty_engine is None:
60
- penalty_sum = logits_chunk.new_zeros(())
61
- else:
62
- penalty_mean = penalty_engine(
63
- logits=logits_chunk.unsqueeze(0),
64
- inputs=i_chunk.unsqueeze(0),
65
- targets=t_chunk.unsqueeze(0),
66
- loss_mask=w_chunk.unsqueeze(0),
67
- )
68
- penalty_sum = penalty_mean * w_chunk.sum()
69
-
70
- return ce_sum, penalty_sum
71
-
72
- for start in range(0, N, chunk_size):
73
- end = min(start + chunk_size, N)
74
- chunk_args = (
75
- hidden[start:end],
76
- inputs[start:end],
77
- targets[start:end],
78
- token_weight[start:end],
79
- )
80
- if torch.is_grad_enabled():
81
- ce_sum, penalty_sum = checkpoint(
82
- _chunk_fn, *chunk_args, use_reentrant=False,
83
- )
84
- else:
85
- ce_sum, penalty_sum = _chunk_fn(*chunk_args)
86
-
87
- total_ce = total_ce + ce_sum
88
- total_penalty = total_penalty + penalty_sum
89
-
90
- return total_ce, total_penalty
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/utils.py DELETED
@@ -1,331 +0,0 @@
1
- import torch
2
- import time
3
- import json
4
- from pathlib import Path
5
-
6
- def get_step_lr_lambda(warmup_steps, total_steps):
7
- def lr_lambda(current_step):
8
- if current_step < warmup_steps:
9
- return float(current_step) / float(max(1, warmup_steps))
10
- elif current_step < total_steps * 0.4:
11
- return 1.0
12
- else:
13
- progress = (current_step - total_steps * 0.4) / (total_steps * 0.3)
14
- return max(0.1, 1.0 - 0.9 * progress)
15
- return lr_lambda
16
-
17
- def freeze_layers(model, freeze):
18
- for idx in freeze:
19
- for param in model.blocks[idx].parameters():
20
- param.requires_grad = False
21
-
22
- def unfreeze_all_layers(model):
23
- for param in model.parameters():
24
- param.requires_grad = True
25
-
26
- def log_progress(text):
27
- fixed_width = 82
28
- formatted_text = f"║ {text:<{fixed_width}} ║"
29
- print(formatted_text)
30
-
31
- def save_checkpoint(path: Path, epoch: int, global_step: int, model, optimizer, scheduler, best_val_loss: float):
32
- """Save full training state so training can be resumed exactly."""
33
- torch.save({
34
- "epoch": epoch,
35
- "global_step": global_step,
36
- "model_state_dict": model.state_dict(),
37
- "optimizer_state_dict": optimizer.state_dict(),
38
- "scheduler_state_dict": scheduler.state_dict(),
39
- "best_val_loss": best_val_loss,
40
- }, path)
41
-
42
-
43
- def load_checkpoint(path: Path, model, optimizer, scheduler, device):
44
- """Load full training state. Returns (start_epoch, global_step, best_val_loss)."""
45
- ckpt = torch.load(path, map_location=device)
46
- model.load_state_dict(ckpt["model_state_dict"])
47
- optimizer.load_state_dict(ckpt["optimizer_state_dict"])
48
- scheduler.load_state_dict(ckpt["scheduler_state_dict"])
49
- start_epoch = ckpt["epoch"] + 1
50
- global_step = ckpt["global_step"]
51
- best_val_loss = ckpt["best_val_loss"]
52
- log_progress(f"Resumed from checkpoint: epoch {ckpt['epoch']+1}, step {global_step}, best_val_loss {best_val_loss:.5f}")
53
- return start_epoch, global_step, best_val_loss
54
-
55
- def format_time(seconds: float) -> str:
56
- h = int(seconds // 3600)
57
- m = int((seconds % 3600) // 60)
58
- s = seconds % 60
59
- return f"{h}h {m}m {s:.1f}s"
60
-
61
- def estimate_training_flops(
62
- model: torch.nn.Module,
63
- num_layers: int,
64
- max_seq_len: int,
65
- d_model: int,
66
- epochs: int,
67
- batches_per_epoch: int,
68
- batch_size: int,
69
- ) -> dict:
70
- n_params = sum(p.numel() for p in model.parameters())
71
- flops_per_token = 6 * n_params + 6 * num_layers * max_seq_len * d_model
72
- total_token_slots = epochs * batches_per_epoch * batch_size * max_seq_len
73
- total_flops = flops_per_token * total_token_slots
74
- total_tflops = total_flops / 1e12
75
-
76
- return {
77
- "n_params": n_params,
78
- "flops_per_token": flops_per_token,
79
- "total_batches_per_epoch": batches_per_epoch,
80
- "total_token_slots": total_token_slots,
81
- "total_flops": total_flops,
82
- "total_tflops": total_tflops,
83
- }
84
-
85
- def print_training_flops_summary(flops_info: dict, epochs: int) -> None:
86
- """In bảng tổng FLOPs cần cho training."""
87
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
88
- print("║ TINH TONG FLOPs CAN CHO TOAN BO TRAINING ║")
89
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
90
- print(f"So tham so model: {flops_info['n_params']:,}")
91
- print(
92
- f"Tong so TFLOP can cho toan bo qua trinh training "
93
- f"({epochs} epoch, {flops_info['total_batches_per_epoch']} batch/epoch): "
94
- f"{flops_info['total_tflops']:,.2f} TFLOP"
95
- )
96
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
97
-
98
- class TflopsBenchmarker:
99
- def __init__(
100
- self,
101
- flops_per_token: float,
102
- total_flops_needed: float,
103
- total_batches_per_epoch: int,
104
- epochs: int,
105
- device: torch.device,
106
- warmup_steps: int = 100,
107
- target_steps: int = 500,
108
- ):
109
- self.flops_per_token = flops_per_token
110
- self.total_flops_needed = total_flops_needed
111
- self.total_batches_per_epoch = total_batches_per_epoch
112
- self.epochs = epochs
113
- self.device = device
114
-
115
- self.warmup_steps = min(warmup_steps, total_batches_per_epoch)
116
- self.target_steps = min(target_steps, total_batches_per_epoch)
117
- self.reported = self.target_steps <= self.warmup_steps
118
-
119
- self.tokens = 0
120
- self.start_time = None
121
-
122
- def is_bench_step(self, epoch: int, start_epoch: int, batch_idx: int) -> bool:
123
- return (
124
- (not self.reported)
125
- and (epoch == start_epoch)
126
- and (self.warmup_steps <= batch_idx < self.target_steps)
127
- )
128
-
129
- def on_step_begin(self, batch_idx: int, batch_tokens: int) -> None:
130
- """Gọi khi bắt đầu step nằm trong vùng đo."""
131
- if batch_idx == self.warmup_steps:
132
- if self.device.type == "cuda":
133
- torch.cuda.synchronize()
134
- self.start_time = time.perf_counter()
135
- self.tokens += batch_tokens
136
-
137
- def on_step_end(self, batch_idx: int) -> None:
138
- """Gọi ở step cuối vùng đo → in kết quả."""
139
- if batch_idx != self.target_steps - 1:
140
- return
141
-
142
- if self.device.type == "cuda":
143
- torch.cuda.synchronize()
144
-
145
- elapsed = time.perf_counter() - self.start_time
146
- measured_steps = self.target_steps - self.warmup_steps
147
- flops = self.flops_per_token * self.tokens
148
- tflops_per_sec = (flops / elapsed) / 1e12 if elapsed > 0 else 0.0
149
- avg_time_per_step = elapsed / measured_steps if measured_steps > 0 else 0.0
150
- token_slots_per_sec = self.tokens / elapsed if elapsed > 0 else 0.0
151
-
152
- print()
153
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
154
- print(
155
- f"[DO TFLOPS] Do {measured_steps} step training that "
156
- f"(step {self.warmup_steps + 1} → {self.target_steps}), "
157
- f"mat {elapsed:.2f}s (da bo qua {self.warmup_steps} step warm-up dau)"
158
- )
159
- print(f"[DO TOC DO] Trung binh moi step: {avg_time_per_step * 1000:,.2f} ms/step")
160
- print(f"[DO TOC DO] Thong luong tensor thuc te: {token_slots_per_sec:,.0f} token-slot/s")
161
-
162
- if tflops_per_sec > 0:
163
- print(
164
- f"[DO TFLOPS] TFLOPS uoc tinh theo cong thuc model: "
165
- f"{tflops_per_sec:,.2f} TFLOPS"
166
- )
167
-
168
- total_steps_all_epochs = self.epochs * self.total_batches_per_epoch
169
-
170
- est_time_actual = avg_time_per_step * total_steps_all_epochs
171
- print(f"[Cach 1 - Thuc te] Uoc tinh thoi gian train (ngoai suy toc do do duoc): {format_time(est_time_actual)}")
172
-
173
- # Cách 2 – lý thuyết
174
- est_time_theory = self.total_flops_needed / (tflops_per_sec * 1e12)
175
- print(f"[Cach 2 - Ly thuyet] Uoc tinh thoi gian train (Tong FLOPs / TFLOPS do duoc): {format_time(est_time_theory)}")
176
- else:
177
- print("[CẢNH BÁO] Thời gian đo quá nhỏ để tính TFLOPS đáng tin cậy.")
178
-
179
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
180
- self.reported = True
181
-
182
- class KernelLogger:
183
- def __init__(self, enabled: bool = False, log_step: int = 500):
184
- self.enabled = enabled
185
- self.log_step = log_step
186
- self.reported = not enabled
187
- self._prof_ctx = None
188
-
189
- def should_log(self, epoch: int, start_epoch: int, batch_idx: int) -> bool:
190
- return (
191
- (not self.reported)
192
- and (epoch == start_epoch)
193
- and (batch_idx == self.log_step)
194
- )
195
-
196
- def start(self):
197
- self._prof_ctx = torch.profiler.profile(
198
- activities=[
199
- torch.profiler.ProfilerActivity.CPU,
200
- torch.profiler.ProfilerActivity.CUDA,
201
- ],
202
- record_shapes=True,
203
- profile_memory=True,
204
- with_stack=True,
205
- with_flops=True,
206
- )
207
- self._prof_ctx.__enter__()
208
-
209
- def stop_and_report(self, batch_idx: int, epoch: int, device: torch.device):
210
- if device.type == "cuda":
211
- torch.cuda.synchronize()
212
-
213
- self._prof_ctx.__exit__(None, None, None)
214
-
215
- # Gom theo operator + input shape để chẩn đoán GEMM/Linear shape
216
- # mà không phải lưu timeline gồm hàng triệu event.
217
- key_avgs = self._prof_ctx.key_averages(group_by_input_shape=True)
218
-
219
- def _self_cuda_us(e):
220
- if hasattr(e, "self_cuda_time_total"):
221
- return e.self_cuda_time_total
222
- return getattr(e, "self_device_time_total", 0)
223
-
224
- def _cuda_total_us(e):
225
- if hasattr(e, "cuda_time_total"):
226
- return e.cuda_time_total
227
- return getattr(e, "device_time_total", 0)
228
-
229
- total_self_cuda = sum(_self_cuda_us(e) for e in key_avgs)
230
- total_self_cpu = sum(e.self_cpu_time_total for e in key_avgs)
231
-
232
- rows = []
233
- for e in key_avgs:
234
- self_cuda = _self_cuda_us(e)
235
- cuda_total = _cuda_total_us(e)
236
-
237
- cuda_pct = (
238
- self_cuda / total_self_cuda * 100
239
- if total_self_cuda > 0 else 0.0
240
- )
241
-
242
- cpu_pct = (
243
- e.self_cpu_time_total / total_self_cpu * 100
244
- if total_self_cpu > 0 else 0.0
245
- )
246
-
247
- if cuda_pct >= 0.1 or cpu_pct >= 1.0:
248
- rows.append({
249
- "name": e.key,
250
- "input_shapes": e.input_shapes,
251
- "self_cuda_ms": self_cuda / 1000,
252
- "cuda_total_ms": cuda_total / 1000,
253
- "cuda_pct": cuda_pct,
254
- "self_cpu_ms": e.self_cpu_time_total / 1000,
255
- "cpu_total_ms": e.cpu_time_total / 1000,
256
- "cpu_pct": cpu_pct,
257
- "calls": e.count,
258
- "flops": getattr(e, "flops", 0),
259
- "self_cpu_memory_bytes": getattr(e, "self_cpu_memory_usage", 0),
260
- "self_device_memory_bytes": getattr(e, "self_device_memory_usage", 0),
261
- "source_stack": list(getattr(e, "stack", []))[:5],
262
- })
263
-
264
- rows.sort(
265
- key=lambda r: (r["self_cuda_ms"], r["self_cpu_ms"]),
266
- reverse=True,
267
- )
268
- rows = rows[:20]
269
-
270
- print()
271
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
272
- print(f"[LOG KERNEL] Top kernel tại step {batch_idx} (epoch {epoch+1})")
273
- print(
274
- f"{'Name':<55}"
275
- f"{'Self CUDA':>12}"
276
- f"{'CUDA Total':>12}"
277
- f"{'CUDA %':>9}"
278
- f"{'CPU %':>9}"
279
- f"{'Calls':>9}"
280
- )
281
-
282
- for row in rows:
283
- print(
284
- f"{row['name'][:55]:<55}"
285
- f"{row['self_cuda_ms']:>9.2f}ms"
286
- f"{row['cuda_total_ms']:>9.2f}ms"
287
- f"{row['cuda_pct']:>8.2f}%"
288
- f"{row['cpu_pct']:>8.2f}%"
289
- f"{row['calls']:>9}"
290
- )
291
-
292
- device_info = {"type": device.type}
293
- if device.type == "cuda":
294
- device_info.update({
295
- "name": torch.cuda.get_device_name(device),
296
- "capability": list(torch.cuda.get_device_capability(device)),
297
- })
298
-
299
- report = {
300
- "format": "compact-kernel-profile-v1",
301
- "torch_version": torch.__version__,
302
- "device": device_info,
303
- "epoch": epoch + 1,
304
- "batch_idx": batch_idx,
305
- "display_step": batch_idx + 1,
306
- "totals": {
307
- "self_cuda_ms": total_self_cuda / 1000,
308
- "self_cpu_ms": total_self_cpu / 1000,
309
- },
310
- "filters": {
311
- "min_cuda_pct": 0.1,
312
- "min_cpu_pct": 1.0,
313
- "max_rows": 20,
314
- },
315
- "operators": rows,
316
- }
317
-
318
- trace_file = Path(
319
- f"torch_profile_epoch{epoch+1}_step{batch_idx}_summary.json"
320
- )
321
- trace_file.write_text(
322
- json.dumps(report, indent=2, ensure_ascii=False),
323
- encoding="utf-8",
324
- )
325
-
326
- print("────────────────────────────────────────────────────────────────────────────────────")
327
- print(f"Compact profiler JSON: {trace_file}")
328
- print("╠════════════════════════════════════════════════════════════════════════════════════╣")
329
-
330
- self.reported = True
331
- self._prof_ctx = None