zeguru commited on
Commit
6c01195
·
verified ·
1 Parent(s): b0145ec

Upload folder using huggingface_hub

Browse files
.gitignore CHANGED
@@ -123,9 +123,17 @@ temp/
123
  *.sqlite3
124
  *.db
125
 
 
 
 
 
 
 
 
126
  # =========================
127
  # uv
128
  # =========================
129
  # Keep uv.lock tracked.
130
  # Do NOT add uv.lock to .gitignore.
131
 
 
 
123
  *.sqlite3
124
  *.db
125
 
126
+
127
+ # =========================
128
+ # Jupyter
129
+ # =========================
130
+ .gradio/
131
+
132
+
133
  # =========================
134
  # uv
135
  # =========================
136
  # Keep uv.lock tracked.
137
  # Do NOT add uv.lock to .gitignore.
138
 
139
+
README.md CHANGED
@@ -11,16 +11,38 @@ short_description: A set of tiny Transformers, from scratch
11
 
12
  # Tiny Text Transformers
13
 
14
- A small Transformer built from scratch in PyTorch,
15
- with multiple NLP tasks built on the same Transformer core.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  ## Tasks
18
 
19
  ### Text Generation
20
- Character-level autoregressive language model trained on Shakespeare.
21
 
22
  ### Text Classification
23
- Character-level Transformer classifier trained on AG News.
24
 
25
  ### Structured Output
26
- Coming next...
 
 
 
 
 
 
 
 
 
11
 
12
  # Tiny Text Transformers
13
 
14
+ ---
15
+ title: Tiny Transformers
16
+ emoji: 🧠
17
+ colorFrom: blue
18
+ colorTo: purple
19
+ sdk: gradio
20
+ app_file: app.py
21
+ pinned: false
22
+ short_description: A set of tiny Transformers, from scratch
23
+ ---
24
+
25
+ # Tiny Text Transformers
26
+
27
+ A set of small transformer models, less than 1M params for multiple NLP tasks built on the same Transformer core.
28
+
29
+ Goal: Light, highly specialized and useful in a specific domain
30
 
31
  ## Tasks
32
 
33
  ### Text Generation
34
+ Character-level autoregressive language model. Shakespear corpus used for proof of concept.
35
 
36
  ### Text Classification
37
+ News / text classifier. AG News dataset used for proof of concept.
38
 
39
  ### Structured Output
40
+ Generate structured output for a limited instruction-set problem. For instance a cli command.
41
+
42
+ ### Translation
43
+ Encoder Decoder block for experimental educational purposes.
44
+
45
+ ### Hugging Faces
46
+ https://huggingface.co/spaces/zeguru/tiny-transformers
47
+
48
+
app.py CHANGED
@@ -12,7 +12,7 @@ from src.task.inference.classify import TextClassifier
12
 
13
  # Paths
14
  # DATA_PATH = Path("data/tiny_shakespear.txt")
15
- MODEL_PATH = Path("checkpoints/tiny-text-transformer.pt")
16
 
