ViuAI commited on
Commit
ff9b7bf
·
verified ·
1 Parent(s): fa47862

Upload 2 files

Browse files
Files changed (2) hide show
  1. code/model.py +28 -23
  2. code/train.py +124 -69
code/model.py CHANGED
@@ -7,13 +7,14 @@ from dataclasses import dataclass
7
 
8
  @dataclass
9
  class ModelConfig:
10
- """Configuration class for ViuResonance100M."""
11
  vocab_size: int = 64000
12
  d_model: int = 1024
13
  n_layers: int = 12
14
  n_heads: int = 16
15
  dropout: float = 0.1
16
- chunk_size: int = 512
 
17
 
18
  class RMSNorm(nn.Module):
19
  def __init__(self, dim: int, eps: float = 1e-6):
@@ -22,9 +23,12 @@ class RMSNorm(nn.Module):
22
  self.eps = eps
23
 
24
  def forward(self, x: torch.Tensor) -> torch.Tensor:
25
- # Enforce float32 accumulation for stability, then cast back to original dtype
26
- norm_x = x.float() * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.eps) * self.weight.float()
27
- return norm_x.to(x.dtype)
 
 
 
28
 
29
  class ResonanceLayerKaggle(nn.Module):
30
  def __init__(self, config: ModelConfig):
@@ -42,6 +46,9 @@ class ResonanceLayerKaggle(nn.Module):
42
  self.to_v = nn.Linear(config.d_model, config.d_model, bias=False)
43
  self.to_out = nn.Linear(config.d_model, config.d_model, bias=False)
44
  self.norm = RMSNorm(config.d_model)
 
 
 
45
 
46
  def forward(self, x: torch.Tensor) -> torch.Tensor:
47
  B, T, C = x.shape
@@ -51,31 +58,33 @@ class ResonanceLayerKaggle(nn.Module):
51
  freq = torch.tanh(self.to_freq(h)) * 2.0 # B,T,H
52
  phase = torch.tanh(self.to_phase(h)) * math.pi
53
 
54
- # Use torch.long for absolute positions to avoid float precision issues during subtraction
55
- pos = torch.arange(T, device=x.device, dtype=torch.long)
56
- out = torch.zeros(B, T, C, device=x.device, dtype=x.dtype)
 
 
 
 
 
57
 
58
  for i in range(0, T, self.chunk_size):
59
  end = min(i+self.chunk_size, T)
 
 
60
  phase_i = phase[:, i:end, :].permute(0,2,1).unsqueeze(-1) # B,H,chunk,1
61
  phase_j = phase.permute(0,2,1).unsqueeze(-2) # B,H,1,T
62
 
63
- # Robust distance calculation in torch.long to prevent overflow/dtype issues
64
  row_idx = torch.arange(i, end, device=x.device, dtype=torch.long)[:, None]
65
  col_idx = pos[None, :]
66
  dist_long = (row_idx - col_idx).clamp(min=0)
67
- dist = dist_long.to(freq.dtype).view(1, 1, end-i, T)
68
 
69
  freq_i = freq[:, i:end, :].permute(0,2,1).unsqueeze(-1)
70
  angle = (phase_i - phase_j) + freq_i * dist * 0.05
71
 
72
- # Pure Cosine similarity. We apply the sqrt(d) scaling during the gating phase below
73
- score = torch.cos(angle)
74
-
75
- # Boolean causal mask for precision and stability
76
- causal_bool = (row_idx >= col_idx).view(1,1,end-i,T)
77
 
78
- # Mask out future tokens
79
  score = score.masked_fill(~causal_bool, 0.0)
80
  score = score / math.sqrt(self.head_dim)
81
 
@@ -86,11 +95,10 @@ class ResonanceLayerKaggle(nn.Module):
86
 
87
  V_t = V.permute(0,2,1,3).contiguous() # B,H,T,D
88
  Bh = B * self.n_heads
89
- assert C == self.n_heads * self.head_dim, f"C ({C}) != n_heads*head_dim ({self.n_heads*self.head_dim})"
90
 
91
- s = score.reshape(Bh, end-i, T)
92
  v = V_t.reshape(Bh, T, self.head_dim)
93
- o = torch.bmm(s, v).view(B, self.n_heads, end-i, self.head_dim).permute(0,2,1,3).reshape(B, end-i, C)
94
  out[:, i:end] = o
95
 
96
  return self.to_out(out)
@@ -105,10 +113,8 @@ class ViuResonance100M(nn.Module):
105
  self.head = nn.Linear(config.d_model, config.vocab_size, bias=False)
106
  self.resid_dropout = nn.Dropout(config.dropout)
107
 
108
- # Proper initialization sequence to preserve weight tying!
109
- # First randomly initialize everything...
110
  self.apply(self._init_weights)
111
- # THEN explicitly tie the weights
112
  self.head.weight = self.emb.weight
113
 
114
  def _init_weights(self, module: nn.Module):
