garima-mahato commited on
Commit
7fb86cb
·
verified ·
1 Parent(s): 5b92639

Update model.py

Browse files
Files changed (1) hide show
  1. model.py +556 -550
model.py CHANGED
@@ -1,551 +1,557 @@
1
- import os
2
- import math
3
- from typing import List, Optional, Tuple, Union
4
- import time
5
- import inspect
6
- from dataclasses import dataclass
7
- import torch
8
- import torch.nn as nn
9
- from torch.nn import functional as F
10
- import torch.utils.checkpoint
11
- from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss
12
- from torch.utils.data import DataLoader
13
-
14
- from datasets import load_dataset
15
- from transformers import GPT2Tokenizer
16
-
17
- import pytorch_lightning as pl
18
- from pytorch_lightning.callbacks import LearningRateMonitor, RichProgressBar
19
- from pytorch_lightning.loggers import WandbLogger
20
- from lightning.pytorch.callbacks.progress.rich_progress import RichProgressBarTheme
21
- from pytorch_lightning.callbacks import ModelCheckpoint
22
-
23
- @dataclass
24
- class SmolLM2Config:
25
- hidden_size: int = 576
26
- intermediate_size: int = 1536
27
- num_hidden_layers: int = 30
28
- num_attention_heads: int = 9
29
- num_key_value_heads: int = 3
30
- hidden_act: str = "silu"
31
- max_position_embeddings: int = 2048
32
- initializer_range: float = 0.041666666666666664
33
- rms_norm_eps: float = 1.0e-05
34
- vocab_size: int = 49152
35
- rope_theta: float = 10000.0
36
- use_cache: bool = True
37
- tie_word_embeddings: bool = True
38
- torch_dtype: str = "float32"
39
- block_size: int = 512
40
-
41
- class SmolLM2RMSNorm(nn.Module):
42
- def __init__(self, hidden_size, eps=1e-6):
43
- """
44
- SmolLM2RMSNorm is equivalent to T5LayerNorm
45
- """
46
- super().__init__()
47
- self.weight = nn.Parameter(torch.ones(hidden_size))
48
- self.variance_epsilon = eps
49
-
50
- def forward(self, hidden_states):
51
- input_dtype = hidden_states.dtype
52
- variance = hidden_states.to(torch.float32).pow(2).mean(-1, keepdim=True)
53
- hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
54
-
55
- return (self.weight * hidden_states).to(input_dtype)
56
-
57
-
58
- class SmolLM2RotaryEmbedding(torch.nn.Module):
59
- def __init__(self, dim, max_position_embeddings=2048, base=10000, device=None):
60
- super().__init__()
61
- inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float().to(device) / dim))
62
- self.register_buffer("inv_freq", inv_freq, persistent=False)
63
-
64
- # Build here to make `torch.jit.trace` work.
65
- self.max_seq_len_cached = max_position_embeddings
66
- t = torch.arange(self.max_seq_len_cached, device=self.inv_freq.device, dtype=self.inv_freq.dtype)
67
- freqs = torch.einsum("i,j->ij", t, self.inv_freq)
68
- # Different from paper, but it uses a different permutation in order to obtain the same calculation
69
- emb = torch.cat((freqs, freqs), dim=-1)
70
- dtype = torch.get_default_dtype()
71
- self.register_buffer("cos_cached", emb.cos()[None, None, :, :].to(dtype), persistent=False)
72
- self.register_buffer("sin_cached", emb.sin()[None, None, :, :].to(dtype), persistent=False)
73
-
74
- def forward(self, x, seq_len=None):
75
- # x: [bs, num_attention_heads, seq_len, head_size]
76
- # This `if` block is unlikely to be run after we build sin/cos in `__init__`. Keep the logic here just in case.
77
- if seq_len > self.max_seq_len_cached:
78
- self.max_seq_len_cached = seq_len
79
- t = torch.arange(self.max_seq_len_cached, device=x.device, dtype=self.inv_freq.dtype)
80
- freqs = torch.einsum("i,j->ij", t, self.inv_freq)
81
- # Different from paper, but it uses a different permutation in order to obtain the same calculation
82
- emb = torch.cat((freqs, freqs), dim=-1).to(x.device)
83
- self.register_buffer("cos_cached", emb.cos()[None, None, :, :].to(x.dtype), persistent=False)
84
- self.register_buffer("sin_cached", emb.sin()[None, None, :, :].to(x.dtype), persistent=False)
85
- return (
86
- self.cos_cached[:, :, :seq_len, ...].to(dtype=x.dtype),
87
- self.sin_cached[:, :, :seq_len, ...].to(dtype=x.dtype),
88
- )
89
-
90
-
91
- def rotate_half(x):
92
- """Rotates half the hidden dims of the input."""
93
- x1 = x[..., : x.shape[-1] // 2]
94
- x2 = x[..., x.shape[-1] // 2 :]
95
- return torch.cat((-x2, x1), dim=-1)
96
-
97
-
98
- def apply_rotary_pos_emb(q, k, cos, sin, position_ids):
99
- # The first two dimensions of cos and sin are always 1, so we can `squeeze` them.
100
- cos = cos.squeeze(1).squeeze(0) # [seq_len, dim]
101
- sin = sin.squeeze(1).squeeze(0) # [seq_len, dim]
102
- cos = cos.unsqueeze(0) # [bs, 1, seq_len, dim]
103
- sin = sin.unsqueeze(0) # [bs, 1, seq_len, dim]
104
- q_embed = (q * cos) + (rotate_half(q) * sin)
105
- k_embed = (k * cos) + (rotate_half(k) * sin)
106
- return q_embed, k_embed
107
-
108
- def _precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0) -> torch.Tensor:
109
- """Precompute the frequency tensor for complex exponentials (cos + i*sin)"""
110
- # Only compute frequencies for half the dimension
111
- freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
112
- t = torch.arange(end)
113
- freqs = torch.outer(t, freqs) # [seq_len, dim//2]
114
-
115
- # Compute cos and sin
116
- freqs_cos = torch.cos(freqs) # [seq_len, dim//2]
117
- freqs_sin = torch.sin(freqs) # [seq_len, dim//2]
118
-
119
- # Stack real and imaginary parts
120
- freqs_cis = torch.stack([freqs_cos, freqs_sin], dim=-1) # [seq_len, dim//2, 2]
121
-
122
- return freqs_cis
123
-
124
-
125
- class SmolLM2MLP(nn.Module):
126
- def __init__(
127
- self,
128
- hidden_size: int,
129
- intermediate_size: int,
130
- hidden_act: str,
131
- ):
132
- super().__init__()
133
- self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
134
- self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
135
- self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
136
- self.act_fn = nn.SiLU()
137
-
138
- def forward(self, x):
139
- return self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
140
-
141
- def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
142
- """
143
- This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
144
- num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
145
- """
146
- batch, num_key_value_heads, slen, head_dim = hidden_states.shape
147
- if n_rep == 1:
148
- return hidden_states
149
- hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
150
- return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
151
-
152
-
153
- class SmolLM2Attention(nn.Module):
154
- """Multi-headed attention from 'Attention Is All You Need' paper"""
155
-
156
- def __init__(self, config: SmolLM2Config):
157
- super().__init__()
158
- self.config = config
159
- self.hidden_size = config.hidden_size
160
- self.num_heads = config.num_attention_heads
161
- self.head_dim = self.hidden_size // self.num_heads
162
- self.num_key_value_heads = config.num_key_value_heads
163
- self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
164
- self.max_position_embeddings = config.max_position_embeddings
165
-
166
- if (self.head_dim * self.num_heads) != self.hidden_size:
167
- raise ValueError(
168
- f"hidden_size must be divisible by num_heads (got `hidden_size`: {self.hidden_size}"
169
- f" and `num_heads`: {self.num_heads})."
170
- )
171
- self.q_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim, bias=False)
172
- self.k_proj = nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False)
173
- self.v_proj = nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False)
174
- self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.hidden_size, bias=False)
175
- self.rotary_emb = SmolLM2RotaryEmbedding(self.head_dim, max_position_embeddings=self.max_position_embeddings)
176
-
177
- def _shape(self, tensor: torch.Tensor, seq_len: int, bsz: int):
178
- return tensor.view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2).contiguous()
179
-
180
- def forward(
181
- self,
182
- hidden_states: torch.Tensor,
183
- attention_mask: Optional[torch.Tensor] = None,
184
- position_ids: Optional[torch.LongTensor] = None,
185
- past_key_value: Optional[Tuple[torch.Tensor]] = None,
186
- output_attentions: bool = False,
187
- use_cache: bool = False,
188
- is_sdpa: bool = True,
189
- is_causal = None
190
- ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]:
191
- bsz, q_len, _ = hidden_states.size()
192
-
193
- query_states = self.q_proj(hidden_states).view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
194
- key_states = self.k_proj(hidden_states).view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
195
- value_states = self.v_proj(hidden_states).view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
196
-
197
- kv_seq_len = key_states.shape[-2]
198
- if past_key_value is not None:
199
- kv_seq_len += past_key_value[0].shape[-2]
200
- cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len)
201
- query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin, position_ids)
202
- # [bsz, nh, t, hd]
203
-
204
- if past_key_value is not None:
205
- # reuse k, v, self_attention
206
- key_states = torch.cat([past_key_value[0], key_states], dim=2)
207
- value_states = torch.cat([past_key_value[1], value_states], dim=2)
208
-
209
- past_key_value = (key_states, value_states) if use_cache else None
210
-
211
- if is_sdpa:
212
- key = key_states
213
- value = value_states
214
- query = query_states
215
- if self.num_key_value_groups:
216
- key = repeat_kv(key, self.num_key_value_groups)
217
- value = repeat_kv(value, self.num_key_value_groups)
218
-
219
- causal_mask = attention_mask
220
- if attention_mask is not None:
221
- causal_mask = causal_mask[:, :, :, : key.shape[-2]]
222
-
223
- # SDPA with memory-efficient backend is bugged with non-contiguous inputs and custom attn_mask for some torch versions
224
- # Reference: https://github.com/pytorch/pytorch/issues/112577.
225
- query = query.contiguous()
226
- key = key.contiguous()
227
- value = value.contiguous()
228
-
229
- # We dispatch to SDPA's Flash Attention or Efficient kernels via this `is_causal` if statement instead of an inline conditional assignment
230
- # in SDPA to support both torch.compile's dynamic shapes and full graph options. An inline conditional prevents dynamic shapes from compiling.
231
- if is_causal is None:
232
- is_causal = causal_mask is None and query.shape[2] > 1
233
-
234
- attn_output = torch.nn.functional.scaled_dot_product_attention(
235
- query,
236
- key,
237
- value,
238
- attn_mask=causal_mask,
239
- dropout_p=0.0,
240
- scale=self.head_dim**-0.5,
241
- is_causal=is_causal,
242
- )
243
- attn_output = attn_output.transpose(1, 2).contiguous()
244
- else:
245
- attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) / math.sqrt(self.head_dim)
246
-
247
- if attn_weights.size() != (bsz, self.num_heads, q_len, kv_seq_len):
248
- raise ValueError(
249
- f"Attention weights should be of size {(bsz, self.num_heads, q_len, kv_seq_len)}, but is"
250
- f" {attn_weights.size()}"
251
- )
252
-
253
- if attention_mask is not None:
254
- if attention_mask.size() != (bsz, 1, q_len, kv_seq_len):
255
- raise ValueError(
256
- f"Attention mask should be of size {(bsz, 1, q_len, kv_seq_len)}, but is {attention_mask.size()}"
257
- )
258
- attn_weights = attn_weights + attention_mask
259
- attn_weights = torch.max(
260
- attn_weights, torch.tensor(torch.finfo(attn_weights.dtype).min, device=attn_weights.device)
261
- )
262
-
263
- # upcast attention to fp32
264
- attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype)
265
- attn_output = torch.matmul(attn_weights, value_states)
266
-
267
- if attn_output.size() != (bsz, self.num_heads, q_len, self.head_dim):
268
- raise ValueError(
269
- f"`attn_output` should be of size {(bsz, self.num_heads, q_len, self.head_dim)}, but is"
270
- f" {attn_output.size()}"
271
- )
272
-
273
- attn_output = attn_output.transpose(1, 2)
274
- attn_output = attn_output.reshape(bsz, q_len, self.hidden_size)
275
-
276
- attn_output = self.o_proj(attn_output)
277
-
278
- if not output_attentions:
279
- attn_weights = None
280
-
281
- return attn_output, attn_weights, past_key_value
282
-
283
-
284
- class SmolLM2DecoderLayer(nn.Module):
285
- def __init__(self, config: SmolLM2Config):
286
- super().__init__()
287
- self.hidden_size = config.hidden_size
288
- self.self_attn = SmolLM2Attention(config=config)
289
- self.mlp = SmolLM2MLP(
290
- hidden_size=self.hidden_size,
291
- intermediate_size=config.intermediate_size,
292
- hidden_act=config.hidden_act,
293
- )
294
- self.input_layernorm = SmolLM2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
295
- self.post_attention_layernorm = SmolLM2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
296
-
297
- def forward(
298
- self,
299
- hidden_states: torch.Tensor,
300
- attention_mask: Optional[torch.Tensor] = None,
301
- position_ids: Optional[torch.LongTensor] = None,
302
- past_key_value: Optional[Tuple[torch.Tensor]] = None,
303
- output_attentions: Optional[bool] = False,
304
- use_cache: Optional[bool] = False,
305
- ) -> Tuple[torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]]]:
306
- """
307
- Args:
308
- hidden_states (`torch.FloatTensor`): input to the layer of shape `(batch, seq_len, embed_dim)`
309
- attention_mask (`torch.FloatTensor`, *optional*): attention mask of size
310
- `(batch, 1, tgt_len, src_len)` where padding elements are indicated by very large negative values.
311
- output_attentions (`bool`, *optional*):
312
- Whether or not to return the attentions tensors of all attention layers. See `attentions` under
313
- returned tensors for more detail.
314
- use_cache (`bool`, *optional*):
315
- If set to `True`, `past_key_values` key value states are returned and can be used to speed up decoding
316
- (see `past_key_values`).
317
- past_key_value (`Tuple(torch.FloatTensor)`, *optional*): cached past key and value projection states
318
- """
319
-
320
- residual = hidden_states
321
-
322
- hidden_states = self.input_layernorm(hidden_states)
323
-
324
- # Self Attention
325
- hidden_states, self_attn_weights, present_key_value = self.self_attn(
326
- hidden_states=hidden_states,
327
- attention_mask=attention_mask,
328
- position_ids=position_ids,
329
- past_key_value=past_key_value,
330
- output_attentions=output_attentions,
331
- use_cache=use_cache,
332
- )
333
- hidden_states = residual + hidden_states
334
-
335
- # Fully Connected
336
- residual = hidden_states
337
- hidden_states = self.post_attention_layernorm(hidden_states)
338
- hidden_states = self.mlp(hidden_states)
339
- hidden_states = residual + hidden_states
340
-
341
- outputs = (hidden_states,)
342
-
343
- if output_attentions:
344
- outputs += (self_attn_weights,)
345
-
346
- if use_cache:
347
- outputs += (present_key_value,)
348
-
349
- return outputs
350
-
351
- class SmolLM2Model(nn.Module):
352
- def __init__(self, config: SmolLM2Config):
353
- super().__init__()
354
- self.config = config
355
- self.vocab_size = config.vocab_size
356
- self.head_dim = config.hidden_size // config.num_attention_heads
357
-
358
- self.dtype = getattr(torch, config.torch_dtype) if hasattr(torch, config.torch_dtype) else torch.float32
359
-
360
- self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size)
361
- self.layers = nn.ModuleList([SmolLM2DecoderLayer(config) for _ in range(config.num_hidden_layers)])
362
- self.norm = SmolLM2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
363
-
364
- self.freqs_cis = _precompute_freqs_cis(
365
- self.head_dim,
366
- config.max_position_embeddings,
367
- config.rope_theta,
368
- )
369
-
370
- self.apply(self._init_weights)
371
-
372
- self.to(self.dtype)
373
-
374
- def _init_weights(self, module):
375
- if isinstance(module, nn.Linear):
376
- torch.nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range)
377
- elif isinstance(module, nn.Embedding):
378
- torch.nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range)
379
-
380
- def forward(
381
- self,
382
- input_ids: torch.Tensor,
383
- attention_mask: Optional[torch.Tensor] = None,
384
- ) -> torch.Tensor:
385
- hidden_states = self.embed_tokens(input_ids)
386
-
387
- if attention_mask is not None:
388
- attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)
389
- attention_mask = attention_mask.to(dtype=hidden_states.dtype)
390
- attention_mask = (1.0 - attention_mask) * torch.finfo(hidden_states.dtype).min
391
-
392
- freqs_cis = self.freqs_cis.to(device=hidden_states.device, dtype=hidden_states.dtype)
393
-
394
- for layer in self.layers:
395
- hidden_states = layer(hidden_states, attention_mask, freqs_cis)[0]
396
-
397
- hidden_states = self.norm(hidden_states)
398
- return hidden_states
399
-
400
- class SmolLM2ForCausalLM(nn.Module):
401
- def __init__(self, config: SmolLM2Config):
402
- super().__init__()
403
- self.config = config
404
- self.model = SmolLM2Model(config)
405
- self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
406
-
407
- # Tie weights if configured
408
- if config.tie_word_embeddings:
409
- self.lm_head.weight = self.model.embed_tokens.weight
410
-
411
- def forward(
412
- self,
413
- input_ids: torch.Tensor,
414
- attention_mask: Optional[torch.Tensor] = None,
415
- labels: Optional[torch.Tensor] = None,
416
- ) -> torch.Tensor:
417
- hidden_states = self.model(input_ids, attention_mask)
418
- logits = self.lm_head(hidden_states)
419
-
420
- loss = None
421
- if labels is not None:
422
- shift_logits = logits[..., :-1, :].contiguous()
423
- shift_labels = labels[..., 1:].contiguous()
424
- loss = F.cross_entropy(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
425
-
426
- return logits, loss
427
-
428
- @torch.no_grad()
429
- def generate(self, idx, max_new_tokens, temperature=1.0, top_k=None):
430
- """
431
- Generate text given a starting sequence of tokens.
432
- Args:
433
- idx (torch.Tensor): Starting token indices, shape (B, T)
434
- max_new_tokens (int): Number of tokens to generate
435
- temperature (float): Sampling temperature (1.0 = no change, < 1.0 = less random, > 1.0 = more random)
436
- top_k (int): If specified, only sample from the top k most probable tokens
437
- """
438
- for _ in range(max_new_tokens):
439
- # if the sequence context is growing too long we must crop it at block_size
440
- idx_cond = (
441
- idx
442
- if idx.size(1) <= self.config.block_size
443
- else idx[:, -self.config.block_size :]
444
- )
445
- # forward the model to get the logits for the index in the sequence
446
- logits, _ = self(idx_cond)
447
- # pluck the logits at the final step and scale by desired temperature
448
- logits = logits[:, -1, :] / temperature
449
- # optionally crop the logits to only the top k options
450
- if top_k is not None:
451
- v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
452
- logits[logits < v[:, [-1]]] = -float("Inf")
453
- # apply softmax to convert logits to (normalized) probabilities
454
- probs = F.softmax(logits, dim=-1)
455
- # sample from the distribution
456
- idx_next = torch.multinomial(probs, num_samples=1)
457
- # append sampled index to the running sequence
458
- idx = torch.cat((idx, idx_next), dim=1)
459
-
460
- return idx
461
-
462
-
463
- class plSmolLM2(pl.LightningModule):
464
- def __init__(self, config: SmolLM2Config, lr, warmup_steps, max_steps, step=None):
465
- super().__init__()
466
- self.save_hyperparameters()
467
- self.config = config
468
- self.model = SmolLM2ForCausalLM(self.config)
469
- self.criterion = nn.CrossEntropyLoss()
470
- self.tokenizer = tokenizer
471
- self.generation_prompt = "Hello there! Today, we are going to talk about "
472
- self._generating = False
473
- self.start_step = step if step is not None else 0
474
-
475
- def forward(self, x):
476
- return self.model(x)
477
-
478
- def training_step(self, batch, batch_idx):
479
- input_ids = batch["input_ids"]
480
- target_ids = batch["labels"]
481
- logits, _ = self(input_ids)
482
- loss = self.criterion(logits.view(-1, logits.size(-1)), target_ids.view(-1))
483
-
484
- # Log the loss with 4 decimal precision
485
- self.log(
486
- "train_loss", loss, prog_bar=True, on_step=True, on_epoch=False, logger=True
487
- )
488
- print(f"Step: {self.start_step+self.global_step}, Train Loss: {loss}")
489
-
490
- # Generate text every n steps, but only if we're not already generating
491
- if (self.global_step) % log_every_n_steps == 0 and not self._generating:
492
- self._generating = True
493
- self.generate_and_log_sample()
494
- self._generating = False
495
- #self.step = self.step + 1
496
-
497
- return loss
498
-
499
- def generate_and_log_sample(self):
500
- """Generate and log a sample of text from the model"""
501
- try:
502
- # Encode the prompt
503
- prompt_ids = self.tokenizer.encode(
504
- self.generation_prompt, return_tensors="pt"
505
- ).to(self.device)
506
-
507
- # Generate new tokens
508
- generated_ids = self.model.generate(
509
- prompt_ids, max_new_tokens=50, temperature=0.8, top_k=40
510
- )
511
-
512
- # Decode the generated tokens
513
- generated_text = self.tokenizer.decode(generated_ids[0].tolist())
514
-
515
- # Create a formatted message
516
- message = (
517
- f"\n{'='*40}\n"
518
- f"Step {self.global_step} generation:\n"
519
- f"Prompt: {self.generation_prompt}\n"
520
- f"Generated: {generated_text}\n"
521
- f"{'='*40}\n"
522
- )
523
-
524
- print(message)
525
-
526
- # Log to WandB
527
- if hasattr(self.logger, "experiment"):
528
- self.logger.experiment.log(
529
- {"generated_text": generated_text, "global_step": self.global_step}
530
- )
531
- except Exception as e:
532
- print(f"Generation failed with error: {str(e)}")
533
-
534
- def configure_optimizers(self):
535
- optimizer = torch.optim.AdamW(self.parameters(), lr=self.hparams.lr)
536
-
537
- def lr_lambda(current_step):
538
- if current_step < self.hparams.warmup_steps:
539
- return self.hparams.lr * (current_step + 1) / self.hparams.warmup_steps
540
- elif current_step > self.hparams.max_steps:
541
- return self.hparams.lr * 0.1
542
- decay_ratio = (current_step - self.hparams.warmup_steps) / (
543
- self.hparams.max_steps - self.hparams.warmup_steps
544
- )
545
- coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio))
546
- return self.hparams.lr * 0.1 + coeff * (
547
- self.hparams.lr - self.hparams.lr * 0.1
548
- )
549
-
550
- scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)
 
 
 
 
 
 
551
  return [optimizer], [scheduler]
 