17
  CLASSIFIER_MODEL_PATH = Path(
18
  "checkpoints/news-classifier.pt"
@@ -55,12 +55,14 @@ def generate_text(
55
  prompt,
56
  max_new_tokens,
57
  temperature,
 
58
  ):
59
 
60
  return text_generator.generate(
61
  prompt_text=prompt,
62
  max_new_tokens=max_new_tokens,
63
  temperature=temperature,
 
64
  )
65
 
66
  # B. Classifier
@@ -96,41 +98,6 @@ def classify_text(text):
96
 
97
 
98
 
99
-
100
-
101
- # Gradio Interface
102
- # demo = gr.Interface(
103
- # fn=generate_text,
104
- # inputs=[
105
- # gr.Textbox(
106
- # label="Prompt",
107
- # value="ROMEO:",
108
- # ),
109
- # gr.Slider(
110
- # minimum=10,
111
- # maximum=500,
112
- # value=300,
113
- # step=10,
114
- # label="Max new tokens",
115
- # ),
116
- # gr.Slider(
117
- # minimum=0.1,
118
- # maximum=1.5,
119
- # value=0.7,
120
- # step=0.1,
121
- # label="Temperature",
122
- # ),
123
- # ],
124
- # outputs=gr.Textbox(
125
- # label="Generated text",
126
- # lines=15,
127
- # ),
128
- # title="Tiny Text Transformer",
129
- # description=(
130
- # "A tiny character-level Transformer built from scratch in PyTorch, demonstrated on text generation and AG News classification."
131
- # ),
132
- # )
133
-
134
  with gr.Blocks() as demo:
135
 
136
  gr.Markdown(
@@ -167,6 +134,13 @@ with gr.Blocks() as demo:
167
  step=0.1,
168
  label="Temperature",
169
  ),
 
 
 
 
 
 
 
170
  ],
171
  outputs=gr.Textbox(
172
  label="Generated text",
 
12
 
13
  # Paths
14
  # DATA_PATH = Path("data/tiny_shakespear.txt")
15
+ MODEL_PATH = Path("checkpoints/tiny_text_generator.pt")
16
 
17
  CLASSIFIER_MODEL_PATH = Path(
18
  "checkpoints/news-classifier.pt"
 
55
  prompt,
56
  max_new_tokens,
57
  temperature,
58
+ top_p,
59
  ):
60
 
61
  return text_generator.generate(
62
  prompt_text=prompt,
63
  max_new_tokens=max_new_tokens,
64
  temperature=temperature,
65
+ top_p=top_p
66
  )
67
 
68
  # B. Classifier
 
98
 
99
 
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  with gr.Blocks() as demo:
102
 
103
  gr.Markdown(
 
134
  step=0.1,
135
  label="Temperature",
136
  ),
137
+ gr.Slider(
138
+ minimum=0.1,
139
+ maximum=1.0,
140
+ value=0.9,
141
+ step=0.1,
142
+ label="Top P",
143
+ ),
144
  ],