@@ -130,6 +136,5 @@ class ViuResonance100M(nn.Module):
130
  logits = self.head(self.norm(x))
131
  loss = None
132
  if targets is not None:
133
- # targets with value -100 are automatically ignored in cross_entropy
134
  loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-100)
135
  return logits, loss
 
7
 
8
  @dataclass
9
  class ModelConfig:
10
+ """Configuration for ViuResonance100M - T4 Optimized"""
11
  vocab_size: int = 64000
12
  d_model: int = 1024
13
  n_layers: int = 12
14
  n_heads: int = 16
15
  dropout: float = 0.1
16
+ chunk_size: int = 256 # T4 ke liye 256, 512 se OOM aayega
17
+ max_seq_len: int = 4096
18
 
19
  class RMSNorm(nn.Module):
20
  def __init__(self, dim: int, eps: float = 1e-6):
 
23
  self.eps = eps
24
 
25
  def forward(self, x: torch.Tensor) -> torch.Tensor:
26
+ # T4 fp16 me stable rakhne ke liye float32 me norm
27
+ # Original se same logic, bas efficient
28
+ return F.rms_norm(x.float(), (x.shape[-1],), self.weight.float(), self.eps).to(x.dtype)
29
+ # Fallback agar F.rms_norm na ho:
30
+ # norm_x = x.float() * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.eps)
31
+ # return (norm_x * self.weight.float()).to(x.dtype)
32
 
33
  class ResonanceLayerKaggle(nn.Module):
34
  def __init__(self, config: ModelConfig):
 
46
  self.to_v = nn.Linear(config.d_model, config.d_model, bias=False)
47
  self.to_out = nn.Linear(config.d_model, config.d_model, bias=False)
48
  self.norm = RMSNorm(config.d_model)
49
+
50
+ # T4: pos cache - har forward me arange banane se bachao
51
+ self.register_buffer("pos_cache", torch.arange(config.max_seq_len, dtype=torch.long), persistent=False)
52
 
53
  def forward(self, x: torch.Tensor) -> torch.Tensor:
54
  B, T, C = x.shape
 
58
  freq = torch.tanh(self.to_freq(h)) * 2.0 # B,T,H
59
  phase = torch.tanh(self.to_phase(h)) * math.pi
60
 
61
+ # pos cache use karo, T tak slice
62
+ if T > self.pos_cache.shape[0]:
63
+ # Agar kabhi bada T aaye to dynamically banao
64
+ pos = torch.arange(T, device=x.device, dtype=torch.long)
65
+ else:
66
+ pos = self.pos_cache[:T]
67
+
68
+ out = torch.empty(B, T, C, device=x.device, dtype=x.dtype)
69
 
70
  for i in range(0, T, self.chunk_size):
71
  end = min(i+self.chunk_size, T)
72
+ chunk_len = end - i
73
+
74
  phase_i = phase[:, i:end, :].permute(0,2,1).unsqueeze(-1) # B,H,chunk,1
75
  phase_j = phase.permute(0,2,1).unsqueeze(-2) # B,H,1,T
76
 
 
77
  row_idx = torch.arange(i, end, device=x.device, dtype=torch.long)[:, None]
78
  col_idx = pos[None, :]
79
  dist_long = (row_idx - col_idx).clamp(min=0)
80
+ dist = dist_long.to(freq.dtype).view(1, 1, chunk_len, T)
81
 
82
  freq_i = freq[:, i:end, :].permute(0,2,1).unsqueeze(-1)
83
  angle = (phase_i - phase_j) + freq_i * dist * 0.05
84
 
85
+ score = torch.cos(angle) # B,H,chunk,T
 
 
 
 
86
 
87
+ causal_bool = (row_idx >= col_idx).view(1,1,chunk_len,T)
88
  score = score.masked_fill(~causal_bool, 0.0)
89
  score = score / math.sqrt(self.head_dim)
90
 
 
95
 
96
  V_t = V.permute(0,2,1,3).contiguous() # B,H,T,D
97
  Bh = B * self.n_heads
 
98
 
99
+ s = score.reshape(Bh, chunk_len, T)
100
  v = V_t.reshape(Bh, T, self.head_dim)
101
+ o = torch.bmm(s, v).view(B, self.n_heads, chunk_len, self.head_dim).permute(0,2,1,3).reshape(B, chunk_len, C)
102
  out[:, i:end] = o
103
 
104
  return self.to_out(out)
 
113
  self.head = nn.Linear(config.d_model, config.vocab_size, bias=False)
114
  self.resid_dropout = nn.Dropout(config.dropout)
115
 
116
+ # Sahi order: pehle init, phir tie
 
117
  self.apply(self._init_weights)
 
118
  self.head.weight = self.emb.weight
119
 
120
  def _init_weights(self, module: nn.Module):
 
136
  logits = self.head(self.norm(x))
137
  loss = None
138
  if targets is not None:
 
139
  loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-100)
140
  return logits, loss
code/train.py CHANGED
@@ -1,9 +1,11 @@
1
  import os
