ThingsAI commited on
Commit
38b7e42
·
verified ·
1 Parent(s): 5c383d8

Upload folder using huggingface_hub

Browse files
Files changed (5) hide show
  1. README.md +6 -6
  2. config.json +2 -2
  3. configuration_dwarf.py +1 -4
  4. model.safetensors +2 -2
  5. modeling_dwarf.py +58 -80
README.md CHANGED
@@ -12,11 +12,11 @@ tags:
12
  - small-language-model
13
  pipeline_tag: text-generation
14
  model-index:
15
- - name: Dwarf-15M
16
  results: []
17
  ---
18
 
19
- # Dwarf-15M
20
 
21
  A **15.54M parameter** shell/bash specialist language model that translates natural language into Linux commands.
22
 
@@ -25,8 +25,8 @@ A **15.54M parameter** shell/bash specialist language model that translates natu
25
  ```python
26
  from transformers import AutoTokenizer, AutoModelForCausalLM
27
 
28
- tokenizer = AutoTokenizer.from_pretrained("ThingAI/Dwarf-15M", trust_remote_code=True)
29
- model = AutoModelForCausalLM.from_pretrained("ThingAI/Dwarf-15M", trust_remote_code=True)
30
 
31
  prompt = "<|user|>\nFind all Python files modified in the last 3 days\n<|end|>\n<|assistant|>\n"
32
  inputs = tokenizer(prompt, return_tensors="pt")
@@ -79,7 +79,7 @@ Dwarf-15M takes natural language descriptions of tasks and produces the correspo
79
  - English: helpful-instructions, FineWeb — 10.3%
80
  - CoT: Magpie-Reasoning — 1.1%
81
 
82
- **SFT:** 900 curated Linux command pairs, 5 epochs, lr=4e-5. Training time: 19 seconds.
83
 
84
  **Tokenizer:** [DwarfGoToken](https://huggingface.co/ThingAI/DwarfGoToken) — 8,202 token BPE with syntax-aware pre-tokenization for shell operators (2>&1, &&, >>).
85
 
@@ -122,7 +122,7 @@ Apache 2.0
122
 
123
  ```bibtex
