aethertp commited on
Commit
8ca632a
·
verified ·
1 Parent(s): 5f31a30

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. README.md +16 -48
  2. config.json +5 -0
  3. configuration_picolm.py +28 -0
  4. modeling_picolm.py +105 -0
README.md CHANGED
@@ -12,13 +12,7 @@ tags:
12
  - on-device
13
  pipeline_tag: text-generation
14
  widget:
15
- - text: '<|im_start|>user
16
-
17
- What is the capital of France?<|im_end|>
18
-
19
- <|im_start|>assistant
20
-
21
- '
22
  ---
23
 
24
  # PicoLM-80M-Instruct 🚀
@@ -35,7 +29,7 @@ Trained completely from scratch on Kaggle dual Tesla T4 GPUs with zero budget, P
35
  - **Parameters:** 80,242,240 (~80.2M)
36
  - **Context Window:** 2,048 tokens
37
  - **Vocabulary:** 16,384 (Single-digit regex split, Byte-level BPE)
38
- - **Format:** Safetensors (FP16)
39
  - **Primary Language:** English + Python Code
40
  - **License:** Apache 2.0
41
 
@@ -43,7 +37,7 @@ Trained completely from scratch on Kaggle dual Tesla T4 GPUs with zero budget, P
43
 
44
  ## 📊 Empirical Benchmark Results (Verified)
45
 
46
- Unlike models with estimated metrics, all scores below were **empirically measured** directly on the model weights using standard log-likelihood evaluations:
47
 
48
  | Benchmark / Task | Random Baseline | SmolLM2-135M (HF) | Gemma 3 270M (Google) | PicoLM-80M-Instruct (Ours) |
49
  | :--- | :--- | :--- | :--- | :--- |
@@ -55,54 +49,28 @@ Unlike models with estimated metrics, all scores below were **empirically measur
55
 
56
  ---
57
 
58
- ## 🏗️ Architecture Specifications
59
-
60
- - **Layers:** 20 Transformer blocks (Deep & Thin)
61
- - **Hidden Dimension ($d_{model}$):** 576
62
- - **Attention Heads (Q):** 9 (Head dim = 64)
63
- - **KV Heads (GQA):** 3 (3:1 Grouped-Query Attention)
64
- - **Intermediate Dimension:** 1536 (SwiGLU)
65
- - **Normalization:** Pre-RMSNorm with **QK-Norm** (prevents attention logit blowup)
66
- - **Positional Encoding:** RoPE ($ heta = 10000$)
67
- - **Weights Tying:** Input embeddings and LM Head weights are shared (Zero parameter waste)
68
-
69
- ---
70
-
71
- ## 📚 Training Curriculum
72
-
73
- 1. **Pre-Training (Stage 1 - ~307M Tokens):**
74
- - 54% Cosmopedia v2 (Textbooks & reasoning)
75
- - 23% FineWeb-Edu Dedup (Educational web articles)
76
- - 12% Python-Edu (Algorithms and code syntax)
77
- - 11% SmolTalk Dialogues
78
- - Optimization: WSD (Warmup-Stable-Decay) across 2x Tesla T4 GPUs.
79
- 2. **Chat Alignment (Stage 2 - SFT):**
80
- - 15,000 curated dialogues from SmolTalk.
81
- - Strict Prompt-Masking (`ignore_index = -100`) applied so only assistant responses receive loss updates.
82
-
83
- ---
84
 
85
- ## 💻 Quickstart Inference
86
 
87
  ```python
88
- import torch
89
- from transformers import PreTrainedTokenizerFast
90
 
91
- # Load tokenizer
92
- tokenizer = PreTrainedTokenizerFast.from_pretrained("aethertp/PicoLM-80M-Instruct")
 
93
 
94
- # Format prompt using ChatML
95
- user_prompt = "What is the capital of France?"
96
- formatted = f"<|im_start|>user\n{user_prompt}<|im_end|>\n<|im_start|>assistant\n"
97
- input_ids = torch.tensor([tokenizer.encode(formatted, add_special_tokens=False)])
98
 
99
- # Generate response
100
- # (Model weights available in model.safetensors)
101
  ```
102
 
103
  ---
104
 
105
  ## ⚠️ Limitations
106
 
107
- - **Factual Depth:** With 80M parameters, the model cannot serve as a reliable encyclopedia for obscure facts. Factual queries should be supported by RAG.
108
- - **Multi-step Math:** Elementary arithmetic works, but multi-variable algebra requires external verification.
 
12
  - on-device
13
  pipeline_tag: text-generation
14
  widget:
15
+ - text: "<|im_start|>user\nWhat is the capital of France?<|im_end|>\n<|im_start|>assistant\n"
 
 
 
 
 
 
16
  ---
17
 
18
  # PicoLM-80M-Instruct 🚀
 
29
  - **Parameters:** 80,242,240 (~80.2M)
30
  - **Context Window:** 2,048 tokens
31
  - **Vocabulary:** 16,384 (Single-digit regex split, Byte-level BPE)
32
+ - **Format:** Safetensors (FP16) & GGUF
33
  - **Primary Language:** English + Python Code