2
- # Setup env vars for robust Kaggle training
3
  os.environ["NCCL_P2P_DISABLE"] = "1"
4
  os.environ["NCCL_IB_DISABLE"] = "1"
5
  os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"
6
  os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
 
 
7
 
8
  import math
9
  import time
@@ -14,7 +16,8 @@ from pathlib import Path
14
  import torch
15
  import torch.distributed as dist
16
  from torch.nn.parallel import DistributedDataParallel as DDP
17
- from torch.cuda.amp import GradScaler
 
18
  import numpy as np
19
  from transformers import AutoTokenizer
20
  from huggingface_hub import hf_hub_download, HfApi
@@ -70,43 +73,60 @@ def load_tokenizer_robust(hf_token: str, model_repo: str, subfolder: str, is_mai
70
  p = Path(p_str)
71
  if (p / "tokenizer.json").exists() or (p / "vocab.json").exists():
72
  try:
73
- return AutoTokenizer.from_pretrained(str(p), local_files_only=True, use_fast=fast, trust_remote_code=True)
 
 
74
  except Exception as e:
 
75
  if "sentencepiece" in str(e).lower() or "tiktoken" in str(e).lower():
76
  continue
77
- try:
78
- return AutoTokenizer.from_pretrained(model_repo, subfolder=subfolder, token=hf_token, use_fast=fast, trust_remote_code=True)
79
- except Exception as e:
80
- pass
81
- try:
82
- return AutoTokenizer.from_pretrained(model_repo, token=hf_token, use_fast=fast, trust_remote_code=True)
83
- except Exception as e:
84
- pass
 
 
 
 
85
 
86
  if is_main:
87
  logger.warning("Tokenizer failed, using dummy vocab 64000")
88
  return None
89
 
90
  def load_checkpoint(model, optimizer, scaler, api, token, repo_id, filename="checkpoints/latest_chk.pt", strict=False):
91
- local_path = "latest_chk.pt"
 
 
 
92
  try:
93
  logger.info(f"Checking for existing checkpoint on HF: {filename}")
94
- hf_hub_download(repo_id=repo_id, filename=filename, local_dir=".", token=token)
95
- # Security Note: weights_only=False is used here to allow loading the optimizer state safely inside our trusted environment.
96
- ckpt = torch.load(local_path, map_location="cpu", weights_only=False)
 
 
97
  missing, unexpected = model.load_state_dict(ckpt["model"], strict=strict)
98
  if missing or unexpected:
99
- logger.warning(f"Checkpoint loaded with Missing keys: {missing} | Unexpected keys: {unexpected}")
100
  else:
101
  logger.info("Model weights loaded successfully.")
102
 
103
- optimizer.load_state_dict(ckpt["optimizer"])
 
 
 
104
  if "scaler" in ckpt and scaler is not None:
105
- scaler.load_state_dict(ckpt["scaler"])
 
 
 
106
 
107
  step = ckpt.get("step", 0)
108
  logger.info(f"Resuming from step {step}")
109
- Path(local_path).unlink(missing_ok=True)
110
  return step
111
  except (EntryNotFoundError, HfHubHTTPError):
112
  logger.info("No checkpoint found on HF. Starting from scratch.")
@@ -136,6 +156,7 @@ class ShardPool:
136
  files = self.api.list_repo_files(repo_id=self.repo_id, repo_type="model", token=self.token)
137
  self.available_files = [f for f in files if f.startswith("shards/") and f.endswith(".bin") and not f.endswith("_val.bin")]
138
  np.random.shuffle(self.available_files)
 
139
 
140
  def fill(self, ctx_len):
141
  max_attempts = self.max_pool * 5
@@ -145,22 +166,33 @@ class ShardPool:
145
  raise RuntimeError("Failed to fill ShardPool: too many invalid shards.")
146
  if not self.available_files:
147
  self._refresh_file_list()
 
 
148
  f = self.available_files.pop()
149
  local_p = hf_hub_download(repo_id=self.repo_id, repo_type="dataset", filename=f, local_dir=self.work_dir, token=self.token)
150
 
151
- # Safe dtype selection
152
  dtype = np.uint16 if self.vocab_size <= 65535 else np.uint32
153
  mmap_arr = np.memmap(local_p, dtype=dtype, mode="r")
154
 
155
  if len(mmap_arr) < ctx_len + 1:
156
- logger.warning(f"Shard {f} is too small ({len(mmap_arr)} tokens). Discarding.")
157
  del mmap_arr
158
  Path(local_p).unlink(missing_ok=True)
159
  attempts += 1
160
  continue
 
 
 
 
 
 
 
 
 
 
161
 
162
  self.pool.append((local_p, mmap_arr))
163
- logger.info(f"Loaded shard {f}")
164
 
165
  def sample_batch(self, batch_size, ctx_len, rng):
166
  xs, ys = [], []
@@ -186,59 +218,73 @@ class ShardPool:
186
  logger.warning(f"Failed to delete {old_path}: {e}")
187
 
188
  def main():
189
- parser = argparse.ArgumentParser()
190
- parser.add_argument("--max-steps", type=int, default=100)
191
- parser.add_argument("--micro-batch", type=int, default=2)
192
  parser.add_argument("--grad-accum", type=int, default=16)
193
  parser.add_argument("--eval-every", type=int, default=500)
194
  parser.add_argument("--save-every", type=int, default=500)
195
  parser.add_argument("--rotate-every", type=int, default=500)
196
  parser.add_argument("--lr", type=float, default=3e-4)
197
  parser.add_argument("--weight-decay", type=float, default=0.1)
198
- parser.add_argument("--ctx-len", type=int, default=2048)
199
  parser.add_argument("--vocab-size", type=int, default=None)
200
  args = parser.parse_args()
201
 
 
 
 
 
 
202
  token = get_hf_token()
203
  rank, local_rank, world_size, device, is_ddp = setup_ddp()
204
  is_main = (rank == 0)
205
 
206
- # Random seed
207
  seed = 42 + rank
208
  torch.manual_seed(seed)
 
209
  np.random.seed(seed)
210
  rng = np.random.default_rng(seed)
211
 
212
- # Load resources
213
  tokenizer = load_tokenizer_robust(token, "ViuAI/ViuRec", "tokenizer", is_main)
214
  vocab_size = args.vocab_size or (len(tokenizer) if tokenizer is not None else 64000)
215
-
216
- from model import ViuResonance100M, ModelConfig
217
- config = ModelConfig(vocab_size=vocab_size)
218
- model = ViuResonance100M(config).to(device)
219
-
220
- if is_ddp:
221
- model = DDP(model, device_ids=[local_rank], output_device=local_rank, broadcast_buffers=False)
222
- unwrapped_model = model.module
223
- else:
224
- unwrapped_model = model
225
 
226
- # Optional torch.compile fallback
 
 
 
 
227
  try:
228
- model = torch.compile(model)
229
- if is_main: logger.info("Compiled model successfully.")
 
230
  except Exception as e:
231
- if is_main: logger.warning(f"Failed to compile model: {e}. Falling back to eager mode.")
 
 
 
 
 
 
232
 
233
- # Optimizer
234
  try:
235
  optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=args.weight_decay, betas=(0.9, 0.95), fused=True)