145
  outputs=gr.Textbox(
146
  label="Generated text",
checkpoints/tiny_text_generator.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2d1156aa40f6d84f6dcde3aa40081d8098881260f993313354d0dd0f1b2022e7
3
+ size 435879
logs/text_generator.txt ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ModelConfig(context_length=64, d_model=64, d_ff=256, num_layers=2, dropout=0.1)
2
+ TrainingConfig(batch_size=32, learning_rate=0.001, num_steps=10000, eval_interval=1000, eval_batches=20, num_train_samples=None)
3
+ Corpus length: 1,115,394 characters
4
+ Vocabulary size: 67
5
+ Token count: 1,115,394
6
+ Train tokens: 1,003,854
7
+ Val tokens: 55,770
8
+ Test tokens: 55,770
9
+ Train examples: 1,003,790
10
+ Val examples: 55,706
11
+ Test examples: 55,706
12
+ TinyTransformerLM(
13
+ (input_representation): InputRepresentation(
14
+ (token_embedding): Embedding(
15
+ (embedding): Embedding(67, 64)
16
+ )
17
+ (positional_embedding): PositionalEmbedding(
18
+ (embedding): Embedding(64, 64)
19
+ )
20
+ )
21
+ (transformer): Transformer(
22
+ (blocks): ModuleList(
23
+ (0-1): 2 x TransformerBlock(
24
+ (attention): Attention(
25
+ (query): Linear(in_features=64, out_features=64, bias=False)
26
+ (key): Linear(in_features=64, out_features=64, bias=False)
27
+ (value): Linear(in_features=64, out_features=64, bias=False)
28
+ )
29
+ (feed_forward): FeedForward(
30
+ (net): Sequential(
31
+ (0): Linear(in_features=64, out_features=256, bias=True)
32
+ (1): GELU(approximate='none')
33
+ (2): Linear(in_features=256, out_features=64, bias=True)
34
+ )
35
+ )
36
+ (layer_norm_1): LayerNorm((64,), eps=1e-05, elementwise_affine=True, bias=True)
37
+ (layer_norm_2): LayerNorm((64,), eps=1e-05, elementwise_affine=True, bias=True)
38
+ (dropout): Dropout(p=0.1, inplace=False)
39
+ )
40
+ )
41
+ )
42
+ (head): Linear(in_features=64, out_features=67, bias=True)
43
+ )
44
+ Test loss: 1.7971
45
+ Parameters: 104,003
scripts/generate_text.py CHANGED
@@ -8,9 +8,9 @@ from src.task.inference.inference import TextGenerator
8
 
9
  # Runtime configs
10
 
11
- checkpoint = "checkpoints/tiny-text-transformer.pt"
12
 
13
- TEXT_TOKENIZER_PATH = Path("checkpoints/shakespeare-tokenizer.json")
14
  tokenizer = CharacterTokenizer.load(
15
  TEXT_TOKENIZER_PATH
16
  )
@@ -41,6 +41,7 @@ output = text_generator.generate(
41
  prompt_text="ROMEO:",
42
  max_new_tokens=300,
43
  temperature=0.7,
 
44
  )
45
 
46
  print(output)
 
8
 
9
  # Runtime configs
10
 
11
+ checkpoint = "checkpoints/tiny_text_generator.pt"
12
 
13
+ TEXT_TOKENIZER_PATH = Path("checkpoints/shakespeare_tokenizer.json")
14
  tokenizer = CharacterTokenizer.load(
15
  TEXT_TOKENIZER_PATH
16
  )
 
41
  prompt_text="ROMEO:",
42
  max_new_tokens=300,
43
  temperature=0.7,
44
+ top_p=0.8
45
  )
46
 
47
  print(output)
scripts/train_generator.py CHANGED
@@ -10,28 +10,37 @@ from src.task.config import ModelConfig,TrainingConfig
10
 
11
  from src.task.model import TinyTransformerLM
12
  from src.task.training.autoregressive import train, evaluate
13
-
14
-
15
 
16
  def separator(title: str) -> None:
17
  print("\n" + "=" * 60)
18
  print(title)
19
  print("=" * 60)
20
 
 
 
 
 
 
 
21
  # Runtime configs
22
  corpus = "data/tiny_shakespear.txt"
23
- checkpoint = "txt-transformer.pt"
24
 
25
  # Configs
26
  separator("Configuration")
27
 
28
  torch.manual_seed(42)
29
 
 
30
  model_config = ModelConfig()
31
  training_config = TrainingConfig()
32
 
33
- print(model_config)
34
- print(training_config)
 
 
 
35
 
36
 
37
  # Load the corpus
@@ -43,7 +52,7 @@ text = data_path.read_text(
43
  encoding="utf-8",
44
  )
45
 
46
- print(f"Corpus length: {len(text):,} characters")
47
 
48
 
49
  # Tokens
@@ -59,8 +68,8 @@ tokens = torch.tensor(
59
  dtype=torch.long,
60
  )
61
 
62
- print(f"Vocabulary size: {tokenizer.vocab_size}")
63
- print(f"Token count: {len(tokens):,}")
64
 
65
 
66
  # Train/Val/Test split
@@ -71,9 +80,9 @@ train_tokens, val_tokens, test_tokens = split_tokens(
71
  tokens,
72
  )
73
 
74
- print(f"Train tokens: {len(train_tokens):,}")
75
- print(f"Val tokens: {len(val_tokens):,}")
76
- print(f"Test tokens: {len(test_tokens):,}")
77
 
78
 
79
  # Datasets
@@ -94,9 +103,9 @@ test_dataset = AutoRegressiveDataset(
94
  context_length=model_config.context_length,
95
  )
96
 