1
+ import os
2
+ import math
3
+ from typing import List, Optional, Tuple, Union
4
+ import time
5
+ import inspect
6
+ from dataclasses import dataclass
7
+ import torch
8
+ import torch.nn as nn
9
+ from torch.nn import functional as F
10
+ import torch.utils.checkpoint
11
+ from torch.nn import BCEWithLogitsLoss, CrossEntropyLoss, MSELoss
12
+ from torch.utils.data import DataLoader
13
+
14
+ from datasets import load_dataset
15
+ from transformers import GPT2Tokenizer
16
+
17
+ import pytorch_lightning as pl
18
+ from pytorch_lightning.callbacks import LearningRateMonitor, RichProgressBar
19
+ from pytorch_lightning.loggers import WandbLogger
20
+ from lightning.pytorch.callbacks.progress.rich_progress import RichProgressBarTheme
21
+ from pytorch_lightning.callbacks import ModelCheckpoint
22
+
23
+ @dataclass
24
+ class SmolLM2Config:
25
+ hidden_size: int = 576
26
+ intermediate_size: int = 1536
27
+ num_hidden_layers: int = 30
28
+ num_attention_heads: int = 9
29
+ num_key_value_heads: int = 3
30
+ hidden_act: str = "silu"
31
+ max_position_embeddings: int = 2048
32
+ initializer_range: float = 0.041666666666666664
33
+ rms_norm_eps: float = 1.0e-05
34
+ vocab_size: int = 49152
35
+ rope_theta: float = 10000.0
36
+ use_cache: bool = True
37
+ tie_word_embeddings: bool = True
38
+ torch_dtype: str = "float32"
39
+ block_size: int = 512
40
+
41
+ tokenizer: GPT2Tokenizer = GPT2Tokenizer.from_pretrained(
42
+ "HuggingFaceTB/cosmo2-tokenizer"
43
+ )
44
+ tokenizer.pad_token = tokenizer.eos_token
45
+ vocab_size = tokenizer.vocab_size
46
+
47
+ class SmolLM2RMSNorm(nn.Module):
48
+ def __init__(self, hidden_size, eps=1e-6):
49
+ """
50
+ SmolLM2RMSNorm is equivalent to T5LayerNorm
51
+ """
52
+ super().__init__()
53
+ self.weight = nn.Parameter(torch.ones(hidden_size))
54
+ self.variance_epsilon = eps
55
+
56
+ def forward(self, hidden_states):
57
+ input_dtype = hidden_states.dtype
58
+ variance = hidden_states.to(torch.float32).pow(2).mean(-1, keepdim=True)
59
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
60
+
61
+ return (self.weight * hidden_states).to(input_dtype)
62
+
63
+
64
+ class SmolLM2RotaryEmbedding(torch.nn.Module):
65
+ def __init__(self, dim, max_position_embeddings=2048, base=10000, device=None):
66
+ super().__init__()
67
+ inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float().to(device) / dim))
68
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
69
+
70
+ # Build here to make `torch.jit.trace` work.
71
+ self.max_seq_len_cached = max_position_embeddings
72
+ t = torch.arange(self.max_seq_len_cached, device=self.inv_freq.device, dtype=self.inv_freq.dtype)
73
+ freqs = torch.einsum("i,j->ij", t, self.inv_freq)
74
+ # Different from paper, but it uses a different permutation in order to obtain the same calculation
75
+ emb = torch.cat((freqs, freqs), dim=-1)
76
+ dtype = torch.get_default_dtype()
77
+ self.register_buffer("cos_cached", emb.cos()[None, None, :, :].to(dtype), persistent=False)
78
+ self.register_buffer("sin_cached", emb.sin()[None, None, :, :].to(dtype), persistent=False)
79
+
80
+ def forward(self, x, seq_len=None):
81
+ # x: [bs, num_attention_heads, seq_len, head_size]
82
+ # This `if` block is unlikely to be run after we build sin/cos in `__init__`. Keep the logic here just in case.
83
+ if seq_len > self.max_seq_len_cached:
84
+ self.max_seq_len_cached = seq_len
85
+ t = torch.arange(self.max_seq_len_cached, device=x.device, dtype=self.inv_freq.dtype)
86
+ freqs = torch.einsum("i,j->ij", t, self.inv_freq)
87
+ # Different from paper, but it uses a different permutation in order to obtain the same calculation
88
+ emb = torch.cat((freqs, freqs), dim=-1).to(x.device)
89
+ self.register_buffer("cos_cached", emb.cos()[None, None, :, :].to(x.dtype), persistent=False)
90
+ self.register_buffer("sin_cached", emb.sin()[None, None, :, :].to(x.dtype), persistent=False)
91
+ return (
92
+ self.cos_cached[:, :, :seq_len, ...].to(dtype=x.dtype),
93
+ self.sin_cached[:, :, :seq_len, ...].to(dtype=x.dtype),
94
+ )
95
+
96
+
97
+ def rotate_half(x):
98
+ """Rotates half the hidden dims of the input."""
99
+ x1 = x[..., : x.shape[-1] // 2]
100
+ x2 = x[..., x.shape[-1] // 2 :]
101
+ return torch.cat((-x2, x1), dim=-1)
102
+
103
+
104
+ def apply_rotary_pos_emb(q, k, cos, sin, position_ids):
105
+ # The first two dimensions of cos and sin are always 1, so we can `squeeze` them.
106
+ cos = cos.squeeze(1).squeeze(0) # [seq_len, dim]
107
+ sin = sin.squeeze(1).squeeze(0) # [seq_len, dim]
108
+ cos = cos.unsqueeze(0) # [bs, 1, seq_len, dim]
109
+ sin = sin.unsqueeze(0) # [bs, 1, seq_len, dim]
110
+ q_embed = (q * cos) + (rotate_half(q) * sin)
111
+ k_embed = (k * cos) + (rotate_half(k) * sin)
112
+ return q_embed, k_embed
113
+
114
+ def _precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0) -> torch.Tensor:
115
+ """Precompute the frequency tensor for complex exponentials (cos + i*sin)"""
116
+ # Only compute frequencies for half the dimension
117
+ freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
118
+ t = torch.arange(end)
119
+ freqs = torch.outer(t, freqs) # [seq_len, dim//2]
120
+
121
+ # Compute cos and sin
122
+ freqs_cos = torch.cos(freqs) # [seq_len, dim//2]
123
+ freqs_sin = torch.sin(freqs) # [seq_len, dim//2]
124
+
125
+ # Stack real and imaginary parts
126
+ freqs_cis = torch.stack([freqs_cos, freqs_sin], dim=-1) # [seq_len, dim//2, 2]
127
+
128
+ return freqs_cis
129
+
130
+
131
+ class SmolLM2MLP(nn.Module):
132
+ def __init__(
133
+ self,
134
+ hidden_size: int,
135
+ intermediate_size: int,
136
+ hidden_act: str,
137
+ ):
138
+ super().__init__()
139
+ self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
140
+ self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
141
+ self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
142
+ self.act_fn = nn.SiLU()
143
+
144
+ def forward(self, x):
145
+ return self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
146
+
147
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
148
+ """
149
+ This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
150
+ num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
151
+ """
152
+ batch, num_key_value_heads, slen, head_dim = hidden_states.shape
153
+ if n_rep == 1:
154
+ return hidden_states
155
+ hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
156
+ return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
157
+
158
+
159
+ class SmolLM2Attention(nn.Module):
160
+ """Multi-headed attention from 'Attention Is All You Need' paper"""
161
+
162
+ def __init__(self, config: SmolLM2Config):
163
+ super().__init__()
164
+ self.config = config
165
+ self.hidden_size = config.hidden_size
166
+ self.num_heads = config.num_attention_heads
167
+ self.head_dim = self.hidden_size // self.num_heads
168
+ self.num_key_value_heads = config.num_key_value_heads
169
+ self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
170
+ self.max_position_embeddings = config.max_position_embeddings
171
+
172
+ if (self.head_dim * self.num_heads) != self.hidden_size:
173
+ raise ValueError(
174
+ f"hidden_size must be divisible by num_heads (got `hidden_size`: {self.hidden_size}"
175
+ f" and `num_heads`: {self.num_heads})."
176
+ )
177
+ self.q_proj = nn.Linear(self.hidden_size, self.num_heads * self.head_dim, bias=False)
178
+ self.k_proj = nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False)
179
+ self.v_proj = nn.Linear(self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False)
180
+ self.o_proj = nn.Linear(self.num_heads * self.head_dim, self.hidden_size, bias=False)
181
+ self.rotary_emb = SmolLM2RotaryEmbedding(self.head_dim, max_position_embeddings=self.max_position_embeddings)
182
+
183
+ def _shape(self, tensor: torch.Tensor, seq_len: int, bsz: int):
184
+ return tensor.view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2).contiguous()
185
+
186
+ def forward(
187
+ self,
188
+ hidden_states: torch.Tensor,
189
+ attention_mask: Optional[torch.Tensor] = None,
190
+ position_ids: Optional[torch.LongTensor] = None,
191
+ past_key_value: Optional[Tuple[torch.Tensor]] = None,
192
+ output_attentions: bool = False,
193
+ use_cache: bool = False,
194
+ is_sdpa: bool = True,
195
+ is_causal = None
196
+ ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]:
197
+ bsz, q_len, _ = hidden_states.size()
198
+
199
+ query_states = self.q_proj(hidden_states).view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
200
+ key_states = self.k_proj(hidden_states).view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
201
+ value_states = self.v_proj(hidden_states).view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
202
+
203
+ kv_seq_len = key_states.shape[-2]
204
+ if past_key_value is not None:
205
+ kv_seq_len += past_key_value[0].shape[-2]
206
+ cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len)
207
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin, position_ids)
208
+ # [bsz, nh, t, hd]
209
+
210
+ if past_key_value is not None:
211
+ # reuse k, v, self_attention
212
+ key_states = torch.cat([past_key_value[0], key_states], dim=2)
213
+ value_states = torch.cat([past_key_value[1], value_states], dim=2)
214
+
215
+ past_key_value = (key_states, value_states) if use_cache else None
216
+
217
+ if is_sdpa:
218
+ key = key_states
219
+ value = value_states
220
+ query = query_states
221
+ if self.num_key_value_groups:
222
+ key = repeat_kv(key, self.num_key_value_groups)
223
+ value = repeat_kv(value, self.num_key_value_groups)
224
+
225
+ causal_mask = attention_mask
226
+ if attention_mask is not None:
227
+ causal_mask = causal_mask[:, :, :, : key.shape[-2]]
228
+
229
+ # SDPA with memory-efficient backend is bugged with non-contiguous inputs and custom attn_mask for some torch versions
230
+ # Reference: https://github.com/pytorch/pytorch/issues/112577.
231
+ query = query.contiguous()
232
+ key = key.contiguous()
233
+ value = value.contiguous()
234
+
235
+ # We dispatch to SDPA's Flash Attention or Efficient kernels via this `is_causal` if statement instead of an inline conditional assignment
236
+ # in SDPA to support both torch.compile's dynamic shapes and full graph options. An inline conditional prevents dynamic shapes from compiling.
237
+ if is_causal is None:
238
+ is_causal = causal_mask is None and query.shape[2] > 1
239
+
240
+ attn_output = torch.nn.functional.scaled_dot_product_attention(
241
+ query,
242
+ key,
243
+ value,
244
+ attn_mask=causal_mask,
245
+ dropout_p=0.0,
246
+ scale=self.head_dim**-0.5,
247
+ is_causal=is_causal,
248
+ )
249
+ attn_output = attn_output.transpose(1, 2).contiguous()
250
+ else:
251
+ attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) / math.sqrt(self.head_dim)
252
+
253
+ if attn_weights.size() != (bsz, self.num_heads, q_len, kv_seq_len):
254
+ raise ValueError(
255
+ f"Attention weights should be of size {(bsz, self.num_heads, q_len, kv_seq_len)}, but is"
256
+ f" {attn_weights.size()}"
257
+ )
258
+
259
+ if attention_mask is not None:
260
+ if attention_mask.size() != (bsz, 1, q_len, kv_seq_len):
261
+ raise ValueError(
262
+ f"Attention mask should be of size {(bsz, 1, q_len, kv_seq_len)}, but is {attention_mask.size()}"
263
+ )
264
+ attn_weights = attn_weights + attention_mask
265
+ attn_weights = torch.max(
266
+ attn_weights, torch.tensor(torch.finfo(attn_weights.dtype).min, device=attn_weights.device)
267
+ )
268
+
269
+ # upcast attention to fp32
270
+ attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype)
271
+ attn_output = torch.matmul(attn_weights, value_states)
272
+
273
+ if attn_output.size() != (bsz, self.num_heads, q_len, self.head_dim):
274
+ raise ValueError(
275
+ f"`attn_output` should be of size {(bsz, self.num_heads, q_len, self.head_dim)}, but is"
276
+ f" {attn_output.size()}"
277
+ )
278
+
279
+ attn_output = attn_output.transpose(1, 2)
280
+ attn_output = attn_output.reshape(bsz, q_len, self.hidden_size)
281
+
282
+ attn_output = self.o_proj(attn_output)
283
+
284
+ if not output_attentions:
285
+ attn_weights = None
286
+
287
+ return attn_output, attn_weights, past_key_value
288
+
289
+
290
+ class SmolLM2DecoderLayer(nn.Module):
291
+ def __init__(self, config: SmolLM2Config):
292
+ super().__init__()
293
+ self.hidden_size = config.hidden_size
294
+ self.self_attn = SmolLM2Attention(config=config)
295
+ self.mlp = SmolLM2MLP(
296
+ hidden_size=self.hidden_size,
297
+ intermediate_size=config.intermediate_size,
298
+ hidden_act=config.hidden_act,
299
+ )
300
+ self.input_layernorm = SmolLM2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
301
+ self.post_attention_layernorm = SmolLM2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
302
+
303
+ def forward(
304
+ self,
305
+ hidden_states: torch.Tensor,
306
+ attention_mask: Optional[torch.Tensor] = None,
307
+ position_ids: Optional[torch.LongTensor] = None,
308
+ past_key_value: Optional[Tuple[torch.Tensor]] = None,
309
+ output_attentions: Optional[bool] = False,
310
+ use_cache: Optional[bool] = False,
311
+ ) -> Tuple[torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]]]:
312
+ """
313
+ Args:
314
+ hidden_states (`torch.FloatTensor`): input to the layer of shape `(batch, seq_len, embed_dim)`
315
+ attention_mask (`torch.FloatTensor`, *optional*): attention mask of size
316
+ `(batch, 1, tgt_len, src_len)` where padding elements are indicated by very large negative values.
317
+ output_attentions (`bool`, *optional*):
318
+ Whether or not to return the attentions tensors of all attention layers. See `attentions` under
319
+ returned tensors for more detail.
320
+ use_cache (`bool`, *optional*):
321
+ If set to `True`, `past_key_values` key value states are returned and can be used to speed up decoding
322
+ (see `past_key_values`).
323
+ past_key_value (`Tuple(torch.FloatTensor)`, *optional*): cached past key and value projection states
324
+ """
325
+
326
+ residual = hidden_states
327
+
328
+ hidden_states = self.input_layernorm(hidden_states)
329
+
330
+ # Self Attention
331
+ hidden_states, self_attn_weights, present_key_value = self.self_attn(
332
+ hidden_states=hidden_states,
333
+ attention_mask=attention_mask,
334
+ position_ids=position_ids,
335
+ past_key_value=past_key_value,
336
+ output_attentions=output_attentions,
337
+ use_cache=use_cache,
338
+ )
339
+ hidden_states = residual + hidden_states
340
+
341
+ # Fully Connected
342
+ residual = hidden_states
343
+ hidden_states = self.post_attention_layernorm(hidden_states)
344
+ hidden_states = self.mlp(hidden_states)
345
+ hidden_states = residual + hidden_states
346
+
347
+ outputs = (hidden_states,)
348
+
349
+ if output_attentions:
350
+ outputs += (self_attn_weights,)
351
+
352
+ if use_cache:
353
+ outputs += (present_key_value,)
354
+
355
+ return outputs
356
+
357
+ class SmolLM2Model(nn.Module):
358
+ def __init__(self, config: SmolLM2Config):
359
+ super().__init__()
360
+ self.config = config
361
+ self.vocab_size = config.vocab_size
362
+ self.head_dim = config.hidden_size // config.num_attention_heads
363
+
364
+ self.dtype = getattr(torch, config.torch_dtype) if hasattr(torch, config.torch_dtype) else torch.float32
365
+
366
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size)
367
+ self.layers = nn.ModuleList([SmolLM2DecoderLayer(config) for _ in range(config.num_hidden_layers)])
368
+ self.norm = SmolLM2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
369
+
370
+ self.freqs_cis = _precompute_freqs_cis(
371
+ self.head_dim,
372
+ config.max_position_embeddings,
373
+ config.rope_theta,
374
+ )
375
+
376
+ self.apply(self._init_weights)
377
+
378
+ self.to(self.dtype)
379
+
380
+ def _init_weights(self, module):
381
+ if isinstance(module, nn.Linear):
382
+ torch.nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range)
383
+ elif isinstance(module, nn.Embedding):
384
+ torch.nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range)
385
+
386
+ def forward(
387
+ self,
388
+ input_ids: torch.Tensor,
389
+ attention_mask: Optional[torch.Tensor] = None,
390
+ ) -> torch.Tensor:
391
+ hidden_states = self.embed_tokens(input_ids)
392
+
393
+ if attention_mask is not None:
394
+ attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)
395
+ attention_mask = attention_mask.to(dtype=hidden_states.dtype)
396
+ attention_mask = (1.0 - attention_mask) * torch.finfo(hidden_states.dtype).min
397
+
398
+ freqs_cis = self.freqs_cis.to(device=hidden_states.device, dtype=hidden_states.dtype)
399
+
400
+ for layer in self.layers:
401
+ hidden_states = layer(hidden_states, attention_mask, freqs_cis)[0]
402
+
403
+ hidden_states = self.norm(hidden_states)
404
+ return hidden_states
405
+
406
+ class SmolLM2ForCausalLM(nn.Module):
407
+ def __init__(self, config: SmolLM2Config):
408
+ super().__init__()
409
+ self.config = config
410
+ self.model = SmolLM2Model(config)
411
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
412
+
413
+ # Tie weights if configured
414
+ if config.tie_word_embeddings:
415
+ self.lm_head.weight = self.model.embed_tokens.weight
416
+
417
+ def forward(
418
+ self,
419
+ input_ids: torch.Tensor,
420
+ attention_mask: Optional[torch.Tensor] = None,
421
+ labels: Optional[torch.Tensor] = None,
422
+ ) -> torch.Tensor:
423
+ hidden_states = self.model(input_ids, attention_mask)
424
+ logits = self.lm_head(hidden_states)
425
+
426
+ loss = None
427
+ if labels is not None:
428
+ shift_logits = logits[..., :-1, :].contiguous()
429
+ shift_labels = labels[..., 1:].contiguous()
430
+ loss = F.cross_entropy(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
431
+
432
+ return logits, loss
433
+
434
+ @torch.no_grad()
435
+ def generate(self, idx, max_new_tokens, temperature=1.0, top_k=None):
436
+ """
437
+ Generate text given a starting sequence of tokens.
438
+ Args:
439
+ idx (torch.Tensor): Starting token indices, shape (B, T)
440
+ max_new_tokens (int): Number of tokens to generate
441
+ temperature (float): Sampling temperature (1.0 = no change, < 1.0 = less random, > 1.0 = more random)
442
+ top_k (int): If specified, only sample from the top k most probable tokens
443
+ """
444
+ for _ in range(max_new_tokens):
445
+ # if the sequence context is growing too long we must crop it at block_size
446
+ idx_cond = (
447
+ idx
448
+ if idx.size(1) <= self.config.block_size
449
+ else idx[:, -self.config.block_size :]
450
+ )
451
+ # forward the model to get the logits for the index in the sequence
452
+ logits, _ = self(idx_cond)
453
+ # pluck the logits at the final step and scale by desired temperature
454
+ logits = logits[:, -1, :] / temperature
455
+ # optionally crop the logits to only the top k options
456
+ if top_k is not None:
457
+ v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
458
+ logits[logits < v[:, [-1]]] = -float("Inf")
459
+ # apply softmax to convert logits to (normalized) probabilities
460
+ probs = F.softmax(logits, dim=-1)
461
+ # sample from the distribution
462
+ idx_next = torch.multinomial(probs, num_samples=1)
463
+ # append sampled index to the running sequence
464
+ idx = torch.cat((idx, idx_next), dim=1)
465
+
466
+ return idx
467
+
468
+
469
+ class plSmolLM2(pl.LightningModule):
470
+ def __init__(self, config: SmolLM2Config, lr, warmup_steps, max_steps, step=None):
471
+ super().__init__()
472
+ self.save_hyperparameters()
473
+ self.config = config
474
+ self.model = SmolLM2ForCausalLM(self.config)
475
+ self.criterion = nn.CrossEntropyLoss()
476
+ self.tokenizer = tokenizer
477
+ self.generation_prompt = "Hello there! Today, we are going to talk about "
478
+ self._generating = False
479
+ self.start_step = step if step is not None else 0
480
+
481
+ def forward(self, x):
482
+ return self.model(x)
483
+
484
+ def training_step(self, batch, batch_idx):
485
+ input_ids = batch["input_ids"]
486
+ target_ids = batch["labels"]
487
+ logits, _ = self(input_ids)
488
+ loss = self.criterion(logits.view(-1, logits.size(-1)), target_ids.view(-1))
489
+
490
+ # Log the loss with 4 decimal precision
491
+ self.log(
492
+ "train_loss", loss, prog_bar=True, on_step=True, on_epoch=False, logger=True
493
+ )
494
+ print(f"Step: {self.start_step+self.global_step}, Train Loss: {loss}")
495
+
496
+ # Generate text every n steps, but only if we're not already generating
497
+ if (self.global_step) % log_every_n_steps == 0 and not self._generating:
498
+ self._generating = True
499
+ self.generate_and_log_sample()
500
+ self._generating = False
501
+ #self.step = self.step + 1
502
+
503
+ return loss
504
+
505
+ def generate_and_log_sample(self):
506
+ """Generate and log a sample of text from the model"""
507
+ try:
508
+ # Encode the prompt
509
+ prompt_ids = self.tokenizer.encode(
510
+ self.generation_prompt, return_tensors="pt"
511
+ ).to(self.device)
512
+
513
+ # Generate new tokens
514
+ generated_ids = self.model.generate(
515
+ prompt_ids, max_new_tokens=50, temperature=0.8, top_k=40
516
+ )
517
+
518
+ # Decode the generated tokens
519
+ generated_text = self.tokenizer.decode(generated_ids[0].tolist())
520
+
521
+ # Create a formatted message
522
+ message = (
523
+ f"\n{'='*40}\n"
524
+ f"Step {self.global_step} generation:\n"
525
+ f"Prompt: {self.generation_prompt}\n"
526
+ f"Generated: {generated_text}\n"
527
+ f"{'='*40}\n"
528
+ )
529
+
530
+ print(message)
531
+
532
+ # Log to WandB
533
+ if hasattr(self.logger, "experiment"):
534
+ self.logger.experiment.log(
535
+ {"generated_text": generated_text, "global_step": self.global_step}
536
+ )
537
+ except Exception as e:
538
+ print(f"Generation failed with error: {str(e)}")
539
+
540
+ def configure_optimizers(self):
541
+ optimizer = torch.optim.AdamW(self.parameters(), lr=self.hparams.lr)
542
+
543
+ def lr_lambda(current_step):
544
+ if current_step < self.hparams.warmup_steps:
545
+ return self.hparams.lr * (current_step + 1) / self.hparams.warmup_steps
546
+ elif current_step > self.hparams.max_steps:
547
+ return self.hparams.lr * 0.1
548
+ decay_ratio = (current_step - self.hparams.warmup_steps) / (
549
+ self.hparams.max_steps - self.hparams.warmup_steps
550
+ )
551
+ coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio))
552
+ return self.hparams.lr * 0.1 + coeff * (
553
+ self.hparams.lr - self.hparams.lr * 0.1
554
+ )
555
+
556
+ scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)
557
  return [optimizer], [scheduler]