236
- except:
237
- if is_main: logger.warning("fused=True failed for AdamW. Falling back to non-fused.")
 
238
  optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=args.weight_decay, betas=(0.9, 0.95))
239
 
240
- # GradScaler for safe fp16/bf16
241
- scaler = GradScaler(enabled=not torch.cuda.is_bf16_supported())
 
 
 
 
242
 
243
  api = HfApi()
244
  start_step = load_checkpoint(unwrapped_model, optimizer, scaler, api, token, "ViuAI/ViuRec", strict=False)
@@ -247,7 +293,7 @@ def main():
247
  shard_pool = ShardPool(api, "ViuAI/viuai-500m-data", work_dir / "shards", token, vocab_size)
248
  shard_pool.fill(args.ctx_len)
249
 
250
- # Val Data Safely (cap at 20M tokens)
251
  val_tokens = None
252
  if is_main:
253
  logger.info("Loading validation data...")
@@ -260,10 +306,11 @@ def main():
260
  val_p = hf_hub_download(repo_id="ViuAI/viuai-500m-data", repo_type="dataset", filename=val_files[0], local_dir=work_dir, token=token)
261
  dtype = np.uint16 if vocab_size <= 65535 else np.uint32
262
  val_arr = np.memmap(val_p, dtype=dtype, mode="r")
263
- max_toks = 20_000_000
264
- val_tokens = np.array(val_arr[:max_toks]) # Copy to RAM, safely capped
265
  del val_arr
266
- Path(val_p).unlink(missing_ok=True)
 
267
  except Exception as e:
268
  logger.warning(f"Failed to load val data: {e}")
269
 
@@ -272,13 +319,12 @@ def main():
272
  if val_tokens is None or len(val_tokens) < args.ctx_len + 1: return 0.0
273
  unwrapped_model.eval()
274
  val_loss = 0.0
275
- val_iters = 20
276
  for _ in range(val_iters):
277
  i = rng.integers(0, len(val_tokens) - args.ctx_len - 1)
278
  x = torch.from_numpy(val_tokens[i:i+args.ctx_len].astype(np.int64)).unsqueeze(0).to(device, non_blocking=True)
279
  y = torch.from_numpy(val_tokens[i+1:i+1+args.ctx_len].astype(np.int64)).unsqueeze(0).to(device, non_blocking=True)
280
- with torch.autocast(device_type="cuda", dtype=torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16):
281
- # Pass use_checkpoint=False for evaluation
282
  _, loss = unwrapped_model(x, y, use_checkpoint=False)
283
  val_loss += loss.item()
284
  unwrapped_model.train()
@@ -287,13 +333,16 @@ def main():
287
  # Training Loop
288
  model.train()