97
- print(f"Train examples: {len(train_dataset):,}")
98
- print(f"Val examples: {len(val_dataset):,}")
99
- print(f"Test examples: {len(test_dataset):,}")
100
 
101
 
102
  # Models
@@ -107,7 +116,7 @@ model = TinyTransformerLM(
107
  vocab_size=tokenizer.vocab_size,
108
  )
109
 
110
- print(model)
111
 
112
 
113
  # Train........
@@ -130,8 +139,11 @@ test_loss = evaluate(
130
  training_config=training_config,
131
  )
132
 
133
- print(f"Test loss: {test_loss:.4f}")
 
 
134
 
 
135
 
136
  # --------------------------------------------------
137
  # Save checkpoint
 
10
 
11
  from src.task.model import TinyTransformerLM
12
  from src.task.training.autoregressive import train, evaluate
13
+ from src.task.training.util import TrainingLogger, count_parameters
 
14
 
15
  def separator(title: str) -> None:
16
  print("\n" + "=" * 60)
17
  print(title)
18
  print("=" * 60)
19
 
20
+ # Take stok
21
+ logger = TrainingLogger(
22
+ "logs/text_generator.txt"
23
+ )
24
+
25
+
26
  # Runtime configs
27
  corpus = "data/tiny_shakespear.txt"
28
+ checkpoint = "tiny_text_generator.pt"
29
 
30
  # Configs
31
  separator("Configuration")
32
 
33
  torch.manual_seed(42)
34
 
35
+ # Generator
36
  model_config = ModelConfig()
37
  training_config = TrainingConfig()
38
 
39
+ training_config.num_steps = 10000
40
+ training_config.eval_interval = 1000
41
+
42
+ logger.log(model_config)
43
+ logger.log(training_config)
44
 
45
 
46
  # Load the corpus
 
52
  encoding="utf-8",
53
  )
54
 
55
+ logger.log(f"Corpus length: {len(text):,} characters")
56
 
57
 
58
  # Tokens
 
68
  dtype=torch.long,
69
  )
70
 
71
+ logger.log(f"Vocabulary size: {tokenizer.vocab_size}")
72
+ logger.log(f"Token count: {len(tokens):,}")
73
 
74
 
75
  # Train/Val/Test split
 
80
  tokens,
81
  )
82
 
83
+ logger.log(f"Train tokens: {len(train_tokens):,}")
84
+ logger.log(f"Val tokens: {len(val_tokens):,}")
85
+ logger.log(f"Test tokens: {len(test_tokens):,}")
86
 
87
 
88
  # Datasets
 
103
  context_length=model_config.context_length,
104
  )
105
 
106
+ logger.log(f"Train examples: {len(train_dataset):,}")
107
+ logger.log(f"Val examples: {len(val_dataset):,}")
108
+ logger.log(f"Test examples: {len(test_dataset):,}")
109
 
110
 
111
  # Models
 
116
  vocab_size=tokenizer.vocab_size,
117
  )
118
 
119
+ logger.log(model)
120
 
121
 
122
  # Train........
 
139
  training_config=training_config,
140
  )
141
 
142
+ logger.log(f"Test loss: {test_loss:.4f}")
143
+
144
+
145
 
146
+ logger.log(f"Parameters: {count_parameters(model):,}")
147
 
148
  # --------------------------------------------------
149
  # Save checkpoint
src/infra/generation.py CHANGED
@@ -8,6 +8,7 @@ def prompt(
8
  context_length,
9
  max_new_tokens,
10
  temperature=1.0,
 
11
  ):
12
 
13
  if temperature <= 0:
@@ -29,14 +30,17 @@ def prompt(
29
 
30
  next_token_logits = (
31
  next_token_logits / temperature
32
- )
33
 
34
- probabilities = torch.softmax(
35
- next_token_logits,
36
- dim=-1,
37
- )
 
 
 
38
 
39
- next_token = torch.multinomial(
40
  probabilities,
41
  num_samples=1,
42
  )
@@ -46,4 +50,48 @@ def prompt(
46
  dim=1,
47
  )
48
 
49
- return tokens
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  context_length,
9
  max_new_tokens,
10
  temperature=1.0,
11
+ top_p=None
12
  ):
