dustylm-sdk 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dustylm_sdk-0.1.1/PKG-INFO +143 -0
- dustylm_sdk-0.1.1/README.md +118 -0
- dustylm_sdk-0.1.1/dustylm/__init__.py +4 -0
- dustylm_sdk-0.1.1/dustylm/inference.py +461 -0
- dustylm_sdk-0.1.1/dustylm_sdk.egg-info/PKG-INFO +143 -0
- dustylm_sdk-0.1.1/dustylm_sdk.egg-info/SOURCES.txt +9 -0
- dustylm_sdk-0.1.1/dustylm_sdk.egg-info/dependency_links.txt +1 -0
- dustylm_sdk-0.1.1/dustylm_sdk.egg-info/requires.txt +7 -0
- dustylm_sdk-0.1.1/dustylm_sdk.egg-info/top_level.txt +1 -0
- dustylm_sdk-0.1.1/pyproject.toml +50 -0
- dustylm_sdk-0.1.1/setup.cfg +4 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dustylm-sdk
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: The official Python SDK for DustyLM: an 8M-parameter model that talks like a robot vacuum.
|
|
5
|
+
Author-email: Mahmood Khordoo <m.khordoo@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/khordoo/dustylm-sdk
|
|
8
|
+
Project-URL: Source, https://github.com/khordoo/dustylm-sdk
|
|
9
|
+
Keywords: llm,slm,small-language-model,transformer,pytorch,onnx,inference
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: torch>=2.0
|
|
20
|
+
Requires-Dist: tokenizers>=0.15
|
|
21
|
+
Requires-Dist: huggingface-hub>=0.20
|
|
22
|
+
Requires-Dist: numpy>=1.24
|
|
23
|
+
Provides-Extra: onnx
|
|
24
|
+
Requires-Dist: onnxruntime>=1.15; extra == "onnx"
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img src="https://raw.githubusercontent.com/khordoo/dustylm-sdk/main/assets/logo.png" alt="DustyLM Logo" width="200"/>
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
# dustylm
|
|
31
|
+
|
|
32
|
+
Run DustyLM, the 8M parameter robot vacuum language model, in a few lines.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install dustylm-sdk
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from dustylm import DustyLM
|
|
40
|
+
model = DustyLM.from_pretrained("mkhordoo/dusty-8m-sft")
|
|
41
|
+
response = model.chat([{"role": "user", "content": "who are you?"}])
|
|
42
|
+
print(response["choices"][0]["message"]["content"])
|
|
43
|
+
|
|
44
|
+
# beep. i am a little robot. i clean floors and find crumbs.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Backends
|
|
48
|
+
|
|
49
|
+
| Backend | Install | Model file | Best for |
|
|
50
|
+
|---|---|---:|---|
|
|
51
|
+
| `torch` (default) | `pip install dustylm-sdk` | ~32 MB (FP32) | Inspection and experimentation |
|
|
52
|
+
| `onnx` | `pip install dustylm-sdk[onnx]` | ~8–10 MB (int8) | A compact model artifact |
|
|
53
|
+
|
|
54
|
+
These sizes describe the model files, not the complete Python environment.
|
|
55
|
+
Installation size varies by platform. Both backends currently include PyTorch as
|
|
56
|
+
an SDK dependency.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# ONNX backend
|
|
60
|
+
model = DustyLM.from_pretrained("mkhordoo/dusty-8m-sft", backend="onnx")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Loading Local Checkpoints
|
|
64
|
+
|
|
65
|
+
Pass a Hugging Face repository ID to download its model and tokenizer
|
|
66
|
+
automatically, as shown in the quick-start example:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
model = DustyLM.from_pretrained("mkhordoo/dusty-8m-sft")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For files already downloaded to your machine, pass their local directory
|
|
73
|
+
instead. With the default filenames, that directory must contain
|
|
74
|
+
`tokenizer.json` and either `model.pt` (PyTorch) or `model_int8.onnx` (ONNX):
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
model = DustyLM.from_pretrained("./my-checkpoint/")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
You can use any local directory as the artifact root. `model_file` and
|
|
81
|
+
`tokenizer_file` are resolved relative to that directory, so they may use
|
|
82
|
+
different filenames or relative paths.
|
|
83
|
+
|
|
84
|
+
If you trained a model with the main
|
|
85
|
+
[`dusty-lm`](https://github.com/khordoo/dusty-lm) repository, its checkpoint and
|
|
86
|
+
tokenizer are stored in separate artifact directories. Load the promoted SFT
|
|
87
|
+
checkpoint with:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from dustylm import DustyLM
|
|
91
|
+
|
|
92
|
+
model = DustyLM.from_pretrained(
|
|
93
|
+
"artifacts/checkpoints",
|
|
94
|
+
model_file="dusty8m_sft.pt",
|
|
95
|
+
tokenizer_file="../tokenizers/dusty_tokenizer.json",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
response = model.chat([{"role": "user", "content": "who are you?"}])
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
To inspect an intermediate checkpoint instead, change `model_file` to an
|
|
102
|
+
existing step file such as `dusty8m_sft_step_100.pt`.
|
|
103
|
+
|
|
104
|
+
The SDK infers several architecture dimensions from PyTorch checkpoint shapes
|
|
105
|
+
and currently expects DustyLM-compatible configurations.
|
|
106
|
+
|
|
107
|
+
## API
|
|
108
|
+
|
|
109
|
+
### DustyLM.from_pretrained
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
DustyLM.from_pretrained(
|
|
113
|
+
repo_id_or_path: str = "mkhordoo/dusty-8m-sft",
|
|
114
|
+
*,
|
|
115
|
+
model_file: str | None = None,
|
|
116
|
+
tokenizer_file: str | None = None,
|
|
117
|
+
backend: str = "torch",
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| Argument | Default | Description |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `repo_id_or_path` | `"mkhordoo/dusty-8m-sft"` | HF Hub repo ID or local directory path |
|
|
124
|
+
| `model_file` | `"model.pt"` (torch) / `"model_int8.onnx"` (onnx) | Override the model filename in the directory |
|
|
125
|
+
| `tokenizer_file` | `"tokenizer.json"` | Override the tokenizer filename |
|
|
126
|
+
| `backend` | `"torch"` | `"torch"` or `"onnx"` |
|
|
127
|
+
|
|
128
|
+
### DustyLM.chat
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
model.chat(
|
|
132
|
+
messages: list[dict],
|
|
133
|
+
temperature: float = 0.7,
|
|
134
|
+
max_tokens: int = 64,
|
|
135
|
+
top_p: float = 0.9,
|
|
136
|
+
) -> dict
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Returns an OpenAI-style chat completion dict.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
Built from the [dusty-lm](https://github.com/khordoo/dusty-lm) training repository.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/khordoo/dustylm-sdk/main/assets/logo.png" alt="DustyLM Logo" width="200"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# dustylm
|
|
6
|
+
|
|
7
|
+
Run DustyLM, the 8M parameter robot vacuum language model, in a few lines.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
pip install dustylm-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from dustylm import DustyLM
|
|
15
|
+
model = DustyLM.from_pretrained("mkhordoo/dusty-8m-sft")
|
|
16
|
+
response = model.chat([{"role": "user", "content": "who are you?"}])
|
|
17
|
+
print(response["choices"][0]["message"]["content"])
|
|
18
|
+
|
|
19
|
+
# beep. i am a little robot. i clean floors and find crumbs.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Backends
|
|
23
|
+
|
|
24
|
+
| Backend | Install | Model file | Best for |
|
|
25
|
+
|---|---|---:|---|
|
|
26
|
+
| `torch` (default) | `pip install dustylm-sdk` | ~32 MB (FP32) | Inspection and experimentation |
|
|
27
|
+
| `onnx` | `pip install dustylm-sdk[onnx]` | ~8–10 MB (int8) | A compact model artifact |
|
|
28
|
+
|
|
29
|
+
These sizes describe the model files, not the complete Python environment.
|
|
30
|
+
Installation size varies by platform. Both backends currently include PyTorch as
|
|
31
|
+
an SDK dependency.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
# ONNX backend
|
|
35
|
+
model = DustyLM.from_pretrained("mkhordoo/dusty-8m-sft", backend="onnx")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Loading Local Checkpoints
|
|
39
|
+
|
|
40
|
+
Pass a Hugging Face repository ID to download its model and tokenizer
|
|
41
|
+
automatically, as shown in the quick-start example:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
model = DustyLM.from_pretrained("mkhordoo/dusty-8m-sft")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For files already downloaded to your machine, pass their local directory
|
|
48
|
+
instead. With the default filenames, that directory must contain
|
|
49
|
+
`tokenizer.json` and either `model.pt` (PyTorch) or `model_int8.onnx` (ONNX):
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
model = DustyLM.from_pretrained("./my-checkpoint/")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
You can use any local directory as the artifact root. `model_file` and
|
|
56
|
+
`tokenizer_file` are resolved relative to that directory, so they may use
|
|
57
|
+
different filenames or relative paths.
|
|
58
|
+
|
|
59
|
+
If you trained a model with the main
|
|
60
|
+
[`dusty-lm`](https://github.com/khordoo/dusty-lm) repository, its checkpoint and
|
|
61
|
+
tokenizer are stored in separate artifact directories. Load the promoted SFT
|
|
62
|
+
checkpoint with:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from dustylm import DustyLM
|
|
66
|
+
|
|
67
|
+
model = DustyLM.from_pretrained(
|
|
68
|
+
"artifacts/checkpoints",
|
|
69
|
+
model_file="dusty8m_sft.pt",
|
|
70
|
+
tokenizer_file="../tokenizers/dusty_tokenizer.json",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
response = model.chat([{"role": "user", "content": "who are you?"}])
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
To inspect an intermediate checkpoint instead, change `model_file` to an
|
|
77
|
+
existing step file such as `dusty8m_sft_step_100.pt`.
|
|
78
|
+
|
|
79
|
+
The SDK infers several architecture dimensions from PyTorch checkpoint shapes
|
|
80
|
+
and currently expects DustyLM-compatible configurations.
|
|
81
|
+
|
|
82
|
+
## API
|
|
83
|
+
|
|
84
|
+
### DustyLM.from_pretrained
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
DustyLM.from_pretrained(
|
|
88
|
+
repo_id_or_path: str = "mkhordoo/dusty-8m-sft",
|
|
89
|
+
*,
|
|
90
|
+
model_file: str | None = None,
|
|
91
|
+
tokenizer_file: str | None = None,
|
|
92
|
+
backend: str = "torch",
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
| Argument | Default | Description |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `repo_id_or_path` | `"mkhordoo/dusty-8m-sft"` | HF Hub repo ID or local directory path |
|
|
99
|
+
| `model_file` | `"model.pt"` (torch) / `"model_int8.onnx"` (onnx) | Override the model filename in the directory |
|
|
100
|
+
| `tokenizer_file` | `"tokenizer.json"` | Override the tokenizer filename |
|
|
101
|
+
| `backend` | `"torch"` | `"torch"` or `"onnx"` |
|
|
102
|
+
|
|
103
|
+
### DustyLM.chat
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
model.chat(
|
|
107
|
+
messages: list[dict],
|
|
108
|
+
temperature: float = 0.7,
|
|
109
|
+
max_tokens: int = 64,
|
|
110
|
+
top_p: float = 0.9,
|
|
111
|
+
) -> dict
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Returns an OpenAI-style chat completion dict.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
Built from the [dusty-lm](https://github.com/khordoo/dusty-lm) training repository.
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"""Inference-only DustyLM — load from Hugging Face Hub or a local directory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
import torch
|
|
10
|
+
import torch.nn as nn
|
|
11
|
+
|
|
12
|
+
Backend = Literal["torch", "onnx"]
|
|
13
|
+
|
|
14
|
+
HUB_REPO = "mkhordoo/dusty-8m-sft"
|
|
15
|
+
HUB_FILES = {
|
|
16
|
+
"torch": "model.pt",
|
|
17
|
+
"onnx": "model_int8.onnx",
|
|
18
|
+
"tokenizer": "tokenizer.json",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
CHATML_START_TOKEN = "<|im_start|>"
|
|
22
|
+
CHATML_END_TOKEN = "<|im_end|>"
|
|
23
|
+
SUPPORTED_ROLES = {"system", "user", "assistant"}
|
|
24
|
+
|
|
25
|
+
DEFAULT_CONFIG = {
|
|
26
|
+
"num_layers": 8,
|
|
27
|
+
"vocab_size": 4096,
|
|
28
|
+
"max_seq_len": 256,
|
|
29
|
+
"embed_dim": 256,
|
|
30
|
+
"num_heads": 8,
|
|
31
|
+
"num_kv_heads": 4,
|
|
32
|
+
"hidden_dim": 1024,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ── Model (PyTorch) ──────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def model_from_config(config: dict) -> "DustyLMModel":
|
|
40
|
+
model = DustyLMModel(**config)
|
|
41
|
+
return model
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class ForwardContext:
|
|
46
|
+
position_ids: "torch.Tensor"
|
|
47
|
+
rope_sin: "torch.Tensor"
|
|
48
|
+
rope_cos: "torch.Tensor"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _rotate_half(x):
|
|
52
|
+
x1 = x[..., : x.shape[-1] // 2]
|
|
53
|
+
x2 = x[..., x.shape[-1] // 2 :]
|
|
54
|
+
return torch.cat([-x2, x1], dim=-1)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _apply_rope(x, sin, cos):
|
|
58
|
+
return x * cos + _rotate_half(x) * sin
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RotaryPositionalEmbedding(nn.Module):
|
|
62
|
+
def __init__(self, head_dim: int, max_seq_len: int, base: int = 10000):
|
|
63
|
+
super().__init__()
|
|
64
|
+
inv_freq = base ** (-torch.arange(0, head_dim, 2) / head_dim)
|
|
65
|
+
inv_freq = torch.cat([inv_freq, inv_freq], dim=-1)
|
|
66
|
+
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
|
67
|
+
self._build_cache(max_seq_len)
|
|
68
|
+
|
|
69
|
+
def _build_cache(self, max_seq_len: int):
|
|
70
|
+
positions = torch.arange(
|
|
71
|
+
max_seq_len, dtype=self.inv_freq.dtype, device=self.inv_freq.device
|
|
72
|
+
)
|
|
73
|
+
emb = torch.outer(positions, self.inv_freq)
|
|
74
|
+
self.register_buffer("sin_cache", emb.sin(), persistent=False)
|
|
75
|
+
self.register_buffer("cos_cache", emb.cos(), persistent=False)
|
|
76
|
+
|
|
77
|
+
def forward(self, position_ids):
|
|
78
|
+
sin = self.sin_cache[position_ids].unsqueeze(0).unsqueeze(1)
|
|
79
|
+
cos = self.cos_cache[position_ids].unsqueeze(0).unsqueeze(1)
|
|
80
|
+
return sin, cos
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class MultiHeadAttention(nn.Module):
|
|
84
|
+
def __init__(self, embed_dim, num_heads, num_kv_heads, max_seq_len):
|
|
85
|
+
super().__init__()
|
|
86
|
+
self.num_heads = num_heads
|
|
87
|
+
self.num_kv_heads = num_kv_heads
|
|
88
|
+
self.head_dim = embed_dim // num_heads
|
|
89
|
+
self.kv_repeat = num_heads // num_kv_heads
|
|
90
|
+
self.qkv_proj = nn.Linear(
|
|
91
|
+
embed_dim, embed_dim + 2 * num_kv_heads * self.head_dim, bias=False
|
|
92
|
+
)
|
|
93
|
+
self.proj = nn.Linear(embed_dim, embed_dim, bias=False)
|
|
94
|
+
|
|
95
|
+
def forward(self, x, context, past_kv=None):
|
|
96
|
+
B, T, D = x.shape
|
|
97
|
+
qkv = self.qkv_proj(x)
|
|
98
|
+
q_dim = self.num_heads * self.head_dim
|
|
99
|
+
kv_dim = self.num_kv_heads * self.head_dim
|
|
100
|
+
q, k, v = qkv.split([q_dim, kv_dim, kv_dim], dim=-1)
|
|
101
|
+
|
|
102
|
+
q = q.reshape(B, T, self.num_heads, self.head_dim).transpose(1, 2)
|
|
103
|
+
k = k.reshape(B, T, self.num_kv_heads, self.head_dim).transpose(1, 2)
|
|
104
|
+
v = v.reshape(B, T, self.num_kv_heads, self.head_dim).transpose(1, 2)
|
|
105
|
+
|
|
106
|
+
q = _apply_rope(q, context.rope_sin, context.rope_cos)
|
|
107
|
+
k = _apply_rope(k, context.rope_sin, context.rope_cos)
|
|
108
|
+
|
|
109
|
+
past_len = 0
|
|
110
|
+
if past_kv is not None:
|
|
111
|
+
pk, pv = past_kv
|
|
112
|
+
past_len = pk.shape[2]
|
|
113
|
+
k = torch.cat([pk, k], dim=2)
|
|
114
|
+
v = torch.cat([pv, v], dim=2)
|
|
115
|
+
|
|
116
|
+
present_kv = (k, v)
|
|
117
|
+
k = k.repeat_interleave(self.kv_repeat, dim=1)
|
|
118
|
+
v = v.repeat_interleave(self.kv_repeat, dim=1)
|
|
119
|
+
|
|
120
|
+
scores = q @ k.transpose(-2, -1) / (self.head_dim**0.5)
|
|
121
|
+
if T > 1:
|
|
122
|
+
total_len = past_len + T
|
|
123
|
+
mask = torch.triu(
|
|
124
|
+
torch.ones(T, total_len, device=x.device, dtype=torch.bool),
|
|
125
|
+
diagonal=past_len + 1,
|
|
126
|
+
)
|
|
127
|
+
scores = scores.masked_fill(mask, float("-inf"))
|
|
128
|
+
|
|
129
|
+
attn = torch.softmax(scores, dim=-1)
|
|
130
|
+
out = (attn @ v).transpose(1, 2).reshape(B, T, D)
|
|
131
|
+
return self.proj(out), present_kv
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class TransformerBlock(nn.Module):
|
|
135
|
+
def __init__(
|
|
136
|
+
self, embed_dim, num_heads, num_kv_heads, max_seq_len, hidden_dim=None
|
|
137
|
+
):
|
|
138
|
+
super().__init__()
|
|
139
|
+
hidden_dim = hidden_dim or 4 * embed_dim
|
|
140
|
+
self.att_norm = nn.RMSNorm(embed_dim)
|
|
141
|
+
self.attention = MultiHeadAttention(
|
|
142
|
+
embed_dim, num_heads, num_kv_heads, max_seq_len
|
|
143
|
+
)
|
|
144
|
+
self.mlp = nn.Sequential(
|
|
145
|
+
nn.Linear(embed_dim, hidden_dim, bias=False),
|
|
146
|
+
nn.GELU(),
|
|
147
|
+
nn.Linear(hidden_dim, embed_dim, bias=False),
|
|
148
|
+
)
|
|
149
|
+
self.mlp_norm = nn.RMSNorm(embed_dim)
|
|
150
|
+
|
|
151
|
+
def forward(self, x, context, kv_cache=None):
|
|
152
|
+
a, pkv = self.attention(self.att_norm(x), context, kv_cache)
|
|
153
|
+
x = x + a
|
|
154
|
+
x = x + self.mlp(self.mlp_norm(x))
|
|
155
|
+
return x, pkv
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class DustyLMModel(nn.Module):
|
|
159
|
+
def __init__(
|
|
160
|
+
self,
|
|
161
|
+
num_layers,
|
|
162
|
+
vocab_size,
|
|
163
|
+
max_seq_len,
|
|
164
|
+
embed_dim,
|
|
165
|
+
num_heads,
|
|
166
|
+
num_kv_heads,
|
|
167
|
+
hidden_dim=None,
|
|
168
|
+
rope_base=10000,
|
|
169
|
+
):
|
|
170
|
+
super().__init__()
|
|
171
|
+
head_dim = embed_dim // num_heads
|
|
172
|
+
self.rope = RotaryPositionalEmbedding(head_dim, max_seq_len, rope_base)
|
|
173
|
+
self.embed = nn.Embedding(vocab_size, embed_dim)
|
|
174
|
+
self.layers = nn.ModuleList(
|
|
175
|
+
[
|
|
176
|
+
TransformerBlock(
|
|
177
|
+
embed_dim, num_heads, num_kv_heads, max_seq_len, hidden_dim
|
|
178
|
+
)
|
|
179
|
+
for _ in range(num_layers)
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
self.final_norm = nn.RMSNorm(embed_dim)
|
|
183
|
+
self.vocab_proj = nn.Linear(embed_dim, vocab_size, bias=False)
|
|
184
|
+
self.max_seq_len = max_seq_len
|
|
185
|
+
|
|
186
|
+
def forward(self, x, kv_cache=None):
|
|
187
|
+
B, T = x.shape
|
|
188
|
+
use_cache = kv_cache is not None
|
|
189
|
+
|
|
190
|
+
past_len = 0
|
|
191
|
+
if use_cache and kv_cache[0] is not None:
|
|
192
|
+
past_len = kv_cache[0][0].shape[2]
|
|
193
|
+
|
|
194
|
+
position_ids = torch.arange(
|
|
195
|
+
past_len, past_len + T, dtype=torch.long, device=x.device
|
|
196
|
+
)
|
|
197
|
+
sin, cos = self.rope(position_ids)
|
|
198
|
+
ctx = ForwardContext(position_ids=position_ids, rope_sin=sin, rope_cos=cos)
|
|
199
|
+
|
|
200
|
+
h = self.embed(x)
|
|
201
|
+
next_kv = [] if use_cache else None
|
|
202
|
+
for i, layer in enumerate(self.layers):
|
|
203
|
+
h, pkv = layer(h, ctx, kv_cache[i] if use_cache else None)
|
|
204
|
+
if use_cache:
|
|
205
|
+
next_kv.append(pkv)
|
|
206
|
+
|
|
207
|
+
logits = self.vocab_proj(self.final_norm(h))
|
|
208
|
+
if use_cache:
|
|
209
|
+
return logits, next_kv
|
|
210
|
+
return logits
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ── Tokenizer ─────────────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def load_tokenizer(path: Path):
|
|
217
|
+
from tokenizers import Tokenizer
|
|
218
|
+
|
|
219
|
+
return Tokenizer.from_file(str(path))
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def format_chatml(messages: list[dict]) -> str:
|
|
223
|
+
chunks = []
|
|
224
|
+
for msg in messages:
|
|
225
|
+
role, content = msg.get("role", "user"), msg.get("content", "")
|
|
226
|
+
if not content.strip():
|
|
227
|
+
continue
|
|
228
|
+
chunks.append(f"{CHATML_START_TOKEN}{role}\n{content}{CHATML_END_TOKEN}\n")
|
|
229
|
+
chunks.append(f"{CHATML_START_TOKEN}assistant\n")
|
|
230
|
+
return "".join(chunks)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ── Sampling ──────────────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def sample(logits, temperature=0.7, top_p=0.9):
|
|
237
|
+
logits = logits / max(temperature, 1e-8)
|
|
238
|
+
probs = torch.softmax(logits, dim=-1)
|
|
239
|
+
|
|
240
|
+
if top_p < 1.0:
|
|
241
|
+
sorted_probs, sorted_indices = torch.sort(probs, descending=True)
|
|
242
|
+
cumsum = torch.cumsum(sorted_probs, dim=-1)
|
|
243
|
+
remove = cumsum > top_p
|
|
244
|
+
remove[..., 1:] = remove[..., :-1].clone()
|
|
245
|
+
remove[..., 0] = False
|
|
246
|
+
sorted_probs[remove] = 0.0
|
|
247
|
+
probs = sorted_probs / sorted_probs.sum(dim=-1, keepdim=True)
|
|
248
|
+
probs = probs.gather(-1, sorted_indices.argsort(-1))
|
|
249
|
+
|
|
250
|
+
return int(torch.multinomial(probs, 1).item())
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# ── Main class ───────────────────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class DustyLM:
|
|
257
|
+
"""Load and chat with a DustyLM model."""
|
|
258
|
+
|
|
259
|
+
def __init__(self, model, tokenizer, config: dict, backend: Backend):
|
|
260
|
+
self._model = model
|
|
261
|
+
self._tokenizer = tokenizer
|
|
262
|
+
self._config = config
|
|
263
|
+
self._backend = backend
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def from_pretrained(
|
|
267
|
+
cls,
|
|
268
|
+
repo_id_or_path: str = HUB_REPO,
|
|
269
|
+
*,
|
|
270
|
+
model_file: str | None = None,
|
|
271
|
+
tokenizer_file: str | None = None,
|
|
272
|
+
backend: Backend = "torch",
|
|
273
|
+
) -> "DustyLM":
|
|
274
|
+
"""Load from Hugging Face Hub or a local directory.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
repo_id_or_path: HF repo ID (e.g. "mkhordoo/dusty-8m-sft")
|
|
278
|
+
or local directory path.
|
|
279
|
+
model_file: Model filename in the directory (default depends on backend:
|
|
280
|
+
"model.pt" for torch, "model_int8.onnx" for onnx).
|
|
281
|
+
tokenizer_file: Tokenizer filename (default "tokenizer.json").
|
|
282
|
+
backend: "torch" (default) or "onnx".
|
|
283
|
+
"""
|
|
284
|
+
model_file = model_file or HUB_FILES[backend if backend == "onnx" else "torch"]
|
|
285
|
+
tokenizer_file = tokenizer_file or HUB_FILES["tokenizer"]
|
|
286
|
+
|
|
287
|
+
path = Path(repo_id_or_path)
|
|
288
|
+
is_local = path.exists()
|
|
289
|
+
|
|
290
|
+
if is_local:
|
|
291
|
+
tokenizer_path = path / tokenizer_file
|
|
292
|
+
else:
|
|
293
|
+
from huggingface_hub import hf_hub_download
|
|
294
|
+
|
|
295
|
+
tokenizer_path = Path(
|
|
296
|
+
hf_hub_download(repo_id=repo_id_or_path, filename=tokenizer_file)
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
tokenizer = load_tokenizer(tokenizer_path)
|
|
300
|
+
|
|
301
|
+
if backend == "torch":
|
|
302
|
+
model, config = cls._load_torch(repo_id_or_path, model_file, is_local)
|
|
303
|
+
elif backend == "onnx":
|
|
304
|
+
model, config = cls._load_onnx(repo_id_or_path, model_file, is_local)
|
|
305
|
+
else:
|
|
306
|
+
raise ValueError(f"Unknown backend: {backend}")
|
|
307
|
+
|
|
308
|
+
return cls(model=model, tokenizer=tokenizer, config=config, backend=backend)
|
|
309
|
+
|
|
310
|
+
@classmethod
|
|
311
|
+
def _load_torch(cls, repo_id_or_path, model_file, is_local):
|
|
312
|
+
if is_local:
|
|
313
|
+
ckpt_path = Path(repo_id_or_path) / model_file
|
|
314
|
+
else:
|
|
315
|
+
from huggingface_hub import hf_hub_download
|
|
316
|
+
|
|
317
|
+
ckpt_path = Path(
|
|
318
|
+
hf_hub_download(repo_id=repo_id_or_path, filename=model_file)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
state = torch.load(ckpt_path, map_location="cpu", weights_only=True)
|
|
322
|
+
state.pop("rope.sin_cache", None)
|
|
323
|
+
state.pop("rope.cos_cache", None)
|
|
324
|
+
|
|
325
|
+
config = dict(DEFAULT_CONFIG)
|
|
326
|
+
config["vocab_size"] = state["vocab_proj.weight"].shape[0]
|
|
327
|
+
config["embed_dim"] = state["embed.weight"].shape[1]
|
|
328
|
+
config["num_layers"] = sum(
|
|
329
|
+
1
|
|
330
|
+
for k in state
|
|
331
|
+
if k.startswith("layers.") and k.endswith(".att_norm.weight")
|
|
332
|
+
)
|
|
333
|
+
qkv_out = state["layers.0.attention.qkv_proj.weight"].shape[0]
|
|
334
|
+
head_dim = config["embed_dim"] // config["num_heads"]
|
|
335
|
+
num_kv_heads = (qkv_out - config["embed_dim"]) // (2 * head_dim)
|
|
336
|
+
config["num_kv_heads"] = num_kv_heads
|
|
337
|
+
config["num_heads"] = config["num_heads"] or config["num_kv_heads"]
|
|
338
|
+
config["hidden_dim"] = state["layers.0.mlp.0.weight"].shape[0]
|
|
339
|
+
|
|
340
|
+
model = model_from_config(config)
|
|
341
|
+
model.load_state_dict(state)
|
|
342
|
+
model.eval()
|
|
343
|
+
return model, config
|
|
344
|
+
|
|
345
|
+
@classmethod
|
|
346
|
+
def _load_onnx(cls, repo_id_or_path, model_file, is_local):
|
|
347
|
+
import onnxruntime as ort
|
|
348
|
+
|
|
349
|
+
if is_local:
|
|
350
|
+
onnx_path = str(Path(repo_id_or_path) / model_file)
|
|
351
|
+
else:
|
|
352
|
+
from huggingface_hub import hf_hub_download
|
|
353
|
+
|
|
354
|
+
onnx_path = hf_hub_download(repo_id=repo_id_or_path, filename=model_file)
|
|
355
|
+
|
|
356
|
+
session = ort.InferenceSession(onnx_path)
|
|
357
|
+
config = dict(DEFAULT_CONFIG)
|
|
358
|
+
return session, config
|
|
359
|
+
|
|
360
|
+
def chat(
|
|
361
|
+
self,
|
|
362
|
+
messages: list[dict[str, Any]],
|
|
363
|
+
temperature: float = 0.7,
|
|
364
|
+
max_tokens: int = 64,
|
|
365
|
+
top_p: float = 0.9,
|
|
366
|
+
) -> dict:
|
|
367
|
+
"""Chat completion returning an OpenAI-style response dict."""
|
|
368
|
+
prompt = format_chatml(messages)
|
|
369
|
+
|
|
370
|
+
if self._backend == "torch":
|
|
371
|
+
return self._chat_torch(prompt, temperature, max_tokens, top_p)
|
|
372
|
+
else:
|
|
373
|
+
return self._chat_onnx(prompt, temperature, max_tokens, top_p)
|
|
374
|
+
|
|
375
|
+
def _chat_torch(self, prompt, temperature, max_tokens, top_p):
|
|
376
|
+
prompt_ids = self._tokenizer.encode(prompt).ids
|
|
377
|
+
max_seq = self._config["max_seq_len"]
|
|
378
|
+
|
|
379
|
+
input_ids = torch.tensor([prompt_ids])
|
|
380
|
+
past_len = input_ids.shape[1]
|
|
381
|
+
|
|
382
|
+
if max_seq - past_len < max_tokens:
|
|
383
|
+
max_tokens = max_seq - past_len
|
|
384
|
+
|
|
385
|
+
if max_tokens <= 0:
|
|
386
|
+
return self._response("", 0)
|
|
387
|
+
|
|
388
|
+
model = self._model
|
|
389
|
+
device = next(model.parameters()).device
|
|
390
|
+
input_ids = input_ids.to(device)
|
|
391
|
+
|
|
392
|
+
num_layers = self._config["num_layers"]
|
|
393
|
+
kv_cache = [None] * num_layers
|
|
394
|
+
logits, kv_cache = model(input_ids, kv_cache)
|
|
395
|
+
end_token_id = self._tokenizer.token_to_id(CHATML_END_TOKEN)
|
|
396
|
+
generated = []
|
|
397
|
+
|
|
398
|
+
for _ in range(max_tokens):
|
|
399
|
+
next_id = sample(logits[0, -1, :], temperature, top_p)
|
|
400
|
+
generated.append(next_id)
|
|
401
|
+
if next_id == end_token_id:
|
|
402
|
+
break
|
|
403
|
+
|
|
404
|
+
inp = torch.tensor([[next_id]], device=device)
|
|
405
|
+
logits, kv_cache = model(inp, kv_cache)
|
|
406
|
+
|
|
407
|
+
content = self._decode_completion(generated)
|
|
408
|
+
return self._response(content, len(generated))
|
|
409
|
+
|
|
410
|
+
def _chat_onnx(self, prompt, temperature, max_tokens, top_p):
|
|
411
|
+
import numpy as np
|
|
412
|
+
|
|
413
|
+
prompt_ids = self._tokenizer.encode(prompt).ids
|
|
414
|
+
max_seq = self._config["max_seq_len"]
|
|
415
|
+
|
|
416
|
+
gen_count = min(max_tokens, max_seq - len(prompt_ids))
|
|
417
|
+
if gen_count <= 0:
|
|
418
|
+
content = self._tokenizer.decode(prompt_ids).strip()
|
|
419
|
+
return self._response(content, 0)
|
|
420
|
+
|
|
421
|
+
session = self._model
|
|
422
|
+
end_token_id = self._tokenizer.token_to_id(CHATML_END_TOKEN)
|
|
423
|
+
generated = []
|
|
424
|
+
|
|
425
|
+
for step in range(gen_count):
|
|
426
|
+
input_ids = np.array([prompt_ids + generated], dtype=np.int64)
|
|
427
|
+
outputs = session.run(None, {"input_ids": input_ids})
|
|
428
|
+
logits = outputs[0]
|
|
429
|
+
last_logits = torch.tensor(logits[0, -1, :])
|
|
430
|
+
next_id = sample(last_logits, temperature, top_p)
|
|
431
|
+
generated.append(next_id)
|
|
432
|
+
if next_id == end_token_id:
|
|
433
|
+
break
|
|
434
|
+
|
|
435
|
+
content = self._decode_completion(generated)
|
|
436
|
+
return self._response(content, len(generated))
|
|
437
|
+
|
|
438
|
+
def _decode_completion(self, token_ids: list[int]) -> str:
|
|
439
|
+
end_token_id = self._tokenizer.token_to_id(CHATML_END_TOKEN)
|
|
440
|
+
if end_token_id in token_ids:
|
|
441
|
+
token_ids = token_ids[: token_ids.index(end_token_id)]
|
|
442
|
+
return self._tokenizer.decode(token_ids).strip()
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def _response(content: str, completion_tokens: int) -> dict:
|
|
446
|
+
return {
|
|
447
|
+
"object": "chat.completion",
|
|
448
|
+
"model": "dusty8m",
|
|
449
|
+
"choices": [
|
|
450
|
+
{
|
|
451
|
+
"index": 0,
|
|
452
|
+
"message": {"role": "assistant", "content": content},
|
|
453
|
+
"finish_reason": "stop" if completion_tokens > 0 else "length",
|
|
454
|
+
}
|
|
455
|
+
],
|
|
456
|
+
"usage": {
|
|
457
|
+
"prompt_tokens": 0,
|
|
458
|
+
"completion_tokens": completion_tokens,
|
|
459
|
+
"total_tokens": completion_tokens,
|
|
460
|
+
},
|
|
461
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dustylm-sdk
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: The official Python SDK for DustyLM: an 8M-parameter model that talks like a robot vacuum.
|
|
5
|
+
Author-email: Mahmood Khordoo <m.khordoo@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/khordoo/dustylm-sdk
|
|
8
|
+
Project-URL: Source, https://github.com/khordoo/dustylm-sdk
|
|
9
|
+
Keywords: llm,slm,small-language-model,transformer,pytorch,onnx,inference
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: torch>=2.0
|
|
20
|
+
Requires-Dist: tokenizers>=0.15
|
|
21
|
+
Requires-Dist: huggingface-hub>=0.20
|
|
22
|
+
Requires-Dist: numpy>=1.24
|
|
23
|
+
Provides-Extra: onnx
|
|
24
|
+
Requires-Dist: onnxruntime>=1.15; extra == "onnx"
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img src="https://raw.githubusercontent.com/khordoo/dustylm-sdk/main/assets/logo.png" alt="DustyLM Logo" width="200"/>
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
# dustylm
|
|
31
|
+
|
|
32
|
+
Run DustyLM, the 8M parameter robot vacuum language model, in a few lines.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install dustylm-sdk
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from dustylm import DustyLM
|
|
40
|
+
model = DustyLM.from_pretrained("mkhordoo/dusty-8m-sft")
|
|
41
|
+
response = model.chat([{"role": "user", "content": "who are you?"}])
|
|
42
|
+
print(response["choices"][0]["message"]["content"])
|
|
43
|
+
|
|
44
|
+
# beep. i am a little robot. i clean floors and find crumbs.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Backends
|
|
48
|
+
|
|
49
|
+
| Backend | Install | Model file | Best for |
|
|
50
|
+
|---|---|---:|---|
|
|
51
|
+
| `torch` (default) | `pip install dustylm-sdk` | ~32 MB (FP32) | Inspection and experimentation |
|
|
52
|
+
| `onnx` | `pip install dustylm-sdk[onnx]` | ~8–10 MB (int8) | A compact model artifact |
|
|
53
|
+
|
|
54
|
+
These sizes describe the model files, not the complete Python environment.
|
|
55
|
+
Installation size varies by platform. Both backends currently include PyTorch as
|
|
56
|
+
an SDK dependency.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# ONNX backend
|
|
60
|
+
model = DustyLM.from_pretrained("mkhordoo/dusty-8m-sft", backend="onnx")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Loading Local Checkpoints
|
|
64
|
+
|
|
65
|
+
Pass a Hugging Face repository ID to download its model and tokenizer
|
|
66
|
+
automatically, as shown in the quick-start example:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
model = DustyLM.from_pretrained("mkhordoo/dusty-8m-sft")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
For files already downloaded to your machine, pass their local directory
|
|
73
|
+
instead. With the default filenames, that directory must contain
|
|
74
|
+
`tokenizer.json` and either `model.pt` (PyTorch) or `model_int8.onnx` (ONNX):
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
model = DustyLM.from_pretrained("./my-checkpoint/")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
You can use any local directory as the artifact root. `model_file` and
|
|
81
|
+
`tokenizer_file` are resolved relative to that directory, so they may use
|
|
82
|
+
different filenames or relative paths.
|
|
83
|
+
|
|
84
|
+
If you trained a model with the main
|
|
85
|
+
[`dusty-lm`](https://github.com/khordoo/dusty-lm) repository, its checkpoint and
|
|
86
|
+
tokenizer are stored in separate artifact directories. Load the promoted SFT
|
|
87
|
+
checkpoint with:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from dustylm import DustyLM
|
|
91
|
+
|
|
92
|
+
model = DustyLM.from_pretrained(
|
|
93
|
+
"artifacts/checkpoints",
|
|
94
|
+
model_file="dusty8m_sft.pt",
|
|
95
|
+
tokenizer_file="../tokenizers/dusty_tokenizer.json",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
response = model.chat([{"role": "user", "content": "who are you?"}])
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
To inspect an intermediate checkpoint instead, change `model_file` to an
|
|
102
|
+
existing step file such as `dusty8m_sft_step_100.pt`.
|
|
103
|
+
|
|
104
|
+
The SDK infers several architecture dimensions from PyTorch checkpoint shapes
|
|
105
|
+
and currently expects DustyLM-compatible configurations.
|
|
106
|
+
|
|
107
|
+
## API
|
|
108
|
+
|
|
109
|
+
### DustyLM.from_pretrained
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
DustyLM.from_pretrained(
|
|
113
|
+
repo_id_or_path: str = "mkhordoo/dusty-8m-sft",
|
|
114
|
+
*,
|
|
115
|
+
model_file: str | None = None,
|
|
116
|
+
tokenizer_file: str | None = None,
|
|
117
|
+
backend: str = "torch",
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| Argument | Default | Description |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `repo_id_or_path` | `"mkhordoo/dusty-8m-sft"` | HF Hub repo ID or local directory path |
|
|
124
|
+
| `model_file` | `"model.pt"` (torch) / `"model_int8.onnx"` (onnx) | Override the model filename in the directory |
|
|
125
|
+
| `tokenizer_file` | `"tokenizer.json"` | Override the tokenizer filename |
|
|
126
|
+
| `backend` | `"torch"` | `"torch"` or `"onnx"` |
|
|
127
|
+
|
|
128
|
+
### DustyLM.chat
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
model.chat(
|
|
132
|
+
messages: list[dict],
|
|
133
|
+
temperature: float = 0.7,
|
|
134
|
+
max_tokens: int = 64,
|
|
135
|
+
top_p: float = 0.9,
|
|
136
|
+
) -> dict
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Returns an OpenAI-style chat completion dict.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
Built from the [dusty-lm](https://github.com/khordoo/dusty-lm) training repository.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dustylm
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dustylm-sdk"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "The official Python SDK for DustyLM: an 8M-parameter model that talks like a robot vacuum."
|
|
9
|
+
keywords = [
|
|
10
|
+
"llm",
|
|
11
|
+
"slm",
|
|
12
|
+
"small-language-model",
|
|
13
|
+
"transformer",
|
|
14
|
+
"pytorch",
|
|
15
|
+
"onnx",
|
|
16
|
+
"inference"
|
|
17
|
+
]
|
|
18
|
+
readme = "README.md"
|
|
19
|
+
license = "MIT"
|
|
20
|
+
authors = [
|
|
21
|
+
{ name = "Mahmood Khordoo", email = "m.khordoo@gmail.com" },
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Intended Audience :: Education",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
31
|
+
]
|
|
32
|
+
requires-python = ">=3.11"
|
|
33
|
+
dependencies = [
|
|
34
|
+
"torch>=2.0",
|
|
35
|
+
"tokenizers>=0.15",
|
|
36
|
+
"huggingface-hub>=0.20",
|
|
37
|
+
"numpy>=1.24",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
onnx = [
|
|
42
|
+
"onnxruntime>=1.15",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://github.com/khordoo/dustylm-sdk"
|
|
47
|
+
Source = "https://github.com/khordoo/dustylm-sdk"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.packages.find]
|
|
50
|
+
include = ["dustylm*"]
|