289
  if is_main:
290
- logger.info(f"Starting training for {args.max_steps} steps...")
291
 
292
  for step in range(start_step, args.max_steps):
293
  # LR Schedule
294
  warmup = 1000
295
  progress = min(1.0, step / max(1, warmup))
296
- lr = args.lr * progress if step < warmup else args.lr * (0.5 * (1.0 + math.cos(math.pi * (step - warmup) / max(1, args.max_steps - warmup))))
 
 
 
297
  for param_group in optimizer.param_groups:
298
  param_group['lr'] = lr
299
 
@@ -306,7 +355,7 @@ def main():
306
  x = torch.from_numpy(x_np).to(device, non_blocking=True)
307
  y = torch.from_numpy(y_np).to(device, non_blocking=True)
308
 
309
- with torch.autocast(device_type="cuda", dtype=torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16):
310
  _, loss = model(x, y)
311
  loss = loss / args.grad_accum
312
 
@@ -314,20 +363,22 @@ def main():
314
  accum_loss += loss.item()
315
  del x, y, loss
316
 
317
- # Safe Gradient Clipping with Scaler
318
  scaler.unscale_(optimizer)
319
  torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
320
 
321
- # Check for NaNs
322
  has_nan = not math.isfinite(accum_loss)
323
- for p in model.parameters():
324
- if p.grad is not None and not torch.isfinite(p.grad).all():
325
- has_nan = True
326
- break
 
327
 
328
  if has_nan:
329
- if is_main: logger.warning(f"NaN/Inf detected at step {step}! Skipping optimizer step.")
330
  optimizer.zero_grad(set_to_none=True)
 
331
  continue
332
 
333
  scaler.step(optimizer)
@@ -337,13 +388,15 @@ def main():
337
 
338
  if is_main and step % 10 == 0:
339
  tok_s = (args.micro_batch * args.grad_accum * args.ctx_len * world_size) / dt if dt > 0 else 0
340
- mem_alloc = torch.cuda.memory_allocated(device) / 1e9
341
- mem_res = torch.cuda.memory_reserved(device) / 1e9
342
- logger.info(f"step {step:5d}/{args.max_steps} | loss {accum_loss:.4f} | lr {lr:.2e} | {tok_s:.0f} tok/s | mem alloc={mem_alloc:.2f}GB res={mem_res:.2f}GB")
343
 
344
  if step > start_step and step % args.rotate_every == 0:
345
  shard_pool.rotate_one()
346
  shard_pool.fill(args.ctx_len)
 
 
347
 
348
  if step > start_step and step % args.eval_every == 0 and is_main:
349
  val_loss = evaluate()
@@ -353,16 +406,18 @@ def main():
353
  if is_ddp: dist.barrier()
354
  if is_main:
355
  logger.info(f"Saving checkpoint at step {step}...")
 
 
356
  chk = {
357
  "model": unwrapped_model.state_dict(),
358
  "optimizer": optimizer.state_dict(),
359
  "scaler": scaler.state_dict(),
360
  "step": step,
361
  }
362
- torch.save(chk, "latest_chk.pt")
363
  try:
