Text Generation
Transformers
Safetensors
Vietnamese
SAI
custom-code
vietnamese
causal-lm
custom_code
Instructions to use thongbuind/SAI_35M with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use thongbuind/SAI_35M with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="thongbuind/SAI_35M", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("thongbuind/SAI_35M", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use thongbuind/SAI_35M with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "thongbuind/SAI_35M" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "thongbuind/SAI_35M", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/thongbuind/SAI_35M
- SGLang
How to use thongbuind/SAI_35M with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "thongbuind/SAI_35M" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "thongbuind/SAI_35M", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "thongbuind/SAI_35M" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "thongbuind/SAI_35M", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use thongbuind/SAI_35M with Docker Model Runner:
docker model run hf.co/thongbuind/SAI_35M
Keep a single inference source tree with original filenames
Browse files- src/model/DecoderBlock.py → DecoderBlock.py +2 -2
- src/model/GroupedQueryAttention.py → GroupedQueryAttention.py +1 -1
- README.md +2 -2
- src/model/RotaryPositionalEmbedding.py → RotaryPositionalEmbedding.py +0 -0
- src/model/SwiGLU.py → SwiGLU.py +0 -0
- src/model/TransformerModel.py → TransformerModel.py +3 -3
- src/inference/generate.py → generate.py +0 -0
- generation_sai.py +0 -196
- modeling_sai.py +6 -6
- runtime_decoder.py +0 -28
- runtime_gqa.py +0 -96
- runtime_rope.py +0 -33
- runtime_swiglu.py +0 -14
- runtime_transformer.py +0 -114
- src/__init__.py +0 -0
- src/data/Dataset.py +0 -260
- src/data/__init__.py +0 -0
- src/inference/__init__.py +0 -0
- src/model/__init__.py +0 -0
- src/tokenizer/__init__.py +0 -0
- src/tokenizer/encoders.py +0 -224
- src/tokenizer/tokenizer.model +0 -3
- src/tokenizer/tokenizer.vocab +0 -0
- src/tokenizer/train_tokenizer.py +0 -103
- src/training/PenaltyEngine.py +0 -188
- src/training/__init__.py +0 -0
- src/training/finetune.py +0 -581
- src/training/pretrain.py +0 -421
- src/utils/__init__.py +0 -0
- src/utils/chunked_loss.py +0 -90
- src/utils/utils.py +0 -331
src/model/DecoderBlock.py → DecoderBlock.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
import torch.nn as nn
|
| 2 |
-
from
|
| 3 |
-
from
|
| 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
|
| 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 |
-
|
| 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 `
|
| 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
|
| 4 |
-
from
|
| 5 |
-
from
|
| 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 .
|
| 7 |
-
from .
|
| 8 |
-
from .
|
| 9 |
-
from .
|
| 10 |
-
from .
|
| 11 |
-
from .
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|