shawneil commited on
Commit
d33490b
Β·
verified Β·
1 Parent(s): 1efda0d

Update HF-compatible weights

Browse files
Files changed (4) hide show
  1. README.md +49 -0
  2. config.json +19 -0
  3. hf_wrapper.py +228 -0
  4. model.safetensors +3 -0
README.md ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language: en
3
+ tags:
4
+ - function-calling
5
+ - mobile-actions
6
+ - nanomind
7
+ - tool-use
8
+ license: apache-2.0
9
+ ---
10
+
11
+ # NanoMind β€” Mobile Actions (Function Calling)
12
+
13
+ Fine-tuned on [google/mobile-actions](https://huggingface.co/datasets/google/mobile-actions)
14
+ for on-device function calling (calendar, email, contacts, maps, flashlight, Wi-Fi).
15
+
16
+ ## Quick Start
17
+
18
+ ```python
19
+ from hf_wrapper import NanoMindForFunctionCalling
20
+ import tiktoken, torch, json
21
+
22
+ model = NanoMindForFunctionCalling.from_pretrained(
23
+ "shawneil/NanoMind-MobileActions"
24
+ )
25
+ model.eval()
26
+ enc = tiktoken.get_encoding("gpt2")
27
+
28
+ # Build a prompt in the same format used during training
29
+ tools_json = json.dumps([{"function": {"name": "create_calendar_event",
30
+ "description": "Creates a calendar event.",
31
+ "parameters": {"properties": {"title": {"type": "STRING"},
32
+ "datetime": {"type": "STRING"}}, "required": ["title","datetime"]}}}])
33
+
34
+ prompt = (
35
+ "<|tools|>\n" + tools_json + "\n<|endtools|>\n"
36
+ "<|system|>\nYou are a function-calling assistant.\n<|endoftext|>\n"
37
+ "<|user|>\nSet a meeting tomorrow at 3 PM called 'Budget Review'.\n<|endoftext|>\n"
38
+ "<|assistant|>\n"
39
+ )
40
+ ids = torch.tensor([enc.encode_ordinary(prompt)], dtype=torch.long)
41
+ out = model.generate_text(ids, max_new_tokens=80, temperature=0.0, top_k=1)
42
+ print(enc.decode(out[0].tolist()))
43
+ ```
44
+
45
+ ## Training details
46
+ - Base: NanoMind SFT-Dolly (shawneil/NanoMind-SFT-Dolly)
47
+ - Dataset: google/mobile-actions (~9.65k rows)
48
+ - Loss only on assistant/tool-call tokens
49
+ - fp16 Β· torch.compile Β· 2Γ— T4 DDP
config.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "nanomind",
3
+ "architectures": [
4
+ "NanoMindForFunctionCalling"
5
+ ],
6
+ "task": "function_calling",
7
+ "train_step": 300,
8
+ "vocab_size": 50257,
9
+ "d_model": 512,
10
+ "n_heads": 8,
11
+ "n_kv_heads": 2,
12
+ "n_layers": 8,
13
+ "max_seq_len": 1024,
14
+ "ff_mult": 4,
15
+ "dropout": 0.0,
16
+ "use_moe": false,
17
+ "num_experts": 4,
18
+ "top_k_experts": 2
19
+ }
hf_wrapper.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ """
3
+ NanoMindForFunctionCalling β€” HuggingFace PreTrainedModel wrapper.
4
+
5
+ Usage after downloading from HF hub:
6
+ from hf_wrapper import NanoMindForFunctionCalling
7
+ model = NanoMindForFunctionCalling.from_pretrained("shawneil/NanoMind-MobileActions")
8
+ # model is ready for inference, no separate architecture file needed
9
+ """
10
+
11
+ import json, math, torch, torch.nn as nn, torch.nn.functional as F
12
+ from dataclasses import dataclass, asdict
13
+ from pathlib import Path
14
+
15
+ try:
16
+ from transformers import PreTrainedModel, PretrainedConfig
17
+ HF_AVAILABLE = True
18
+ except ImportError:
19
+ HF_AVAILABLE = False
20
+
21
+
22
+ # ── Minimal standalone architecture ──────────────────────────────────
23
+ @dataclass
24
+ class ModelConfig:
25
+ vocab_size: int = 50257
26
+ d_model: int = 512
27
+ n_heads: int = 8
28
+ n_kv_heads: int = 2
29
+ n_layers: int = 8
30
+ max_seq_len: int = 512
31
+ ff_mult: int = 4
32
+ dropout: float = 0.0
33
+ use_moe: bool = False
34
+ num_experts: int = 4
35
+ top_k_experts: int = 2
36
+
37
+ class RMSNorm(nn.Module):
38
+ def __init__(self, dim, eps=1e-6):
39
+ super().__init__(); self.eps = eps
40
+ self.weight = nn.Parameter(torch.ones(dim))
41
+ def forward(self, x):
42
+ x32 = x.float()
43
+ return (x32 * x32.pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt()
44
+ ).to(x.dtype).clone() * self.weight
45
+
46
+ def _freqs_cis(head_dim, max_len, theta=10000.0):
47
+ freqs = 1.0 / (theta ** (torch.arange(0, head_dim, 2).float() / head_dim))
48
+ t = torch.arange(max_len, device=freqs.device)
49
+ freqs = torch.outer(t, freqs)
50
+ return torch.polar(torch.ones_like(freqs), freqs)
51
+
52
+ def _rope(xq, xk, fc):
53
+ def rot(x, f):
54
+ xc = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
55
+ return torch.view_as_real(xc * f[:x.shape[1]].unsqueeze(0).unsqueeze(2)
56
+ ).flatten(3).to(x.dtype)
57
+ return rot(xq, fc), rot(xk, fc)
58
+
59
+ class GQAttn(nn.Module):
60
+ def __init__(self, cfg):
61
+ super().__init__()
62
+ self.nh = cfg.n_heads; self.nkv = cfg.n_kv_heads
63
+ self.hd = cfg.d_model // cfg.n_heads
64
+ self.q = nn.Linear(cfg.d_model, cfg.n_heads * self.hd, bias=False)
65
+ self.k = nn.Linear(cfg.d_model, cfg.n_kv_heads * self.hd, bias=False)
66
+ self.v = nn.Linear(cfg.d_model, cfg.n_kv_heads * self.hd, bias=False)
67
+ self.o = nn.Linear(cfg.n_heads * self.hd, cfg.d_model, bias=False)
68
+ self.drop = cfg.dropout
69
+ def forward(self, x, fc):
70
+ B, T, _ = x.shape
71
+ q = self.q(x).view(B, T, self.nh, self.hd)
72
+ k = self.k(x).view(B, T, self.nkv, self.hd)
73
+ v = self.v(x).view(B, T, self.nkv, self.hd)
74
+ q, k = _rope(q, k, fc)
75
+ r = self.nh // self.nkv
76
+ k = k.repeat_interleave(r, 2); v = v.repeat_interleave(r, 2)
77
+ q, k, v = q.transpose(1,2), k.transpose(1,2), v.transpose(1,2)
78
+ out = F.scaled_dot_product_attention(q, k, v, None,
79
+ self.drop if self.training else 0., is_causal=True)
80
+ return self.o(out.transpose(1,2).contiguous().view(B, T, -1))
81
+
82
+ class SwiGLU(nn.Module):
83
+ def __init__(self, cfg):
84
+ super().__init__()
85
+ h = (int(cfg.d_model * cfg.ff_mult * 2 / 3) + 63) // 64 * 64
86
+ self.w1 = nn.Linear(cfg.d_model, h, bias=False)
87
+ self.w2 = nn.Linear(h, cfg.d_model, bias=False)
88
+ self.w3 = nn.Linear(cfg.d_model, h, bias=False)
89
+ def forward(self, x): return self.w2(F.silu(self.w1(x)) * self.w3(x))
90
+
91
+ class Block(nn.Module):
92
+ def __init__(self, cfg):
93
+ super().__init__()
94
+ self.an = RMSNorm(cfg.d_model); self.fn = RMSNorm(cfg.d_model)
95
+ self.attn = GQAttn(cfg); self.ff = SwiGLU(cfg)
96
+ self.drop = nn.Dropout(cfg.dropout)
97
+ def forward(self, x, fc):
98
+ x = x + self.drop(self.attn(self.an(x), fc))
99
+ return x + self.drop(self.ff(self.fn(x)))
100
+
101
+ class _CoreModel(nn.Module):
102
+ def __init__(self, cfg):
103
+ super().__init__()
104
+ self.cfg = cfg
105
+ self.embed = nn.Embedding(cfg.vocab_size, cfg.d_model)
106
+ self.drop = nn.Dropout(cfg.dropout)
107
+ self.blocks = nn.ModuleList([Block(cfg) for _ in range(cfg.n_layers)])
108
+ self.norm = RMSNorm(cfg.d_model)
109
+ self.lm_head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
110
+ self.embed.weight = self.lm_head.weight
111
+ self.register_buffer("freqs_cis",
112
+ _freqs_cis(cfg.d_model // cfg.n_heads, cfg.max_seq_len * 2))
113
+ def forward(self, idx, targets=None, loss_mask=None):
114
+ B, T = idx.shape
115
+ x = self.drop(self.embed(idx))
116
+ fc = self.freqs_cis[:T]
117
+ for blk in self.blocks: x = blk(x, fc)
118
+ logits = self.lm_head(self.norm(x))
119
+ loss = None
120
+ if targets is not None:
121
+ fl = logits.view(-1, logits.size(-1))
122
+ ft = targets.view(-1)
123
+ if loss_mask is not None:
124
+ m = loss_mask.view(-1).bool()
125
+ fl = fl[m]; ft = ft[m]
126
+ loss = F.cross_entropy(fl, ft, ignore_index=-1)
127
+ return logits, loss
128
+ @torch.no_grad()
129
+ def generate(self, idx, max_new_tokens=200, temperature=0.8, top_k=50):
130
+ for _ in range(max_new_tokens):
131
+ ic = idx[:, -self.cfg.max_seq_len:]
132
+ logits, _ = self(ic)
133
+ logits = logits[:, -1, :] / temperature
134
+ v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
135
+ logits[logits < v[:, [-1]]] = float("-inf")
136
+ idx = torch.cat([idx, torch.multinomial(F.softmax(logits,-1), 1)], 1)
137
+ return idx
138
+
139
+
140
+ # ── HF-compatible wrapper ─────────────────────────────────────────────
141
+ if HF_AVAILABLE:
142
+ class NanoMindConfig(PretrainedConfig):
143
+ model_type = "nanomind"
144
+ def __init__(self, vocab_size=50257, d_model=512, n_heads=8,
145
+ n_kv_heads=2, n_layers=8, max_seq_len=512,
146
+ ff_mult=4, dropout=0.0, **kwargs):
147
+ super().__init__(**kwargs)
148
+ self.vocab_size = vocab_size
149
+ self.d_model = d_model
150
+ self.n_heads = n_heads
151
+ self.n_kv_heads = n_kv_heads
152
+ self.n_layers = n_layers
153
+ self.max_seq_len = max_seq_len
154
+ self.ff_mult = ff_mult
155
+ self.dropout = dropout
156
+
157
+ class NanoMindForFunctionCalling(PreTrainedModel):
158
+ config_class = NanoMindConfig
159
+
160
+ def __init__(self, config: NanoMindConfig):
161
+ super().__init__(config)
162
+ cfg = ModelConfig(
163
+ vocab_size=config.vocab_size, d_model=config.d_model,
164
+ n_heads=config.n_heads, n_kv_heads=config.n_kv_heads,
165
+ n_layers=config.n_layers, max_seq_len=config.max_seq_len,
166
+ ff_mult=config.ff_mult, dropout=config.dropout,
167
+ )
168
+ self.model = _CoreModel(cfg)
169
+ self.post_init()
170
+
171
+ def forward(self, input_ids, labels=None, loss_mask=None, **kwargs):
172
+ logits, loss = self.model(input_ids, labels, loss_mask)
173
+ from transformers.modeling_outputs import CausalLMOutput
174
+ return CausalLMOutput(loss=loss, logits=logits)
175
+
176
+ @torch.no_grad()
177
+ def generate_text(self, idx, max_new_tokens=200, temperature=0.8, top_k=50):
178
+ return self.model.generate(idx, max_new_tokens, temperature, top_k)
179
+
180
+ @classmethod
181
+ def from_nanomind_checkpoint(cls, ckpt_path: str):
182
+ """Load from a raw NanoMind .pt checkpoint (no HF config needed)."""
183
+ ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False)
184
+ raw_cfg = ckpt.get("config", {})
185
+ hf_cfg = NanoMindConfig(
186
+ vocab_size = raw_cfg.get("vocab_size", 50257),
187
+ d_model = raw_cfg.get("d_model", 512),
188
+ n_heads = raw_cfg.get("n_heads", 8),
189
+ n_kv_heads = raw_cfg.get("n_kv_heads", 2),
190
+ n_layers = raw_cfg.get("n_layers", 8),
191
+ max_seq_len = raw_cfg.get("max_seq_len", 512),
192
+ ff_mult = raw_cfg.get("ff_mult", 4),
193
+ dropout = raw_cfg.get("dropout", 0.0),
194
+ )
195
+ wrapper = cls(hf_cfg)
196
+ # remap keys: model.xxx β†’ model.xxx (already correct)
197
+ state = ckpt["model"]
198
+ # if saved without wrapper prefix, add it
199
+ if not any(k.startswith("model.") for k in state):
200
+ state = {"model." + k: v for k, v in state.items()}
201
+ wrapper.load_state_dict(state, strict=True)
202
+ return wrapper
203
+
204
+ else:
205
+ # Fallback when transformers not installed
206
+ class NanoMindForFunctionCalling(nn.Module):
207
+ def __init__(self, cfg: ModelConfig):
208
+ super().__init__()
209
+ self.model = _CoreModel(cfg)
210
+
211
+ @classmethod
212
+ def from_checkpoint(cls, ckpt_path):
213
+ ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False)
214
+ raw_cfg = ckpt.get("config", {})
215
+ cfg = ModelConfig(**{k: v for k, v in raw_cfg.items()
216
+ if k in ModelConfig.__dataclass_fields__})
217
+ obj = cls(cfg)
218
+ state = ckpt["model"]
219
+ if not any(k.startswith("model.") for k in state):
220
+ state = {"model." + k: v for k, v in state.items()}
221
+ obj.load_state_dict(state, strict=True)
222
+ return obj
223
+
224
+ def forward(self, idx, targets=None, loss_mask=None):
225
+ return self.model(idx, targets, loss_mask)
226
+
227
+ def generate_text(self, idx, **kw):
228
+ return self.model.generate(idx, **kw)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:306542b1cd779ac09cb716c291fb89011f27a229cb68919e371a8a03009e6582
3
+ size 296596728