364
  api.upload_file(
365
- path_or_fileobj="latest_chk.pt",
366
  path_in_repo="checkpoints/latest_chk.pt",
367
  repo_id="ViuAI/ViuRec",
368
  token=token
 
1
  import os
2
+ # T4 ke liye robust env
3
  os.environ["NCCL_P2P_DISABLE"] = "1"
4
  os.environ["NCCL_IB_DISABLE"] = "1"
5
  os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"
6
  os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
7
+ # T4 optimization
8
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
9
 
10
  import math
11
  import time
 
16
  import torch
17
  import torch.distributed as dist
18
  from torch.nn.parallel import DistributedDataParallel as DDP
19
+ # FIX 1: Naya amp API - T4 ke liye must (bf16 nahi, fp16)
20
+ from torch.amp import GradScaler, autocast
21
  import numpy as np
22
  from transformers import AutoTokenizer
23
  from huggingface_hub import hf_hub_download, HfApi
 
73
  p = Path(p_str)
74
  if (p / "tokenizer.json").exists() or (p / "vocab.json").exists():
75
  try:
76
+ tok = AutoTokenizer.from_pretrained(str(p), local_files_only=True, use_fast=fast, trust_remote_code=True)
77
+ if is_main: logger.info(f"Loaded tokenizer from {p} fast={fast}")
78
+ return tok
79
  except Exception as e:
80
+ if is_main: logger.warning(f"Local tokenizer {p} fast={fast} failed: {e}")
81
  if "sentencepiece" in str(e).lower() or "tiktoken" in str(e).lower():
82
  continue
83
+ # HF se try
84
+ for attempt_subfolder in [subfolder, None]:
85
+ try:
86
+ if attempt_subfolder:
87
+ tok = AutoTokenizer.from_pretrained(model_repo, subfolder=attempt_subfolder, token=hf_token, use_fast=fast, trust_remote_code=True)
88
+ else:
89
+ tok = AutoTokenizer.from_pretrained(model_repo, token=hf_token, use_fast=fast, trust_remote_code=True)
90
+ if is_main: logger.info(f"Loaded tokenizer from HF {model_repo} subfolder={attempt_subfolder} fast={fast}")
91
+ return tok
92
+ except Exception as e:
93
+ if is_main: logger.warning(f"HF tokenizer try failed fast={fast} subfolder={attempt_subfolder}: {e}")
94
+ continue
95
 
96
  if is_main:
97
  logger.warning("Tokenizer failed, using dummy vocab 64000")
98
  return None
99
 
100
  def load_checkpoint(model, optimizer, scaler, api, token, repo_id, filename="checkpoints/latest_chk.pt", strict=False):
101
+ # FIX 2: Path bug fix - hf_hub_download local_dir="." + filename="checkpoints/..." => ./checkpoints/...
102
+ # Toh local_path bhi wahi hona chahiye
103
+ local_path = Path(filename) # checkpoints/latest_chk.pt
104
+ local_path.parent.mkdir(parents=True, exist_ok=True)
105
  try:
106
  logger.info(f"Checking for existing checkpoint on HF: {filename}")
107
+ # download return karta hai actual local path
108
+ downloaded_path = hf_hub_download(repo_id=repo_id, filename=filename, local_dir=".", token=token)
109
+ logger.info(f"Downloaded checkpoint to {downloaded_path}")
110
+ # Security: trusted env me hi weights_only=False use karo
111
+ ckpt = torch.load(downloaded_path, map_location="cpu", weights_only=False)
112
  missing, unexpected = model.load_state_dict(ckpt["model"], strict=strict)
113
  if missing or unexpected:
114
+ logger.warning(f"Checkpoint loaded with Missing: {len(missing)} | Unexpected: {len(unexpected)}")
115
  else:
116
  logger.info("Model weights loaded successfully.")
117
 
118
+ try:
119
+ optimizer.load_state_dict(ckpt["optimizer"])
120
+ except Exception as e:
121
+ logger.warning(f"Optimizer state load failed, continuing: {e}")
122
  if "scaler" in ckpt and scaler is not None:
123
+ try:
124
+ scaler.load_state_dict(ckpt["scaler"])
125
+ except Exception as e:
126
+ logger.warning(f"Scaler state load failed: {e}")
127
 
128
  step = ckpt.get("step", 0)
129
  logger.info(f"Resuming from step {step}")
 
130
  return step
131
  except (EntryNotFoundError, HfHubHTTPError):
132
  logger.info("No checkpoint found on HF. Starting from scratch.")
 
156
  files = self.api.list_repo_files(repo_id=self.repo_id, repo_type="model", token=self.token)
157
  self.available_files = [f for f in files if f.startswith("shards/") and f.endswith(".bin") and not f.endswith("_val.bin")]
158
  np.random.shuffle(self.available_files)
159
+ logger.info(f"Found {len(self.available_files)} training shards")
160
 
161
  def fill(self, ctx_len):
162
  max_attempts = self.max_pool * 5
 
166
  raise RuntimeError("Failed to fill ShardPool: too many invalid shards.")
167
  if not self.available_files:
168
  self._refresh_file_list()
169
+ if not self.available_files:
170
+ raise RuntimeError("No shard files available")
171
  f = self.available_files.pop()
172
  local_p = hf_hub_download(repo_id=self.repo_id, repo_type="dataset", filename=f, local_dir=self.work_dir, token=self.token)
173
 
 
174
  dtype = np.uint16 if self.vocab_size <= 65535 else np.uint32
175
  mmap_arr = np.memmap(local_p, dtype=dtype, mode="r")
176
 
177
  if len(mmap_arr) < ctx_len + 1:
178
+ logger.warning(f"Shard {f} too small ({len(mmap_arr)}). Discarding.")
179
  del mmap_arr
180
  Path(local_p).unlink(missing_ok=True)
181
  attempts += 1
182
  continue
183
+
184
+ # FIX 3: Vocab validation - token vocab se bada toh shard corrupt
185
+ if len(mmap_arr) > 1000:
186
+ sample_max = int(mmap_arr[:1000].max())
187
+ if sample_max >= self.vocab_size:
188
+ logger.warning(f"Shard {f} has token {sample_max} >= vocab {self.vocab_size}. Discarding.")
189
+ del mmap_arr
190
+ Path(local_p).unlink(missing_ok=True)
191
+ attempts += 1
192
+ continue
193
 
194
  self.pool.append((local_p, mmap_arr))
195
+ logger.info(f"Loaded shard {f} ({len(mmap_arr)} tokens)")
196
 
197
  def sample_batch(self, batch_size, ctx_len, rng):
198
  xs, ys = [], []
 
218
  logger.warning(f"Failed to delete {old_path}: {e}")
219
 
220
  def main():
221
+ parser = argparse.ArgumentParser(description="ViuResonance Training - T4 Optimized")
222
+ parser.add_argument("--max-steps", type=int, default=10000)
223
+ parser.add_argument("--micro-batch", type=int, default=1, help="T4 16GB ke liye 1 rakho")
224
  parser.add_argument("--grad-accum", type=int, default=16)
225
  parser.add_argument("--eval-every", type=int, default=500)
226
  parser.add_argument("--save-every", type=int, default=500)
227
  parser.add_argument("--rotate-every", type=int, default=500)
228
  parser.add_argument("--lr", type=float, default=3e-4)
229
  parser.add_argument("--weight-decay", type=float, default=0.1)
230
+ parser.add_argument("--ctx-len", type=int, default=1024, help="T4 pe 1024 safe, 2048 OOM de sakta hai")
231
  parser.add_argument("--vocab-size", type=int, default=None)
232
  args = parser.parse_args()
233
 
234
+ # FIX 4: T4 TF32 optimization
235
+ torch.backends.cuda.matmul.allow_tf32 = True
236
+ torch.backends.cudnn.allow_tf32 = True
237
+ torch.backends.cudnn.benchmark = True
238
+
239
  token = get_hf_token()
240
  rank, local_rank, world_size, device, is_ddp = setup_ddp()
241
  is_main = (rank == 0)
242
 
243
+ # FIX 5: Seed properly for CUDA
244
  seed = 42 + rank
245
  torch.manual_seed(seed)
246
+ torch.cuda.manual_seed_all(seed)
247
  np.random.seed(seed)
248
  rng = np.random.default_rng(seed)
249
 
250
+ # Load tokenizer
251
  tokenizer = load_tokenizer_robust(token, "ViuAI/ViuRec", "tokenizer", is_main)
252
  vocab_size = args.vocab_size or (len(tokenizer) if tokenizer is not None else 64000)
253
+ if is_main:
254
+ logger.info(f"Using vocab_size={vocab_size}, ctx_len={args.ctx_len}, device={device}, is_ddp={is_ddp}")
 
 
 
 
 
 
 
 
255
 
256
+ from model_fixed_T4 import ViuResonance100M, ModelConfig
257
+ config = ModelConfig(vocab_size=vocab_size, chunk_size=256) # T4 ke liye 256
258
+ unwrapped_model = ViuResonance100M(config).to(device)
259
+
260
+ # FIX 6: torch.compile pehle, DDP baad me - T4 par compile thoda heavy hai, fallback rakha hai
261
  try:
262
+ unwrapped_model = torch.compile(unwrapped_model, mode="default") # T4 ke liye reduce-overhead bhi try kar sakte ho
263
+ if is_main: logger.info("Compiled model successfully (before DDP).")
264
+ compiled = True
265
  except Exception as e:
266
+ if is_main: logger.warning(f"Failed to compile model: {e}. Falling back to eager.")
267
+ compiled = False
268
+
269
+ if is_ddp:
270
+ model = DDP(unwrapped_model, device_ids=[local_rank], output_device=local_rank, broadcast_buffers=False)
271
+ else:
272
+ model = unwrapped_model
273
 
274
+ # Optimizer - T4 Turing me fused AdamW support nahi hota, fallback rahega
275
  try:
276
  optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=args.weight_decay, betas=(0.9, 0.95), fused=True)
