feat: SAGE v2 upgrade (GQA, CoT, W&B, FineWeb-Edu)
Browse files- requirements.txt +1 -0
- sage/config.py +2 -0
- sage/data.py +9 -2
- sage/finetune.py +13 -0
- sage/memory.py +11 -16
- sage/model.py +22 -8
- sage/train.py +14 -0
- sage_single.py +25 -7
requirements.txt
CHANGED
|
@@ -6,3 +6,4 @@ tqdm>=4.66.1
|
|
| 6 |
bitsandbytes>=0.41.0
|
| 7 |
numpy<2.0.0
|
| 8 |
huggingface_hub>=0.20.0
|
|
|
|
|
|
| 6 |
bitsandbytes>=0.41.0
|
| 7 |
numpy<2.0.0
|
| 8 |
huggingface_hub>=0.20.0
|
| 9 |
+
wandb>=0.16.0
|
sage/config.py
CHANGED
|
@@ -7,6 +7,7 @@ class SageConfig:
|
|
| 7 |
# Model dimensions corresponding to T4 (16GB VRAM) fit
|
| 8 |
d_model: int = 512
|
| 9 |
n_heads: int = 8
|
|
|
|
| 10 |
n_layers: int = 6
|
| 11 |
d_ff: int = 2048
|
| 12 |
|
|
@@ -32,6 +33,7 @@ class SageConfig:
|
|
| 32 |
|
| 33 |
# Checkpointing and path details
|
| 34 |
checkpoint_dir: str = "checkpoints"
|
|
|
|
| 35 |
|
| 36 |
@property
|
| 37 |
def device(self):
|
|
|
|
| 7 |
# Model dimensions corresponding to T4 (16GB VRAM) fit
|
| 8 |
d_model: int = 512
|
| 9 |
n_heads: int = 8
|
| 10 |
+
n_kv_heads: int = 4 # GQA: must divide n_heads
|
| 11 |
n_layers: int = 6
|
| 12 |
d_ff: int = 2048
|
| 13 |
|
|
|
|
| 33 |
|
| 34 |
# Checkpointing and path details
|
| 35 |
checkpoint_dir: str = "checkpoints"
|
| 36 |
+
project_name: str = "sage-v2"
|
| 37 |
|
| 38 |
@property
|
| 39 |
def device(self):
|
sage/data.py
CHANGED
|
@@ -79,7 +79,7 @@ class StreamingTextDataset(IterableDataset):
|
|
| 79 |
|
| 80 |
def __init__(
|
| 81 |
self,
|
| 82 |
-
dataset_name: str = "
|
| 83 |
split: str = "train",
|
| 84 |
seq_len: int = 512,
|
| 85 |
tokenizer: Optional[SageTokenizer] = None,
|
|
@@ -97,6 +97,13 @@ class StreamingTextDataset(IterableDataset):
|
|
| 97 |
self.text_field = text_field
|
| 98 |
self.min_doc_len = min_doc_len
|
| 99 |
self.max_doc_len = max_doc_len
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
def _stream_tokens(self) -> Iterator[int]:
|
| 102 |
"""Yields individual token IDs from the HuggingFace dataset stream."""
|
|
@@ -164,7 +171,7 @@ class StreamingTextDataset(IterableDataset):
|
|
| 164 |
|
| 165 |
def create_dataloader(
|
| 166 |
config: SageConfig,
|
| 167 |
-
dataset_name: str = "
|
| 168 |
split: str = "train",
|
| 169 |
tokenizer: Optional[SageTokenizer] = None,
|
| 170 |
) -> DataLoader:
|
|
|
|
| 79 |
|
| 80 |
def __init__(
|
| 81 |
self,
|
| 82 |
+
dataset_name: str = "HuggingFaceFW/fineweb-edu",
|
| 83 |
split: str = "train",
|
| 84 |
seq_len: int = 512,
|
| 85 |
tokenizer: Optional[SageTokenizer] = None,
|
|
|
|
| 97 |
self.text_field = text_field
|
| 98 |
self.min_doc_len = min_doc_len
|
| 99 |
self.max_doc_len = max_doc_len
|
| 100 |
+
|
| 101 |
+
# Auto-adjust configuration based on popular datasets
|
| 102 |
+
if "fineweb-edu" in dataset_name.lower():
|
| 103 |
+
self.text_field = "text"
|
| 104 |
+
self.split = "train" if split == "train" else split
|
| 105 |
+
elif "tinystories" in dataset_name.lower():
|
| 106 |
+
self.text_field = "text"
|
| 107 |
|
| 108 |
def _stream_tokens(self) -> Iterator[int]:
|
| 109 |
"""Yields individual token IDs from the HuggingFace dataset stream."""
|
|
|
|
| 171 |
|
| 172 |
def create_dataloader(
|
| 173 |
config: SageConfig,
|
| 174 |
+
dataset_name: str = "HuggingFaceFW/fineweb-edu",
|
| 175 |
split: str = "train",
|
| 176 |
tokenizer: Optional[SageTokenizer] = None,
|
| 177 |
) -> DataLoader:
|
sage/finetune.py
CHANGED
|
@@ -16,6 +16,7 @@ import torch
|
|
| 16 |
import torch.nn as nn
|
| 17 |
from torch.amp import GradScaler, autocast
|
| 18 |
from tqdm import tqdm
|
|
|
|
| 19 |
from typing import Optional, List
|
| 20 |
|
| 21 |
from .config import SageConfig
|
|
@@ -156,6 +157,13 @@ def finetune_instruction(
|
|
| 156 |
if use_lora:
|
| 157 |
model = inject_lora(model, rank=lora_rank)
|
| 158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
optimizer = create_optimizer(model, config)
|
| 160 |
|
| 161 |
# AMP setup
|
|
@@ -213,6 +221,10 @@ def finetune_instruction(
|
|
| 213 |
avg = accum_loss / 10
|
| 214 |
pbar.set_postfix(loss=f"{avg:.4f}", lr=f"{lr:.2e}")
|
| 215 |
logger.info(f"finetune step={step+1} | loss={avg:.4f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
accum_loss = 0.0
|
| 217 |
|
| 218 |
# Merge LoRA weights back for clean inference
|
|
@@ -221,6 +233,7 @@ def finetune_instruction(
|
|
| 221 |
|
| 222 |
save_checkpoint(model, None, total_steps, config.checkpoint_dir, filename="sage_finetuned.pt")
|
| 223 |
logger.info("Instruction fine-tuning complete. Checkpoint saved as sage_finetuned.pt")
|
|
|
|
| 224 |
return model
|
| 225 |
|
| 226 |
|
|
|
|
| 16 |
import torch.nn as nn
|
| 17 |
from torch.amp import GradScaler, autocast
|
| 18 |
from tqdm import tqdm
|
| 19 |
+
import wandb
|
| 20 |
from typing import Optional, List
|
| 21 |
|
| 22 |
from .config import SageConfig
|
|
|
|
| 157 |
if use_lora:
|
| 158 |
model = inject_lora(model, rank=lora_rank)
|
| 159 |
|
| 160 |
+
# ------- W&B Logging -------
|
| 161 |
+
wandb.init(
|
| 162 |
+
project=config.project_name,
|
| 163 |
+
name=f"finetune-{time.strftime('%Y%m%d-%H%M')}",
|
| 164 |
+
config=config.__dict__,
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
optimizer = create_optimizer(model, config)
|
| 168 |
|
| 169 |
# AMP setup
|
|
|
|
| 221 |
avg = accum_loss / 10
|
| 222 |
pbar.set_postfix(loss=f"{avg:.4f}", lr=f"{lr:.2e}")
|
| 223 |
logger.info(f"finetune step={step+1} | loss={avg:.4f}")
|
| 224 |
+
wandb.log({
|
| 225 |
+
"finetune/loss": avg,
|
| 226 |
+
"finetune/lr": lr,
|
| 227 |
+
}, step=step + 1)
|
| 228 |
accum_loss = 0.0
|
| 229 |
|
| 230 |
# Merge LoRA weights back for clean inference
|
|
|
|
| 233 |
|
| 234 |
save_checkpoint(model, None, total_steps, config.checkpoint_dir, filename="sage_finetuned.pt")
|
| 235 |
logger.info("Instruction fine-tuning complete. Checkpoint saved as sage_finetuned.pt")
|
| 236 |
+
wandb.finish()
|
| 237 |
return model
|
| 238 |
|
| 239 |
|
sage/memory.py
CHANGED
|
@@ -178,22 +178,17 @@ class RAGManager:
|
|
| 178 |
# ===================================================================
|
| 179 |
|
| 180 |
DEFAULT_SYSTEM_PROMPT = (
|
| 181 |
-
"You are a high-quality reasoning assistant
|
| 182 |
-
"
|
| 183 |
-
"
|
| 184 |
-
"
|
| 185 |
-
"
|
| 186 |
-
"
|
| 187 |
-
"
|
| 188 |
-
"
|
| 189 |
-
"
|
| 190 |
-
"
|
| 191 |
-
"
|
| 192 |
-
"\n"
|
| 193 |
-
"Output behavior goal:\n"
|
| 194 |
-
"- Think in steps.\n"
|
| 195 |
-
"- Explain logic clearly.\n"
|
| 196 |
-
"- Produce final answer only after reasoning."
|
| 197 |
)
|
| 198 |
|
| 199 |
class ConversationHistory:
|
|
|
|
| 178 |
# ===================================================================
|
| 179 |
|
| 180 |
DEFAULT_SYSTEM_PROMPT = (
|
| 181 |
+
"You are SAGE, a high-quality reasoning assistant. "
|
| 182 |
+
"Your goal is to provide accurate, structured, and deep logical explanations.\n\n"
|
| 183 |
+
"CRITICAL GUIDELINES:\n"
|
| 184 |
+
"1. THINKING PHASE: You must ALWAYS start your response with a <thinking> section. "
|
| 185 |
+
"In this section, break down the user's request, identify key constraints, and plan your logical steps.\n"
|
| 186 |
+
"2. RESPONSE PHASE: After completing your internal reasoning, provide your final answer within <response> tags.\n"
|
| 187 |
+
"3. QUALITY: Prioritize step-by-step mathematical or logical derivation over short answers.\n"
|
| 188 |
+
"4. NO REPETITION: Avoid filler words or circular logic.\n\n"
|
| 189 |
+
"RESPONSE TEMPLATE:\n"
|
| 190 |
+
"<thinking>\n[Step-by-step logic here]\n</thinking>\n"
|
| 191 |
+
"<response>\n[Final clear answer here]\n</response>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
)
|
| 193 |
|
| 194 |
class ConversationHistory:
|
sage/model.py
CHANGED
|
@@ -32,17 +32,30 @@ def apply_rotary_emb(xq: torch.Tensor, xk: torch.Tensor, freqs_cis: torch.Tensor
|
|
| 32 |
|
| 33 |
return xq_out.type_as(xq), xk_out.type_as(xk)
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
class CausalSelfAttention(nn.Module):
|
| 36 |
def __init__(self, config: SageConfig):
|
| 37 |
super().__init__()
|
| 38 |
self.n_heads = config.n_heads
|
|
|
|
|
|
|
| 39 |
self.d_model = config.d_model
|
| 40 |
assert self.d_model % self.n_heads == 0
|
| 41 |
self.head_dim = self.d_model // self.n_heads
|
| 42 |
|
| 43 |
-
self.wq = nn.Linear(self.d_model, self.
|
| 44 |
-
self.wk = nn.Linear(self.d_model, self.
|
| 45 |
-
self.wv = nn.Linear(self.d_model, self.
|
| 46 |
self.wo = nn.Linear(self.d_model, self.d_model, bias=False)
|
| 47 |
|
| 48 |
self.resid_dropout = nn.Dropout(config.dropout)
|
|
@@ -54,14 +67,11 @@ class CausalSelfAttention(nn.Module):
|
|
| 54 |
x: torch.Tensor,
|
| 55 |
freqs_cis: torch.Tensor,
|
| 56 |
kv_cache: Optional[Tuple[torch.Tensor, torch.Tensor]] = None
|
| 57 |
-
) -> Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
|
| 58 |
-
B, T, C = x.size() # batch, seq_len, d_model
|
| 59 |
-
|
| 60 |
q, k, v = self.wq(x), self.wk(x), self.wv(x)
|
| 61 |
|
| 62 |
q = q.view(B, T, self.n_heads, self.head_dim)
|
| 63 |
-
k = k.view(B, T, self.
|
| 64 |
-
v = v.view(B, T, self.
|
| 65 |
|
| 66 |
q, k = apply_rotary_emb(q, k, freqs_cis)
|
| 67 |
|
|
@@ -74,6 +84,10 @@ class CausalSelfAttention(nn.Module):
|
|
| 74 |
else:
|
| 75 |
new_kv_cache = None
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
# Move heads to correct dimension: (B, n_heads, T, head_dim)
|
| 78 |
q = q.transpose(1, 2)
|
| 79 |
k = k.transpose(1, 2)
|
|
|
|
| 32 |
|
| 33 |
return xq_out.type_as(xq), xk_out.type_as(xk)
|
| 34 |
|
| 35 |
+
def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor:
|
| 36 |
+
"""Repeat Key/Value heads n_rep times to match number of Query heads."""
|
| 37 |
+
if n_rep == 1:
|
| 38 |
+
return x
|
| 39 |
+
B, T, n_kv_heads, head_dim = x.size()
|
| 40 |
+
return (
|
| 41 |
+
x[:, :, :, None, :]
|
| 42 |
+
.expand(B, T, n_kv_heads, n_rep, head_dim)
|
| 43 |
+
.reshape(B, T, n_kv_heads * n_rep, head_dim)
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
class CausalSelfAttention(nn.Module):
|
| 47 |
def __init__(self, config: SageConfig):
|
| 48 |
super().__init__()
|
| 49 |
self.n_heads = config.n_heads
|
| 50 |
+
self.n_kv_heads = config.n_kv_heads
|
| 51 |
+
self.n_rep = self.n_heads // self.n_kv_heads
|
| 52 |
self.d_model = config.d_model
|
| 53 |
assert self.d_model % self.n_heads == 0
|
| 54 |
self.head_dim = self.d_model // self.n_heads
|
| 55 |
|
| 56 |
+
self.wq = nn.Linear(self.d_model, self.n_heads * self.head_dim, bias=False)
|
| 57 |
+
self.wk = nn.Linear(self.d_model, self.n_kv_heads * self.head_dim, bias=False)
|
| 58 |
+
self.wv = nn.Linear(self.d_model, self.n_kv_heads * self.head_dim, bias=False)
|
| 59 |
self.wo = nn.Linear(self.d_model, self.d_model, bias=False)
|
| 60 |
|
| 61 |
self.resid_dropout = nn.Dropout(config.dropout)
|
|
|
|
| 67 |
x: torch.Tensor,
|
| 68 |
freqs_cis: torch.Tensor,
|
| 69 |
kv_cache: Optional[Tuple[torch.Tensor, torch.Tensor]] = None
|
|
|
|
|
|
|
|
|
|
| 70 |
q, k, v = self.wq(x), self.wk(x), self.wv(x)
|
| 71 |
|
| 72 |
q = q.view(B, T, self.n_heads, self.head_dim)
|
| 73 |
+
k = k.view(B, T, self.n_kv_heads, self.head_dim)
|
| 74 |
+
v = v.view(B, T, self.n_kv_heads, self.head_dim)
|
| 75 |
|
| 76 |
q, k = apply_rotary_emb(q, k, freqs_cis)
|
| 77 |
|
|
|
|
| 84 |
else:
|
| 85 |
new_kv_cache = None
|
| 86 |
|
| 87 |
+
# Repeat KV heads to match Q heads (GQA)
|
| 88 |
+
k = repeat_kv(k, self.n_rep)
|
| 89 |
+
v = repeat_kv(v, self.n_rep)
|
| 90 |
+
|
| 91 |
# Move heads to correct dimension: (B, n_heads, T, head_dim)
|
| 92 |
q = q.transpose(1, 2)
|
| 93 |
k = k.transpose(1, 2)
|
sage/train.py
CHANGED
|
@@ -11,6 +11,7 @@ import torch
|
|
| 11 |
import torch.nn as nn
|
| 12 |
from torch.amp import GradScaler, autocast
|
| 13 |
from tqdm import tqdm
|
|
|
|
| 14 |
from typing import Optional
|
| 15 |
|
| 16 |
from .config import SageConfig
|
|
@@ -140,6 +141,13 @@ def train(
|
|
| 140 |
loader = create_dataloader(config, dataset_name=dataset_name, tokenizer=tok)
|
| 141 |
data_iter = iter(loader)
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
# ------- gradient checkpointing (saves VRAM) -------
|
| 144 |
base_model = getattr(model, "module", model)
|
| 145 |
if hasattr(base_model, "layers"):
|
|
@@ -220,6 +228,11 @@ def train(
|
|
| 220 |
logger.info(
|
| 221 |
f"step={step+1} | loss={avg_loss:.4f} | ppl={perplexity:.2f} | lr={lr:.2e}"
|
| 222 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
accum_loss = 0.0
|
| 224 |
|
| 225 |
# ------- checkpoint every 100 steps -------
|
|
@@ -228,4 +241,5 @@ def train(
|
|
| 228 |
logger.info(f"Checkpoint saved at step {step + 1}")
|
| 229 |
|
| 230 |
logger.info("Training complete.")
|
|
|
|
| 231 |
return model
|
|
|
|
| 11 |
import torch.nn as nn
|
| 12 |
from torch.amp import GradScaler, autocast
|
| 13 |
from tqdm import tqdm
|
| 14 |
+
import wandb
|
| 15 |
from typing import Optional
|
| 16 |
|
| 17 |
from .config import SageConfig
|
|
|
|
| 141 |
loader = create_dataloader(config, dataset_name=dataset_name, tokenizer=tok)
|
| 142 |
data_iter = iter(loader)
|
| 143 |
|
| 144 |
+
# ------- W&B Logging -------
|
| 145 |
+
wandb.init(
|
| 146 |
+
project=config.project_name,
|
| 147 |
+
name=f"pretrain-{time.strftime('%Y%m%d-%H%M')}",
|
| 148 |
+
config=config.__dict__,
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
# ------- gradient checkpointing (saves VRAM) -------
|
| 152 |
base_model = getattr(model, "module", model)
|
| 153 |
if hasattr(base_model, "layers"):
|
|
|
|
| 228 |
logger.info(
|
| 229 |
f"step={step+1} | loss={avg_loss:.4f} | ppl={perplexity:.2f} | lr={lr:.2e}"
|
| 230 |
)
|
| 231 |
+
wandb.log({
|
| 232 |
+
"train/loss": avg_loss,
|
| 233 |
+
"train/perplexity": perplexity,
|
| 234 |
+
"train/lr": lr,
|
| 235 |
+
}, step=step + 1)
|
| 236 |
accum_loss = 0.0
|
| 237 |
|
| 238 |
# ------- checkpoint every 100 steps -------
|
|
|
|
| 241 |
logger.info(f"Checkpoint saved at step {step + 1}")
|
| 242 |
|
| 243 |
logger.info("Training complete.")
|
| 244 |
+
wandb.finish()
|
| 245 |
return model
|
sage_single.py
CHANGED
|
@@ -30,6 +30,7 @@ from torch.amp import GradScaler, autocast
|
|
| 30 |
from torch.utils.data import IterableDataset, DataLoader
|
| 31 |
from tqdm import tqdm
|
| 32 |
import tiktoken
|
|
|
|
| 33 |
|
| 34 |
__version__ = "1.0.0"
|
| 35 |
|
|
@@ -42,6 +43,7 @@ __version__ = "1.0.0"
|
|
| 42 |
class SageConfig:
|
| 43 |
d_model: int = 512
|
| 44 |
n_heads: int = 8
|
|
|
|
| 45 |
n_layers: int = 6
|
| 46 |
d_ff: int = 2048
|
| 47 |
n_experts: int = 4
|
|
@@ -57,6 +59,7 @@ class SageConfig:
|
|
| 57 |
weight_decay: float = 0.01
|
| 58 |
max_grad_norm: float = 1.0
|
| 59 |
checkpoint_dir: str = "checkpoints"
|
|
|
|
| 60 |
|
| 61 |
@property
|
| 62 |
def device(self):
|
|
@@ -151,15 +154,22 @@ def apply_rotary_emb(xq, xk, freqs_cis):
|
|
| 151 |
xk_out = torch.view_as_real(xk_ * fc).flatten(3)
|
| 152 |
return xq_out.type_as(xq), xk_out.type_as(xk)
|
| 153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
class CausalSelfAttention(nn.Module):
|
| 155 |
def __init__(self, config):
|
| 156 |
super().__init__()
|
| 157 |
self.n_heads = config.n_heads
|
|
|
|
|
|
|
| 158 |
self.d_model = config.d_model
|
| 159 |
self.head_dim = config.d_model // config.n_heads
|
| 160 |
-
self.wq = nn.Linear(config.d_model, config.
|
| 161 |
-
self.wk = nn.Linear(config.d_model, config.
|
| 162 |
-
self.wv = nn.Linear(config.d_model, config.
|
| 163 |
self.wo = nn.Linear(config.d_model, config.d_model, bias=False)
|
| 164 |
self.resid_dropout = nn.Dropout(config.dropout)
|
| 165 |
|
|
@@ -167,8 +177,8 @@ class CausalSelfAttention(nn.Module):
|
|
| 167 |
B, T, C = x.size()
|
| 168 |
q, k, v = self.wq(x), self.wk(x), self.wv(x)
|
| 169 |
q = q.view(B, T, self.n_heads, self.head_dim)
|
| 170 |
-
k = k.view(B, T, self.
|
| 171 |
-
v = v.view(B, T, self.
|
| 172 |
q, k = apply_rotary_emb(q, k, freqs_cis)
|
| 173 |
if kv_cache is not None:
|
| 174 |
k = torch.cat([kv_cache[0], k], dim=1)
|
|
@@ -176,6 +186,7 @@ class CausalSelfAttention(nn.Module):
|
|
| 176 |
new_kv = (k, v)
|
| 177 |
else:
|
| 178 |
new_kv = None
|
|
|
|
| 179 |
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 180 |
is_causal = kv_cache is None and T > 1
|
| 181 |
try:
|
|
@@ -293,11 +304,13 @@ def clean_text(text):
|
|
| 293 |
return text.strip()
|
| 294 |
|
| 295 |
class StreamingTextDataset(IterableDataset):
|
| 296 |
-
def __init__(self, dataset_name="
|
| 297 |
super().__init__()
|
| 298 |
self.dataset_name, self.split, self.seq_len = dataset_name, split, seq_len
|
| 299 |
self.tokenizer = tokenizer or SageTokenizer()
|
| 300 |
self.buffer_size, self.text_field = buffer_size, text_field
|
|
|
|
|
|
|
| 301 |
|
| 302 |
def _tokens(self):
|
| 303 |
from datasets import load_dataset
|
|
@@ -321,7 +334,7 @@ class StreamingTextDataset(IterableDataset):
|
|
| 321 |
random.shuffle(buf)
|
| 322 |
yield from buf
|
| 323 |
|
| 324 |
-
def create_dataloader(config, dataset_name="
|
| 325 |
tok = tokenizer or SageTokenizer()
|
| 326 |
ds = StreamingTextDataset(dataset_name=dataset_name, seq_len=config.max_seq_len, tokenizer=tok)
|
| 327 |
return DataLoader(ds, batch_size=config.batch_size, num_workers=0, pin_memory=True, drop_last=True)
|
|
@@ -362,6 +375,7 @@ def train_model(model, config, total_steps=500, dataset_name="roneneldan/TinySto
|
|
| 362 |
scaler = GradScaler("cuda", enabled=use_amp and amp_dtype == torch.float16)
|
| 363 |
loader = create_dataloader(config, dataset_name, tok)
|
| 364 |
data_iter = iter(loader)
|
|
|
|
| 365 |
model.train()
|
| 366 |
accum_loss, t0 = 0.0, time.time()
|
| 367 |
pbar = tqdm(range(start_step, total_steps), desc="Training")
|
|
@@ -387,11 +401,13 @@ def train_model(model, config, total_steps=500, dataset_name="roneneldan/TinySto
|
|
| 387 |
if (step + 1) % 10 == 0:
|
| 388 |
avg = accum_loss / 10
|
| 389 |
pbar.set_postfix(loss=f"{avg:.4f}", ppl=f"{math.exp(min(avg,20)):.1f}", lr=f"{lr:.2e}")
|
|
|
|
| 390 |
accum_loss = 0.0
|
| 391 |
if (step + 1) % 100 == 0:
|
| 392 |
save_checkpoint(model, opt, step + 1, config.checkpoint_dir)
|
| 393 |
save_checkpoint(model, opt, total_steps, config.checkpoint_dir)
|
| 394 |
logger.info("Training complete.")
|
|
|
|
| 395 |
return model
|
| 396 |
|
| 397 |
|
|
@@ -517,6 +533,7 @@ def finetune(model, config, samples=None, steps=200, use_lora=True, tokenizer=No
|
|
| 517 |
use_amp = device.type == "cuda"
|
| 518 |
amp_dtype = torch.bfloat16 if use_amp and torch.cuda.is_bf16_supported() else torch.float16
|
| 519 |
scaler = GradScaler("cuda", enabled=use_amp and amp_dtype == torch.float16)
|
|
|
|
| 520 |
model.train(); accum = 0.0
|
| 521 |
for step in tqdm(range(steps), desc="Fine-tuning"):
|
| 522 |
lr = get_lr(step, config, steps)
|
|
@@ -537,6 +554,7 @@ def finetune(model, config, samples=None, steps=200, use_lora=True, tokenizer=No
|
|
| 537 |
if use_lora: model = merge_lora(model)
|
| 538 |
save_checkpoint(model, None, steps, config.checkpoint_dir, "sage_finetuned.pt")
|
| 539 |
logger.info("Fine-tuning complete.")
|
|
|
|
| 540 |
return model
|
| 541 |
|
| 542 |
|
|
|
|
| 30 |
from torch.utils.data import IterableDataset, DataLoader
|
| 31 |
from tqdm import tqdm
|
| 32 |
import tiktoken
|
| 33 |
+
import wandb
|
| 34 |
|
| 35 |
__version__ = "1.0.0"
|
| 36 |
|
|
|
|
| 43 |
class SageConfig:
|
| 44 |
d_model: int = 512
|
| 45 |
n_heads: int = 8
|
| 46 |
+
n_kv_heads: int = 4
|
| 47 |
n_layers: int = 6
|
| 48 |
d_ff: int = 2048
|
| 49 |
n_experts: int = 4
|
|
|
|
| 59 |
weight_decay: float = 0.01
|
| 60 |
max_grad_norm: float = 1.0
|
| 61 |
checkpoint_dir: str = "checkpoints"
|
| 62 |
+
project_name: str = "sage-v2"
|
| 63 |
|
| 64 |
@property
|
| 65 |
def device(self):
|
|
|
|
| 154 |
xk_out = torch.view_as_real(xk_ * fc).flatten(3)
|
| 155 |
return xq_out.type_as(xq), xk_out.type_as(xk)
|
| 156 |
|
| 157 |
+
def repeat_kv(x, n_rep):
|
| 158 |
+
if n_rep == 1: return x
|
| 159 |
+
B, T, n_kv_heads, head_dim = x.size()
|
| 160 |
+
return x[:, :, :, None, :].expand(B, T, n_kv_heads, n_rep, head_dim).reshape(B, T, n_kv_heads * n_rep, head_dim)
|
| 161 |
+
|
| 162 |
class CausalSelfAttention(nn.Module):
|
| 163 |
def __init__(self, config):
|
| 164 |
super().__init__()
|
| 165 |
self.n_heads = config.n_heads
|
| 166 |
+
self.n_kv_heads = config.n_kv_heads
|
| 167 |
+
self.n_rep = self.n_heads // self.n_kv_heads
|
| 168 |
self.d_model = config.d_model
|
| 169 |
self.head_dim = config.d_model // config.n_heads
|
| 170 |
+
self.wq = nn.Linear(config.d_model, config.n_heads * self.head_dim, bias=False)
|
| 171 |
+
self.wk = nn.Linear(config.d_model, config.n_kv_heads * self.head_dim, bias=False)
|
| 172 |
+
self.wv = nn.Linear(config.d_model, config.n_kv_heads * self.head_dim, bias=False)
|
| 173 |
self.wo = nn.Linear(config.d_model, config.d_model, bias=False)
|
| 174 |
self.resid_dropout = nn.Dropout(config.dropout)
|
| 175 |
|
|
|
|
| 177 |
B, T, C = x.size()
|
| 178 |
q, k, v = self.wq(x), self.wk(x), self.wv(x)
|
| 179 |
q = q.view(B, T, self.n_heads, self.head_dim)
|
| 180 |
+
k = k.view(B, T, self.n_kv_heads, self.head_dim)
|
| 181 |
+
v = v.view(B, T, self.n_kv_heads, self.head_dim)
|
| 182 |
q, k = apply_rotary_emb(q, k, freqs_cis)
|
| 183 |
if kv_cache is not None:
|
| 184 |
k = torch.cat([kv_cache[0], k], dim=1)
|
|
|
|
| 186 |
new_kv = (k, v)
|
| 187 |
else:
|
| 188 |
new_kv = None
|
| 189 |
+
k, v = repeat_kv(k, self.n_rep), repeat_kv(v, self.n_rep)
|
| 190 |
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 191 |
is_causal = kv_cache is None and T > 1
|
| 192 |
try:
|
|
|
|
| 304 |
return text.strip()
|
| 305 |
|
| 306 |
class StreamingTextDataset(IterableDataset):
|
| 307 |
+
def __init__(self, dataset_name="HuggingFaceFW/fineweb-edu", split="train", seq_len=512, tokenizer=None, buffer_size=1000, text_field="text"):
|
| 308 |
super().__init__()
|
| 309 |
self.dataset_name, self.split, self.seq_len = dataset_name, split, seq_len
|
| 310 |
self.tokenizer = tokenizer or SageTokenizer()
|
| 311 |
self.buffer_size, self.text_field = buffer_size, text_field
|
| 312 |
+
if "fineweb-edu" in dataset_name.lower(): self.text_field = "text"
|
| 313 |
+
elif "tinystories" in dataset_name.lower(): self.text_field = "text"
|
| 314 |
|
| 315 |
def _tokens(self):
|
| 316 |
from datasets import load_dataset
|
|
|
|
| 334 |
random.shuffle(buf)
|
| 335 |
yield from buf
|
| 336 |
|
| 337 |
+
def create_dataloader(config, dataset_name="HuggingFaceFW/fineweb-edu", tokenizer=None):
|
| 338 |
tok = tokenizer or SageTokenizer()
|
| 339 |
ds = StreamingTextDataset(dataset_name=dataset_name, seq_len=config.max_seq_len, tokenizer=tok)
|
| 340 |
return DataLoader(ds, batch_size=config.batch_size, num_workers=0, pin_memory=True, drop_last=True)
|
|
|
|
| 375 |
scaler = GradScaler("cuda", enabled=use_amp and amp_dtype == torch.float16)
|
| 376 |
loader = create_dataloader(config, dataset_name, tok)
|
| 377 |
data_iter = iter(loader)
|
| 378 |
+
wandb.init(project=config.project_name, name=f"pretrain-{time.strftime('%Y%m%d-%H%M')}", config=config.__dict__)
|
| 379 |
model.train()
|
| 380 |
accum_loss, t0 = 0.0, time.time()
|
| 381 |
pbar = tqdm(range(start_step, total_steps), desc="Training")
|
|
|
|
| 401 |
if (step + 1) % 10 == 0:
|
| 402 |
avg = accum_loss / 10
|
| 403 |
pbar.set_postfix(loss=f"{avg:.4f}", ppl=f"{math.exp(min(avg,20)):.1f}", lr=f"{lr:.2e}")
|
| 404 |
+
wandb.log({"train/loss": avg, "train/perplexity": math.exp(min(avg, 20)), "train/lr": lr}, step=step + 1)
|
| 405 |
accum_loss = 0.0
|
| 406 |
if (step + 1) % 100 == 0:
|
| 407 |
save_checkpoint(model, opt, step + 1, config.checkpoint_dir)
|
| 408 |
save_checkpoint(model, opt, total_steps, config.checkpoint_dir)
|
| 409 |
logger.info("Training complete.")
|
| 410 |
+
wandb.finish()
|
| 411 |
return model
|
| 412 |
|
| 413 |
|
|
|
|
| 533 |
use_amp = device.type == "cuda"
|
| 534 |
amp_dtype = torch.bfloat16 if use_amp and torch.cuda.is_bf16_supported() else torch.float16
|
| 535 |
scaler = GradScaler("cuda", enabled=use_amp and amp_dtype == torch.float16)
|
| 536 |
+
wandb.init(project=config.project_name, name=f"finetune-{time.strftime('%Y%m%d-%H%M')}", config=config.__dict__)
|
| 537 |
model.train(); accum = 0.0
|
| 538 |
for step in tqdm(range(steps), desc="Fine-tuning"):
|
| 539 |
lr = get_lr(step, config, steps)
|
|
|
|
| 554 |
if use_lora: model = merge_lora(model)
|
| 555 |
save_checkpoint(model, None, steps, config.checkpoint_dir, "sage_finetuned.pt")
|
| 556 |
logger.info("Fine-tuning complete.")
|
| 557 |
+
wandb.finish()
|
| 558 |
return model
|
| 559 |
|
| 560 |
|