13
 
14
  if temperature <= 0:
 
30
 
31
  next_token_logits = (
32
  next_token_logits / temperature
33
+ )
34
 
35
+ if top_p is not None:
36
+ probabilities = get_top_p(top_p, next_token_logits)
37
+ else:
38
+ probabilities = torch.softmax(
39
+ next_token_logits,
40
+ dim=-1,
41
+ )
42
 
43
+ next_token = torch.multinomial( #watermark my be done here
44
  probabilities,
45
  num_samples=1,
46
  )
 
50
  dim=1,
51
  )
52
 
53
+ return tokens
54
+
55
+
56
+ def get_top_p(top_p,next_logits):
57
+
58
+ probabilities = torch.softmax(
59
+ next_logits,
60
+ dim=-1,
61
+ )
62
+
63
+ sorted_probabilities, sorted_indices = torch.sort(
64
+ probabilities,
65
+ descending=True,
66
+ )
67
+
68
+ cumulative_probabilities = torch.cumsum(
69
+ sorted_probabilities,
70
+ dim=-1,
71
+ )
72
+
73
+ remove = cumulative_probabilities > top_p
74
+
75
+ # Keep the first token that crosses the threshold.
76
+ remove[..., 1:] = remove[..., :-1].clone()
77
+ remove[..., 0] = False
78
+
79
+ sorted_probabilities = sorted_probabilities.masked_fill(
80
+ remove,
81
+ 0.0,
82
+ )
83
+
84
+ probabilities = torch.zeros_like(probabilities)
85
+
86
+ probabilities.scatter_(
87
+ -1,
88
+ sorted_indices,
89
+ sorted_probabilities,
90
+ )
91
+
92
+ probabilities = probabilities / probabilities.sum(
93
+ dim=-1,
94
+ keepdim=True,
95
+ )
96
+
97
+ return probabilities
src/task/inference/inference.py CHANGED
@@ -35,6 +35,7 @@ class TextGenerator:
35
  prompt_text: str,
36
  max_new_tokens=300,
37
  temperature=0.7,
 
38
  ):
39
 
40
  prompt_tokens = torch.tensor(
@@ -49,6 +50,7 @@ class TextGenerator:
49
  context_length=self.context_length,
50
  max_new_tokens=max_new_tokens,
51
  temperature=temperature,
 
52
  )
53
 
54
  return self.tokenizer.decode(
 
35
  prompt_text: str,
36
  max_new_tokens=300,
37
  temperature=0.7,
38
+ top_p=None,
39
  ):
40
 
41
  prompt_tokens = torch.tensor(
 
50
  context_length=self.context_length,
51
  max_new_tokens=max_new_tokens,
52
  temperature=temperature,
53
+ top_p=top_p,
54
  )
55
 
56
  return self.tokenizer.decode(
src/task/training/util.py CHANGED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+
4
+ class TrainingLogger:
5
+
6
+ def __init__(self, path):
7
+ self.path = Path(path)
8
+ self.path.parent.mkdir(
9
+ parents=True,
10
+ exist_ok=True,
11
+ )
12
+
13
+ def log(self, message):
14
+ message = str(message)
15
+ print(message)
16
+
17
+ with self.path.open(
18
+ "a",
19
+ encoding="utf-8",
20
+ ) as file:
21
+ file.write(message + "\n")
22
+
23
+
24
+ def count_parameters(model):
25
+ return sum(
26
+ parameter.numel()
27
+ for parameter in model.parameters()
28
+ if parameter.requires_grad
29
+ )
30
+
31
+ print(
32
+ f"Parameters: "
33
+ f"{count_parameters(model):,}"
34
+ )