277
+ if is_main: logger.info("Using fused AdamW")
278
+ except Exception as e:
279
+ if is_main: logger.warning(f"fused=True failed ({e}). Using non-fused AdamW for T4.")
280
  optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=args.weight_decay, betas=(0.9, 0.95))
281
 
282
+ # FIX 7: Naya GradScaler API + T4 = fp16 (bf16_supported=False hoga T4 par)
283
+ use_bf16 = torch.cuda.is_available() and torch.cuda.is_bf16_supported()
284
+ amp_dtype = torch.bfloat16 if use_bf16 else torch.float16
285
+ scaler = GradScaler('cuda', enabled=not use_bf16)
286
+ if is_main:
287
+ logger.info(f"T4 Detected: bf16_supported={use_bf16}, using amp_dtype={amp_dtype}, scaler_enabled={scaler.is_enabled()}")
288
 
289
  api = HfApi()
290
  start_step = load_checkpoint(unwrapped_model, optimizer, scaler, api, token, "ViuAI/ViuRec", strict=False)
 
293
  shard_pool = ShardPool(api, "ViuAI/viuai-500m-data", work_dir / "shards", token, vocab_size)
294
  shard_pool.fill(args.ctx_len)
295
 
296
+ # Val Data (only main)
297
  val_tokens = None
298
  if is_main:
299
  logger.info("Loading validation data...")
 
306
  val_p = hf_hub_download(repo_id="ViuAI/viuai-500m-data", repo_type="dataset", filename=val_files[0], local_dir=work_dir, token=token)
307
  dtype = np.uint16 if vocab_size <= 65535 else np.uint32
308
  val_arr = np.memmap(val_p, dtype=dtype, mode="r")
