tensorless-pytorch 0.2.0__tar.gz → 0.3.0__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.
- {tensorless_pytorch-0.2.0/tensorless_pytorch.egg-info → tensorless_pytorch-0.3.0}/PKG-INFO +13 -5
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/README.md +12 -4
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/pyproject.toml +1 -1
- tensorless_pytorch-0.3.0/tensorless/auto/config.py +242 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/config.py +8 -0
- tensorless_pytorch-0.3.0/tensorless/devices/__init__.py +17 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/devices/device.py +81 -1
- tensorless_pytorch-0.3.0/tensorless/models/__init__.py +13 -0
- tensorless_pytorch-0.3.0/tensorless/models/mlp.py +142 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/models/registry.py +25 -4
- tensorless_pytorch-0.3.0/tensorless/models/transformer.py +491 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/runtime.py +17 -3
- tensorless_pytorch-0.3.0/tensorless/tokenization/bpe_tokenizer.py +279 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/training/data_prep.py +46 -16
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/training/trainer.py +51 -8
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0/tensorless_pytorch.egg-info}/PKG-INFO +13 -5
- tensorless_pytorch-0.2.0/tensorless/auto/config.py +0 -136
- tensorless_pytorch-0.2.0/tensorless/devices/__init__.py +0 -3
- tensorless_pytorch-0.2.0/tensorless/models/__init__.py +0 -5
- tensorless_pytorch-0.2.0/tensorless/models/mlp.py +0 -64
- tensorless_pytorch-0.2.0/tensorless/models/transformer.py +0 -175
- tensorless_pytorch-0.2.0/tensorless/tokenization/bpe_tokenizer.py +0 -128
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/LICENSE +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/MANIFEST.in +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/api_reference.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/architecture.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/automatic_mode.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/checkpointing.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/cli.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/configuration.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/contributing.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/examples.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/inference.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/installation.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/limitations.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/quickstart.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/roadmap.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/tl_format.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/training.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/troubleshooting.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/docs/tutorial.md +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/examples/tabular_classification_example.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/examples/tabular_regression_example.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/examples/text_classification_example.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/examples/text_generation_example.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/setup.cfg +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/__init__.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/_version.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/api.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/auto/__init__.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/auto/detector.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/checkpoint/__init__.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/checkpoint/manager.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/cli/__init__.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/cli/main.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/data/__init__.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/data/english_grammar.txt +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/data/fingerprint.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/data/inspector.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/data/loader.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/data/tabular.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/errors.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/serialization/__init__.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/serialization/tl_format.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/tokenization/__init__.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/tokenization/char_tokenizer.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/training/__init__.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless/training/early_stopping.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless_pytorch.egg-info/SOURCES.txt +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless_pytorch.egg-info/dependency_links.txt +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless_pytorch.egg-info/entry_points.txt +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless_pytorch.egg-info/requires.txt +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tensorless_pytorch.egg-info/top_level.txt +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tests/test_auto_detection.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tests/test_checkpoint_resume.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tests/test_cli.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tests/test_data_loading.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tests/test_end_to_end.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tests/test_fingerprint.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tests/test_serialization.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tests/test_train_tabular.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tests/test_train_text_classification.py +0 -0
- {tensorless_pytorch-0.2.0 → tensorless_pytorch-0.3.0}/tests/test_train_text_generation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tensorless-pytorch
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Automatic PyTorch training with portable models and minimal setup.
|
|
5
5
|
Author: Tensorless PyTorch Contributors
|
|
6
6
|
License: MIT
|
|
@@ -41,10 +41,18 @@ tokenizer; use `tokenizer="char"` for a character-level model. Tensorless PyTorc
|
|
|
41
41
|
derives model size, batch size, epochs, validation, device, and BPE vocabulary
|
|
42
42
|
size from the data, while every setting can be overridden.
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
Text is tokenized once up front (not re-tokenized every epoch) and streamed
|
|
45
|
+
through PyTorch in fixed-size batches. Model size is auto-scaled with corpus
|
|
46
|
+
size across four tiers -- small, lower-mid, upper-mid, large -- capped at
|
|
47
|
+
"upper-mid" (roughly 50M-350M parameters) unless you pass explicit
|
|
48
|
+
`d_model=`/`layers=`/etc. yourself. New training runs use the `architecture="v2"`
|
|
49
|
+
backbone (RoPE + RMSNorm + SwiGLU + KV-cached generation); older `.tl`
|
|
50
|
+
checkpoints keep loading and running on the original `"v1"` backbone
|
|
51
|
+
automatically. CUDA training automatically uses `fp16` or `bf16` when
|
|
52
|
+
supported (with gradient scaling and checkpointed scaler state), TPU (XLA)
|
|
53
|
+
training is supported via `device="tpu"`, and large auto-sized models enable
|
|
54
|
+
gradient checkpointing automatically to fit in memory. Reduce `batch_size` if
|
|
55
|
+
memory is limited.
|
|
48
56
|
|
|
49
57
|
## English starter pretraining
|
|
50
58
|
|
|
@@ -27,10 +27,18 @@ tokenizer; use `tokenizer="char"` for a character-level model. Tensorless PyTorc
|
|
|
27
27
|
derives model size, batch size, epochs, validation, device, and BPE vocabulary
|
|
28
28
|
size from the data, while every setting can be overridden.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
Text is tokenized once up front (not re-tokenized every epoch) and streamed
|
|
31
|
+
through PyTorch in fixed-size batches. Model size is auto-scaled with corpus
|
|
32
|
+
size across four tiers -- small, lower-mid, upper-mid, large -- capped at
|
|
33
|
+
"upper-mid" (roughly 50M-350M parameters) unless you pass explicit
|
|
34
|
+
`d_model=`/`layers=`/etc. yourself. New training runs use the `architecture="v2"`
|
|
35
|
+
backbone (RoPE + RMSNorm + SwiGLU + KV-cached generation); older `.tl`
|
|
36
|
+
checkpoints keep loading and running on the original `"v1"` backbone
|
|
37
|
+
automatically. CUDA training automatically uses `fp16` or `bf16` when
|
|
38
|
+
supported (with gradient scaling and checkpointed scaler state), TPU (XLA)
|
|
39
|
+
training is supported via `device="tpu"`, and large auto-sized models enable
|
|
40
|
+
gradient checkpointing automatically to fit in memory. Reduce `batch_size` if
|
|
41
|
+
memory is limited.
|
|
34
42
|
|
|
35
43
|
## English starter pretraining
|
|
36
44
|
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Automatic configuration.
|
|
2
|
+
|
|
3
|
+
Turns a `Dataset` + a user-supplied `TrainConfig` (with mostly-None fields)
|
|
4
|
+
into a fully-resolved `ResolvedConfig`. Every automatic choice is driven by
|
|
5
|
+
simple, explainable heuristics based on dataset size and available
|
|
6
|
+
hardware -- this is not meant to be state-of-the-art NAS, it's meant to
|
|
7
|
+
produce a *sane, working* default so `tl.train("./data")` just works,
|
|
8
|
+
scaling all the way from tiny CPU-only experiments up to "upper-mid"
|
|
9
|
+
scale models (roughly 100M-350M parameters for transformers) when the
|
|
10
|
+
data and hardware support it.
|
|
11
|
+
|
|
12
|
+
Model-size tiers (approximate parameter counts, transformer LM):
|
|
13
|
+
small < 10M
|
|
14
|
+
lower-mid 10M - 50M
|
|
15
|
+
upper-mid 50M - 350M <- Tensorless auto-config tops out here
|
|
16
|
+
large 350M - 3B (never auto-selected; pass explicit
|
|
17
|
+
d_model=/layers=/etc. yourself)
|
|
18
|
+
extreme 3B+ (out of scope for this project)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import Any, Dict, Tuple
|
|
24
|
+
|
|
25
|
+
from ..config import TrainConfig, ResolvedConfig
|
|
26
|
+
from ..data.loader import Dataset
|
|
27
|
+
from ..devices.device import auto_select_device, get_torch_device, recommend_max_params
|
|
28
|
+
from .detector import detect_task
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Architecture sizing
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
def _text_model_tier(n_examples: int) -> Tuple[int, int, int, int]:
|
|
36
|
+
"""Return (d_model, layers, heads, ff_mult), scaled up to the
|
|
37
|
+
"upper-mid" ceiling as effective dataset size grows. `ff_mult` is
|
|
38
|
+
interpreted by the v2 (SwiGLU) architecture as a multiplier on the
|
|
39
|
+
gated hidden width, same as v1's plain MLP hidden width.
|
|
40
|
+
"""
|
|
41
|
+
if n_examples < 200:
|
|
42
|
+
return 64, 2, 2, 2 # tiny / smoke-test scale
|
|
43
|
+
elif n_examples < 5_000:
|
|
44
|
+
return 128, 4, 4, 3 # small
|
|
45
|
+
elif n_examples < 50_000:
|
|
46
|
+
return 256, 6, 8, 4 # small / lower-mid boundary
|
|
47
|
+
elif n_examples < 200_000:
|
|
48
|
+
return 384, 8, 8, 4 # lower-mid
|
|
49
|
+
elif n_examples < 1_000_000:
|
|
50
|
+
return 640, 12, 10, 4 # lower-mid / upper-mid boundary (~80M params)
|
|
51
|
+
else:
|
|
52
|
+
return 1024, 16, 16, 4 # upper-mid ceiling (~250-300M params)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _tabular_model_tier(n_examples: int) -> Tuple[int, int, int, int]:
|
|
56
|
+
if n_examples < 500:
|
|
57
|
+
return 32, 2, 1, 2
|
|
58
|
+
elif n_examples < 20_000:
|
|
59
|
+
return 64, 3, 1, 2
|
|
60
|
+
elif n_examples < 200_000:
|
|
61
|
+
return 128, 5, 1, 2
|
|
62
|
+
else:
|
|
63
|
+
return 256, 8, 1, 2 # upper-mid ceiling for tabular data
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _estimate_transformer_params(vocab_size: int, d_model: int, layers: int, ff_mult: int) -> int:
|
|
67
|
+
swiglu_hidden = max(int(d_model * ff_mult * (2 / 3)), d_model)
|
|
68
|
+
per_layer = 4 * d_model * d_model + 3 * d_model * swiglu_hidden # attn proj + swiglu gate/up/down
|
|
69
|
+
return vocab_size * d_model + layers * per_layer
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _fit_within_param_budget(
|
|
73
|
+
vocab_size: int, d_model: int, layers: int, heads: int, ff_mult: int, max_params: int
|
|
74
|
+
) -> Tuple[int, int, int, int]:
|
|
75
|
+
"""If the tier-selected architecture would exceed what the detected
|
|
76
|
+
hardware can comfortably hold, scale it back down -- first by
|
|
77
|
+
trimming depth, then width -- until it fits. Keeps `d_model`
|
|
78
|
+
divisible by `heads` throughout.
|
|
79
|
+
"""
|
|
80
|
+
while _estimate_transformer_params(vocab_size, d_model, layers, ff_mult) > max_params and layers > 2:
|
|
81
|
+
layers -= 1
|
|
82
|
+
while _estimate_transformer_params(vocab_size, d_model, layers, ff_mult) > max_params and d_model > 64:
|
|
83
|
+
d_model = max(64, d_model - 64)
|
|
84
|
+
while d_model % heads != 0 and heads > 1:
|
|
85
|
+
heads -= 1
|
|
86
|
+
return d_model, layers, heads, ff_mult
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _auto_batch_size(n_examples: int, max_seq_len: int, device: str) -> int:
|
|
90
|
+
# GPUs/TPUs benefit from a larger token budget per step than CPU.
|
|
91
|
+
token_budget = 32768 if device in ("cuda", "tpu") else 8192
|
|
92
|
+
sequence_batch = max(1, token_budget // max_seq_len)
|
|
93
|
+
if n_examples < 200:
|
|
94
|
+
return min(8, sequence_batch)
|
|
95
|
+
elif n_examples < 2000:
|
|
96
|
+
return min(16, sequence_batch)
|
|
97
|
+
elif n_examples < 20000:
|
|
98
|
+
return min(32, sequence_batch)
|
|
99
|
+
elif n_examples < 200000:
|
|
100
|
+
return min(64, sequence_batch)
|
|
101
|
+
else:
|
|
102
|
+
return min(128, sequence_batch)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _auto_epochs(n_examples: int) -> int:
|
|
106
|
+
if n_examples < 200:
|
|
107
|
+
return 40
|
|
108
|
+
elif n_examples < 2000:
|
|
109
|
+
return 20
|
|
110
|
+
elif n_examples < 20000:
|
|
111
|
+
return 10
|
|
112
|
+
else:
|
|
113
|
+
return 5
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _effective_text_size(ds: Dataset) -> int:
|
|
117
|
+
"""Estimate useful training examples for raw corpora."""
|
|
118
|
+
if ds.kind in ("text", "text_labeled"):
|
|
119
|
+
return max(len(ds), sum(len(text) for text in ds.texts) // 200)
|
|
120
|
+
return len(ds)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _auto_vocab_size(ds: Dataset, n_examples: int) -> int:
|
|
124
|
+
if ds.kind not in ("text", "text_labeled"):
|
|
125
|
+
return 1000
|
|
126
|
+
unique_chars = len(set("".join(ds.texts)))
|
|
127
|
+
# Bigger corpora justify (and benefit from) a bigger BPE vocabulary --
|
|
128
|
+
# fewer tokens per sequence, more of max_seq_len spent on real context.
|
|
129
|
+
if n_examples < 5_000:
|
|
130
|
+
ceiling = 2048
|
|
131
|
+
elif n_examples < 200_000:
|
|
132
|
+
ceiling = 8192
|
|
133
|
+
else:
|
|
134
|
+
ceiling = 32000
|
|
135
|
+
return min(ceiling, max(64, unique_chars * 8))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _auto_num_workers(device: str) -> int:
|
|
139
|
+
import os
|
|
140
|
+
|
|
141
|
+
cpu_count = os.cpu_count() or 1
|
|
142
|
+
if device in ("cuda", "tpu"):
|
|
143
|
+
# Leave headroom for the main process / accelerator driver threads.
|
|
144
|
+
return max(0, min(4, cpu_count - 1))
|
|
145
|
+
return 0 # CPU training: extra worker processes just add overhead
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def resolve_config(ds: Dataset, user: TrainConfig) -> ResolvedConfig:
|
|
149
|
+
n = _effective_text_size(ds)
|
|
150
|
+
task = user.task or detect_task(ds)
|
|
151
|
+
model_type = user.model_type or (
|
|
152
|
+
"transformer" if task in ("text-generation", "text-classification") else "mlp"
|
|
153
|
+
)
|
|
154
|
+
tokenizer = user.tokenizer or "bpe"
|
|
155
|
+
if tokenizer not in ("char", "bpe"):
|
|
156
|
+
raise ValueError("tokenizer must be 'char' or 'bpe'")
|
|
157
|
+
|
|
158
|
+
architecture = user.architecture or "v2"
|
|
159
|
+
if architecture not in ("v1", "v2"):
|
|
160
|
+
raise ValueError("architecture must be 'v1' or 'v2'")
|
|
161
|
+
|
|
162
|
+
device, precision = auto_select_device(user.device, user.precision)
|
|
163
|
+
torch_device = get_torch_device(device)
|
|
164
|
+
|
|
165
|
+
if model_type == "transformer":
|
|
166
|
+
d_model, layers, heads, ff_mult = _text_model_tier(n)
|
|
167
|
+
else:
|
|
168
|
+
d_model, layers, heads, ff_mult = _tabular_model_tier(n)
|
|
169
|
+
|
|
170
|
+
max_seq_len = user.max_seq_len or (256 if ds.kind in ("text", "text_labeled") else 1)
|
|
171
|
+
bpe_vocab_size = (
|
|
172
|
+
user.bpe_vocab_size if user.bpe_vocab_size is not None else _auto_vocab_size(ds, n)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Memory-aware cap: only clamp automatically-chosen sizes, never a
|
|
176
|
+
# user's explicit d_model=/layers=/heads=/ff_mult= override.
|
|
177
|
+
user_set_arch = any(
|
|
178
|
+
v is not None for v in (user.d_model, user.layers, user.heads, user.ff_mult)
|
|
179
|
+
)
|
|
180
|
+
if model_type == "transformer" and not user_set_arch:
|
|
181
|
+
max_params = recommend_max_params(torch_device)
|
|
182
|
+
d_model, layers, heads, ff_mult = _fit_within_param_budget(
|
|
183
|
+
bpe_vocab_size if tokenizer == "bpe" else 256, d_model, layers, heads, ff_mult, max_params
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
gradient_checkpointing = user.gradient_checkpointing
|
|
187
|
+
if gradient_checkpointing is None:
|
|
188
|
+
# Auto-enable on GPU/TPU once the model is large enough that
|
|
189
|
+
# activation memory (not parameter memory) becomes the bottleneck.
|
|
190
|
+
approx_params = _estimate_transformer_params(
|
|
191
|
+
bpe_vocab_size if tokenizer == "bpe" else 256, d_model, layers, ff_mult
|
|
192
|
+
) if model_type == "transformer" else 0
|
|
193
|
+
gradient_checkpointing = device in ("cuda", "tpu") and approx_params > 60_000_000
|
|
194
|
+
|
|
195
|
+
compile_model = user.compile
|
|
196
|
+
if compile_model is None:
|
|
197
|
+
compile_model = device == "cuda"
|
|
198
|
+
|
|
199
|
+
num_workers = user.num_workers if user.num_workers is not None else _auto_num_workers(device)
|
|
200
|
+
|
|
201
|
+
out = user.out or "model.tl"
|
|
202
|
+
checkpoint_dir = user.checkpoint_dir or (out + ".ckpt")
|
|
203
|
+
|
|
204
|
+
resolved = ResolvedConfig(
|
|
205
|
+
out=out,
|
|
206
|
+
force=bool(user.force),
|
|
207
|
+
resume=user.resume,
|
|
208
|
+
ask_on_data_change=bool(user.ask_on_data_change),
|
|
209
|
+
task=task,
|
|
210
|
+
model_type=model_type,
|
|
211
|
+
architecture=architecture,
|
|
212
|
+
d_model=user.d_model or d_model,
|
|
213
|
+
layers=user.layers or layers,
|
|
214
|
+
heads=user.heads or heads,
|
|
215
|
+
ff_mult=user.ff_mult or ff_mult,
|
|
216
|
+
dropout=user.dropout if user.dropout is not None else 0.1,
|
|
217
|
+
max_seq_len=max_seq_len,
|
|
218
|
+
tokenizer=tokenizer,
|
|
219
|
+
bpe_vocab_size=bpe_vocab_size,
|
|
220
|
+
optimizer=user.optimizer or "adamw",
|
|
221
|
+
learning_rate=user.learning_rate or (3e-4 if model_type == "transformer" else 1e-3),
|
|
222
|
+
weight_decay=user.weight_decay if user.weight_decay is not None else 0.01,
|
|
223
|
+
batch_size=user.batch_size if user.batch_size is not None else _auto_batch_size(n, max_seq_len, device),
|
|
224
|
+
epochs=user.epochs or _auto_epochs(n),
|
|
225
|
+
max_steps=user.max_steps,
|
|
226
|
+
gradient_accumulation_steps=user.gradient_accumulation_steps or 1,
|
|
227
|
+
grad_clip=user.grad_clip if user.grad_clip is not None else 1.0,
|
|
228
|
+
warmup_steps=user.warmup_steps if user.warmup_steps is not None else min(100, max(1, n // 10)),
|
|
229
|
+
val_split=user.val_split if user.val_split is not None else (0.1 if n >= 50 else 0.0),
|
|
230
|
+
patience=user.patience if user.patience is not None else 3,
|
|
231
|
+
min_delta=user.min_delta if user.min_delta is not None else 1e-4,
|
|
232
|
+
device=device,
|
|
233
|
+
precision=precision,
|
|
234
|
+
checkpoint_every=user.checkpoint_every or 50,
|
|
235
|
+
checkpoint_dir=checkpoint_dir,
|
|
236
|
+
gradient_checkpointing=gradient_checkpointing,
|
|
237
|
+
compile=compile_model,
|
|
238
|
+
num_workers=num_workers,
|
|
239
|
+
seed=user.seed,
|
|
240
|
+
verbose=user.verbose,
|
|
241
|
+
)
|
|
242
|
+
return resolved
|
|
@@ -27,6 +27,7 @@ class TrainConfig:
|
|
|
27
27
|
# --- task / architecture ---
|
|
28
28
|
task: Optional[str] = None # "text-generation", "classification", "regression"
|
|
29
29
|
model_type: Optional[str] = None # "transformer", "mlp"
|
|
30
|
+
architecture: Optional[str] = None # "v1" (legacy) or "v2" (RoPE/RMSNorm/SwiGLU); default "v2"
|
|
30
31
|
d_model: Optional[int] = None
|
|
31
32
|
layers: Optional[int] = None
|
|
32
33
|
heads: Optional[int] = None
|
|
@@ -35,6 +36,9 @@ class TrainConfig:
|
|
|
35
36
|
max_seq_len: Optional[int] = None
|
|
36
37
|
tokenizer: Optional[str] = None # "char" or "bpe"
|
|
37
38
|
bpe_vocab_size: Optional[int] = None
|
|
39
|
+
gradient_checkpointing: Optional[bool] = None # trade compute for memory on big models
|
|
40
|
+
compile: Optional[bool] = None # torch.compile() on CUDA when available
|
|
41
|
+
num_workers: Optional[int] = None # DataLoader worker processes
|
|
38
42
|
|
|
39
43
|
# --- optimization ---
|
|
40
44
|
optimizer: Optional[str] = None # "adamw", "adam", "sgd"
|
|
@@ -86,6 +90,7 @@ class ResolvedConfig:
|
|
|
86
90
|
|
|
87
91
|
task: str
|
|
88
92
|
model_type: str
|
|
93
|
+
architecture: str
|
|
89
94
|
d_model: int
|
|
90
95
|
layers: int
|
|
91
96
|
heads: int
|
|
@@ -94,6 +99,9 @@ class ResolvedConfig:
|
|
|
94
99
|
max_seq_len: int
|
|
95
100
|
tokenizer: str
|
|
96
101
|
bpe_vocab_size: int
|
|
102
|
+
gradient_checkpointing: bool
|
|
103
|
+
compile: bool
|
|
104
|
+
num_workers: int
|
|
97
105
|
|
|
98
106
|
optimizer: str
|
|
99
107
|
learning_rate: float
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .device import (
|
|
2
|
+
auto_select_device,
|
|
3
|
+
get_torch_device,
|
|
4
|
+
get_device_info,
|
|
5
|
+
mark_step,
|
|
6
|
+
recommend_max_params,
|
|
7
|
+
tpu_core_count,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"auto_select_device",
|
|
12
|
+
"get_torch_device",
|
|
13
|
+
"get_device_info",
|
|
14
|
+
"mark_step",
|
|
15
|
+
"recommend_max_params",
|
|
16
|
+
"tpu_core_count",
|
|
17
|
+
]
|
|
@@ -9,7 +9,8 @@ trainer (see `training/trainer.py`) will catch that and fall back too.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
import os
|
|
13
|
+
from typing import Any, Dict, Optional, Tuple
|
|
13
14
|
|
|
14
15
|
import torch
|
|
15
16
|
|
|
@@ -85,6 +86,85 @@ def auto_select_device(user_device: Optional[str], user_precision: Optional[str]
|
|
|
85
86
|
return device, precision
|
|
86
87
|
|
|
87
88
|
|
|
89
|
+
def tpu_core_count() -> int:
|
|
90
|
+
"""Number of TPU cores visible to this process (1 if not on TPU)."""
|
|
91
|
+
try:
|
|
92
|
+
import torch_xla.core.xla_model as xm
|
|
93
|
+
|
|
94
|
+
return xm.xrt_world_size()
|
|
95
|
+
except Exception:
|
|
96
|
+
try:
|
|
97
|
+
import torch_xla.runtime as xr
|
|
98
|
+
|
|
99
|
+
return xr.world_size()
|
|
100
|
+
except Exception:
|
|
101
|
+
return 1
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def mark_step(device: torch.device) -> None:
|
|
105
|
+
"""XLA/TPU tensors are lazily evaluated -- without periodically calling
|
|
106
|
+
`xm.mark_step()` the compute graph never actually executes and a
|
|
107
|
+
'TPU-enabled' training run will just silently build up an ever-larger
|
|
108
|
+
graph (or hang) instead of doing real work. This is a no-op on every
|
|
109
|
+
other device, so it's safe to call unconditionally from the trainer.
|
|
110
|
+
"""
|
|
111
|
+
if device.type == "xla":
|
|
112
|
+
try:
|
|
113
|
+
import torch_xla.core.xla_model as xm
|
|
114
|
+
|
|
115
|
+
xm.mark_step()
|
|
116
|
+
except Exception:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_device_info(device: torch.device) -> Dict[str, Any]:
|
|
121
|
+
"""Human-readable + programmatic info about the active compute device,
|
|
122
|
+
used both for the startup log line and for memory-aware auto-sizing.
|
|
123
|
+
"""
|
|
124
|
+
info: Dict[str, Any] = {"type": device.type, "name": device.type, "memory_gb": None, "num_devices": 1}
|
|
125
|
+
try:
|
|
126
|
+
if device.type == "cuda":
|
|
127
|
+
idx = device.index if device.index is not None else 0
|
|
128
|
+
props = torch.cuda.get_device_properties(idx)
|
|
129
|
+
info["name"] = props.name
|
|
130
|
+
info["memory_gb"] = round(props.total_memory / (1024 ** 3), 2)
|
|
131
|
+
info["num_devices"] = torch.cuda.device_count()
|
|
132
|
+
info["bf16"] = _cuda_supports_bf16()
|
|
133
|
+
elif device.type == "xla":
|
|
134
|
+
info["name"] = "TPU"
|
|
135
|
+
info["num_devices"] = tpu_core_count()
|
|
136
|
+
elif device.type == "mps":
|
|
137
|
+
info["name"] = "Apple Silicon (MPS)"
|
|
138
|
+
else:
|
|
139
|
+
info["name"] = "CPU"
|
|
140
|
+
info["num_devices"] = os.cpu_count() or 1
|
|
141
|
+
except Exception:
|
|
142
|
+
pass
|
|
143
|
+
return info
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def recommend_max_params(device: torch.device) -> int:
|
|
147
|
+
"""Rough ceiling on trainable parameter count so an auto-configured
|
|
148
|
+
model has a reasonable chance of fitting in memory alongside AdamW's
|
|
149
|
+
optimizer state (~2x params) and activations. Deliberately
|
|
150
|
+
conservative -- users can always override architecture args by hand.
|
|
151
|
+
"""
|
|
152
|
+
info = get_device_info(device)
|
|
153
|
+
if device.type == "cuda" and info.get("memory_gb"):
|
|
154
|
+
# Rule of thumb for AdamW + fp16/bf16 mixed precision + activations:
|
|
155
|
+
# budget roughly 1M params per 15MB of GPU memory, capped well
|
|
156
|
+
# inside the "upper-mid" tier regardless of how much memory exists.
|
|
157
|
+
budget = int(info["memory_gb"] * 1024 * (1_000_000 / 15))
|
|
158
|
+
return max(2_000_000, min(budget, 350_000_000))
|
|
159
|
+
if device.type == "xla":
|
|
160
|
+
# TPU v3/v4 cores typically expose >=16GB HBM each.
|
|
161
|
+
return 350_000_000
|
|
162
|
+
if device.type == "mps":
|
|
163
|
+
return 60_000_000
|
|
164
|
+
# CPU: keep it trainable in a reasonable amount of wall-clock time.
|
|
165
|
+
return 40_000_000
|
|
166
|
+
|
|
167
|
+
|
|
88
168
|
def get_torch_device(device: str) -> torch.device:
|
|
89
169
|
"""Convert our string device name into a torch.device, with a
|
|
90
170
|
runtime fallback to CPU if the requested backend is unavailable.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .registry import build_model
|
|
2
|
+
from .transformer import TinyTransformer, TinyTransformerV1, TinyTransformerV2
|
|
3
|
+
from .mlp import TabularMLP, TabularMLPV1, TabularMLPV2
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"build_model",
|
|
7
|
+
"TinyTransformer",
|
|
8
|
+
"TinyTransformerV1",
|
|
9
|
+
"TinyTransformerV2",
|
|
10
|
+
"TabularMLP",
|
|
11
|
+
"TabularMLPV1",
|
|
12
|
+
"TabularMLPV2",
|
|
13
|
+
]
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""MLP model for tabular data (classification / regression).
|
|
2
|
+
|
|
3
|
+
Numeric columns feed directly into the network; each categorical column
|
|
4
|
+
gets its own small embedding table, and all features are concatenated
|
|
5
|
+
before the hidden layers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
12
|
+
import torch
|
|
13
|
+
import torch.nn as nn
|
|
14
|
+
import torch.nn.functional as F
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _embedding_dim(vocab_size: int) -> int:
|
|
18
|
+
# Common rule of thumb, capped to keep tiny-data models tiny.
|
|
19
|
+
return max(2, min(32, round(1.6 * (vocab_size ** 0.56))))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TabularMLPV1(nn.Module):
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
n_numeric: int,
|
|
26
|
+
categorical_vocab_sizes: List[int],
|
|
27
|
+
d_model: int,
|
|
28
|
+
layers: int,
|
|
29
|
+
dropout: float,
|
|
30
|
+
task: str,
|
|
31
|
+
n_classes: int = 0,
|
|
32
|
+
):
|
|
33
|
+
super().__init__()
|
|
34
|
+
self.task = task
|
|
35
|
+
self.n_numeric = n_numeric
|
|
36
|
+
self.categorical_vocab_sizes = categorical_vocab_sizes
|
|
37
|
+
|
|
38
|
+
self.embeddings = nn.ModuleList(
|
|
39
|
+
[nn.Embedding(v, _embedding_dim(v)) for v in categorical_vocab_sizes]
|
|
40
|
+
)
|
|
41
|
+
cat_dim = sum(_embedding_dim(v) for v in categorical_vocab_sizes)
|
|
42
|
+
in_dim = n_numeric + cat_dim
|
|
43
|
+
|
|
44
|
+
dims = [in_dim] + [d_model] * layers
|
|
45
|
+
modules = []
|
|
46
|
+
for i in range(len(dims) - 1):
|
|
47
|
+
modules += [nn.Linear(dims[i], dims[i + 1]), nn.ReLU(), nn.Dropout(dropout)]
|
|
48
|
+
self.backbone = nn.Sequential(*modules)
|
|
49
|
+
|
|
50
|
+
out_dim = n_classes if task == "classification" else 1
|
|
51
|
+
self.head = nn.Linear(dims[-1], out_dim)
|
|
52
|
+
|
|
53
|
+
def forward(self, numeric: torch.Tensor, categorical: torch.Tensor) -> torch.Tensor:
|
|
54
|
+
parts = []
|
|
55
|
+
if self.n_numeric > 0:
|
|
56
|
+
parts.append(numeric)
|
|
57
|
+
for i, emb in enumerate(self.embeddings):
|
|
58
|
+
parts.append(emb(categorical[:, i]))
|
|
59
|
+
x = torch.cat(parts, dim=1) if parts else numeric
|
|
60
|
+
x = self.backbone(x)
|
|
61
|
+
out = self.head(x)
|
|
62
|
+
if self.task == "regression":
|
|
63
|
+
return out.squeeze(-1)
|
|
64
|
+
return out
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _ResidualBlock(nn.Module):
|
|
68
|
+
"""Pre-norm residual MLP block (LayerNorm -> Linear -> GELU -> Linear
|
|
69
|
+
-> residual add). Deeper `TabularMLPV2` stacks of these train more
|
|
70
|
+
stably than plain stacked Linear+ReLU at larger widths/depths.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, d_model: int, dropout: float):
|
|
74
|
+
super().__init__()
|
|
75
|
+
self.norm = nn.LayerNorm(d_model)
|
|
76
|
+
self.fc1 = nn.Linear(d_model, d_model)
|
|
77
|
+
self.fc2 = nn.Linear(d_model, d_model)
|
|
78
|
+
self.drop = nn.Dropout(dropout)
|
|
79
|
+
|
|
80
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
81
|
+
h = self.norm(x)
|
|
82
|
+
h = F.gelu(self.fc1(h))
|
|
83
|
+
h = self.drop(h)
|
|
84
|
+
h = self.fc2(h)
|
|
85
|
+
return x + self.drop(h)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TabularMLPV2(nn.Module):
|
|
89
|
+
"""Residual, pre-norm tabular MLP -- same public interface as
|
|
90
|
+
`TabularMLPV1`, but scales more gracefully to the wider/deeper configs
|
|
91
|
+
Tensorless now auto-selects for larger tabular datasets.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
n_numeric: int,
|
|
97
|
+
categorical_vocab_sizes: List[int],
|
|
98
|
+
d_model: int,
|
|
99
|
+
layers: int,
|
|
100
|
+
dropout: float,
|
|
101
|
+
task: str,
|
|
102
|
+
n_classes: int = 0,
|
|
103
|
+
):
|
|
104
|
+
super().__init__()
|
|
105
|
+
self.task = task
|
|
106
|
+
self.n_numeric = n_numeric
|
|
107
|
+
self.categorical_vocab_sizes = categorical_vocab_sizes
|
|
108
|
+
|
|
109
|
+
self.embeddings = nn.ModuleList(
|
|
110
|
+
[nn.Embedding(v, _embedding_dim(v)) for v in categorical_vocab_sizes]
|
|
111
|
+
)
|
|
112
|
+
cat_dim = sum(_embedding_dim(v) for v in categorical_vocab_sizes)
|
|
113
|
+
in_dim = n_numeric + cat_dim
|
|
114
|
+
|
|
115
|
+
self.in_proj = nn.Linear(in_dim, d_model)
|
|
116
|
+
self.blocks = nn.ModuleList([_ResidualBlock(d_model, dropout) for _ in range(layers)])
|
|
117
|
+
self.norm_f = nn.LayerNorm(d_model)
|
|
118
|
+
|
|
119
|
+
out_dim = n_classes if task == "classification" else 1
|
|
120
|
+
self.head = nn.Linear(d_model, out_dim)
|
|
121
|
+
|
|
122
|
+
def forward(self, numeric: torch.Tensor, categorical: torch.Tensor) -> torch.Tensor:
|
|
123
|
+
parts = []
|
|
124
|
+
if self.n_numeric > 0:
|
|
125
|
+
parts.append(numeric)
|
|
126
|
+
for i, emb in enumerate(self.embeddings):
|
|
127
|
+
parts.append(emb(categorical[:, i]))
|
|
128
|
+
x = torch.cat(parts, dim=1) if parts else numeric
|
|
129
|
+
x = self.in_proj(x)
|
|
130
|
+
for block in self.blocks:
|
|
131
|
+
x = block(x)
|
|
132
|
+
x = self.norm_f(x)
|
|
133
|
+
out = self.head(x)
|
|
134
|
+
if self.task == "regression":
|
|
135
|
+
return out.squeeze(-1)
|
|
136
|
+
return out
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# New training runs default to the residual v2 architecture; v1 stays
|
|
140
|
+
# available (and is what old checkpoints without an "architecture" key
|
|
141
|
+
# in their config resolve to) for backward compatibility.
|
|
142
|
+
TabularMLP = TabularMLPV2
|
|
@@ -5,6 +5,14 @@ config + the dataset-derived metadata (vocab size, n_classes, etc). Kept
|
|
|
5
5
|
separate from `auto/config.py` so new model types/backends can be added
|
|
6
6
|
here without touching auto-configuration logic, per the "extensible
|
|
7
7
|
architecture" requirement.
|
|
8
|
+
|
|
9
|
+
Also owns the v1/v2 architecture switch: `cfg.get("architecture", "v1")`
|
|
10
|
+
decides which concrete class gets built. Checkpoints saved before the v2
|
|
11
|
+
upgrade have no "architecture" key in their stored config at all, so
|
|
12
|
+
`.get(..., "v1")` transparently resolves them to the original
|
|
13
|
+
implementation -- old `.tl` files keep loading and running correctly.
|
|
14
|
+
New training runs get "v2" (better quality, KV-cache generation, scales
|
|
15
|
+
to "upper-mid" sizes) unless the user explicitly pins `architecture="v1"`.
|
|
8
16
|
"""
|
|
9
17
|
|
|
10
18
|
from __future__ import annotations
|
|
@@ -13,8 +21,8 @@ from typing import Any, Dict
|
|
|
13
21
|
|
|
14
22
|
import torch.nn as nn
|
|
15
23
|
|
|
16
|
-
from .transformer import
|
|
17
|
-
from .mlp import
|
|
24
|
+
from .transformer import TinyTransformerV1, TinyTransformerV2
|
|
25
|
+
from .mlp import TabularMLPV1, TabularMLPV2
|
|
18
26
|
from ..errors import ModelError
|
|
19
27
|
|
|
20
28
|
|
|
@@ -26,8 +34,10 @@ def build_model(task: str, model_type: str, cfg: Dict[str, Any], meta: Dict[str,
|
|
|
26
34
|
- text tasks: {"vocab_size": int, "pad_id": int, "n_classes": int}
|
|
27
35
|
- tabular tasks: {"n_numeric": int, "categorical_vocab_sizes": [...], "n_classes": int}
|
|
28
36
|
"""
|
|
37
|
+
architecture = cfg.get("architecture", "v1")
|
|
38
|
+
|
|
29
39
|
if model_type == "transformer":
|
|
30
|
-
|
|
40
|
+
common = dict(
|
|
31
41
|
vocab_size=meta["vocab_size"],
|
|
32
42
|
d_model=cfg["d_model"],
|
|
33
43
|
layers=cfg["layers"],
|
|
@@ -39,8 +49,15 @@ def build_model(task: str, model_type: str, cfg: Dict[str, Any], meta: Dict[str,
|
|
|
39
49
|
n_classes=meta.get("n_classes", 0),
|
|
40
50
|
pad_id=meta.get("pad_id", 0),
|
|
41
51
|
)
|
|
52
|
+
if architecture == "v2":
|
|
53
|
+
return TinyTransformerV2(
|
|
54
|
+
gradient_checkpointing=bool(cfg.get("gradient_checkpointing", False)),
|
|
55
|
+
**common,
|
|
56
|
+
)
|
|
57
|
+
return TinyTransformerV1(**common)
|
|
58
|
+
|
|
42
59
|
elif model_type == "mlp":
|
|
43
|
-
|
|
60
|
+
common = dict(
|
|
44
61
|
n_numeric=meta["n_numeric"],
|
|
45
62
|
categorical_vocab_sizes=meta["categorical_vocab_sizes"],
|
|
46
63
|
d_model=cfg["d_model"],
|
|
@@ -49,5 +66,9 @@ def build_model(task: str, model_type: str, cfg: Dict[str, Any], meta: Dict[str,
|
|
|
49
66
|
task=task,
|
|
50
67
|
n_classes=meta.get("n_classes", 0),
|
|
51
68
|
)
|
|
69
|
+
if architecture == "v2":
|
|
70
|
+
return TabularMLPV2(**common)
|
|
71
|
+
return TabularMLPV1(**common)
|
|
72
|
+
|
|
52
73
|
else:
|
|
53
74
|
raise ModelError(f"Unknown model_type '{model_type}'.")
|