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/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""pyintell: a modular NumPy-based framework for building AI models."""
|
|
2
|
+
|
|
3
|
+
from .tokenization import *
|
|
4
|
+
from .embeddings import *
|
|
5
|
+
from .attention import *
|
|
6
|
+
from .layers import *
|
|
7
|
+
from .transformer import *
|
|
8
|
+
from .loss import *
|
|
9
|
+
from .optim import *
|
|
10
|
+
from .training import *
|
|
11
|
+
from .generation import *
|
|
12
|
+
from .system import *
|
|
13
|
+
from .autograd import *
|
|
14
|
+
from .utilities import *
|
|
15
|
+
from .serialization import *
|
|
16
|
+
from .quantization import *
|
|
17
|
+
from .scheduling import *
|
|
18
|
+
from .finetuning import *
|
|
19
|
+
from .focus import SUPPORTED_FOCUSES, FOCUS_PROFILES, normalize_focus, build_focus_config, focus_description
|
|
20
|
+
from .model import Model
|
|
21
|
+
from .builder import build
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
|
|
25
|
+
# Explicitly expose the high-level model-management API.
|
|
26
|
+
save_model = save_model
|
|
27
|
+
load_model = load_model
|
|
28
|
+
generate = generate
|
|
29
|
+
model_run = model_run
|
|
30
|
+
|
|
31
|
+
__all__ = [name for name in globals() if not name.startswith("_")]
|
pyintell/attention.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Numerically stable attention primitives used by pyintell."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _softmax(x):
|
|
7
|
+
x = np.asarray(x, dtype=np.float32)
|
|
8
|
+
x = x - np.max(x, axis=-1, keepdims=True)
|
|
9
|
+
e = np.exp(np.clip(x, -80.0, 80.0))
|
|
10
|
+
return e / np.maximum(np.sum(e, axis=-1, keepdims=True), 1e-12)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def attention(query, key, value, mask=None, dropout=0.0):
|
|
14
|
+
"""Scaled dot-product attention with stable masking."""
|
|
15
|
+
q, k, v = map(lambda a: np.asarray(a, dtype=np.float32), (query, key, value))
|
|
16
|
+
depth = q.shape[-1]
|
|
17
|
+
scores = q @ np.swapaxes(k, -1, -2) / np.sqrt(max(depth, 1))
|
|
18
|
+
if mask is not None:
|
|
19
|
+
m = np.asarray(mask, dtype=bool)
|
|
20
|
+
scores = np.where(m, scores, -1e9)
|
|
21
|
+
weights = _softmax(scores)
|
|
22
|
+
if dropout:
|
|
23
|
+
raise NotImplementedError("dropout is intentionally disabled in the NumPy inference core")
|
|
24
|
+
return weights @ v
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def scaled_dot_product_attention(query, key, value, mask=None):
|
|
28
|
+
return attention(query, key, value, mask)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def self_attention(x, mask=None):
|
|
32
|
+
return attention(x, x, x, mask)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def cross_attention(query, context, mask=None):
|
|
36
|
+
return attention(query, context, context, mask)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def causal_mask(length):
|
|
40
|
+
return np.tril(np.ones((int(length), int(length)), dtype=bool))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def padding_mask(ids, pad_id=0):
|
|
44
|
+
return np.asarray(ids) != pad_id
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def attention_mask(length, causal=False):
|
|
48
|
+
return causal_mask(length) if causal else np.ones((length, length), dtype=bool)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def causal_attention(x):
|
|
52
|
+
return attention(x, x, x, causal_mask(x.shape[-2]))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def multihead_attention(x, heads=8, mask=None, weights=None):
|
|
56
|
+
"""Multi-head attention.
|
|
57
|
+
|
|
58
|
+
When ``weights`` is supplied it must contain q/k/v/o projection matrices.
|
|
59
|
+
This makes attention deterministic and trainable instead of regenerating
|
|
60
|
+
random projections on every forward pass.
|
|
61
|
+
"""
|
|
62
|
+
x = np.asarray(x, dtype=np.float32)
|
|
63
|
+
heads = int(heads)
|
|
64
|
+
if heads < 1 or x.shape[-1] % heads:
|
|
65
|
+
raise ValueError("embedding dimension must be divisible by heads")
|
|
66
|
+
d_model = x.shape[-1]
|
|
67
|
+
if weights is None:
|
|
68
|
+
q = k = v = x
|
|
69
|
+
output = np.concatenate(
|
|
70
|
+
[attention(a, a, a, mask) for a in np.split(x, heads, axis=-1)], axis=-1
|
|
71
|
+
)
|
|
72
|
+
return output
|
|
73
|
+
q = x @ weights["q"] + weights.get("q_bias", 0.0)
|
|
74
|
+
k = x @ weights["k"] + weights.get("k_bias", 0.0)
|
|
75
|
+
v = x @ weights["v"] + weights.get("v_bias", 0.0)
|
|
76
|
+
d_head = d_model // heads
|
|
77
|
+
q = q.reshape(x.shape[0], heads, d_head).transpose(1, 0, 2)
|
|
78
|
+
k = k.reshape(x.shape[0], heads, d_head).transpose(1, 0, 2)
|
|
79
|
+
v = v.reshape(x.shape[0], heads, d_head).transpose(1, 0, 2)
|
|
80
|
+
attended = attention(q, k, v, mask)
|
|
81
|
+
attended = attended.transpose(1, 0, 2).reshape(x.shape[0], d_model)
|
|
82
|
+
return attended @ weights["o"] + weights.get("o_bias", 0.0)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def multi_query_attention(x, heads=8, mask=None):
|
|
86
|
+
return multihead_attention(x, heads, mask)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def grouped_query_attention(x, heads=8, mask=None):
|
|
90
|
+
return multihead_attention(x, heads, mask)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def local_attention(x, window=128):
|
|
94
|
+
length = x.shape[-2]
|
|
95
|
+
mask = np.zeros((length, length), dtype=bool)
|
|
96
|
+
for i in range(length):
|
|
97
|
+
mask[i, max(0, i - int(window) + 1):i + 1] = True
|
|
98
|
+
return attention(x, x, x, mask)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def sliding_window_attention(x, window=128):
|
|
102
|
+
return local_attention(x, window)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def global_attention(x, mask=None):
|
|
106
|
+
return attention(x, x, x, mask)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def sparse_attention(x, mask):
|
|
110
|
+
return attention(x, x, x, mask)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def block_attention(x, block_size=64):
|
|
114
|
+
return local_attention(x, block_size)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def rotary_attention(x, mask=None):
|
|
118
|
+
return self_attention(x, mask)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def alibi_attention(x, mask=None):
|
|
122
|
+
return self_attention(x, mask)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def flash_attention(query, key, value, mask=None):
|
|
126
|
+
return attention(query, key, value, mask)
|
pyintell/autograd.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Small numerical automatic-differentiation helpers.
|
|
2
|
+
|
|
3
|
+
This module intentionally stays lightweight; it provides finite-difference
|
|
4
|
+
helpers for experiments rather than a production reverse-mode engine.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def gradient(function, x, epsilon=1e-5):
|
|
11
|
+
x = np.asarray(x, dtype=float)
|
|
12
|
+
result = np.zeros_like(x)
|
|
13
|
+
for index in np.ndindex(x.shape):
|
|
14
|
+
plus = x.copy(); minus = x.copy()
|
|
15
|
+
plus[index] += epsilon; minus[index] -= epsilon
|
|
16
|
+
result[index] = (function(plus) - function(minus)) / (2 * epsilon)
|
|
17
|
+
return result
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def compute_gradients(function, x, epsilon=1e-5):
|
|
21
|
+
return gradient(function, x, epsilon)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def numerical_gradient(function, x, epsilon=1e-5):
|
|
25
|
+
return gradient(function, x, epsilon)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def backward(function, x, epsilon=1e-5):
|
|
29
|
+
return gradient(function, x, epsilon)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def requires_grad(x, value=True):
|
|
33
|
+
array = np.asarray(x)
|
|
34
|
+
setattr(array, "requires_grad", value) if hasattr(array, "__dict__") else None
|
|
35
|
+
return array
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def detach(x):
|
|
39
|
+
return np.array(x, copy=True)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def no_grad(function):
|
|
43
|
+
return function
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def zero_grad(gradients):
|
|
47
|
+
if isinstance(gradients, dict):
|
|
48
|
+
for key, value in gradients.items(): gradients[key] = np.zeros_like(value)
|
|
49
|
+
return gradients
|
|
50
|
+
return np.zeros_like(gradients)
|
pyintell/builder.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""High-level model builder."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
from .model import Model
|
|
6
|
+
from .system import ram, storage_info
|
|
7
|
+
from .serialization import set_current_model
|
|
8
|
+
|
|
9
|
+
_SUPPORTED_FOCUS = {
|
|
10
|
+
"intelligence", "natural", "coding", "reasoning", "math", "knowledge",
|
|
11
|
+
"creativity", "conversation", "instruction", "accuracy", "speed", "memory",
|
|
12
|
+
"context", "language", "translation", "summarization", "classification", "roleplay",
|
|
13
|
+
}
|
|
14
|
+
_SUPPORTED_DTYPES = {"float64", "float32", "float16", "bfloat16", "int8", "int4"}
|
|
15
|
+
_SUPPORTED_PLATFORMS = {"auto", "cpu", "gpu", "cuda", "mps", "rocm", "tpu", "android", "mobile"}
|
|
16
|
+
|
|
17
|
+
# Focus is a model specialization, not merely a label. These profiles describe
|
|
18
|
+
# the capabilities the builder should prioritize when choosing defaults.
|
|
19
|
+
_FOCUS_PROFILES = {
|
|
20
|
+
"intelligence": {"reasoning": 1.0, "knowledge": 0.9, "accuracy": 0.9},
|
|
21
|
+
"natural": {"language": 1.0, "conversation": 0.8, "creativity": 0.6},
|
|
22
|
+
"coding": {"reasoning": 1.0, "instruction": 0.9, "accuracy": 0.9},
|
|
23
|
+
"reasoning": {"reasoning": 1.0, "accuracy": 0.9, "math": 0.8},
|
|
24
|
+
"math": {"math": 1.0, "reasoning": 0.9, "accuracy": 0.9},
|
|
25
|
+
"knowledge": {"knowledge": 1.0, "memory": 0.8, "accuracy": 0.8},
|
|
26
|
+
"creativity": {"creativity": 1.0, "language": 0.8, "natural": 0.7},
|
|
27
|
+
"conversation": {"conversation": 1.0, "natural": 0.9, "instruction": 0.7},
|
|
28
|
+
"instruction": {"instruction": 1.0, "accuracy": 0.9, "conversation": 0.6},
|
|
29
|
+
"accuracy": {"accuracy": 1.0, "reasoning": 0.8, "knowledge": 0.7},
|
|
30
|
+
"speed": {"speed": 1.0, "efficiency": 0.9},
|
|
31
|
+
"memory": {"memory": 1.0, "knowledge": 0.8, "context": 0.7},
|
|
32
|
+
"context": {"context": 1.0, "memory": 0.8, "language": 0.6},
|
|
33
|
+
"language": {"language": 1.0, "natural": 0.8, "translation": 0.7},
|
|
34
|
+
"translation": {"translation": 1.0, "language": 0.9, "accuracy": 0.8},
|
|
35
|
+
"summarization": {"summarization": 1.0, "language": 0.8, "accuracy": 0.7},
|
|
36
|
+
"classification": {"classification": 1.0, "accuracy": 0.9, "instruction": 0.6},
|
|
37
|
+
"roleplay": {"roleplay": 1.0, "conversation": 0.9, "creativity": 0.8},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _dtype_bytes(dtype):
|
|
42
|
+
return {"float64": 8, "float32": 4, "float16": 2, "bfloat16": 2, "int8": 1, "int4": 0.5}[dtype]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _normalize_focus(focus):
|
|
46
|
+
focuses = [focus] if isinstance(focus, str) else list(focus)
|
|
47
|
+
if not focuses:
|
|
48
|
+
raise ValueError("focus must contain at least one focus")
|
|
49
|
+
normalized = []
|
|
50
|
+
for item in focuses:
|
|
51
|
+
if not isinstance(item, str):
|
|
52
|
+
raise TypeError("focus values must be strings")
|
|
53
|
+
value = item.strip().lower()
|
|
54
|
+
if value not in _SUPPORTED_FOCUS:
|
|
55
|
+
raise ValueError(f"unsupported focus values: {[value]}")
|
|
56
|
+
if value not in normalized:
|
|
57
|
+
normalized.append(value)
|
|
58
|
+
return normalized
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _build_focus_config(focuses):
|
|
62
|
+
priorities = {}
|
|
63
|
+
for focus in focuses:
|
|
64
|
+
for capability, weight in _FOCUS_PROFILES[focus].items():
|
|
65
|
+
priorities[capability] = priorities.get(capability, 0.0) + weight
|
|
66
|
+
scale = max(priorities.values(), default=1.0)
|
|
67
|
+
priorities = {key: round(value / scale, 4) for key, value in priorities.items()}
|
|
68
|
+
return {"focuses": list(focuses), "priorities": priorities}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _estimate_architecture(target, vocab_size, settings, focus_config):
|
|
72
|
+
# Focus influences defaults only; explicit architecture settings always win.
|
|
73
|
+
priorities = focus_config["priorities"]
|
|
74
|
+
reasoning_boost = max(priorities.get("reasoning", 0.0), priorities.get("math", 0.0))
|
|
75
|
+
context_boost = priorities.get("context", 0.0)
|
|
76
|
+
speed_boost = priorities.get("speed", 0.0)
|
|
77
|
+
default_layers = round((target / 2.0e6) ** 0.45)
|
|
78
|
+
default_layers += int(round(reasoning_boost * 2))
|
|
79
|
+
default_layers -= int(round(speed_boost))
|
|
80
|
+
layers = max(1, int(settings.get("layers", default_layers)))
|
|
81
|
+
|
|
82
|
+
heads_default = 4 if target < 10_000_000 else 8
|
|
83
|
+
heads_default += int(round(reasoning_boost))
|
|
84
|
+
heads = max(1, int(settings.get("heads", heads_default)))
|
|
85
|
+
|
|
86
|
+
embedding_default = round((target / max(vocab_size, 1)) ** 0.5 * 8)
|
|
87
|
+
if context_boost:
|
|
88
|
+
embedding_default += int(round(embedding_default * 0.05 * context_boost))
|
|
89
|
+
embedding_size = max(32, int(settings.get("embedding_size", embedding_default)))
|
|
90
|
+
embedding_size = max(heads, (embedding_size // heads) * heads)
|
|
91
|
+
|
|
92
|
+
hidden_multiplier = 4
|
|
93
|
+
if reasoning_boost:
|
|
94
|
+
hidden_multiplier += int(round(reasoning_boost))
|
|
95
|
+
hidden_size = max(embedding_size, int(settings.get("hidden_size", embedding_size * hidden_multiplier)))
|
|
96
|
+
estimated = vocab_size * embedding_size + layers * (4 * embedding_size**2 + 2 * embedding_size * hidden_size) + embedding_size * vocab_size
|
|
97
|
+
return {"layers": layers, "heads": heads, "embedding_size": embedding_size, "hidden_size": hidden_size, "estimated_parameters": estimated}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _validate_positive_int(settings, key, default, minimum=1):
|
|
101
|
+
value = int(settings.get(key, default))
|
|
102
|
+
if value < minimum:
|
|
103
|
+
raise ValueError(f"settings['{key}'] must be at least {minimum}")
|
|
104
|
+
return value
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _validate_positive_float(settings, key, default):
|
|
108
|
+
value = float(settings.get(key, default))
|
|
109
|
+
if value <= 0:
|
|
110
|
+
raise ValueError(f"settings['{key}'] must be positive")
|
|
111
|
+
return value
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def build(vocab, reverse_vocab, dataset, parameters, focus, dtype=None, settings=None, model_name=None):
|
|
115
|
+
"""Build a configurable, focus-specialized Transformer model.
|
|
116
|
+
|
|
117
|
+
``focus`` selects the capabilities the model should prioritize. Multiple
|
|
118
|
+
focuses can be supplied and are merged into a normalized priority map.
|
|
119
|
+
Focus changes architecture defaults when explicit architecture settings are
|
|
120
|
+
omitted and is persisted in the model for training/inference tooling.
|
|
121
|
+
"""
|
|
122
|
+
if not isinstance(vocab, dict) or not isinstance(reverse_vocab, dict):
|
|
123
|
+
raise TypeError("vocab and reverse_vocab must be dictionaries")
|
|
124
|
+
if not isinstance(parameters, int) or isinstance(parameters, bool) or parameters <= 0:
|
|
125
|
+
raise ValueError("parameters must be a positive integer")
|
|
126
|
+
if settings is None:
|
|
127
|
+
settings = {}
|
|
128
|
+
elif not isinstance(settings, dict):
|
|
129
|
+
raise TypeError("settings must be a dictionary or None")
|
|
130
|
+
else:
|
|
131
|
+
settings = dict(settings)
|
|
132
|
+
dtype = "float32" if dtype is None else str(dtype).lower()
|
|
133
|
+
if dtype not in _SUPPORTED_DTYPES:
|
|
134
|
+
raise ValueError(f"unsupported dtype: {dtype}")
|
|
135
|
+
|
|
136
|
+
focuses = _normalize_focus(focus)
|
|
137
|
+
focus_config = _build_focus_config(focuses)
|
|
138
|
+
|
|
139
|
+
if model_name is not None:
|
|
140
|
+
if not isinstance(model_name, str):
|
|
141
|
+
raise TypeError("model_name must be a string or None")
|
|
142
|
+
model_name = model_name.strip()
|
|
143
|
+
if not model_name:
|
|
144
|
+
raise ValueError("model_name must not be empty")
|
|
145
|
+
|
|
146
|
+
platform = str(settings.get("platform", "auto")).lower()
|
|
147
|
+
if platform not in _SUPPORTED_PLATFORMS:
|
|
148
|
+
raise ValueError(f"unsupported platform: {platform}")
|
|
149
|
+
device = settings.get("device")
|
|
150
|
+
seed = settings.get("seed")
|
|
151
|
+
if seed is not None:
|
|
152
|
+
if isinstance(seed, bool):
|
|
153
|
+
raise TypeError("settings['seed'] must be an integer or None")
|
|
154
|
+
seed = int(seed)
|
|
155
|
+
batch_size = _validate_positive_int(settings, "batch_size", 1)
|
|
156
|
+
context_length = _validate_positive_int(settings, "context_length", 512)
|
|
157
|
+
epochs = _validate_positive_int(settings, "epochs", 1)
|
|
158
|
+
gradient_accumulation = _validate_positive_int(settings, "gradient_accumulation", 1)
|
|
159
|
+
learning_rate = _validate_positive_float(settings, "learning_rate", 3e-4)
|
|
160
|
+
weight_decay = float(settings.get("weight_decay", 0.0))
|
|
161
|
+
gradient_clip = float(settings.get("gradient_clip", 1.0))
|
|
162
|
+
dropout = float(settings.get("dropout", 0.0))
|
|
163
|
+
if weight_decay < 0:
|
|
164
|
+
raise ValueError("settings['weight_decay'] must not be negative")
|
|
165
|
+
if gradient_clip < 0:
|
|
166
|
+
raise ValueError("settings['gradient_clip'] must not be negative")
|
|
167
|
+
if not 0 <= dropout < 1:
|
|
168
|
+
raise ValueError("settings['dropout'] must be in [0, 1)")
|
|
169
|
+
optimizer = str(settings.get("optimizer", "adamw")).lower()
|
|
170
|
+
if optimizer not in {"sgd", "adam", "adamw", "rmsprop", "adagrad", "adadelta"}:
|
|
171
|
+
raise ValueError(f"unsupported optimizer: {optimizer}")
|
|
172
|
+
|
|
173
|
+
architecture = _estimate_architecture(parameters, len(vocab), settings, focus_config)
|
|
174
|
+
weight_bytes = parameters * _dtype_bytes(dtype)
|
|
175
|
+
required_storage = int(math.ceil(weight_bytes * 1.25))
|
|
176
|
+
required_ram = int(math.ceil(weight_bytes * (2.0 if dtype in {"int4", "int8"} else 4.0)))
|
|
177
|
+
available_ram = ram().get("available")
|
|
178
|
+
available_storage = storage_info().get("free")
|
|
179
|
+
if available_ram is not None and required_ram > available_ram:
|
|
180
|
+
raise MemoryError(f"requested model may require about {required_ram / 2**30:.2f} GiB RAM, but only {available_ram / 2**30:.2f} GiB is available")
|
|
181
|
+
if available_storage is not None and required_storage > available_storage:
|
|
182
|
+
raise OSError(f"requested model may require about {required_storage / 2**30:.2f} GiB storage, but only {available_storage / 2**30:.2f} GiB is free")
|
|
183
|
+
|
|
184
|
+
final_settings = dict(settings)
|
|
185
|
+
final_settings.update({
|
|
186
|
+
"model_name": model_name or settings.get("model_name", "pyintell_model"),
|
|
187
|
+
"platform": platform,
|
|
188
|
+
"device": device,
|
|
189
|
+
"seed": seed,
|
|
190
|
+
"layers": architecture["layers"],
|
|
191
|
+
"heads": architecture["heads"],
|
|
192
|
+
"embedding_size": architecture["embedding_size"],
|
|
193
|
+
"hidden_size": architecture["hidden_size"],
|
|
194
|
+
"context_length": context_length,
|
|
195
|
+
"batch_size": batch_size,
|
|
196
|
+
"learning_rate": learning_rate,
|
|
197
|
+
"epochs": epochs,
|
|
198
|
+
"optimizer": optimizer,
|
|
199
|
+
"weight_decay": weight_decay,
|
|
200
|
+
"gradient_clip": gradient_clip,
|
|
201
|
+
"gradient_accumulation": gradient_accumulation,
|
|
202
|
+
"dropout": dropout,
|
|
203
|
+
"focus_config": focus_config,
|
|
204
|
+
"estimated_parameters": architecture["estimated_parameters"],
|
|
205
|
+
"estimated_weight_bytes": int(weight_bytes),
|
|
206
|
+
"estimated_ram_bytes": required_ram,
|
|
207
|
+
"estimated_storage_bytes": required_storage,
|
|
208
|
+
})
|
|
209
|
+
model = Model(vocab, reverse_vocab, dataset, parameters, focuses, dtype, final_settings)
|
|
210
|
+
set_current_model(model)
|
|
211
|
+
return model
|
pyintell/embeddings.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Embedding layers implemented with NumPy."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def embedding(tokens, dimensions=128, weights=None, vocab_size=None):
|
|
7
|
+
ids = np.asarray(tokens, dtype=np.int64)
|
|
8
|
+
if weights is None:
|
|
9
|
+
if vocab_size is None: vocab_size = int(ids.max()) + 1 if ids.size else 1
|
|
10
|
+
weights = np.random.randn(vocab_size, dimensions).astype(np.float32) * 0.02
|
|
11
|
+
return weights[ids]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def positional_embedding(sequence_length, dimensions, weights=None):
|
|
15
|
+
if weights is None: weights = np.random.randn(sequence_length, dimensions).astype(np.float32) * 0.02
|
|
16
|
+
return weights[:sequence_length]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def sinusoidal_embedding(sequence_length, dimensions):
|
|
20
|
+
positions = np.arange(sequence_length)[:, None]
|
|
21
|
+
div = np.exp(np.arange(0, dimensions, 2) * (-np.log(10000.0) / dimensions))
|
|
22
|
+
result = np.zeros((sequence_length, dimensions), dtype=np.float32)
|
|
23
|
+
result[:, 0::2] = np.sin(positions * div)
|
|
24
|
+
result[:, 1::2] = np.cos(positions * div[:result[:, 1::2].shape[1]])
|
|
25
|
+
return result
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def rotary_embedding(sequence_length, dimensions): return sinusoidal_embedding(sequence_length, dimensions)
|
|
29
|
+
def position_embedding(sequence_length, dimensions): return positional_embedding(sequence_length, dimensions)
|
|
30
|
+
|
|
31
|
+
def embedding_similarity(a, b):
|
|
32
|
+
a = np.asarray(a); b = np.asarray(b)
|
|
33
|
+
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-12))
|
pyintell/finetuning.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Fine-tuning helpers and configuration primitives."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def freeze(model): setattr(model, "frozen", True); return model
|
|
7
|
+
def unfreeze(model): setattr(model, "frozen", False); return model
|
|
8
|
+
def freeze_layers(model, layers=None): setattr(model, "frozen_layers", layers); return model
|
|
9
|
+
def unfreeze_layers(model, layers=None): setattr(model, "frozen_layers", []); return model
|
|
10
|
+
def trainable(model): return not getattr(model, "frozen", False)
|
|
11
|
+
def finetune(model, dataset, **settings): setattr(model, "finetune_settings", dict(settings)); return model
|
|
12
|
+
|
|
13
|
+
def lora(model, rank=8, alpha=16): setattr(model, "lora", {"rank": rank, "alpha": alpha}); return model
|
|
14
|
+
def qlora(model, rank=8, alpha=16): setattr(model, "qlora", {"rank": rank, "alpha": alpha}); return model
|
|
15
|
+
def adapter(model, size=64): setattr(model, "adapter_size", size); return model
|
|
16
|
+
def prompt_tuning(model, tokens=20): setattr(model, "prompt_tuning_tokens", tokens); return model
|
|
17
|
+
def prefix_tuning(model, tokens=20): setattr(model, "prefix_tuning_tokens", tokens); return model
|
|
18
|
+
|
|
19
|
+
def parameter_efficient_finetuning(model, method="lora", **kwargs):
|
|
20
|
+
methods = {"lora": lora, "qlora": qlora, "adapter": adapter, "prompt_tuning": prompt_tuning, "prefix_tuning": prefix_tuning}
|
|
21
|
+
if method not in methods: raise ValueError(f"unknown fine-tuning method: {method}")
|
|
22
|
+
return methods[method](model, **kwargs)
|
|
23
|
+
|
|
24
|
+
def gradient_clipping(gradients, max_norm=1.0):
|
|
25
|
+
norm = np.sqrt(sum(np.sum(np.asarray(g) ** 2) for g in gradients.values())) if isinstance(gradients, dict) else np.linalg.norm(gradients)
|
|
26
|
+
scale = min(1.0, max_norm / (norm + 1e-12)); return {k: v*scale for k,v in gradients.items()} if isinstance(gradients, dict) else gradients*scale
|
|
27
|
+
|
|
28
|
+
def weight_decay(value, rate=0.01): return value * (1-rate)
|
|
29
|
+
def ema(previous, current, decay=0.99): return decay * previous + (1-decay) * current
|
pyintell/focus.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Alpha focus/capability utilities for pyintell.
|
|
2
|
+
|
|
3
|
+
Focus is intentionally lightweight in 0.1.x. It describes intended model
|
|
4
|
+
specialization and provides metadata/priorities for future training systems.
|
|
5
|
+
It does not claim that a focus alone makes a model capable.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
SUPPORTED_FOCUSES = (
|
|
9
|
+
"intelligence", "natural", "coding", "reasoning", "math", "knowledge",
|
|
10
|
+
"creativity", "conversation", "instruction", "accuracy", "speed", "memory",
|
|
11
|
+
"context", "language", "translation", "summarization", "classification", "roleplay",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
FOCUS_PROFILES = {
|
|
15
|
+
"intelligence": {"reasoning": 1.0, "knowledge": 0.9, "accuracy": 0.9},
|
|
16
|
+
"natural": {"language": 1.0, "conversation": 0.8, "creativity": 0.6},
|
|
17
|
+
"coding": {"reasoning": 1.0, "instruction": 0.9, "accuracy": 0.9},
|
|
18
|
+
"reasoning": {"reasoning": 1.0, "accuracy": 0.9, "math": 0.8},
|
|
19
|
+
"math": {"math": 1.0, "reasoning": 0.9, "accuracy": 0.9},
|
|
20
|
+
"knowledge": {"knowledge": 1.0, "memory": 0.8, "accuracy": 0.8},
|
|
21
|
+
"creativity": {"creativity": 1.0, "language": 0.8, "natural": 0.7},
|
|
22
|
+
"conversation": {"conversation": 1.0, "natural": 0.9, "instruction": 0.7},
|
|
23
|
+
"instruction": {"instruction": 1.0, "accuracy": 0.9, "conversation": 0.6},
|
|
24
|
+
"accuracy": {"accuracy": 1.0, "reasoning": 0.8, "knowledge": 0.7},
|
|
25
|
+
"speed": {"speed": 1.0, "efficiency": 0.9},
|
|
26
|
+
"memory": {"memory": 1.0, "knowledge": 0.8, "context": 0.7},
|
|
27
|
+
"context": {"context": 1.0, "memory": 0.8, "language": 0.6},
|
|
28
|
+
"language": {"language": 1.0, "natural": 0.8, "translation": 0.7},
|
|
29
|
+
"translation": {"translation": 1.0, "language": 0.9, "accuracy": 0.8},
|
|
30
|
+
"summarization": {"summarization": 1.0, "language": 0.8, "accuracy": 0.7},
|
|
31
|
+
"classification": {"classification": 1.0, "accuracy": 0.9, "instruction": 0.6},
|
|
32
|
+
"roleplay": {"roleplay": 1.0, "conversation": 0.9, "creativity": 0.8},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def normalize_focus(focus):
|
|
37
|
+
"""Normalize and validate one or more focus names."""
|
|
38
|
+
if isinstance(focus, str):
|
|
39
|
+
values = [focus]
|
|
40
|
+
else:
|
|
41
|
+
try:
|
|
42
|
+
values = list(focus)
|
|
43
|
+
except TypeError as exc:
|
|
44
|
+
raise TypeError("focus must be a string or iterable of strings") from exc
|
|
45
|
+
if not values:
|
|
46
|
+
raise ValueError("focus must contain at least one focus")
|
|
47
|
+
|
|
48
|
+
result = []
|
|
49
|
+
for value in values:
|
|
50
|
+
if not isinstance(value, str):
|
|
51
|
+
raise TypeError("focus values must be strings")
|
|
52
|
+
value = value.strip().lower()
|
|
53
|
+
if value not in SUPPORTED_FOCUSES:
|
|
54
|
+
raise ValueError(f"unsupported focus: {value}")
|
|
55
|
+
if value not in result:
|
|
56
|
+
result.append(value)
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_focus_config(focus):
|
|
61
|
+
"""Build a serializable alpha focus configuration."""
|
|
62
|
+
focuses = normalize_focus(focus)
|
|
63
|
+
priorities = {}
|
|
64
|
+
for name in focuses:
|
|
65
|
+
for capability, weight in FOCUS_PROFILES[name].items():
|
|
66
|
+
priorities[capability] = priorities.get(capability, 0.0) + weight
|
|
67
|
+
|
|
68
|
+
maximum = max(priorities.values(), default=1.0)
|
|
69
|
+
priorities = {name: round(value / maximum, 4) for name, value in priorities.items()}
|
|
70
|
+
return {"focuses": focuses, "priorities": priorities}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def focus_description(focus):
|
|
74
|
+
"""Return a small human-readable description of a focus selection."""
|
|
75
|
+
config = build_focus_config(focus)
|
|
76
|
+
ranked = sorted(config["priorities"].items(), key=lambda item: (-item[1], item[0]))
|
|
77
|
+
return {
|
|
78
|
+
"focuses": config["focuses"],
|
|
79
|
+
"top_capabilities": [name for name, _ in ranked[:5]],
|
|
80
|
+
"priorities": config["priorities"],
|
|
81
|
+
}
|
pyintell/generation.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Generation and sampling utilities."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from .serialization import get_model
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def sample(logits, temperature=1.0, top_k=None, rng=None):
|
|
9
|
+
"""Sample one token ID from a logits vector."""
|
|
10
|
+
logits = np.asarray(logits, dtype=np.float64)
|
|
11
|
+
if logits.ndim != 1 or logits.size == 0:
|
|
12
|
+
raise ValueError("logits must be a non-empty one-dimensional array")
|
|
13
|
+
if temperature <= 0:
|
|
14
|
+
raise ValueError("temperature must be greater than zero")
|
|
15
|
+
scaled = logits / temperature
|
|
16
|
+
if top_k is not None:
|
|
17
|
+
if isinstance(top_k, bool):
|
|
18
|
+
raise TypeError("top_k must be an integer")
|
|
19
|
+
top_k = int(top_k)
|
|
20
|
+
if top_k <= 0: raise ValueError("top_k must be greater than zero")
|
|
21
|
+
top_k = min(top_k, scaled.size)
|
|
22
|
+
indices = np.argpartition(scaled, -top_k)[-top_k:]
|
|
23
|
+
filtered = np.full_like(scaled, -np.inf)
|
|
24
|
+
filtered[indices] = scaled[indices]
|
|
25
|
+
scaled = filtered
|
|
26
|
+
scaled -= np.max(scaled)
|
|
27
|
+
probabilities = np.exp(scaled)
|
|
28
|
+
total = probabilities.sum()
|
|
29
|
+
if not np.isfinite(total) or total <= 0:
|
|
30
|
+
raise ValueError("logits produced invalid sampling probabilities")
|
|
31
|
+
probabilities /= total
|
|
32
|
+
generator = rng or np.random.default_rng()
|
|
33
|
+
return int(generator.choice(len(probabilities), p=probabilities))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def generate(model_name, prompt, max_tokens=50, temperature=1.0, top_k=None, **kwargs):
|
|
37
|
+
"""Generate a response using a saved model name."""
|
|
38
|
+
model = get_model(model_name)
|
|
39
|
+
if not hasattr(model, "generate"):
|
|
40
|
+
raise TypeError("saved object is not a valid pyintell model")
|
|
41
|
+
return model.generate(prompt, max_tokens=max_tokens, temperature=temperature, top_k=top_k, **kwargs)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def model_run(model_name, max_tokens=50, temperature=1.0, top_k=None, **kwargs):
|
|
45
|
+
"""Run an interactive terminal chat session with a saved model.
|
|
46
|
+
|
|
47
|
+
Invalid prompts or generation errors are reported without terminating the
|
|
48
|
+
entire chat session. Enter ``exit``, ``quit``, ``/exit``, or ``/quit`` to
|
|
49
|
+
stop the session.
|
|
50
|
+
"""
|
|
51
|
+
# Validate/load once before opening the interactive session.
|
|
52
|
+
get_model(model_name)
|
|
53
|
+
print(f"pyintell model '{model_name}' is running.")
|
|
54
|
+
print("Type 'exit' or 'quit' to stop.")
|
|
55
|
+
|
|
56
|
+
while True:
|
|
57
|
+
try:
|
|
58
|
+
prompt = input("You: ")
|
|
59
|
+
except (EOFError, KeyboardInterrupt):
|
|
60
|
+
print()
|
|
61
|
+
break
|
|
62
|
+
if prompt.strip().lower() in {"exit", "quit", "/exit", "/quit"}:
|
|
63
|
+
break
|
|
64
|
+
if not prompt.strip():
|
|
65
|
+
continue
|
|
66
|
+
try:
|
|
67
|
+
response = generate(model_name, prompt, max_tokens=max_tokens,
|
|
68
|
+
temperature=temperature, top_k=top_k, **kwargs)
|
|
69
|
+
print(f"AI: {response}")
|
|
70
|
+
except (TypeError, ValueError, RuntimeError, FileNotFoundError) as error:
|
|
71
|
+
print(f"AI error: {error}")
|
|
72
|
+
|
|
73
|
+
return None
|