--- license: mit language: - en tags: - text-generation - causal-lm - gpt - tinygpt - transformer - llm library_name: transformers pipeline_tag: text-generation base_model: gpt2 widget: - text: Once upon a time, a little boy named Sam example_title: Story start - text: The cat and the dog decided to example_title: Animals --- # TinyGPT A small-scale, **trained-from-scratch** GPT language model. Based on the GPT-2 architecture and fully compatible with `transformers`; downloadable via the Hub, with model source code included in this repo. - **Architecture:** Decoder-only Transformer (custom, Chapter-1 style) - **Parameters:** ~29M (`28,974,161`) - **Context length (`max_seq_len`):** 128 tokens - **Tokenizer:** GPT-2 (vocab size = 50,257, BPE) - **Training data:** First 50,000 stories from [TinyStories](https://huggingface.co/datasets/roneneldan/TinyStories) - **Inference memory (fp32 weights):** ~116MB -- runs comfortably on a single GPU or even CPU --- ## Installation ```bash pip install transformers torch ``` --- ## Usage (with transformers) TinyGPT uses a custom architecture, so the model code is loaded from this repo at runtime. This requires the **`trust_remote_code=True`** flag. ### 1) Simplest approach: Auto classes ```python from transformers import AutoTokenizer, AutoModelForCausalLM repo = "coderian/TinyGPT" tokenizer = AutoTokenizer.from_pretrained(repo) model = AutoModelForCausalLM.from_pretrained( repo, trust_remote_code=True, # loads custom architecture code from repo ) model.eval() # Generation (sampling) prompt = "Once upon a time, a little girl named Lily found" inputs = tokenizer(prompt, return_tensors="pt") output = model.generate( **inputs, max_new_tokens=50, do_sample=True, temperature=0.8, top_k=50, top_p=0.95, ) text = tokenizer.decode(output[0], skip_special_tokens=True) print(text) ``` ### 2) Direct class (code-based usage) If you copied the source code into your own project, `trust_remote_code` is not needed: ```python from models.tinygpt import TinyGPT model = TinyGPT.from_pretrained("coderian/TinyGPT", device="cpu") ``` ### 3) GPU placement and half-precision ```python model = AutoModelForCausalLM.from_pretrained( repo, trust_remote_code=True, torch_dtype="auto", # uses config dtype (fp32) device_map="auto", # places model on GPU if available ) # Lower memory usage with bf16/fp16: model = AutoModelForCausalLM.from_pretrained( repo, trust_remote_code=True, torch_dtype="bf16", # or "float16" device_map="auto", ) ``` ### 4) Simple greedy chat loop ```python def reply(prompt, max_new_tokens=40): inputs = tokenizer(prompt, return_tensors="pt") out = model.generate(**inputs, max_new_tokens=max_new_tokens) return tokenizer.decode(out[0], skip_special_tokens=True) print(reply("One rainy day, Ben and his dog Rex decided to")) ``` > **Note:** The model was trained exclusively on **English** TinyStories data. It works best with simple English story openings. --- ## Repo contents | File | Description | |---|---| | `model.safetensors` | Trained weights (~116MB, fp32) | | `config.json` | Model configuration (`TinyGPTConfig`) | | `generation_config.json` | Default `generate()` parameters | | `tokenizer.json`, `tokenizer_config.json` | GPT-2 tokenizer | | `models/` | Open-source model code (config, tinygpt, transformer_block, attention) | --- ## Architecture TinyGPT is a **decoder-only Transformer**. Input tokens pass through `token + position embedding`, are processed by `num_layers` Transformer blocks, and finally a LayerNorm + linear head produces logits over the vocabulary. ``` Input tokens (B, T) | v +-----------------------------------+ | token_embedding (50,257 -> d) | nn.Embedding | position_embedding (T -> d) | nn.Embedding | x = token_emb + pos_emb | +------------------+-----------------+ v +-----------------------------+ x num_layers (4) | LN1 -> CausalSelfAttn | Pre-LN, residual | + | | LN2 -> FFN (x4) | GELU activation | + | +-----------------------------+ v ln_f (LayerNorm) v lm_head (d -> 50,257) --> logits (B, T, V) ``` ### 1) Embedding layers - **`token_embedding`**: maps each token ID to a `d=256` dimensional vector. `W_e` in R^(50257 x 256) - **`position_embedding`**: adds positional information. Consecutive positions use the same learned embedding; `W_p` in R^(128 x 256) Input vector: `x = W_e[input_ids] + W_p[positions]` ### 2) Causal Self-Attention (masked scaled dot-product) Each position can only attend to **previous** tokens (causal mask). ``` Q = x W_q , K = x W_k , V = x W_v W in R^(256x256) scores = (Q K^T) / sqrt(d) d = 256 scores = masked_fill(upper_triangle, -inf) <- blocks future tokens attn = softmax(scores, dim=-1) output = attn @ V , then output W_o ``` - Scaling by `1/sqrt(d)`: keeps dot-product variance stable at high dimensions, preventing softmax saturation. - `nn.Embedding` and `nn.Linear` weights are initialized from `N(0, 0.02)`. ### 3) Residual connections and Pre-LayerNorm LayerNorm is applied before each sublayer, followed by a residual add: ``` x = x + attention(ln1(x)) x = x + ffn(ln2(x)) ``` Pre-LN keeps training stable in deeper models. ### 4) FFN (Feed-Forward Network) ``` FFN(x) = W2 * GELU(W1 * x + b1) + b2 W1: 256 -> 1024 (expansion x4) GELU: activation W2: 1024 -> 256 (contraction) ``` ### 5) Output ``` logits = lm_head(ln_f(x)) ln_f: LayerNorm(256) lm_head: Linear(256 -> 50,257) ``` During training, `CrossEntropyLoss(logits, shift_right(y))` is used for the language modeling objective. During inference, the `lm_head` logits directly represent the next-token distribution. ### Parameter breakdown | Component | Parameters | Notes | |---|---|---| | `token_embedding` | 12,865,792 | 50,257 x 256 | | `position_embedding` | 32,768 | 128 x 256 | | 1 Transformer block | 789,760 | LN + attn + FFN | | 4 Transformer blocks | 3,159,040 | total body | | `ln_f` + `lm_head` | 12,916,561 | language head | **Total: ~28.97M.** Most parameters reside in the embedding/head due to the large vocabulary; the actual reasoning capacity (transformer body) is ~3.1M. --- ## Training details | Metric | Value | |---|---| | Data | TinyStories (first 50,000 stories, ~11.1M tokens) | | Training samples | 86,636 blocks x 128 tokens | | `embed_dim` | 256 | | `num_layers` | 4 | | `max_seq_len` | 128 | | Optimizer | AdamW, lr `3e-4` | | Batch size | 24 | | Epochs | 2 | | Duration | Short training run on CPU | --- ## Limitations & Notes - English only; produces nonsensical output for non-English input. - 128-token context window; insufficient for tasks requiring long-range context. - Due to its small size, the model does not perform logic, recall factual knowledge, or produce coherent long-form text. - Requires `trust_remote_code=True` to run; the code is open for inspection in this repo's `models/` directory. - No weight decay or other regularization was applied during training. --- ## License MIT -- model weights and code are freely usable. **HF Hub:** [huggingface.co/coderian/TinyGPT](https://huggingface.co/coderian/TinyGPT)