309
+ max_toks = 2_000_000 # T4 RAM ke liye 20M se 2M kiya
310
+ val_tokens = np.array(val_arr[:max_toks])
311
  del val_arr
312
+ # Val file ko delete mat karo taki dobara download na ho - T4 pe time bachega
313
+ # Path(val_p).unlink(missing_ok=True)
314
  except Exception as e:
315
  logger.warning(f"Failed to load val data: {e}")
316
 
 
319
  if val_tokens is None or len(val_tokens) < args.ctx_len + 1: return 0.0
320
  unwrapped_model.eval()
321
  val_loss = 0.0
322
+ val_iters = 10 # T4 pe kam iter
323
  for _ in range(val_iters):
324
  i = rng.integers(0, len(val_tokens) - args.ctx_len - 1)
325
  x = torch.from_numpy(val_tokens[i:i+args.ctx_len].astype(np.int64)).unsqueeze(0).to(device, non_blocking=True)
326
  y = torch.from_numpy(val_tokens[i+1:i+1+args.ctx_len].astype(np.int64)).unsqueeze(0).to(device, non_blocking=True)
327
+ with autocast('cuda', dtype=amp_dtype):
 
328
  _, loss = unwrapped_model(x, y, use_checkpoint=False)
329
  val_loss += loss.item()
330
  unwrapped_model.train()
 
333
  # Training Loop
334
  model.train()
335
  if is_main:
336
+ logger.info(f"Starting training for {args.max_steps} steps from step {start_step}...")
337
 
338
  for step in range(start_step, args.max_steps):
339
  # LR Schedule
340
  warmup = 1000
341
  progress = min(1.0, step / max(1, warmup))
342
+ if step < warmup:
343
+ lr = args.lr * progress
344
+ else:
345
+ lr = args.lr * (0.5 * (1.0 + math.cos(math.pi * (step - warmup) / max(1, args.max_steps - warmup))))
346
  for param_group in optimizer.param_groups:
347
  param_group['lr'] = lr
348
 
 
355
  x = torch.from_numpy(x_np).to(device, non_blocking=True)
356
  y = torch.from_numpy(y_np).to(device, non_blocking=True)
357
 
358
+ with autocast('cuda', dtype=amp_dtype):
359
  _, loss = model(x, y)
360
  loss = loss / args.grad_accum
361
 
 
363
  accum_loss += loss.item()
364
  del x, y, loss
365
 
366
+ # Gradient Clipping
367
  scaler.unscale_(optimizer)
368
  torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
369
 
370
+ # NaN Check
371
  has_nan = not math.isfinite(accum_loss)
372
+ if not has_nan:
373
+ for p in model.parameters():
374
+ if p.grad is not None and not torch.isfinite(p.grad).all():
375
+ has_nan = True
376
+ break
377
 
378
  if has_nan:
379
+ if is_main: logger.warning(f"NaN/Inf detected at step {step}! Skipping.")
380
  optimizer.zero_grad(set_to_none=True)
381
+ scaler.update() # FIX 8: NaN par bhi scaler update karna zaruri hai warna stuck hoga
382
  continue
383
 
384
  scaler.step(optimizer)
 
388
 
389
  if is_main and step % 10 == 0:
390
  tok_s = (args.micro_batch * args.grad_accum * args.ctx_len * world_size) / dt if dt > 0 else 0
391
+ mem_alloc = torch.cuda.memory_allocated(device) / 1e9 if torch.cuda.is_available() else 0
392
+ mem_res = torch.cuda.memory_reserved(device) / 1e9 if torch.cuda.is_available() else 0
393
+ logger.info(f"step {step:5d}/{args.max_steps} | loss {accum_loss:.4f} | lr {lr:.2e} | {tok_s:.0f} tok/s | mem {mem_alloc:.2f}GB/{mem_res:.2f}GB")
394
 
395
  if step > start_step and step % args.rotate_every == 0:
396
  shard_pool.rotate_one()
397
  shard_pool.fill(args.ctx_len)
398
+ if torch.cuda.is_available():
399
+ torch.cuda.empty_cache() # T4 me fragmentation se bachne ke liye
400
 
401
  if step > start_step and step % args.eval_every == 0 and is_main:
402
  val_loss = evaluate()
 
406
  if is_ddp: dist.barrier()
407
  if is_main:
408
  logger.info(f"Saving checkpoint at step {step}...")
409
+ ckpt_path = Path("checkpoints/latest_chk.pt")
410
+ ckpt_path.parent.mkdir(parents=True, exist_ok=True)
411
  chk = {
412
  "model": unwrapped_model.state_dict(),
413
  "optimizer": optimizer.state_dict(),
414
  "scaler": scaler.state_dict(),
415
  "step": step,
416
  }
417
+ torch.save(chk, ckpt_path)
418
  try:
419
  api.upload_file(
420
+ path_or_fileobj=str(ckpt_path),
421
  path_in_repo="checkpoints/latest_chk.pt",
422
  repo_id="ViuAI/ViuRec",
423
  token=token