ThingsAI commited on
Commit
441dc44
·
verified ·
1 Parent(s): 2152b4f

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - en
4
+ license: apache-2.0
5
+ library_name: transformers
6
+ tags:
7
+ - bash
8
+ - shell
9
+ - linux
10
+ - cli
11
+ - code
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
+
23
+ ## Quick Start
24
+
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")
33
+ outputs = model.generate(**inputs, max_new_tokens=100, do_sample=False)
34
+ print(tokenizer.decode(outputs[0], skip_special_tokens=False))
35
+ # → find . -name '*.py' -mtime -3
36
+ ```
37
+
38
+ ## What It Does
39
+
40
+ Dwarf-15M takes natural language descriptions of tasks and produces the corresponding Linux/bash command:
41
+
42
+ | Prompt | Output |
43
+ |---|---|
44
+ | Show current date | `date` |
45
+ | List files | `ls` |
46
+ | Kill process 1234 | `kill 1234` |
47
+ | Delete all .tmp files in current directory | `rm ./*.tmp` |
48
+ | Compress the /home/user/project folder | `tar -czf project.tar.gz /home/user/project/` |
49
+ | Check if port 8080 is in use | `ss -tlnp \| grep 8080` |
50
+ | Restart the nginx service | `sudo systemctl restart nginx` |
51
+ | Find all files containing TODO | `grep -rl 'TODO' .` |
52
+ | Change owner of /var/www to www-data | `sudo chown -R www-data:www-data /var/www` |
53
+ | Run script.sh in background and log output | `nohup ./script.sh > log.txt 2>&1 &` |
54
+ | Find and replace foo with bar in config.txt | `sed -i 's/foo/bar/g' config.txt` |
55
+ | What does chmod 755 do? | chmod 755 sets read+write+execute for owner, read+execute for group and others. |
56
+ | Write a bash function that counts lines | `count_lines() { wc -l < "$1"; }` |
57
+
58
+ ## Architecture
59
+
60
+ | Parameter | Value |
61
+ |---|---|
62
+ | Parameters | 15.54M |
63
+ | Layers | 12 |
64
+ | Hidden dim | 320 |
65
+ | Attention | GQA (5 query, 1 KV head) |
66
+ | FFN | SwiGLU (d_ff=864) |
67
+ | Normalization | RMSNorm |
68
+ | Positional | RoPE (θ=10000) |
69
+ | Vocabulary | 8,202 (DwarfGoToken) |
70
+ | Max sequence | 2,048 |
71
+ | Weight tying | Yes (embed ↔ lm_head) |
72
+
73
+ ## Training
74
+
75
+ **Pretraining:** 21.6B tokens (ratio 1,390:1) on 11 datasets:
76
+ - Shell/bash: The Stack (shell, batchfile), GunA-SD/bash_code — 38.5%
77
+ - Code: The Stack (Python, C), CodeFeedback — 39.1%
78
+ - Instructions: ShellLife (52K NL→command), rlvr-code-data-bash (133K problems) — 11%
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
+
86
+ ## Chat Template
87
+
88
+ ```
89
+ <|user|>
90
+ Your question here
91
+ <|end|>
92
+ <|assistant|>
93
+ Model response here
94
+ <|end|>
95
+ ```
96
+
97
+ ## Intended Use
98
+
99
+ Dwarf-15M is designed as a **CLI assistant** that suggests commands for user review before execution. It is NOT a general-purpose chatbot. Best results on:
100
+ - Simple to medium Linux commands (file ops, process management, networking)
101
+ - Bash one-liners and short functions
102
+ - Command explanations ("What does chmod 755 do?")
103
+
104
+ ## Limitations
105
+
106
+ - 15M parameters — cannot handle complex multi-step reasoning
107
+ - May produce incorrect commands for unusual or very specific requests
108
+ - Should **always** be used with human review before executing any suggested command
109
+ - English only
110
+ - Trained primarily on Ubuntu/Debian commands
111
+
112
+ ## Hardware
113
+
114
+ - Pretrained on RTX 3070 (8GB VRAM) at ~127K tokens/sec
115
+ - Inference: runs on any hardware including CPU, ~30MB model size
116
+
117
+ ## License
118
+
119
+ Apache 2.0
120
+
121
+ ## Citation
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}
129
+ }
130
+ ```
chat_template.jinja ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% for message in messages %}{% if message['role'] == 'system' %}<|system|>
2
+ {{ message['content'] }}
3
+ <|end|>
4
+ {% elif message['role'] == 'user' %}<|user|>
5
+ {{ message['content'] }}
6
+ <|end|>
7
+ {% elif message['role'] == 'assistant' %}<|assistant|>
8
+ {{ message['content'] }}
9
+ <|end|>
10
+ {% endif %}{% endfor %}{% if messages[-1]['role'] != 'assistant' %}<|assistant|>
11
+ {% endif %}
config.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "DwarfForCausalLM"
4
+ ],
5
+ "model_type": "dwarf",
6
+ "auto_map": {
7
+ "AutoConfig": "configuration_dwarf.DwarfConfig",
8
+ "AutoModelForCausalLM": "modeling_dwarf.DwarfForCausalLM"
9
+ },
10
+ "vocab_size": 8202,
11
+ "d_model": 320,
12
+ "n_layers": 12,
13
+ "n_heads": 5,
14
+ "n_kv_heads": 1,
15
+ "d_ff": 864,
16
+ "max_seq_len": 2048,
17
+ "rope_theta": 10000.0,
18
+ "norm_eps": 1e-05,
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
+ }
configuration_dwarf.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dwarf model configuration."""
2
+ from transformers import PretrainedConfig
3
+
4
+
5
+ class DwarfConfig(PretrainedConfig):
6
+ model_type = "dwarf"
7
+
8
+ def __init__(
9
+ self,
10
+ vocab_size=8202,
11
+ d_model=320,
12
+ n_layers=12,
13
+ n_heads=5,
14
+ n_kv_heads=1,
15
+ d_ff=864,
16
+ max_seq_len=2048,
17
+ rope_theta=10000.0,
18
+ norm_eps=1e-5,
19
+ head_dim=64,
20
+ **kwargs,
21
+ ):
22
+ self.vocab_size = vocab_size
23
+ self.d_model = d_model
24
+ self.n_layers = n_layers
25
+ self.n_heads = n_heads
26
+ self.n_kv_heads = n_kv_heads
27
+ self.d_ff = d_ff
28
+ self.max_seq_len = max_seq_len
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)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ed70fe03dec2a2fd6469ca337f72cf222d5375024fdeb8ddb51b5e3bad8f76a3
3
+ size 72674368
modeling_dwarf.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
5
+ from transformers import PreTrainedModel, GenerationMixin
6
+ from .configuration_dwarf import DwarfConfig
7
+
8
+
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):
89
+ def __init__(self, config):
90
+ super().__init__()
91
+ self.gate_proj = nn.Linear(config.d_model, config.d_ff, bias=False)
92
+ self.up_proj = nn.Linear(config.d_model, config.d_ff, bias=False)
93
+ self.down_proj = nn.Linear(config.d_ff, config.d_model, bias=False)
94
+
95
+ def forward(self, x):
96
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
97
+
98
+
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)
119
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.d_model)
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
147
+ if labels is not None:
148
+ loss = F.cross_entropy(
149
+ logits[:, :-1].contiguous().view(-1, logits.size(-1)),
150
+ labels[:, 1:].contiguous().view(-1),
151
+ ignore_index=-100,
152
+ )
153
+
154
+ from transformers.modeling_outputs import CausalLMOutput
155
+ return CausalLMOutput(loss=loss, logits=logits)
156
+
157
+ def prepare_inputs_for_generation(self, input_ids, **kwargs):
158
+ return {"input_ids": input_ids}
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<s>",
4
+ "eos_token": "</s>",
5
+ "extra_special_tokens": [
6
+ "<|system|>",
7
+ "<|user|>",
8
+ "<|assistant|>",
9
+ "<|end|>",
10
+ "<|thinking|>",
11
+ "<|/thinking|>"
12
+ ],
13
+ "is_local": false,
14
+ "local_files_only": false,
15
+ "model_max_length": 1000000000000000019884624838656,
16
+ "pad_token": "<pad>",
17
+ "tokenizer_class": "TokenizersBackend",
18
+ "unk_token": "<unk>"
19
+ }