34
  - **License:** Apache 2.0
35
 
 
37
 
38
  ## 📊 Empirical Benchmark Results (Verified)
39
 
40
+ All scores below were **empirically measured** directly on the model weights using standard log-likelihood evaluations:
41
 
42
  | Benchmark / Task | Random Baseline | SmolLM2-135M (HF) | Gemma 3 270M (Google) | PicoLM-80M-Instruct (Ours) |
43
  | :--- | :--- | :--- | :--- | :--- |
 
49
 
50
  ---
51
 
52
+ ## 💻 Quickstart (Transformers Native)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ You can load and chat with PicoLM directly via Hugging Face `transformers`:
55
 
56
  ```python
57
+ from transformers import AutoModelForCausalLM, AutoTokenizer
 
58
 
59
+ model_id = "aethertp/PicoLM-80M-Instruct"
60
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
61
+ model = AutoModelForCausalLM.from_pretrained(model_id, trust_remote_code=True).cuda()
62
 
63
+ messages = [{"role": "user", "content": "What is the capital of France?"}]
64
+ prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
65
+ inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
 
66
 
67
+ outputs = model.generate(**inputs, max_new_tokens=50, temperature=0.6, do_sample=True)
68
+ print(tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:]))
69
  ```
70
 
71
  ---
72
 
73
  ## ⚠️ Limitations
74
 
75
+ - **Factual Depth:** With 80M parameters, the model cannot serve as a comprehensive encyclopedia. Factual queries should be supported by RAG.
76
+ - **Multi-step Math:** Elementary arithmetic works, but complex multi-variable algebra requires external verification.
config.json CHANGED
@@ -3,6 +3,10 @@
3
  "PicoLMForCausalLM"
4
  ],
5
  "model_type": "picolm",
 
 
 
 
6
  "vocab_size": 16384,
7
  "hidden_size": 576,
8
  "num_hidden_layers": 20,
@@ -12,6 +16,7 @@
12
  "max_position_embeddings": 2048,
13
  "rms_norm_eps": 1e-05,
14
  "rope_theta": 10000.0,
 
15
  "torch_dtype": "float16",
16
  "transformers_version": "4.45.0"
17
  }
 
3
  "PicoLMForCausalLM"
4
  ],
5
  "model_type": "picolm",
6
+ "auto_map": {
7
+ "AutoConfig": "configuration_picolm.PicoLMConfig",
8
+ "AutoModelForCausalLM": "modeling_picolm.PicoLMForCausalLM"
9
+ },
10
  "vocab_size": 16384,
11
  "hidden_size": 576,
12
  "num_hidden_layers": 20,
 
16
  "max_position_embeddings": 2048,
17
  "rms_norm_eps": 1e-05,
18
  "rope_theta": 10000.0,
19
+ "tie_word_embeddings": true,
20
  "torch_dtype": "float16",
21
  "transformers_version": "4.45.0"
22
  }