124
  @misc{dwarf15m2026,
125
- title={Dwarf-15M: A Shell Specialist Language Model},
126
  author={ThingsAI},
127
  year={2026},
128
  url={https://huggingface.co/ThingAI/Dwarf-15M-Instruct}
 
12
  - small-language-model
13
  pipeline_tag: text-generation
14
  model-index:
15
+ - name: Dwarf-15M-Instruct
16
  results: []
17
  ---
18
 
19
+ # Dwarf-15M-Instruct
20
 
21
  A **15.54M parameter** shell/bash specialist language model that translates natural language into Linux commands.
22
 
 
25
  ```python
26
  from transformers import AutoTokenizer, AutoModelForCausalLM
27
 
28
+ tokenizer = AutoTokenizer.from_pretrained("ThingAI/Dwarf-15M-Instruct", trust_remote_code=True)
29
+ model = AutoModelForCausalLM.from_pretrained("ThingAI/Dwarf-15M-Instruct", trust_remote_code=True)
30
 
31
  prompt = "<|user|>\nFind all Python files modified in the last 3 days\n<|end|>\n<|assistant|>\n"
32
  inputs = tokenizer(prompt, return_tensors="pt")
 
79
  - English: helpful-instructions, FineWeb — 10.3%
80
  - CoT: Magpie-Reasoning — 1.1%
81
 
82
+ **SFT:** 557 curated Linux command pairs, 5 epochs, lr=4e-5. Training time: 19 seconds.
83
 
84
  **Tokenizer:** [DwarfGoToken](https://huggingface.co/ThingAI/DwarfGoToken) — 8,202 token BPE with syntax-aware pre-tokenization for shell operators (2>&1, &&, >>).
85
 
 
122
 
123
  ```bibtex
124
  @misc{dwarf15m2026,
125
+ title={Dwarf-15M-Instruct: A Shell Specialist Language Model},
126
  author={ThingsAI},
127
  year={2026},
128
  url={https://huggingface.co/ThingAI/Dwarf-15M-Instruct}
config.json CHANGED
@@ -19,6 +19,6 @@
19
  "head_dim": 64,
20
  "torch_dtype": "float32",
21
  "transformers_version": "4.45.0",
22
- "bos_token_id": 1,
23
- "eos_token_id": 2
24
  }
 
19
  "head_dim": 64,
20
  "torch_dtype": "float32",
21
  "transformers_version": "4.45.0",
22
+ "bos_token_id": 0,
23
+ "eos_token_id": 0
24
  }
configuration_dwarf.py CHANGED
@@ -4,6 +4,7 @@ from transformers import PretrainedConfig
4
 
5
  class DwarfConfig(PretrainedConfig):
6
  model_type = "dwarf"
 
7
 
8
  def __init__(
9
  self,
@@ -29,8 +30,4 @@ class DwarfConfig(PretrainedConfig):
29
  self.rope_theta = rope_theta
30
  self.norm_eps = norm_eps
31
  self.head_dim = head_dim
32
- self.num_hidden_layers = n_layers
33
- self.hidden_size = d_model
34
- self.num_attention_heads = n_heads
35
- self.num_key_value_heads = n_kv_heads
36
  super().__init__(**kwargs)
 
4
 
5
  class DwarfConfig(PretrainedConfig):
6
  model_type = "dwarf"
7
+ has_no_defaults_at_init = True
8
 
9
  def __init__(
10
  self,
 
30
  self.rope_theta = rope_theta
31
  self.norm_eps = norm_eps
32
  self.head_dim = head_dim
 
 
 
 
33
  super().__init__(**kwargs)
model.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:ed70fe03dec2a2fd6469ca337f72cf222d5375024fdeb8ddb51b5e3bad8f76a3
3
- size 72674368
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:748cb8e54a65afc49e9a8e6e76760dd2206c98bb419c88f56cfbc429fe67fdc7
3
+ size 62175720
modeling_dwarf.py CHANGED
@@ -1,4 +1,5 @@
1
  """Dwarf-15M: a 15.54M parameter shell/bash specialist language model."""
 
2
  import torch
3
  import torch.nn as nn
4
  import torch.nn.functional as F
@@ -9,80 +10,67 @@ from .configuration_dwarf import DwarfConfig
9
  class RMSNorm(nn.Module):
10
  def __init__(self, dim, eps=1e-5):
11
  super().__init__()
 
12
  self.eps = eps
13
- self.scale = nn.Parameter(torch.ones(dim))
14
 
15
  def forward(self, x):
16
- rms = x.float().pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt()
17
- return (x.float() * rms).to(x.dtype) * self.scale
18
 
19
 
20
  class RotaryEmbedding(nn.Module):
21
- def __init__(self, head_dim, max_seq_len, theta=10000.0):
22
  super().__init__()
23
- assert head_dim % 2 == 0
24
- self.head_dim = head_dim
25
- self.max_seq_len = max_seq_len
26
- self.theta = theta
27
- self.cos_cache = None
28
- self.sin_cache = None
29
- self._max = 0
30
-
31
- def _build_cache(self, seq_len, device):
32
- inv_freq = 1.0 / (self.theta ** (torch.arange(0, self.head_dim, 2, device=device).float() / self.head_dim))
33
- t = torch.arange(seq_len, device=device).float()
34
- freqs = torch.outer(t, inv_freq)
35
- emb = torch.cat([freqs, freqs], dim=-1)
36
- self.cos_cache = emb.cos()[None, None]
37
- self.sin_cache = emb.sin()[None, None]
38
- self._max = seq_len
39
-
40
- @staticmethod
41
- def _rotate_half(x):
42
- x1, x2 = x.chunk(2, dim=-1)
43
- return torch.cat([-x2, x1], dim=-1)
44
-
45
- def forward(self, q, k):
46
- T = q.size(2)
47
- if self.cos_cache is None or T > self._max or self.cos_cache.device != q.device:
48
- self._build_cache(max(T, self.max_seq_len), q.device)
49
- cos = self.cos_cache[:, :, :T, :]
50
- sin = self.sin_cache[:, :, :T, :]
51
- q = q * cos + self._rotate_half(q) * sin
52
- k = k * cos + self._rotate_half(k) * sin
53
- return q, k
54
-
55
-
56
- class GroupedQueryAttention(nn.Module):
57
  def __init__(self, config):
58
  super().__init__()
59
  self.n_heads = config.n_heads
60
  self.n_kv_heads = config.n_kv_heads
61
- self.n_groups = config.n_heads // config.n_kv_heads
62
  self.head_dim = config.head_dim
63
-
64
- self.q_proj = nn.Linear(config.d_model, config.n_heads * config.head_dim, bias=True)
65
- self.k_proj = nn.Linear(config.d_model, config.n_kv_heads * config.head_dim, bias=True)
66
- self.v_proj = nn.Linear(config.d_model, config.n_kv_heads * config.head_dim, bias=True)
67
  self.o_proj = nn.Linear(config.n_heads * config.head_dim, config.d_model, bias=False)
 
68
 
69
- self.rope = RotaryEmbedding(config.head_dim, config.max_seq_len, config.rope_theta)
70
-
71
- def forward(self, x):
72
  B, T, _ = x.shape
73
  q = self.q_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
74
  k = self.k_proj(x).view(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2)
75
  v = self.v_proj(x).view(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2)
76
-
77
- q, k = self.rope(q, k)
78
-
79
- if self.n_groups > 1:
80
- k = k.repeat_interleave(self.n_groups, dim=1)
81
- v = v.repeat_interleave(self.n_groups, dim=1)
82
-
83
- out = F.scaled_dot_product_attention(q, k, v, attn_mask=None, is_causal=True)
84
- out = out.transpose(1, 2).contiguous().view(B, T, self.n_heads * self.head_dim)
85
- return self.o_proj(out)
86
 
87
 
88
  class SwiGLUFFN(nn.Module):
@@ -99,20 +87,19 @@ class SwiGLUFFN(nn.Module):
99
  class DwarfBlock(nn.Module):
100
  def __init__(self, config):
101
  super().__init__()
102
- self.norm_attn = RMSNorm(config.d_model, config.norm_eps)
103
- self.attn = GroupedQueryAttention(config)
104
- self.norm_ffn = RMSNorm(config.d_model, config.norm_eps)
105
  self.ffn = SwiGLUFFN(config)
106
 
107
- def forward(self, x):
108
- x = x + self.attn(self.norm_attn(x))
109
- x = x + self.ffn(self.norm_ffn(x))
110
  return x
111
 
112
 
113
  class DwarfForCausalLM(PreTrainedModel, GenerationMixin):
114
  config_class = DwarfConfig
115
- _tied_weights_keys = ["lm_head.weight"]
116
 
117
  def __init__(self, config):
118
  super().__init__(config)
@@ -120,27 +107,15 @@ class DwarfForCausalLM(PreTrainedModel, GenerationMixin):
120
  self.layers = nn.ModuleList([DwarfBlock(config) for _ in range(config.n_layers)])
121
  self.norm = RMSNorm(config.d_model, config.norm_eps)
122
  self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
 
 
123
  self.post_init()
124
 
125
- def tie_weights(self, **kwargs):
126
- self.lm_head.weight = self.embed_tokens.weight
127
-
128
- def get_input_embeddings(self):
129
- return self.embed_tokens
130
-
131
- def set_input_embeddings(self, value):
132
- self.embed_tokens = value
133
-
134
- def get_output_embeddings(self):
135
- return self.lm_head
136
-
137
- def set_output_embeddings(self, new_embeddings):
138
- self.lm_head = new_embeddings
139
-
140
  def forward(self, input_ids, attention_mask=None, labels=None, **kwargs):
141
  x = self.embed_tokens(input_ids)
 
142
  for layer in self.layers:
143
- x = layer(x)
144
  logits = self.lm_head(self.norm(x))
145
 
146
  loss = None
@@ -155,4 +130,7 @@ class DwarfForCausalLM(PreTrainedModel, GenerationMixin):
155
  return CausalLMOutput(loss=loss, logits=logits)
156
 
157
  def prepare_inputs_for_generation(self, input_ids, **kwargs):
158
- return {"input_ids": input_ids}
 
 
 
 
1
  """Dwarf-15M: a 15.54M parameter shell/bash specialist language model."""
2
+ import math
3
  import torch
4
  import torch.nn as nn
5
  import torch.nn.functional as F
 
10
  class RMSNorm(nn.Module):
11
  def __init__(self, dim, eps=1e-5):
12
  super().__init__()
13
+ self.weight = nn.Parameter(torch.ones(dim))
14
  self.eps = eps
 
15
 
16
  def forward(self, x):
17
+ return x * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.eps).to(x.dtype) * self.weight
 
18
 
19
 
20
  class RotaryEmbedding(nn.Module):
21
+ def __init__(self, dim, max_seq_len=2048, theta=10000.0):
22
  super().__init__()
23
+ inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2).float() / dim))
24
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
25
+ self._build_cache(max_seq_len)
26
+
27
+ def _build_cache(self, seq_len):
28
+ t = torch.arange(seq_len, dtype=self.inv_freq.dtype)
29
+ freqs = torch.outer(t, self.inv_freq)
30
+ self.register_buffer("cos_cache", freqs.cos(), persistent=False)
31
+ self.register_buffer("sin_cache", freqs.sin(), persistent=False)
32
+
33
+ def forward(self, x, offset=0):
34
+ seq_len = x.shape[1]
35
+ if offset + seq_len > self.cos_cache.shape[0]:
36
+ self._build_cache(offset + seq_len)
37
+ cos = self.cos_cache[offset:offset + seq_len]
38
+ sin = self.sin_cache[offset:offset + seq_len]
39
+ return cos, sin
40
+
41
+
42
+ def apply_rope(x, cos, sin):
43
+ d = x.shape[-1]
44
+ x1, x2 = x[..., :d//2], x[..., d//2:]
45
+ cos = cos[:x.shape[-2], :d//2].unsqueeze(0).unsqueeze(0)
46
+ sin = sin[:x.shape[-2], :d//2].unsqueeze(0).unsqueeze(0)
47
+ return torch.cat([x1 * cos - x2 * sin, x2 * cos + x1 * sin], dim=-1)
48
+
49
+
50
+ class GQAAttention(nn.Module):
 
 
 
 
 
 
51
  def __init__(self, config):
52
  super().__init__()
53
  self.n_heads = config.n_heads
54
  self.n_kv_heads = config.n_kv_heads
 
55
  self.head_dim = config.head_dim
56
+ self.q_proj = nn.Linear(config.d_model, config.n_heads * config.head_dim, bias=False)
57
+ self.k_proj = nn.Linear(config.d_model, config.n_kv_heads * config.head_dim, bias=False)
58
+ self.v_proj = nn.Linear(config.d_model, config.n_kv_heads * config.head_dim, bias=False)
 
59
  self.o_proj = nn.Linear(config.n_heads * config.head_dim, config.d_model, bias=False)
60
+ self.group_size = config.n_heads // config.n_kv_heads
61
 
62
+ def forward(self, x, cos, sin):
 
 
63
  B, T, _ = x.shape
64
  q = self.q_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
65
  k = self.k_proj(x).view(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2)
66
  v = self.v_proj(x).view(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2)
67
+ q = apply_rope(q, cos, sin)
68
+ k = apply_rope(k, cos, sin)
69
+ if self.group_size > 1:
70
+ k = k.unsqueeze(2).expand(-1, -1, self.group_size, -1, -1).reshape(B, self.n_heads, T, self.head_dim)
71
+ v = v.unsqueeze(2).expand(-1, -1, self.group_size, -1, -1).reshape(B, self.n_heads, T, self.head_dim)
72
+ out = F.scaled_dot_product_attention(q, k, v, is_causal=True)
73
+ return self.o_proj(out.transpose(1, 2).reshape(B, T, -1))
 
 
 
74
 
75
 
76
  class SwiGLUFFN(nn.Module):
 
87
  class DwarfBlock(nn.Module):
88
  def __init__(self, config):
89
  super().__init__()
90
+ self.attn_norm = RMSNorm(config.d_model, config.norm_eps)
91
+ self.attn = GQAAttention(config)
92
+ self.ff_norm = RMSNorm(config.d_model, config.norm_eps)
93
  self.ffn = SwiGLUFFN(config)
94
 
95
+ def forward(self, x, cos, sin):
96
+ x = x + self.attn(self.attn_norm(x), cos, sin)
97
+ x = x + self.ffn(self.ff_norm(x))
98
  return x
99
 
100
 
101
  class DwarfForCausalLM(PreTrainedModel, GenerationMixin):
102
  config_class = DwarfConfig
 
103
 
104
  def __init__(self, config):
105
  super().__init__(config)
 
107
  self.layers = nn.ModuleList([DwarfBlock(config) for _ in range(config.n_layers)])
108
  self.norm = RMSNorm(config.d_model, config.norm_eps)
109
  self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
110
+ self.lm_head.weight = self.embed_tokens.weight # weight tying
111
+ self.rope = RotaryEmbedding(config.head_dim, config.max_seq_len, config.rope_theta)
112
  self.post_init()
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  def forward(self, input_ids, attention_mask=None, labels=None, **kwargs):
115
  x = self.embed_tokens(input_ids)
116
+ cos, sin = self.rope(x)
117
  for layer in self.layers:
118
+ x = layer(x, cos, sin)
119
  logits = self.lm_head(self.norm(x))
120
 
121
  loss = None
 
130
  return CausalLMOutput(loss=loss, logits=logits)
131
 
132
  def prepare_inputs_for_generation(self, input_ids, **kwargs):
133
+ return {"input_ids": input_ids}
134
+
135
+ def count_parameters(self):
136
+ return sum(p.numel() for p in self.parameters() if p.requires_grad)