Ideon commited on
Commit
7bbaa17
·
verified ·
1 Parent(s): ae20233

Upload 3 files

Browse files
Files changed (2) hide show
  1. model_adapters.py +119 -4
  2. requirements.txt +3 -0
model_adapters.py CHANGED
@@ -1,5 +1,6 @@
1
  import json
2
  import os
 
3
  from typing import Any, Dict, List
4
 
5
 
@@ -105,12 +106,126 @@ class TransformersAdapter:
105
 
106
  class NanochatAdapter:
107
  def __init__(self, model_id: str):
108
- raise RuntimeError(
109
- "RFAB_HISTORIC_ADAPTER=nanochat is a scaffold. Copy the nanochat runtime "
110
- "from tventurella/mr_chatterbox into this Space and replace NanochatAdapter "
111
- "with that loader before setting DRY_RUN=false."
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  )
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  def parse_json_env(name: str, default):
116
  raw = os.getenv(name)
 
1
  import json
2
  import os
3
+ import glob
4
  from typing import Any, Dict, List
5
 
6
 
 
106
 
107
  class NanochatAdapter:
108
  def __init__(self, model_id: str):
109
+ self.model_id = model_id
110
+ self.model = None
111
+ self.tokenizer = None
112
+ self.device = None
113
+
114
+ def _ensure_loaded(self):
115
+ if self.model is not None and self.tokenizer is not None:
116
+ return
117
+
118
+ import torch
119
+ from huggingface_hub import snapshot_download
120
+ from nanochat.gpt import GPT, GPTConfig
121
+ from nanochat.tokenizer import RustBPETokenizer
122
+
123
+ local_dir = snapshot_download(
124
+ repo_id=self.model_id,
125
+ token=os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_HUB_TOKEN"),
126
  )
127
 
128
+ tokenizer_dir = os.getenv("RFAB_NANOCHAT_TOKENIZER_DIR", "tokenizer")
129
+ tokenizer_path = os.path.join(local_dir, tokenizer_dir)
130
+ self.tokenizer = RustBPETokenizer.from_directory(tokenizer_path)
131
+
132
+ meta_file = os.getenv("RFAB_NANOCHAT_META_FILE") or first_match(local_dir, "meta_*.json")
133
+ model_file = os.getenv("RFAB_NANOCHAT_MODEL_FILE") or first_match(local_dir, "model_*.pt")
134
+ meta_path = file_in_snapshot(local_dir, meta_file)
135
+ model_path = file_in_snapshot(local_dir, model_file)
136
+
137
+ with open(meta_path, "r", encoding="utf-8") as f:
138
+ meta = json.load(f)
139
+
140
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
141
+ config = GPTConfig(**meta["model_config"])
142
+ with torch.device("meta"):
143
+ model = GPT(config)
144
+
145
+ model.to_empty(device=self.device)
146
+ model.init_weights()
147
+ state_dict = torch.load(model_path, map_location=self.device)
148
+ state_dict = {k.removeprefix("_orig_mod."): v for k, v in state_dict.items()}
149
+ model.load_state_dict(state_dict, strict=True, assign=True)
150
+ model.eval()
151
+ self.model = model
152
+
153
+ def generate(self, history, system_prompt, temperature, max_tokens, top_p, top_k):
154
+ import torch
155
+
156
+ self._ensure_loaded()
157
+
158
+ bos = self.tokenizer.get_bos_token_id()
159
+ user_start = self.tokenizer.encode_special("<|user_start|>")
160
+ user_end = self.tokenizer.encode_special("<|user_end|>")
161
+ assistant_start = self.tokenizer.encode_special("<|assistant_start|>")
162
+ assistant_end = self.tokenizer.encode_special("<|assistant_end|>")
163
+
164
+ tokens = [bos]
165
+ if system_prompt:
166
+ tokens += [user_start]
167
+ tokens += self.tokenizer.encode(system_prompt.strip())
168
+ tokens += [user_end, assistant_start, assistant_end]
169
+
170
+ for message in history or []:
171
+ role = message.get("role")
172
+ text = extract_text(message)
173
+ if not text:
174
+ continue
175
+ if role == "user":
176
+ tokens += [user_start]
177
+ tokens += self.tokenizer.encode(text)
178
+ tokens += [user_end]
179
+ elif role == "assistant":
180
+ tokens += [assistant_start]
181
+ tokens += self.tokenizer.encode(text)
182
+ tokens += [assistant_end]
183
+
184
+ tokens += [assistant_start]
185
+ generated = []
186
+
187
+ generate_kwargs = {
188
+ "max_tokens": int(max_tokens),
189
+ "temperature": float(temperature),
190
+ }
191
+ if int(top_k) > 0:
192
+ generate_kwargs["top_k"] = int(top_k)
193
+
194
+ if self.device == "cuda":
195
+ context = torch.amp.autocast(device_type="cuda", dtype=torch.bfloat16)
196
+ else:
197
+ context = nullcontext()
198
+
199
+ with torch.no_grad(), context:
200
+ for token in self.model.generate(tokens, **generate_kwargs):
201
+ if token in (assistant_end, bos):
202
+ break
203
+ generated.append(token)
204
+
205
+ return self.tokenizer.decode(generated).strip()
206
+
207
+
208
+ class nullcontext:
209
+ def __enter__(self):
210
+ return None
211
+
212
+ def __exit__(self, exc_type, exc, tb):
213
+ return False
214
+
215
+
216
+ def first_match(root, pattern):
217
+ matches = sorted(glob.glob(os.path.join(root, pattern)))
218
+ if not matches:
219
+ raise FileNotFoundError(f"No file matching {pattern} in {root}")
220
+ return os.path.basename(matches[0])
221
+
222
+
223
+ def file_in_snapshot(root, filename):
224
+ path = filename if os.path.isabs(filename) else os.path.join(root, filename)
225
+ if not os.path.exists(path):
226
+ raise FileNotFoundError(f"File not found in snapshot: {filename}")
227
+ return path
228
+
229
 
230
  def parse_json_env(name: str, default):
231
  raw = os.getenv(name)
requirements.txt CHANGED
@@ -5,3 +5,6 @@ torch
5
  sentencepiece
6
  huggingface_hub
7
  spaces
 
 
 
 
5
  sentencepiece
6
  huggingface_hub
7
  spaces
8
+ tiktoken
9
+ rustbpe
10
+ nanochat @ git+https://github.com/karpathy/nanochat.git