configuration_picolm.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig
2
+
3
+ class PicoLMConfig(PretrainedConfig):
4
+ model_type = "picolm"
5
+ def __init__(
6
+ self,
7
+ vocab_size=16384,
8
+ hidden_size=576,
9
+ num_hidden_layers=20,
10
+ num_attention_heads=9,
11
+ num_key_value_heads=3,
12
+ intermediate_size=1536,
13
+ max_position_embeddings=2048,
14
+ rms_norm_eps=1e-5,
15
+ rope_theta=10000.0,
16
+ tie_word_embeddings=True,
17
+ **kwargs
18
+ ):
19
+ super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
20
+ self.vocab_size = vocab_size
21
+ self.hidden_size = hidden_size
22
+ self.num_hidden_layers = num_hidden_layers
23
+ self.num_attention_heads = num_attention_heads
24
+ self.num_key_value_heads = num_key_value_heads
25
+ self.intermediate_size = intermediate_size
26
+ self.max_position_embeddings = max_position_embeddings
27
+ self.rms_norm_eps = rms_norm_eps
28
+ self.rope_theta = rope_theta
modeling_picolm.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+ from transformers import PreTrainedModel
6
+ from transformers.modeling_outputs import CausalLMOutputWithPast
7
+ from .configuration_picolm import PicoLMConfig
8
+
9
+ class RMSNorm(nn.Module):
10
+ def __init__(self, dim: int, eps: float = 1e-5):
11
+ super().__init__()
12
+ self.eps = eps
13
+ self.weight = nn.Parameter(torch.ones(dim))
14
+ def forward(self, x):
15
+ return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.weight
16
+
17
+ def precompute_rope_cis(dim: int, max_seq_len: int, theta: float = 10000.0):
18
+ freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
19
+ t = torch.arange(max_seq_len, dtype=torch.float32)
20
+ freqs = torch.outer(t, freqs)
21
+ return torch.cos(freqs), torch.sin(freqs)
22
+
23
+ def apply_rope(x, cos, sin):
24
+ B, H, S, D = x.shape
25
+ cos = cos[:S, :].to(x.device).unsqueeze(0).unsqueeze(0)
26
+ sin = sin[:S, :].to(x.device).unsqueeze(0).unsqueeze(0)
27
+ x1, x2 = x[..., : D // 2], x[..., D // 2 :]
28
+ return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
29
+
30
+ class Attention(nn.Module):
31
+ def __init__(self, args):
32
+ super().__init__()
33
+ self.args = args
34
+ self.head_dim = args.hidden_size // args.num_attention_heads
35
+ self.n_heads = args.num_attention_heads
36
+ self.n_kv_heads = args.num_key_value_heads
37
+ self.num_reps = self.n_heads // self.n_kv_heads
38
+
39
+ self.q_proj = nn.Linear(args.hidden_size, self.n_heads * self.head_dim, bias=False)
40
+ self.k_proj = nn.Linear(args.hidden_size, self.n_kv_heads * self.head_dim, bias=False)
41
+ self.v_proj = nn.Linear(args.hidden_size, self.n_kv_heads * self.head_dim, bias=False)
42
+ self.out_proj = nn.Linear(args.n_heads * self.head_dim, args.hidden_size, bias=False)
43
+ self.q_norm = RMSNorm(self.head_dim, eps=args.rms_norm_eps)
44
+ self.k_norm = RMSNorm(self.head_dim, eps=args.rms_norm_eps)
45
+
46
+ def forward(self, x, cos, sin):
47
+ B, S, C = x.shape
48
+ q = apply_rope(self.q_norm(self.q_proj(x).view(B, S, self.n_heads, self.head_dim).transpose(1, 2)), cos, sin)
49
+ k = apply_rope(self.k_norm(self.k_proj(x).view(B, S, self.n_kv_heads, self.head_dim).transpose(1, 2)), cos, sin)
50
+ v = self.v_proj(x).view(B, S, self.n_kv_heads, self.head_dim).transpose(1, 2)
51
+ if self.num_reps > 1:
52
+ k = k[:, :, None, :, :].expand(B, self.n_kv_heads, self.num_reps, S, self.head_dim).reshape(B, self.n_heads, S, self.head_dim)
53
+ v = v[:, :, None, :, :].expand(B, self.n_kv_heads, self.num_reps, S, self.head_dim).reshape(B, self.n_heads, S, self.head_dim)
54
+ attn_out = F.scaled_dot_product_attention(q, k, v, is_causal=True)
55
+ return self.out_proj(attn_out.transpose(1, 2).contiguous().view(B, S, C))
56
+
57
+ class SwiGLUMLP(nn.Module):
58
+ def __init__(self, args):
59
+ super().__init__()
60
+ self.gate_proj = nn.Linear(args.hidden_size, args.intermediate_size, bias=False)
61
+ self.up_proj = nn.Linear(args.hidden_size, args.intermediate_size, bias=False)
62
+ self.down_proj = nn.Linear(args.intermediate_size, args.hidden_size, bias=False)
63
+ def forward(self, x):
64
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
65
+
66
+ class TransformerBlock(nn.Module):
67
+ def __init__(self, args):
68
+ super().__init__()
69
+ self.attn_norm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
70
+ self.attn = Attention(args)
71
+ self.mlp_norm = RMSNorm(args.hidden_size, eps=args.rms_norm_eps)
72
+ self.mlp = SwiGLUMLP(args)
73
+ def forward(self, x, cos, sin):
74
+ return x + self.mlp(self.mlp_norm(x + self.attn(self.attn_norm(x), cos, sin)))
75
+
76
+ class PicoLMForCausalLM(PreTrainedModel):
77
+ config_class = PicoLMConfig
78
+ def __init__(self, config):
79
+ super().__init__(config)
80
+ self.config = config
81
+ self.tok_embeddings = nn.Embedding(config.vocab_size, config.hidden_size)
82
+ self.blocks = nn.ModuleList([TransformerBlock(config) for _ in range(config.num_hidden_layers)])
83
+ self.final_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
84
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
85
+ self.lm_head.weight = self.tok_embeddings.weight
86
+
87
+ cos, sin = precompute_rope_cis(config.hidden_size // config.num_attention_heads, config.max_position_embeddings, config.rope_theta)
88
+ self.register_buffer("rope_cos", cos, persistent=False)
89
+ self.register_buffer("rope_sin", sin, persistent=False)
90
+
91
+ def forward(self, input_ids, labels=None, **kwargs):
92
+ B, S = input_ids.shape
93
+ x = self.tok_embeddings(input_ids)
94
+ cos, sin = self.rope_cos[:S], self.rope_sin[:S]
95
+ for block in self.blocks:
96
+ x = block(x, cos, sin)
97
+ logits = self.lm_head(self.final_norm(x))
98
+
99
+ loss = None
100
+ if labels is not None:
101
+ shift_logits = logits[..., :-1, :].contiguous()
102
+ shift_labels = labels[..., 1:].contiguous()
103
+ loss = F.cross_entropy(shift_logits.view(-1, self.config.vocab_size), shift_labels.view(-1), ignore_index=-100)
104
+
105
+ return CausalLMOutputWithPast(loss=loss, logits=logits)