PyIntell 0.1.0__py3-none-any.whl
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.
- pyintell/__init__.py +31 -0
- pyintell/attention.py +126 -0
- pyintell/autograd.py +50 -0
- pyintell/builder.py +211 -0
- pyintell/embeddings.py +33 -0
- pyintell/finetuning.py +29 -0
- pyintell/focus.py +81 -0
- pyintell/generation.py +73 -0
- pyintell/layers.py +75 -0
- pyintell/loss.py +39 -0
- pyintell/model.py +224 -0
- pyintell/optim.py +94 -0
- pyintell/quantization.py +25 -0
- pyintell/scheduling.py +16 -0
- pyintell/serialization.py +313 -0
- pyintell/system.py +122 -0
- pyintell/tokenization.py +147 -0
- pyintell/training.py +15 -0
- pyintell/transformer.py +90 -0
- pyintell/utilities.py +444 -0
- pyintell-0.1.0.dist-info/METADATA +226 -0
- pyintell-0.1.0.dist-info/RECORD +25 -0
- pyintell-0.1.0.dist-info/WHEEL +5 -0
- pyintell-0.1.0.dist-info/licenses/LICENSE +21 -0
- pyintell-0.1.0.dist-info/top_level.txt +1 -0
pyintell/layers.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Neural-network layer and activation primitives."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def linear(x, weights, bias=None):
|
|
7
|
+
result = np.asarray(x) @ np.asarray(weights)
|
|
8
|
+
return result if bias is None else result + bias
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def relu(x): return np.maximum(x, 0)
|
|
12
|
+
def leaky_relu(x, negative_slope=0.01):
|
|
13
|
+
x = np.asarray(x); return np.where(x >= 0, x, negative_slope * x)
|
|
14
|
+
def gelu(x):
|
|
15
|
+
x = np.asarray(x); return 0.5 * x * (1.0 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * x**3)))
|
|
16
|
+
def sigmoid(x): return 1.0 / (1.0 + np.exp(-np.asarray(x)))
|
|
17
|
+
def tanh(x): return np.tanh(x)
|
|
18
|
+
def softplus(x): return np.log1p(np.exp(-np.abs(x))) + np.maximum(x, 0)
|
|
19
|
+
def silu(x): return np.asarray(x) * sigmoid(x)
|
|
20
|
+
def swish(x): return silu(x)
|
|
21
|
+
def mish(x): return np.asarray(x) * np.tanh(softplus(x))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def softmax(x, axis=-1):
|
|
25
|
+
x = np.asarray(x); shifted = x - np.max(x, axis=axis, keepdims=True)
|
|
26
|
+
values = np.exp(shifted); return values / np.sum(values, axis=axis, keepdims=True)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def log_softmax(x, axis=-1):
|
|
30
|
+
x = np.asarray(x); shifted = x - np.max(x, axis=axis, keepdims=True)
|
|
31
|
+
return shifted - np.log(np.sum(np.exp(shifted), axis=axis, keepdims=True))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def activation(x, name="gelu"):
|
|
35
|
+
functions = {"relu": relu, "gelu": gelu, "tanh": tanh, "sigmoid": sigmoid,
|
|
36
|
+
"leaky_relu": leaky_relu, "softplus": softplus, "silu": silu,
|
|
37
|
+
"swish": swish, "mish": mish}
|
|
38
|
+
if name not in functions: raise ValueError(f"unknown activation: {name}")
|
|
39
|
+
return functions[name](x)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def layer_norm(x, eps=1e-5):
|
|
43
|
+
x = np.asarray(x); mean = np.mean(x, axis=-1, keepdims=True); variance = np.var(x, axis=-1, keepdims=True)
|
|
44
|
+
return (x - mean) / np.sqrt(variance + eps)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def batch_norm(x, eps=1e-5):
|
|
48
|
+
x = np.asarray(x); mean = np.mean(x, axis=0, keepdims=True); variance = np.var(x, axis=0, keepdims=True)
|
|
49
|
+
return (x - mean) / np.sqrt(variance + eps)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def rms_norm(x, eps=1e-8):
|
|
53
|
+
x = np.asarray(x); return x / np.sqrt(np.mean(x * x, axis=-1, keepdims=True) + eps)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def dropout(x, probability=0.0, training=True):
|
|
57
|
+
if not training or probability <= 0: return x
|
|
58
|
+
if probability >= 1: raise ValueError("dropout probability must be less than 1")
|
|
59
|
+
mask = (np.random.random(np.asarray(x).shape) >= probability) / (1 - probability)
|
|
60
|
+
return np.asarray(x) * mask
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def flatten_layer(x): return np.asarray(x).reshape(-1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def feedforward(x, hidden_size=None, activation_name="gelu"):
|
|
67
|
+
width = x.shape[-1] if hidden_size is None else hidden_size; input_size = x.shape[-1]
|
|
68
|
+
w1 = np.random.randn(input_size, width).astype(np.float32) * 0.02
|
|
69
|
+
w2 = np.random.randn(width, input_size).astype(np.float32) * 0.02
|
|
70
|
+
return linear(activation(linear(x, w1), activation_name), w2)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def mlp(x, hidden_size=None, activation_name="gelu"): return feedforward(x, hidden_size, activation_name)
|
|
74
|
+
def residual(x, function): return np.asarray(x) + function(x)
|
|
75
|
+
def residual_block(x, function): return residual(x, function)
|
pyintell/loss.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Loss functions."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def cross_entropy(logits, targets):
|
|
7
|
+
logits = np.asarray(logits, dtype=np.float64); targets = np.asarray(targets, dtype=np.int64)
|
|
8
|
+
shifted = logits - np.max(logits, axis=-1, keepdims=True)
|
|
9
|
+
log_probs = shifted - np.log(np.sum(np.exp(shifted), axis=-1, keepdims=True))
|
|
10
|
+
flat = log_probs.reshape(-1, log_probs.shape[-1]); target_flat = targets.reshape(-1)
|
|
11
|
+
return float(-np.mean(flat[np.arange(target_flat.size), target_flat]))
|
|
12
|
+
|
|
13
|
+
def binary_cross_entropy(predictions, targets, eps=1e-12):
|
|
14
|
+
p = np.clip(np.asarray(predictions), eps, 1-eps); t = np.asarray(targets)
|
|
15
|
+
return float(-np.mean(t*np.log(p) + (1-t)*np.log(1-p)))
|
|
16
|
+
|
|
17
|
+
def mse(predictions, targets): return float(np.mean((np.asarray(predictions) - np.asarray(targets)) ** 2))
|
|
18
|
+
def mae(predictions, targets): return float(np.mean(np.abs(np.asarray(predictions) - np.asarray(targets))))
|
|
19
|
+
def huber_loss(predictions, targets, delta=1.0):
|
|
20
|
+
error = np.abs(np.asarray(predictions) - np.asarray(targets)); quadratic = np.minimum(error, delta)
|
|
21
|
+
linear = error - quadratic; return float(np.mean(0.5*quadratic**2 + delta*linear))
|
|
22
|
+
|
|
23
|
+
def kl_divergence(p, q, eps=1e-12):
|
|
24
|
+
p = np.clip(np.asarray(p), eps, None); q = np.clip(np.asarray(q), eps, None)
|
|
25
|
+
return float(np.sum(p * (np.log(p) - np.log(q))))
|
|
26
|
+
|
|
27
|
+
def contrastive_loss(a, b, target, margin=1.0):
|
|
28
|
+
distance = np.linalg.norm(np.asarray(a)-np.asarray(b), axis=-1)
|
|
29
|
+
target = np.asarray(target); return float(np.mean(target*distance**2 + (1-target)*np.maximum(0, margin-distance)**2))
|
|
30
|
+
|
|
31
|
+
def label_smoothing(targets, classes, smoothing=0.1):
|
|
32
|
+
result = np.full((len(targets), classes), smoothing/(classes-1), dtype=np.float32)
|
|
33
|
+
result[np.arange(len(targets)), np.asarray(targets)] = 1-smoothing
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
def loss(predictions, targets, kind="cross_entropy"):
|
|
37
|
+
functions = {"cross_entropy": cross_entropy, "mse": mse, "mae": mae, "binary_cross_entropy": binary_cross_entropy, "huber": huber_loss}
|
|
38
|
+
if kind not in functions: raise ValueError(f"unknown loss: {kind}")
|
|
39
|
+
return functions[kind](predictions, targets)
|
pyintell/model.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Trainable language-model object returned by pyintell.build."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from .embeddings import embedding, positional_embedding
|
|
7
|
+
from .transformer import transformer, init_block
|
|
8
|
+
from .tokenization import encode, decode
|
|
9
|
+
from .generation import sample
|
|
10
|
+
from .optim import Optimizer
|
|
11
|
+
from .focus import build_focus_config, normalize_focus
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Model:
|
|
15
|
+
"""A compact, persistent NumPy Transformer language model.
|
|
16
|
+
|
|
17
|
+
Alpha note: this class provides the framework and introspection APIs. Its
|
|
18
|
+
training implementation is intentionally simple and is not production AI
|
|
19
|
+
training yet.
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, vocab, reverse_vocab, dataset, parameters, focus, dtype, settings):
|
|
22
|
+
self.vocab = vocab
|
|
23
|
+
self.reverse_vocab = reverse_vocab
|
|
24
|
+
self.dataset = dataset
|
|
25
|
+
self.requested_parameters = int(parameters)
|
|
26
|
+
self.parameters = self.requested_parameters
|
|
27
|
+
self.focus = tuple(normalize_focus(focus))
|
|
28
|
+
self.requested_dtype = str(dtype)
|
|
29
|
+
storage_dtype = {"bfloat16": "float16", "int4": "uint8"}.get(self.requested_dtype, self.requested_dtype)
|
|
30
|
+
self.dtype = np.dtype(storage_dtype)
|
|
31
|
+
self.settings = dict(settings)
|
|
32
|
+
self.focus_config = dict(self.settings.get("focus_config", build_focus_config(self.focus)))
|
|
33
|
+
self.focus_priorities = dict(self.focus_config.get("priorities", {}))
|
|
34
|
+
self.model_name = str(self.settings.get("model_name", "pyintell_model"))
|
|
35
|
+
self.platform = str(self.settings.get("platform", "auto"))
|
|
36
|
+
self.device = self.settings.get("device")
|
|
37
|
+
self.layers = int(self.settings["layers"])
|
|
38
|
+
self.heads = int(self.settings["heads"])
|
|
39
|
+
self.embedding_size = int(self.settings["embedding_size"])
|
|
40
|
+
self.context_length = int(self.settings["context_length"])
|
|
41
|
+
self.hidden_size = int(self.settings["hidden_size"])
|
|
42
|
+
seed = self.settings.get("seed", None)
|
|
43
|
+
self.rng = np.random.default_rng(seed)
|
|
44
|
+
self.embedding_weights = embedding(np.arange(len(vocab)), dimensions=self.embedding_size,
|
|
45
|
+
vocab_size=len(vocab)).astype(self.dtype)
|
|
46
|
+
self.position_weights = positional_embedding(self.context_length, self.embedding_size).astype(self.dtype)
|
|
47
|
+
self.blocks = [init_block(self.embedding_size, self.heads, self.hidden_size, self.rng)
|
|
48
|
+
for _ in range(self.layers)]
|
|
49
|
+
self.output_weights = (self.rng.standard_normal((self.embedding_size, len(vocab))) *
|
|
50
|
+
(1.0 / np.sqrt(max(self.embedding_size, 1)))).astype(np.float32)
|
|
51
|
+
self.output_bias = np.zeros(len(vocab), dtype=np.float32)
|
|
52
|
+
self.training_steps = 0
|
|
53
|
+
self.training_history = []
|
|
54
|
+
self.optimizer = None
|
|
55
|
+
self.created_at = time.time()
|
|
56
|
+
|
|
57
|
+
def _named_parameters(self):
|
|
58
|
+
params = {"embedding": self.embedding_weights, "position": self.position_weights,
|
|
59
|
+
"output": self.output_weights, "output_bias": self.output_bias}
|
|
60
|
+
for i, block in enumerate(self.blocks):
|
|
61
|
+
for name, value in block.items():
|
|
62
|
+
params[f"blocks.{i}.{name}"] = value
|
|
63
|
+
return params
|
|
64
|
+
|
|
65
|
+
def named_parameters(self):
|
|
66
|
+
return self._named_parameters()
|
|
67
|
+
|
|
68
|
+
def parameter_count(self):
|
|
69
|
+
return int(sum(value.size for value in self._named_parameters().values()))
|
|
70
|
+
|
|
71
|
+
def parameters_info(self):
|
|
72
|
+
return {name: {"shape": tuple(value.shape), "dtype": str(value.dtype), "count": int(value.size)}
|
|
73
|
+
for name, value in self._named_parameters().items()}
|
|
74
|
+
|
|
75
|
+
def get_focus(self):
|
|
76
|
+
"""Return the normalized capabilities selected for this model."""
|
|
77
|
+
return list(self.focus)
|
|
78
|
+
|
|
79
|
+
def set_focus(self, focus):
|
|
80
|
+
"""Change focus metadata without pretending to retrain the model.
|
|
81
|
+
|
|
82
|
+
In 0.1.x this does not modify existing weights. Future training systems
|
|
83
|
+
can use the resulting configuration to implement specialization.
|
|
84
|
+
"""
|
|
85
|
+
config = build_focus_config(focus)
|
|
86
|
+
self.focus = tuple(config["focuses"])
|
|
87
|
+
self.focus_config = config
|
|
88
|
+
self.focus_priorities = dict(config["priorities"])
|
|
89
|
+
self.settings["focus_config"] = config
|
|
90
|
+
return self.get_focus()
|
|
91
|
+
|
|
92
|
+
def focus_scores(self):
|
|
93
|
+
"""Return normalized capability priorities produced by the builder."""
|
|
94
|
+
return dict(self.focus_priorities)
|
|
95
|
+
|
|
96
|
+
def focus_info(self):
|
|
97
|
+
"""Return focus names and their current alpha priority metadata."""
|
|
98
|
+
ranked = sorted(self.focus_priorities.items(), key=lambda item: (-item[1], item[0]))
|
|
99
|
+
return {
|
|
100
|
+
"focuses": self.get_focus(),
|
|
101
|
+
"priorities": self.focus_scores(),
|
|
102
|
+
"top_capabilities": [name for name, _ in ranked[:5]],
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
def forward(self, token_ids):
|
|
106
|
+
ids = np.asarray(token_ids, dtype=np.int64)
|
|
107
|
+
if ids.ndim != 1:
|
|
108
|
+
raise ValueError("token_ids must be a one-dimensional sequence")
|
|
109
|
+
if len(ids) == 0:
|
|
110
|
+
raise ValueError("token_ids must not be empty")
|
|
111
|
+
if len(ids) > self.context_length:
|
|
112
|
+
raise ValueError("sequence exceeds model context length")
|
|
113
|
+
if ids.min() < 0 or ids.max() >= len(self.vocab):
|
|
114
|
+
raise ValueError("token ID outside vocabulary")
|
|
115
|
+
x = self.embedding_weights[ids].astype(np.float32) + self.position_weights[:len(ids)].astype(np.float32)
|
|
116
|
+
return transformer(x, layers=self.layers, heads=self.heads, hidden_size=self.hidden_size,
|
|
117
|
+
causal=True, weights=self.blocks)
|
|
118
|
+
|
|
119
|
+
def logits(self, token_ids):
|
|
120
|
+
return self.forward(token_ids) @ self.output_weights + self.output_bias
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def _softmax(logits):
|
|
124
|
+
shifted = logits - np.max(logits, axis=-1, keepdims=True)
|
|
125
|
+
probabilities = np.exp(np.clip(shifted, -80.0, 80.0))
|
|
126
|
+
return probabilities / np.maximum(np.sum(probabilities, axis=-1, keepdims=True), 1e-12)
|
|
127
|
+
|
|
128
|
+
def _examples(self, dataset):
|
|
129
|
+
for sequence in dataset:
|
|
130
|
+
ids = list(sequence)
|
|
131
|
+
if len(ids) < 2:
|
|
132
|
+
continue
|
|
133
|
+
for end in range(1, len(ids)):
|
|
134
|
+
start = max(0, end - self.context_length)
|
|
135
|
+
yield ids[start:end], ids[end]
|
|
136
|
+
|
|
137
|
+
def evaluate(self, dataset=None, **kwargs):
|
|
138
|
+
data = self.dataset if dataset is None else dataset
|
|
139
|
+
total_loss = 0.0; correct = 0; count = 0
|
|
140
|
+
for inputs, target in self._examples(data):
|
|
141
|
+
probabilities = self._softmax(self.logits(inputs)[-1])
|
|
142
|
+
target = int(target)
|
|
143
|
+
if target < 0 or target >= len(self.vocab):
|
|
144
|
+
raise ValueError("target token ID outside vocabulary")
|
|
145
|
+
total_loss -= float(np.log(np.clip(probabilities[target], 1e-12, 1.0)))
|
|
146
|
+
correct += int(np.argmax(probabilities) == target)
|
|
147
|
+
count += 1
|
|
148
|
+
if count == 0:
|
|
149
|
+
raise ValueError("dataset must contain sequences with at least two token IDs")
|
|
150
|
+
loss = total_loss / count
|
|
151
|
+
return {"loss": loss, "accuracy": correct / count, "samples": count,
|
|
152
|
+
"perplexity": float(np.exp(min(loss, 20.0)))}
|
|
153
|
+
|
|
154
|
+
def train(self, dataset=None, epochs=1, learning_rate=None, optimizer=None, **kwargs):
|
|
155
|
+
"""Train the model's currently supported trainable output head."""
|
|
156
|
+
data = self.dataset if dataset is None else dataset
|
|
157
|
+
epochs = int(epochs if epochs is not None else self.settings.get("epochs", 1))
|
|
158
|
+
learning_rate = float(learning_rate if learning_rate is not None else self.settings.get("learning_rate", 3e-4))
|
|
159
|
+
optimizer = optimizer or self.settings.get("optimizer", "adamw")
|
|
160
|
+
if epochs < 1:
|
|
161
|
+
raise ValueError("epochs must be at least 1")
|
|
162
|
+
examples = list(self._examples(data))
|
|
163
|
+
if not examples:
|
|
164
|
+
raise ValueError("dataset must contain sequences with at least two token IDs")
|
|
165
|
+
trainable = {"output": self.output_weights, "output_bias": self.output_bias}
|
|
166
|
+
if self.optimizer is None or self.optimizer.kind != str(optimizer).lower() or self.optimizer.lr != learning_rate:
|
|
167
|
+
self.optimizer = Optimizer(trainable, kind=optimizer, learning_rate=learning_rate,
|
|
168
|
+
weight_decay=float(kwargs.get("weight_decay", self.settings.get("weight_decay", 0.0))),
|
|
169
|
+
clip_norm=kwargs.get("clip_norm", self.settings.get("gradient_clip")))
|
|
170
|
+
before = self.evaluate(data)
|
|
171
|
+
history = []
|
|
172
|
+
for _ in range(epochs):
|
|
173
|
+
gradients = {"output": np.zeros_like(self.output_weights), "output_bias": np.zeros_like(self.output_bias)}
|
|
174
|
+
total_loss = 0.0
|
|
175
|
+
for inputs, target in examples:
|
|
176
|
+
hidden = self.forward(inputs)[-1].astype(np.float32)
|
|
177
|
+
probabilities = self._softmax(hidden @ self.output_weights + self.output_bias)
|
|
178
|
+
target = int(target)
|
|
179
|
+
total_loss -= float(np.log(np.clip(probabilities[target], 1e-12, 1.0)))
|
|
180
|
+
probabilities[target] -= 1.0
|
|
181
|
+
gradients["output"] += np.outer(hidden, probabilities)
|
|
182
|
+
gradients["output_bias"] += probabilities
|
|
183
|
+
gradients = {k: v / len(examples) for k, v in gradients.items()}
|
|
184
|
+
self.optimizer.step(gradients)
|
|
185
|
+
self.training_steps += 1
|
|
186
|
+
history.append(total_loss / len(examples))
|
|
187
|
+
after = self.evaluate(data)
|
|
188
|
+
record = {"loss": history[-1], "loss_history": history, "epochs": epochs, "samples": len(examples),
|
|
189
|
+
"steps": self.training_steps, "before": before, "after": after,
|
|
190
|
+
"loss_decreased": after["loss"] < before["loss"],
|
|
191
|
+
"accuracy_improved": after["accuracy"] > before["accuracy"]}
|
|
192
|
+
self.training_history.append(record)
|
|
193
|
+
return record
|
|
194
|
+
|
|
195
|
+
def generate(self, prompt, max_tokens=50, temperature=1.0, top_k=None, **kwargs):
|
|
196
|
+
if isinstance(max_tokens, bool) or not isinstance(max_tokens, (int, np.integer)):
|
|
197
|
+
raise TypeError("max_tokens must be an integer")
|
|
198
|
+
max_tokens = int(max_tokens)
|
|
199
|
+
if max_tokens < 0:
|
|
200
|
+
raise ValueError("max_tokens must not be negative")
|
|
201
|
+
if isinstance(prompt, str):
|
|
202
|
+
unknown_token = "<unk>" if "<unk>" in self.vocab else None
|
|
203
|
+
ids = encode(prompt, self.vocab, unknown_token=unknown_token)
|
|
204
|
+
if any(token_id is None for token_id in ids):
|
|
205
|
+
raise ValueError("prompt contains unknown tokens and vocabulary has no '<unk>' token")
|
|
206
|
+
else:
|
|
207
|
+
ids = list(prompt)
|
|
208
|
+
if not ids:
|
|
209
|
+
raise ValueError("prompt must contain at least one known token")
|
|
210
|
+
for _ in range(max_tokens):
|
|
211
|
+
context = ids[-self.context_length:]
|
|
212
|
+
ids.append(sample(self.logits(context)[-1], temperature=temperature, top_k=top_k))
|
|
213
|
+
return decode(ids, self.reverse_vocab) if isinstance(prompt, str) else ids
|
|
214
|
+
|
|
215
|
+
def summary(self):
|
|
216
|
+
actual = self.parameter_count()
|
|
217
|
+
return {"model_name": self.model_name, "platform": self.platform, "device": self.device,
|
|
218
|
+
"parameters": actual, "requested_parameters": self.requested_parameters,
|
|
219
|
+
"focus": list(self.focus), "focus_scores": self.focus_scores(),
|
|
220
|
+
"dtype": self.requested_dtype, "layers": self.layers,
|
|
221
|
+
"heads": self.heads, "embedding_size": self.embedding_size, "hidden_size": self.hidden_size,
|
|
222
|
+
"context_length": self.context_length, "vocab_size": len(self.vocab),
|
|
223
|
+
"training_steps": self.training_steps, "training_history_length": len(self.training_history),
|
|
224
|
+
"settings": dict(self.settings)}
|
pyintell/optim.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Practical NumPy optimizers for pyintell parameters."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Optimizer:
|
|
7
|
+
"""Stateful optimizer supporting SGD, Adam, AdamW, RMSProp and Adagrad."""
|
|
8
|
+
def __init__(self, parameters, kind="adamw", learning_rate=3e-4, weight_decay=0.0,
|
|
9
|
+
beta1=0.9, beta2=0.999, eps=1e-8, momentum=0.0, clip_norm=None):
|
|
10
|
+
self.parameters = parameters
|
|
11
|
+
self.kind = str(kind).lower()
|
|
12
|
+
self.lr = float(learning_rate)
|
|
13
|
+
self.weight_decay = float(weight_decay)
|
|
14
|
+
self.beta1, self.beta2, self.eps = float(beta1), float(beta2), float(eps)
|
|
15
|
+
self.momentum, self.clip_norm = float(momentum), clip_norm
|
|
16
|
+
self.step_count = 0
|
|
17
|
+
self.m = {}
|
|
18
|
+
self.v = {}
|
|
19
|
+
|
|
20
|
+
def step(self, gradients):
|
|
21
|
+
"""Apply one update to a dict of named NumPy arrays."""
|
|
22
|
+
self.step_count += 1
|
|
23
|
+
total = 0.0
|
|
24
|
+
for name, grad in gradients.items():
|
|
25
|
+
if name not in self.parameters:
|
|
26
|
+
continue
|
|
27
|
+
g = np.asarray(grad, dtype=np.float32)
|
|
28
|
+
if self.clip_norm is not None:
|
|
29
|
+
total += float(np.sum(g * g))
|
|
30
|
+
scale = 1.0
|
|
31
|
+
if self.clip_norm is not None:
|
|
32
|
+
norm = np.sqrt(total)
|
|
33
|
+
if norm > float(self.clip_norm):
|
|
34
|
+
scale = float(self.clip_norm) / max(norm, 1e-12)
|
|
35
|
+
for name, grad in gradients.items():
|
|
36
|
+
if name not in self.parameters:
|
|
37
|
+
continue
|
|
38
|
+
p = self.parameters[name]
|
|
39
|
+
g = np.asarray(grad, dtype=np.float32) * scale
|
|
40
|
+
if self.kind == "sgd":
|
|
41
|
+
if self.momentum:
|
|
42
|
+
self.m[name] = self.momentum * self.m.get(name, np.zeros_like(g)) + g
|
|
43
|
+
g = self.m[name]
|
|
44
|
+
update = g
|
|
45
|
+
elif self.kind in ("adam", "adamw"):
|
|
46
|
+
m = self.m[name] = self.beta1 * self.m.get(name, np.zeros_like(g)) + (1 - self.beta1) * g
|
|
47
|
+
v = self.v[name] = self.beta2 * self.v.get(name, np.zeros_like(g)) + (1 - self.beta2) * (g * g)
|
|
48
|
+
mh = m / (1 - self.beta1 ** self.step_count)
|
|
49
|
+
vh = v / (1 - self.beta2 ** self.step_count)
|
|
50
|
+
update = mh / (np.sqrt(vh) + self.eps)
|
|
51
|
+
if self.kind == "adamw" and self.weight_decay:
|
|
52
|
+
p *= (1.0 - self.lr * self.weight_decay)
|
|
53
|
+
elif self.kind == "rmsprop":
|
|
54
|
+
v = self.v[name] = 0.99 * self.v.get(name, np.zeros_like(g)) + 0.01 * (g * g)
|
|
55
|
+
update = g / (np.sqrt(v) + self.eps)
|
|
56
|
+
elif self.kind == "adagrad":
|
|
57
|
+
v = self.v[name] = self.v.get(name, np.zeros_like(g)) + g * g
|
|
58
|
+
update = g / (np.sqrt(v) + self.eps)
|
|
59
|
+
else:
|
|
60
|
+
raise ValueError(f"unknown optimizer: {self.kind}")
|
|
61
|
+
if self.kind != "adamw" and self.weight_decay:
|
|
62
|
+
update = update + self.weight_decay * p
|
|
63
|
+
p[...] = p - self.lr * update
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
def zero_grad(self):
|
|
67
|
+
return {name: np.zeros_like(value, dtype=np.float32) for name, value in self.parameters.items()}
|
|
68
|
+
|
|
69
|
+
def state_dict(self):
|
|
70
|
+
return {"kind": self.kind, "learning_rate": self.lr, "weight_decay": self.weight_decay,
|
|
71
|
+
"step": self.step_count, "m": self.m, "v": self.v}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def optimizer(parameters=None, kind="adamw", learning_rate=3e-4, weight_decay=0.0, **kwargs):
|
|
75
|
+
return Optimizer(parameters or {}, kind, learning_rate, weight_decay, **kwargs)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def sgd(parameters=None, learning_rate=1e-2, **kwargs): return optimizer(parameters, "sgd", learning_rate, **kwargs)
|
|
79
|
+
def adam(parameters=None, learning_rate=3e-4, **kwargs): return optimizer(parameters, "adam", learning_rate, **kwargs)
|
|
80
|
+
def adamw(parameters=None, learning_rate=3e-4, **kwargs): return optimizer(parameters, "adamw", learning_rate, **kwargs)
|
|
81
|
+
def rmsprop(parameters=None, learning_rate=1e-3, **kwargs): return optimizer(parameters, "rmsprop", learning_rate, **kwargs)
|
|
82
|
+
def adagrad(parameters=None, learning_rate=1e-2, **kwargs): return optimizer(parameters, "adagrad", learning_rate, **kwargs)
|
|
83
|
+
|
|
84
|
+
def update_weights(weights, gradients, learning_rate=3e-4, weight_decay=0.0):
|
|
85
|
+
return np.asarray(weights) - learning_rate * (np.asarray(gradients) + weight_decay * np.asarray(weights))
|
|
86
|
+
|
|
87
|
+
def step(weights, gradients, learning_rate=3e-4, weight_decay=0.0):
|
|
88
|
+
return update_weights(weights, gradients, learning_rate, weight_decay)
|
|
89
|
+
|
|
90
|
+
def zero_grad(gradients):
|
|
91
|
+
return {k: np.zeros_like(v) for k, v in gradients.items()} if isinstance(gradients, dict) else np.zeros_like(gradients)
|
|
92
|
+
|
|
93
|
+
def learning_rate(optimizer_state):
|
|
94
|
+
return float(optimizer_state.lr if hasattr(optimizer_state, "lr") else optimizer_state.get("learning_rate", 0.0))
|
pyintell/quantization.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Simple NumPy quantization and compression helpers."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def quantize(x, bits=8):
|
|
7
|
+
x = np.asarray(x, dtype=np.float32)
|
|
8
|
+
if bits == 8:
|
|
9
|
+
scale = max(float(np.max(np.abs(x))) / 127.0, 1e-12); return np.round(x / scale).astype(np.int8), scale
|
|
10
|
+
if bits == 4:
|
|
11
|
+
scale = max(float(np.max(np.abs(x))) / 7.0, 1e-12); return np.clip(np.round(x / scale), -8, 7).astype(np.int8), scale
|
|
12
|
+
raise ValueError("bits must be 8 or 4")
|
|
13
|
+
|
|
14
|
+
def dequantize(values, scale): return np.asarray(values, dtype=np.float32) * scale
|
|
15
|
+
def int8(x): return quantize(x, 8)
|
|
16
|
+
def int4(x): return quantize(x, 4)
|
|
17
|
+
def float16(x): return np.asarray(x, dtype=np.float16)
|
|
18
|
+
def bfloat16(x): return np.asarray(x, dtype=np.float32)
|
|
19
|
+
def prune(x, threshold=0.0): return np.where(np.abs(x) < threshold, 0, x)
|
|
20
|
+
def sparsify(x, threshold=0.0): return prune(x, threshold)
|
|
21
|
+
def compress(x): return np.asarray(x).tobytes()
|
|
22
|
+
def decompress(data, dtype=np.float32): return np.frombuffer(data, dtype=dtype)
|
|
23
|
+
def low_rank(x, rank):
|
|
24
|
+
u, s, vh = np.linalg.svd(np.asarray(x), full_matrices=False); return u[:, :rank], s[:rank], vh[:rank, :]
|
|
25
|
+
def factorize(x, rank): return low_rank(x, rank)
|
pyintell/scheduling.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Learning-rate and training scheduling helpers."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def learning_rate(initial, step=0): return float(initial)
|
|
7
|
+
def lr_scheduler(initial, kind="constant", total_steps=1, step=0):
|
|
8
|
+
if kind == "constant": return float(initial)
|
|
9
|
+
if kind == "linear": return linear_decay(initial, total_steps, step)
|
|
10
|
+
if kind == "cosine": return cosine_decay(initial, total_steps, step)
|
|
11
|
+
raise ValueError(f"unknown scheduler: {kind}")
|
|
12
|
+
|
|
13
|
+
def constant_lr(initial, step=0): return float(initial)
|
|
14
|
+
def linear_decay(initial, total_steps, step): return max(0.0, initial * (1.0 - min(step, total_steps) / max(total_steps, 1)))
|
|
15
|
+
def cosine_decay(initial, total_steps, step): return initial * 0.5 * (1 + math.cos(math.pi * min(step, total_steps) / max(total_steps, 1)))
|
|
16
|
+
def warmup(initial, warmup_steps, step): return initial * min(1.0, (step + 1) / max(warmup_steps, 1))
|