Upload folder using huggingface_hub
Browse files- README.md +20 -3
- config.json +23 -0
- configuration_guppylm.py +90 -0
- decode_input_ids.py +17 -0
- inference.py +124 -0
- modeling_guppylm.py +268 -0
- pytorch_model.bin +3 -0
- test.py +106 -0
- tokenizer.json +0 -0
- tokenizer_config.json +3 -0
README.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
This is a refactor of [arman-bd/guppylm-9M](https://huggingface.co/arman-bd/guppylm-9M) to be compliant with [transformers's custom_model](https://huggingface.co/docs/transformers/custom_models).
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
python inference.py guppylm-9M
|
| 7 |
+
|
| 8 |
+
GuppyLMForCausalLM loaded: 8.7M params
|
| 9 |
+
|
| 10 |
+
Guppy Chat (type 'quit' to exit)
|
| 11 |
+
|
| 12 |
+
You> is there a cat in the room?
|
| 13 |
+
Guppy> i don't like it. it puts its face on the glass by the bubbles.
|
| 14 |
+
|
| 15 |
+
You> I'm sorry. are you hungry?
|
| 16 |
+
Guppy> i don't eat it.
|
| 17 |
+
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
This is a good small LLM for doing execise with SGLang.
|
config.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "guppylm",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"GuppyLMForCausalLM"
|
| 5 |
+
],
|
| 6 |
+
"auto_map": {
|
| 7 |
+
"AutoConfig": "configuration_guppylm.GuppyLMConfig",
|
| 8 |
+
"AutoModel": "modeling_guppylm.GuppyLMModel",
|
| 9 |
+
"AutoModelForCausalLM": "modeling_guppylm.GuppyLMForCausalLM"
|
| 10 |
+
},
|
| 11 |
+
"vocab_size": 4096,
|
| 12 |
+
"max_position_embeddings": 128,
|
| 13 |
+
"hidden_size": 384,
|
| 14 |
+
"num_hidden_layers": 6,
|
| 15 |
+
"num_attention_heads": 6,
|
| 16 |
+
"num_key_value_heads": 6,
|
| 17 |
+
"intermediate_size": 768,
|
| 18 |
+
"hidden_dropout_prob": 0.1,
|
| 19 |
+
"pad_token_id": 0,
|
| 20 |
+
"bos_token_id": 1,
|
| 21 |
+
"eos_token_id": 2,
|
| 22 |
+
"tokenizer_class": "PreTrainedTokenizerFast"
|
| 23 |
+
}
|
configuration_guppylm.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GuppyLM Hugging Face configuration (PreTrainedConfig)."""
|
| 2 |
+
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
|
| 5 |
+
from transformers import PretrainedConfig
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class GuppyLMConfig(PretrainedConfig):
|
| 9 |
+
"""Configuration for GuppyLM.
|
| 10 |
+
|
| 11 |
+
Uses Transformers-standard field names in JSON; modeling code may also read
|
| 12 |
+
aliases via properties (``d_model``, ``n_heads``, etc.).
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
model_type = "guppylm"
|
| 16 |
+
|
| 17 |
+
def __init__(
|
| 18 |
+
self,
|
| 19 |
+
vocab_size: int = 4096,
|
| 20 |
+
max_position_embeddings: int = 128,
|
| 21 |
+
hidden_size: int = 384,
|
| 22 |
+
num_hidden_layers: int = 6,
|
| 23 |
+
num_attention_heads: int = 6,
|
| 24 |
+
intermediate_size: int = 768,
|
| 25 |
+
hidden_dropout_prob: float = 0.1,
|
| 26 |
+
pad_token_id: int = 0,
|
| 27 |
+
bos_token_id: int = 1,
|
| 28 |
+
eos_token_id: int = 2,
|
| 29 |
+
architectures: Optional[List[str]] = None,
|
| 30 |
+
tokenizer_class: Optional[str] = "PreTrainedTokenizerFast",
|
| 31 |
+
attn_implementation: Optional[str] = "eager",
|
| 32 |
+
**kwargs,
|
| 33 |
+
):
|
| 34 |
+
self.vocab_size = vocab_size
|
| 35 |
+
self.max_position_embeddings = max_position_embeddings
|
| 36 |
+
self.hidden_size = hidden_size
|
| 37 |
+
self.num_hidden_layers = num_hidden_layers
|
| 38 |
+
self.num_attention_heads = num_attention_heads
|
| 39 |
+
self.intermediate_size = intermediate_size
|
| 40 |
+
self.hidden_dropout_prob = hidden_dropout_prob
|
| 41 |
+
if architectures is None:
|
| 42 |
+
architectures = ["GuppyLMForCausalLM"]
|
| 43 |
+
self.architectures = architectures
|
| 44 |
+
super().__init__(
|
| 45 |
+
architectures=architectures,
|
| 46 |
+
pad_token_id=pad_token_id,
|
| 47 |
+
bos_token_id=bos_token_id,
|
| 48 |
+
eos_token_id=eos_token_id,
|
| 49 |
+
tokenizer_class=tokenizer_class,
|
| 50 |
+
tie_word_embeddings=True,
|
| 51 |
+
**kwargs,
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
# --- Aliases for original GuppyLM / training code naming ---
|
| 55 |
+
|
| 56 |
+
@property
|
| 57 |
+
def max_seq_len(self) -> int:
|
| 58 |
+
return self.max_position_embeddings
|
| 59 |
+
|
| 60 |
+
@property
|
| 61 |
+
def d_model(self) -> int:
|
| 62 |
+
return self.hidden_size
|
| 63 |
+
|
| 64 |
+
@property
|
| 65 |
+
def n_layers(self) -> int:
|
| 66 |
+
return self.num_hidden_layers
|
| 67 |
+
|
| 68 |
+
@property
|
| 69 |
+
def n_heads(self) -> int:
|
| 70 |
+
return self.num_attention_heads
|
| 71 |
+
|
| 72 |
+
@property
|
| 73 |
+
def ffn_hidden(self) -> int:
|
| 74 |
+
return self.intermediate_size
|
| 75 |
+
|
| 76 |
+
@property
|
| 77 |
+
def dropout(self) -> float:
|
| 78 |
+
return self.hidden_dropout_prob
|
| 79 |
+
|
| 80 |
+
@property
|
| 81 |
+
def pad_id(self) -> int:
|
| 82 |
+
return self.pad_token_id
|
| 83 |
+
|
| 84 |
+
@property
|
| 85 |
+
def bos_id(self) -> int:
|
| 86 |
+
return self.bos_token_id
|
| 87 |
+
|
| 88 |
+
@property
|
| 89 |
+
def eos_id(self) -> int:
|
| 90 |
+
return self.eos_token_id
|
decode_input_ids.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import sys
|
| 3 |
+
import torch
|
| 4 |
+
from transformers import AutoTokenizer
|
| 5 |
+
|
| 6 |
+
input_ids=[32, 720, 103, 432, 146, 167, 1400, 195, 294, 1083, 111]
|
| 7 |
+
input_ids=[1, 88, 64, 779, 2, 64, 1, 89, 64]
|
| 8 |
+
model = "/sgl-workspace/guppylm-9M"
|
| 9 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 10 |
+
model,
|
| 11 |
+
trust_remote_code=True,
|
| 12 |
+
)
|
| 13 |
+
text = tokenizer.decode(
|
| 14 |
+
torch.tensor(input_ids, dtype=torch.long),
|
| 15 |
+
skip_special_tokens=False,
|
| 16 |
+
)
|
| 17 |
+
sys.stdout.write(text)
|
inference.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GuppyLM inference — simple chat."""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, PreTrainedTokenizerFast
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _resolve_pretrained_dir(model_dir: str) -> str:
|
| 10 |
+
"""Directory with Hugging Face layout: ``config.json`` + ``pytorch_model.bin`` / ``model.safetensors``.
|
| 11 |
+
|
| 12 |
+
Accepts either that directory or the path to a weight file inside it (e.g. ``pytorch_model.bin``).
|
| 13 |
+
"""
|
| 14 |
+
path = os.path.abspath(model_dir)
|
| 15 |
+
return path if os.path.isdir(path) else os.path.dirname(path)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _resolve_tokenizer_dir(tokenizer_path: str) -> tuple[str, str]:
|
| 19 |
+
"""Return ``(directory, tokenizer_filename)`` for HF tokenizer loading."""
|
| 20 |
+
path = os.path.abspath(tokenizer_path)
|
| 21 |
+
if os.path.isdir(path):
|
| 22 |
+
return path, "tokenizer.json"
|
| 23 |
+
return os.path.dirname(path), os.path.basename(path)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _load_autotokenizer(tokenizer_path: str, *, trust_remote_code: bool = True):
|
| 27 |
+
"""Load ``AutoTokenizer`` when ``config.json`` is present (declares ``tokenizer_class``); otherwise fast tokenizer only."""
|
| 28 |
+
tok_dir, tokenizer_file = _resolve_tokenizer_dir(tokenizer_path)
|
| 29 |
+
if os.path.isfile(os.path.join(tok_dir, "config.json")):
|
| 30 |
+
return AutoTokenizer.from_pretrained(tok_dir, trust_remote_code=trust_remote_code)
|
| 31 |
+
return PreTrainedTokenizerFast.from_pretrained(tok_dir, tokenizer_file=tokenizer_file)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class GuppyInference:
|
| 35 |
+
def __init__(self, model_dir, device="cpu", trust_remote_code=True):
|
| 36 |
+
self.device = torch.device(device)
|
| 37 |
+
pretrained_dir = _resolve_pretrained_dir(model_dir)
|
| 38 |
+
self.tokenizer = _load_autotokenizer(pretrained_dir, trust_remote_code=trust_remote_code)
|
| 39 |
+
|
| 40 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 41 |
+
pretrained_dir,
|
| 42 |
+
trust_remote_code=trust_remote_code,
|
| 43 |
+
)
|
| 44 |
+
self.model.to(self.device)
|
| 45 |
+
self.model.eval()
|
| 46 |
+
self.config = self.model.config
|
| 47 |
+
|
| 48 |
+
total, _ = self.model.param_count()
|
| 49 |
+
print(f"{self.model.__class__.__name__} loaded: {total/1e6:.1f}M params")
|
| 50 |
+
|
| 51 |
+
def chat_completion(self, messages, temperature=0.001, max_tokens=64,
|
| 52 |
+
top_k=0, **kwargs):
|
| 53 |
+
"""Chat completion — takes messages, returns response."""
|
| 54 |
+
prompt = self._render_chat_prompt(messages)
|
| 55 |
+
input_ids = self.tokenizer.encode(prompt, add_special_tokens=False)
|
| 56 |
+
prompt_tokens = len(input_ids)
|
| 57 |
+
input_t = torch.tensor([input_ids], dtype=torch.long, device=self.device)
|
| 58 |
+
|
| 59 |
+
output_t, _ = self.model.generate_simple(
|
| 60 |
+
input_t, max_new_tokens=max_tokens, temperature=temperature, top_k=top_k
|
| 61 |
+
)
|
| 62 |
+
output_text = self.tokenizer.decode(
|
| 63 |
+
output_t[0].tolist()[prompt_tokens:], skip_special_tokens=False
|
| 64 |
+
)
|
| 65 |
+
# Truncate at first <|im_end|> — don't let the model leak into the next turn
|
| 66 |
+
if "<|im_end|>" in output_text:
|
| 67 |
+
output_text = output_text.split("<|im_end|>")[0]
|
| 68 |
+
# Also strip any <|im_start|> fragments
|
| 69 |
+
if "<|im_start|>" in output_text:
|
| 70 |
+
output_text = output_text.split("<|im_start|>")[0]
|
| 71 |
+
resp_text = output_text.strip()
|
| 72 |
+
|
| 73 |
+
return {
|
| 74 |
+
"choices": [{
|
| 75 |
+
"message": {"role": "assistant", "content": resp_text},
|
| 76 |
+
}],
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
def _render_chat_prompt(self, messages):
|
| 80 |
+
"""Prefer tokenizer ``chat_template`` (HF ``apply_chat_template``); fall back if absent."""
|
| 81 |
+
if getattr(self.tokenizer, "chat_template", None):
|
| 82 |
+
return self.tokenizer.apply_chat_template(
|
| 83 |
+
messages,
|
| 84 |
+
tokenize=False,
|
| 85 |
+
add_generation_prompt=True,
|
| 86 |
+
)
|
| 87 |
+
parts = []
|
| 88 |
+
for msg in messages:
|
| 89 |
+
role = msg.get("role", "user")
|
| 90 |
+
content = msg.get("content") or ""
|
| 91 |
+
if role == "system":
|
| 92 |
+
continue
|
| 93 |
+
parts.append(f"<|im_start|>{role}\n{content}<|im_end|>")
|
| 94 |
+
parts.append("<|im_start|>assistant\n")
|
| 95 |
+
return "\n".join(parts)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def main():
|
| 99 |
+
import argparse
|
| 100 |
+
p = argparse.ArgumentParser(description="Chat with Guppy")
|
| 101 |
+
p.add_argument(
|
| 102 |
+
"model_dir",
|
| 103 |
+
help="Directory with config.json, tokenizer.json, and weights (or path to a weight file in that directory)",
|
| 104 |
+
)
|
| 105 |
+
p.add_argument("--device", default="cpu")
|
| 106 |
+
args = p.parse_args()
|
| 107 |
+
|
| 108 |
+
engine = GuppyInference(args.model_dir, args.device)
|
| 109 |
+
print("\nGuppy Chat (type 'quit' to exit)")
|
| 110 |
+
msgs = []
|
| 111 |
+
while True:
|
| 112 |
+
inp = input("\nYou> ").strip()
|
| 113 |
+
if inp.lower() in ("quit", "exit", "q"):
|
| 114 |
+
break
|
| 115 |
+
msgs.append({"role": "user", "content": inp})
|
| 116 |
+
result = engine.chat_completion(msgs)
|
| 117 |
+
msg = result["choices"][0]["message"]
|
| 118 |
+
if msg.get("content"):
|
| 119 |
+
print(f"Guppy> {msg['content']}")
|
| 120 |
+
msgs.append(msg)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
if __name__ == "__main__":
|
| 124 |
+
main()
|
modeling_guppylm.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
GuppyLM — Hugging Face PreTrainedModel wrapper.
|
| 3 |
+
|
| 4 |
+
Vanilla transformer: multi-head attention, ReLU FFN, LayerNorm, learned positional embeddings.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import math
|
| 8 |
+
from typing import Optional, Tuple, Union
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
import torch.nn.functional as F
|
| 13 |
+
from transformers import PreTrainedModel
|
| 14 |
+
from transformers.generation.utils import GenerationMixin
|
| 15 |
+
from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
|
| 16 |
+
import time
|
| 17 |
+
|
| 18 |
+
try:
|
| 19 |
+
from .configuration_guppylm import GuppyLMConfig
|
| 20 |
+
except ImportError:
|
| 21 |
+
from configuration_guppylm import GuppyLMConfig
|
| 22 |
+
|
| 23 |
+
class Attention(nn.Module):
|
| 24 |
+
def __init__(self, config: GuppyLMConfig, layer_id: int):
|
| 25 |
+
super().__init__()
|
| 26 |
+
self.n_heads = config.n_heads
|
| 27 |
+
self.head_dim = config.d_model // config.n_heads
|
| 28 |
+
self.layer_id = layer_id
|
| 29 |
+
self.qkv = nn.Linear(config.d_model, 3 * config.d_model)
|
| 30 |
+
self.out = nn.Linear(config.d_model, config.d_model)
|
| 31 |
+
self.dropout = nn.Dropout(config.dropout)
|
| 32 |
+
|
| 33 |
+
def forward(self, x, mask=None, forward_batch=None, attention_instances=None):
|
| 34 |
+
B, T, C = x.shape
|
| 35 |
+
|
| 36 |
+
if attention_instances is not None:
|
| 37 |
+
qkv = self.qkv(x).reshape(B, T, 3, self.n_heads*self.head_dim).permute(2, 0, 1, 3)
|
| 38 |
+
q, k, v = qkv[0], qkv[1], qkv[2]
|
| 39 |
+
attn_out = attention_instances[self.layer_id].forward(q, k, v, forward_batch)
|
| 40 |
+
return self.out(attn_out).contiguous().view(B, T, C)
|
| 41 |
+
else:
|
| 42 |
+
qkv = self.qkv(x).reshape(B, T, 3, self.n_heads, self.head_dim).permute(2, 0, 3, 1, 4)
|
| 43 |
+
q, k, v = qkv[0], qkv[1], qkv[2]
|
| 44 |
+
attn = (q @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)
|
| 45 |
+
if mask is not None:
|
| 46 |
+
attn = attn.masked_fill(mask == 0, float("-inf"))
|
| 47 |
+
attn = self.dropout(F.softmax(attn, dim=-1))
|
| 48 |
+
|
| 49 |
+
return self.out((attn @ v).transpose(1, 2).contiguous().view(B, T, C))
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class FFN(nn.Module):
|
| 53 |
+
def __init__(self, config: GuppyLMConfig):
|
| 54 |
+
super().__init__()
|
| 55 |
+
self.up = nn.Linear(config.d_model, config.ffn_hidden)
|
| 56 |
+
self.down = nn.Linear(config.ffn_hidden, config.d_model)
|
| 57 |
+
self.dropout = nn.Dropout(config.dropout)
|
| 58 |
+
|
| 59 |
+
def forward(self, x):
|
| 60 |
+
return self.dropout(self.down(F.relu(self.up(x))))
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class Block(nn.Module):
|
| 64 |
+
def __init__(self, config: GuppyLMConfig, layer_id: int):
|
| 65 |
+
super().__init__()
|
| 66 |
+
self.layer_id = layer_id
|
| 67 |
+
self.norm1 = nn.LayerNorm(config.d_model)
|
| 68 |
+
self.attn = Attention(config, layer_id)
|
| 69 |
+
self.norm2 = nn.LayerNorm(config.d_model)
|
| 70 |
+
self.ffn = FFN(config)
|
| 71 |
+
|
| 72 |
+
def forward(self, x, mask=None, forward_batch=None, attention_instances=None):
|
| 73 |
+
x = x + self.attn(self.norm1(x), mask, forward_batch, attention_instances)
|
| 74 |
+
x = x + self.ffn(self.norm2(x))
|
| 75 |
+
return x
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class GuppyLMModel(PreTrainedModel):
|
| 79 |
+
"""Backbone only (hidden states). Used as ``AutoModel`` for SGLang / Transformers tooling."""
|
| 80 |
+
|
| 81 |
+
config_class = GuppyLMConfig
|
| 82 |
+
base_model_prefix = "model"
|
| 83 |
+
supports_gradient_checkpointing = False
|
| 84 |
+
_supports_attention_backend = True
|
| 85 |
+
|
| 86 |
+
def __init__(self, config: GuppyLMConfig):
|
| 87 |
+
super().__init__(config)
|
| 88 |
+
|
| 89 |
+
# assert 0
|
| 90 |
+
self.tok_emb = nn.Embedding(config.vocab_size, config.d_model)
|
| 91 |
+
self.pos_emb = nn.Embedding(config.max_seq_len, config.d_model)
|
| 92 |
+
self.drop = nn.Dropout(config.dropout)
|
| 93 |
+
self.blocks = nn.ModuleList([Block(config, i) for i in range(config.n_layers)])
|
| 94 |
+
self.norm = nn.LayerNorm(config.d_model)
|
| 95 |
+
self.apply(self._init_weights)
|
| 96 |
+
self.post_init()
|
| 97 |
+
self.input_ids = None
|
| 98 |
+
|
| 99 |
+
def _init_weights(self, m):
|
| 100 |
+
if isinstance(m, nn.Linear):
|
| 101 |
+
nn.init.normal_(m.weight, mean=0.0, std=0.02)
|
| 102 |
+
if m.bias is not None:
|
| 103 |
+
nn.init.zeros_(m.bias)
|
| 104 |
+
elif isinstance(m, nn.Embedding):
|
| 105 |
+
nn.init.normal_(m.weight, mean=0.0, std=0.02)
|
| 106 |
+
|
| 107 |
+
def get_input_embeddings(self):
|
| 108 |
+
return self.tok_emb
|
| 109 |
+
|
| 110 |
+
def set_input_embeddings(self, value: nn.Module):
|
| 111 |
+
self.tok_emb = value
|
| 112 |
+
|
| 113 |
+
def forward(
|
| 114 |
+
self,
|
| 115 |
+
input_ids: Optional[torch.LongTensor] = None,
|
| 116 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 117 |
+
forward_batch=None,
|
| 118 |
+
attention_instances=None,
|
| 119 |
+
return_dict: Optional[bool] = None,
|
| 120 |
+
**kwargs,
|
| 121 |
+
) -> Union[Tuple, BaseModelOutputWithPast]:
|
| 122 |
+
|
| 123 |
+
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
| 124 |
+
if input_ids is None:
|
| 125 |
+
raise ValueError("You must specify `input_ids`.")
|
| 126 |
+
|
| 127 |
+
if attention_instances is None:
|
| 128 |
+
B, T = input_ids.shape
|
| 129 |
+
pos = torch.arange(T, device=input_ids.device)
|
| 130 |
+
x = self.drop(self.tok_emb(input_ids) + self.pos_emb(pos))
|
| 131 |
+
mask = torch.tril(torch.ones(T, T, device=input_ids.device)).unsqueeze(0).unsqueeze(0)
|
| 132 |
+
else:
|
| 133 |
+
pos = forward_batch.positions
|
| 134 |
+
x = self.drop(self.tok_emb(input_ids) + self.pos_emb(pos))
|
| 135 |
+
mask = None
|
| 136 |
+
|
| 137 |
+
for block in self.blocks:
|
| 138 |
+
x = block(x, mask, forward_batch, attention_instances)
|
| 139 |
+
|
| 140 |
+
hidden_states = self.norm(x)
|
| 141 |
+
|
| 142 |
+
if not return_dict:
|
| 143 |
+
return (hidden_states,)
|
| 144 |
+
|
| 145 |
+
return BaseModelOutputWithPast(
|
| 146 |
+
last_hidden_state=hidden_states,
|
| 147 |
+
past_key_values=None,
|
| 148 |
+
hidden_states=None,
|
| 149 |
+
attentions=None,
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
class GuppyLMForCausalLM(PreTrainedModel, GenerationMixin):
|
| 154 |
+
"""Causal LM. Checkpoints may use flat keys (legacy) or nested ``model.*`` keys."""
|
| 155 |
+
|
| 156 |
+
config_class = GuppyLMConfig
|
| 157 |
+
base_model_prefix = "model"
|
| 158 |
+
supports_gradient_checkpointing = False
|
| 159 |
+
_supports_attention_backend = True
|
| 160 |
+
|
| 161 |
+
def __init__(self, config: GuppyLMConfig):
|
| 162 |
+
super().__init__(config)
|
| 163 |
+
|
| 164 |
+
self.model = GuppyLMModel(config)
|
| 165 |
+
self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
|
| 166 |
+
nn.init.normal_(self.lm_head.weight, mean=0.0, std=0.02)
|
| 167 |
+
self.post_init()
|
| 168 |
+
self.lm_head.weight = self.model.tok_emb.weight
|
| 169 |
+
|
| 170 |
+
def get_input_embeddings(self):
|
| 171 |
+
return self.model.tok_emb
|
| 172 |
+
|
| 173 |
+
def set_input_embeddings(self, value: nn.Module):
|
| 174 |
+
self.model.tok_emb = value
|
| 175 |
+
|
| 176 |
+
def get_output_embeddings(self):
|
| 177 |
+
return self.lm_head
|
| 178 |
+
|
| 179 |
+
def set_output_embeddings(self, new_embeddings: nn.Module):
|
| 180 |
+
self.lm_head = new_embeddings
|
| 181 |
+
|
| 182 |
+
def prepare_inputs_for_generation(
|
| 183 |
+
self, input_ids, past_key_values=None, attention_mask=None, **kwargs
|
| 184 |
+
):
|
| 185 |
+
return {"input_ids": input_ids}
|
| 186 |
+
|
| 187 |
+
def load_state_dict(
|
| 188 |
+
self,
|
| 189 |
+
state_dict: dict,
|
| 190 |
+
strict: bool = True,
|
| 191 |
+
assign: bool = False,
|
| 192 |
+
):
|
| 193 |
+
"""Accept legacy flat checkpoints (``tok_emb.*``, ``blocks.*``, …) as well as nested ``model.*``."""
|
| 194 |
+
keys = list(state_dict.keys())
|
| 195 |
+
if keys and not any(k.startswith("model.") for k in keys):
|
| 196 |
+
remapped = {}
|
| 197 |
+
for k, v in state_dict.items():
|
| 198 |
+
if k.startswith("lm_head."):
|
| 199 |
+
remapped[k] = v
|
| 200 |
+
else:
|
| 201 |
+
remapped[f"model.{k}"] = v
|
| 202 |
+
state_dict = remapped
|
| 203 |
+
return super().load_state_dict(state_dict, strict=strict, assign=assign)
|
| 204 |
+
|
| 205 |
+
def forward(
|
| 206 |
+
self,
|
| 207 |
+
input_ids: Optional[torch.LongTensor] = None,
|
| 208 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 209 |
+
labels: Optional[torch.LongTensor] = None,
|
| 210 |
+
return_dict: Optional[bool] = None,
|
| 211 |
+
**kwargs,
|
| 212 |
+
) -> Union[Tuple, CausalLMOutputWithPast]:
|
| 213 |
+
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
| 214 |
+
if input_ids is None:
|
| 215 |
+
raise ValueError("You must specify `input_ids`.")
|
| 216 |
+
|
| 217 |
+
outputs = self.model(input_ids=input_ids, return_dict=True, **kwargs)
|
| 218 |
+
hidden_states = outputs.last_hidden_state
|
| 219 |
+
logits = self.lm_head(hidden_states)
|
| 220 |
+
|
| 221 |
+
loss = None
|
| 222 |
+
if labels is not None:
|
| 223 |
+
loss = F.cross_entropy(
|
| 224 |
+
logits.view(-1, self.config.vocab_size),
|
| 225 |
+
labels.view(-1),
|
| 226 |
+
ignore_index=self.config.pad_token_id,
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
if not return_dict:
|
| 230 |
+
return (loss, logits) if loss is not None else (logits,)
|
| 231 |
+
|
| 232 |
+
return CausalLMOutputWithPast(loss=loss, logits=logits, past_key_values=None)
|
| 233 |
+
|
| 234 |
+
@torch.no_grad()
|
| 235 |
+
def generate_simple(
|
| 236 |
+
self,
|
| 237 |
+
idx: torch.LongTensor,
|
| 238 |
+
max_new_tokens: int = 64,
|
| 239 |
+
temperature: float = 0.7,
|
| 240 |
+
top_k: int = 50,
|
| 241 |
+
**kwargs,
|
| 242 |
+
):
|
| 243 |
+
"""Original sampling loop (non-HF ``generate`` API)."""
|
| 244 |
+
self.eval()
|
| 245 |
+
for _ in range(max_new_tokens):
|
| 246 |
+
idx_cond = idx[:, -self.config.max_seq_len :]
|
| 247 |
+
out = self.forward(input_ids=idx_cond, return_dict=True)
|
| 248 |
+
logits = out.logits[:, -1, :] / temperature
|
| 249 |
+
if top_k > 0:
|
| 250 |
+
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
|
| 251 |
+
logits[logits < v[:, [-1]]] = float("-inf")
|
| 252 |
+
probs = F.softmax(logits, dim=-1)
|
| 253 |
+
next_id = torch.multinomial(probs, num_samples=1)
|
| 254 |
+
idx = torch.cat([idx, next_id], dim=1)
|
| 255 |
+
if next_id.item() == self.config.eos_token_id:
|
| 256 |
+
break
|
| 257 |
+
return idx, []
|
| 258 |
+
|
| 259 |
+
def param_count(self):
|
| 260 |
+
total = sum(p.numel() for p in self.parameters())
|
| 261 |
+
return total, 0
|
| 262 |
+
|
| 263 |
+
def param_summary(self):
|
| 264 |
+
total, _ = self.param_count()
|
| 265 |
+
return f"GuppyLM: {total:,} params ({total / 1e6:.1f}M)"
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
__all__ = ["GuppyLMModel", "GuppyLMForCausalLM", "GuppyLMConfig"]
|
pytorch_model.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:db46a64d497a9007aaf0965e950d836a62c4d9d435593bcba7210e388e468c8d
|
| 3 |
+
size 34928171
|
test.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import aiohttp
|
| 2 |
+
import asyncio
|
| 3 |
+
import os
|
| 4 |
+
import time
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
import random
|
| 7 |
+
|
| 8 |
+
def _base_url() -> str:
|
| 9 |
+
host = os.environ.get("SGLANG_HTTP_HOST", "127.0.0.1")
|
| 10 |
+
port = os.environ.get("SGLANG_HTTP_PORT", "30000")
|
| 11 |
+
return f"http://{host}:{port}"
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
database = [
|
| 15 |
+
["who are you?",
|
| 16 |
+
"i'm just a little fish. i have little body. they work. i eat the red flakes when i can.",
|
| 17 |
+
"hi",
|
| 18 |
+
"i'm just a snail would be nice.",
|
| 19 |
+
"what is the capital of France?",
|
| 20 |
+
"i am a bubble wall.",
|
| 21 |
+
],
|
| 22 |
+
["is there a cat in the room?",
|
| 23 |
+
"i don't like it. it puts its face on the glass by the bubbles.",
|
| 24 |
+
"I'm sorry. are you hungry?",
|
| 25 |
+
"i don't eat it.",
|
| 26 |
+
]
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
async def main(num_tests: int, concurrency: int):
|
| 30 |
+
url = f"{_base_url()}/v1/chat/completions"
|
| 31 |
+
headers = {"Content-Type": "application/json"}
|
| 32 |
+
RED = "\033[0;31m"
|
| 33 |
+
GREEN = "\033[0;32m"
|
| 34 |
+
YELLOW = "\033[0;33m"
|
| 35 |
+
BLUE = "\033[0;34m"
|
| 36 |
+
MAGENTA = "\033[0;35m"
|
| 37 |
+
CYAN = "\033[0;36m"
|
| 38 |
+
WHITE = "\033[0;37m"
|
| 39 |
+
END = "\033[0m"
|
| 40 |
+
timeout = aiohttp.ClientTimeout(
|
| 41 |
+
total=600, # Total timeout for entire request
|
| 42 |
+
connect=10, # Connection timeout
|
| 43 |
+
sock_read=30 # Socket read timeout
|
| 44 |
+
)
|
| 45 |
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
| 46 |
+
|
| 47 |
+
if num_tests == 0:
|
| 48 |
+
# realtime chat
|
| 49 |
+
json = {"model": "guppylm-9M",
|
| 50 |
+
"messages": [],
|
| 51 |
+
"temperature" : 0.001,
|
| 52 |
+
"top_k": 1,
|
| 53 |
+
"stream": False,
|
| 54 |
+
}
|
| 55 |
+
while True:
|
| 56 |
+
user_input = input("user: ")
|
| 57 |
+
json["messages"].append({"role": "user", "content": user_input})
|
| 58 |
+
async with session.post(url, json=json, headers=headers) as response:
|
| 59 |
+
res = (await response.json())["choices"][0]["message"]
|
| 60 |
+
output = res["content"]
|
| 61 |
+
role = res["role"]
|
| 62 |
+
json["messages"].append({"role": role, "content": output})
|
| 63 |
+
print(f"{role}: {output}")
|
| 64 |
+
else:
|
| 65 |
+
concurrency_sem = asyncio.Semaphore(max(1,concurrency))
|
| 66 |
+
async def job():
|
| 67 |
+
json = {"model": "guppylm-9M",
|
| 68 |
+
"messages": [],
|
| 69 |
+
"temperature" : 0.001,
|
| 70 |
+
"top_k": 1,
|
| 71 |
+
"stream": False,
|
| 72 |
+
}
|
| 73 |
+
random_index = random.randint(0, len(database) - 1)
|
| 74 |
+
user_inputs = database[random_index]
|
| 75 |
+
|
| 76 |
+
unexpected_count = 0
|
| 77 |
+
logs = []
|
| 78 |
+
for input, expected in zip(user_inputs[0::2], user_inputs[1::2]):
|
| 79 |
+
json["messages"].append({"role": "user", "content": input})
|
| 80 |
+
logs.append(datetime.now().strftime("%H:%M:%S.%f")[:-3] + " " + " user: " + input)
|
| 81 |
+
async with concurrency_sem:
|
| 82 |
+
async with session.post(url, json=json, headers=headers) as response:
|
| 83 |
+
res = (await response.json())["choices"][0]["message"]
|
| 84 |
+
output = res["content"]
|
| 85 |
+
role = res["role"]
|
| 86 |
+
color = GREEN if output == expected else RED
|
| 87 |
+
unexpected_count += 1 if output != expected else 0
|
| 88 |
+
json["messages"].append({"role": role, "content": output})
|
| 89 |
+
logs.append(datetime.now().strftime("%H:%M:%S.%f")[:-3] + " " + "assistant: " + color + output + END)
|
| 90 |
+
|
| 91 |
+
print("=========\n","\n".join(logs))
|
| 92 |
+
return unexpected_count
|
| 93 |
+
|
| 94 |
+
tasks = []
|
| 95 |
+
for i in range(num_tests):
|
| 96 |
+
tasks.append(asyncio.create_task(job()))
|
| 97 |
+
results = await asyncio.gather(*tasks)
|
| 98 |
+
return sum(results)
|
| 99 |
+
|
| 100 |
+
if 1:
|
| 101 |
+
unexpected_count = asyncio.run(main(10, 10))
|
| 102 |
+
print(f"unexpected count: {unexpected_count}")
|
| 103 |
+
|
| 104 |
+
asyncio.run(main(0, 0))
|
| 105 |
+
|
| 106 |
+
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"chat_template": "{% for message in messages | rejectattr('role', 'equalto', 'system') %}{% if not loop.first %}{{ '\\n' }}{% endif %}<|im_start|>{{ message['role'] }}\n{{ message['content'] }}<|im_end|>{% endfor %}{{ '\\n' }}{% if add_generation_prompt %}<|im_start|>assistant\n{% endif %}"
|
| 3 |
+
}
|