tensorless 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.
@@ -0,0 +1,234 @@
1
+ """The training loop.
2
+
3
+ `run_training` is the single entry point that: prepares data, builds (or
4
+ resumes) the model + optimizer, trains with early stopping, checkpoints
5
+ periodically so interrupted runs can resume, and returns everything
6
+ needed to write the final `.tl` file.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import time
12
+ from typing import Any, Dict, Optional
13
+
14
+ import torch
15
+ import torch.nn as nn
16
+ import torch.nn.functional as F
17
+
18
+ from ..data.loader import Dataset
19
+ from ..devices.device import get_torch_device
20
+ from ..checkpoint.manager import CheckpointManager
21
+ from ..models.registry import build_model
22
+ from ..tokenization.char_tokenizer import CharTokenizer
23
+ from ..data.tabular import TabularPreprocessor
24
+ from .early_stopping import EarlyStopping
25
+ from . import data_prep as dp
26
+
27
+
28
+ def _build_optimizer(model: nn.Module, cfg: Dict[str, Any]) -> torch.optim.Optimizer:
29
+ name = cfg["optimizer"].lower()
30
+ lr = cfg["learning_rate"]
31
+ wd = cfg["weight_decay"]
32
+ if name == "adamw":
33
+ return torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=wd)
34
+ elif name == "adam":
35
+ return torch.optim.Adam(model.parameters(), lr=lr, weight_decay=wd)
36
+ elif name == "sgd":
37
+ return torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9, weight_decay=wd)
38
+ else:
39
+ raise ValueError(f"Unknown optimizer '{name}'")
40
+
41
+
42
+ def _lr_lambda(step: int, warmup_steps: int, total_steps: int) -> float:
43
+ if warmup_steps > 0 and step < warmup_steps:
44
+ return (step + 1) / warmup_steps
45
+ if total_steps <= warmup_steps:
46
+ return 1.0
47
+ progress = (step - warmup_steps) / max(1, total_steps - warmup_steps)
48
+ return max(0.1, 1.0 - progress)
49
+
50
+
51
+ def _compute_loss(task: str, model_type: str, model: nn.Module, batch, device, pad_id: int) -> torch.Tensor:
52
+ if model_type == "transformer" and task == "text-generation":
53
+ x, y = batch
54
+ x, y = x.to(device), y.to(device)
55
+ logits = model(x)
56
+ loss = F.cross_entropy(
57
+ logits.reshape(-1, logits.size(-1)), y.reshape(-1), ignore_index=pad_id
58
+ )
59
+ return loss
60
+ elif model_type == "transformer" and task == "text-classification":
61
+ input_ids, attn_mask, labels = batch
62
+ input_ids, attn_mask, labels = input_ids.to(device), attn_mask.to(device), labels.to(device)
63
+ logits = model(input_ids, attention_mask=attn_mask)
64
+ return F.cross_entropy(logits, labels)
65
+ elif model_type == "mlp" and task == "classification":
66
+ numeric, categorical, target = batch
67
+ numeric, categorical, target = numeric.to(device), categorical.to(device), target.to(device)
68
+ logits = model(numeric, categorical)
69
+ return F.cross_entropy(logits, target)
70
+ elif model_type == "mlp" and task == "regression":
71
+ numeric, categorical, target = batch
72
+ numeric, categorical, target = numeric.to(device), categorical.to(device), target.to(device)
73
+ pred = model(numeric, categorical)
74
+ return F.mse_loss(pred, target)
75
+ else:
76
+ raise ValueError(f"Unsupported task/model_type combination: {task}/{model_type}")
77
+
78
+
79
+ def run_training(
80
+ ds: Dataset,
81
+ cfg: Dict[str, Any],
82
+ checkpoint_mgr: CheckpointManager,
83
+ dataset_fingerprint: str,
84
+ resume_state: Optional[Dict[str, Any]] = None,
85
+ log_fn=print,
86
+ ) -> Dict[str, Any]:
87
+ task = cfg["task"]
88
+ model_type = cfg["model_type"]
89
+ torch.manual_seed(cfg["seed"])
90
+
91
+ device = get_torch_device(cfg["device"])
92
+ if cfg["verbose"]:
93
+ log_fn(f"[tensorless] task={task} model={model_type} device={cfg['device']} precision={cfg['precision']}")
94
+
95
+ # ---- data prep (resume tokenizer/preprocessor if available) ----
96
+ tokenizer = None
97
+ preprocessor = None
98
+ if resume_state is not None:
99
+ if resume_state.get("tokenizer_state") is not None:
100
+ tokenizer = CharTokenizer.from_state_dict(resume_state["tokenizer_state"])
101
+ if resume_state.get("preprocessor_state") is not None:
102
+ preprocessor = TabularPreprocessor.from_state_dict(resume_state["preprocessor_state"])
103
+
104
+ if task == "text-generation":
105
+ prepared = dp.prepare_text_generation(ds, cfg, tokenizer=tokenizer)
106
+ elif task == "text-classification":
107
+ classes = resume_state["meta"]["classes"] if resume_state else None
108
+ prepared = dp.prepare_text_classification(ds, cfg, tokenizer=tokenizer, classes=classes)
109
+ elif task in ("classification", "regression"):
110
+ prepared = dp.prepare_tabular(ds, cfg, task=task, preprocessor=preprocessor)
111
+ else:
112
+ raise ValueError(f"Unsupported task '{task}'")
113
+
114
+ # ---- model / optimizer / scheduler ----
115
+ model = build_model(task, model_type, cfg, prepared.meta).to(device)
116
+ optimizer = _build_optimizer(model, cfg)
117
+
118
+ steps_per_epoch = max(1, len(prepared.train_loader))
119
+ total_steps = cfg.get("max_steps") or steps_per_epoch * cfg["epochs"]
120
+ scheduler = torch.optim.lr_scheduler.LambdaLR(
121
+ optimizer, lr_lambda=lambda s: _lr_lambda(s, cfg["warmup_steps"], total_steps)
122
+ )
123
+
124
+ early_stopper = EarlyStopping(patience=cfg["patience"], min_delta=cfg["min_delta"])
125
+ start_epoch = 0
126
+ global_step = 0
127
+
128
+ if resume_state is not None:
129
+ model.load_state_dict(resume_state["model_state_dict"])
130
+ optimizer.load_state_dict(resume_state["optimizer_state_dict"])
131
+ scheduler.load_state_dict(resume_state["scheduler_state_dict"])
132
+ start_epoch = resume_state["epoch"]
133
+ global_step = resume_state["global_step"]
134
+ early_stopper.best = resume_state.get("early_stopping_best", float("inf"))
135
+ early_stopper.num_bad_checks = resume_state.get("early_stopping_bad_checks", 0)
136
+ if cfg["verbose"]:
137
+ log_fn(f"[tensorless] resuming from checkpoint: epoch={start_epoch}, step={global_step}")
138
+
139
+ pad_id = prepared.meta.get("pad_id", 0)
140
+
141
+ def _checkpoint(epoch: int, training_complete: bool) -> None:
142
+ state = {
143
+ "epoch": epoch,
144
+ "global_step": global_step,
145
+ "model_state_dict": model.state_dict(),
146
+ "optimizer_state_dict": optimizer.state_dict(),
147
+ "scheduler_state_dict": scheduler.state_dict(),
148
+ "early_stopping_best": early_stopper.best,
149
+ "early_stopping_bad_checks": early_stopper.num_bad_checks,
150
+ "config": cfg,
151
+ "meta": prepared.meta,
152
+ "tokenizer_state": prepared.tokenizer.state_dict() if prepared.tokenizer else None,
153
+ "preprocessor_state": prepared.preprocessor.state_dict() if prepared.preprocessor else None,
154
+ "dataset_fingerprint": dataset_fingerprint,
155
+ "training_complete": training_complete,
156
+ }
157
+ checkpoint_mgr.save(state)
158
+
159
+ # ---- training loop ----
160
+ model.train()
161
+ stop = False
162
+ t0 = time.time()
163
+ last_val_loss = None
164
+ last_train_loss = None
165
+
166
+ for epoch in range(start_epoch, cfg["epochs"]):
167
+ for batch in prepared.train_loader:
168
+ loss = _compute_loss(task, model_type, model, batch, device, pad_id)
169
+ last_train_loss = loss.item()
170
+ optimizer.zero_grad()
171
+ loss.backward()
172
+ if cfg["grad_clip"]:
173
+ torch.nn.utils.clip_grad_norm_(model.parameters(), cfg["grad_clip"])
174
+ optimizer.step()
175
+ scheduler.step()
176
+ global_step += 1
177
+
178
+ if global_step % cfg["checkpoint_every"] == 0:
179
+ _checkpoint(epoch, training_complete=False)
180
+
181
+ if cfg.get("max_steps") and global_step >= cfg["max_steps"]:
182
+ stop = True
183
+ break
184
+ if stop:
185
+ break
186
+
187
+ # ---- validation / early stopping ----
188
+ if prepared.val_loader is not None:
189
+ model.eval()
190
+ losses = []
191
+ with torch.no_grad():
192
+ for batch in prepared.val_loader:
193
+ losses.append(_compute_loss(task, model_type, model, batch, device, pad_id).item())
194
+ val_loss = sum(losses) / max(1, len(losses))
195
+ last_val_loss = val_loss
196
+ model.train()
197
+ is_best = early_stopper.step(val_loss, state=None)
198
+ if cfg["verbose"]:
199
+ log_fn(
200
+ f"[tensorless] epoch {epoch + 1}/{cfg['epochs']} "
201
+ f"train_loss={last_train_loss:.4f} val_loss={val_loss:.4f}"
202
+ f"{' (best)' if is_best else ''}"
203
+ )
204
+ if early_stopper.should_stop:
205
+ if cfg["verbose"]:
206
+ log_fn(f"[tensorless] early stopping at epoch {epoch + 1} (no improvement)")
207
+ _checkpoint(epoch, training_complete=True)
208
+ break
209
+ else:
210
+ if cfg["verbose"]:
211
+ log_fn(f"[tensorless] epoch {epoch + 1}/{cfg['epochs']} train_loss={last_train_loss:.4f}")
212
+
213
+ _checkpoint(epoch + 1, training_complete=(epoch + 1 >= cfg["epochs"]))
214
+
215
+ elapsed = time.time() - t0
216
+ if cfg["verbose"]:
217
+ log_fn(f"[tensorless] training finished in {elapsed:.1f}s ({global_step} steps)")
218
+
219
+ metrics = {
220
+ "final_train_loss": last_train_loss,
221
+ "final_val_loss": last_val_loss,
222
+ "global_step": global_step,
223
+ "elapsed_seconds": elapsed,
224
+ }
225
+
226
+ return {
227
+ "model": model,
228
+ "model_state_dict": model.state_dict(),
229
+ "meta": prepared.meta,
230
+ "tokenizer": prepared.tokenizer,
231
+ "preprocessor": prepared.preprocessor,
232
+ "metrics": metrics,
233
+ "device": device,
234
+ }
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: tensorless
3
+ Version: 0.1.0
4
+ Summary: ML with maximum automation and minimum setup.
5
+ Author: Tensorless Contributors
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: torch>=2.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == "dev"
13
+ Dynamic: license-file
14
+
15
+ # Tensorless
16
+
17
+ **ML with maximum automation and minimum setup.**
18
+
19
+ ```python
20
+ import tensorless as tl
21
+
22
+ tl.train("./data")
23
+ ```
24
+
25
+ That's it. Tensorless inspects your dataset, figures out what kind of
26
+ task you're trying to solve, builds and configures a model, trains it,
27
+ validates it, checkpoints it, and saves a single portable `model.tl`
28
+ file you can move anywhere.
29
+
30
+ ```python
31
+ model = tl.run("model.tl") # interactive chat, if it's a text model
32
+ # or
33
+ model = tl.load("model.tl")
34
+ model.predict(...)
35
+ ```
36
+
37
+ Simple by default. Powerful when you need it:
38
+
39
+ ```python
40
+ tl.train(
41
+ "./data",
42
+ d_model=512,
43
+ layers=6,
44
+ learning_rate=3e-4,
45
+ batch_size=32,
46
+ )
47
+ ```
48
+
49
+ ## Why Tensorless
50
+
51
+ Most ML frameworks assume you already know what model you want, how big
52
+ it should be, which optimizer and learning rate to use, and how to wire
53
+ up checkpointing and resumption yourself. Tensorless flips that: it
54
+ makes a reasonable, working choice for all of that automatically, and
55
+ lets you override exactly the parts you care about.
56
+
57
+ It also remembers what it already did. Run `tl.train("./data")` twice on
58
+ the same dataset and it won't retrain — it'll just hand you back the
59
+ model it already trained. Change the data, and it retrains. Get
60
+ interrupted partway through a long run, and the next call resumes right
61
+ where it left off. This is the **Smart Auto Check**, and it's the core
62
+ idea the whole framework is built around.
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ pip install -e .
68
+ ```
69
+
70
+ See [docs/installation.md](docs/installation.md) for details and
71
+ requirements.
72
+
73
+ ## Documentation
74
+
75
+ | Doc | What's in it |
76
+ |---|---|
77
+ | [Installation](docs/installation.md) | Requirements, install steps, verifying your setup |
78
+ | [Quick Start](docs/quickstart.md) | The fastest path to a trained model |
79
+ | [Beginner Tutorial](docs/tutorial.md) | A guided, from-scratch walkthrough |
80
+ | [Automatic Mode](docs/automatic_mode.md) | How auto-detection and auto-configuration work, and the Smart Auto Check |
81
+ | [Training](docs/training.md) | `tl.train()` in depth, all supported tasks and data formats |
82
+ | [Inference](docs/inference.md) | `tl.run()`, `tl.load()`, and the prediction API |
83
+ | [Checkpointing & Resume](docs/checkpointing.md) | How checkpoints work and how resumption is decided |
84
+ | [The `.tl` Format](docs/tl_format.md) | What's inside a `.tl` file and why it's portable |
85
+ | [Configuration](docs/configuration.md) | Every override you can pass, and what it does |
86
+ | [CLI](docs/cli.md) | `tensorless train / run / inspect / info` |
87
+ | [API Reference](docs/api_reference.md) | Full function/class signatures |
88
+ | [Examples](docs/examples.md) | Worked examples for each supported task |
89
+ | [Troubleshooting](docs/troubleshooting.md) | Common errors and what to do about them |
90
+ | [Architecture](docs/architecture.md) | How the codebase is organized, for contributors |
91
+ | [Contributing](docs/contributing.md) | How to add models, backends, or data formats |
92
+ | [Roadmap](docs/roadmap.md) | What's planned |
93
+ | [Limitations](docs/limitations.md) | What Tensorless deliberately doesn't do (yet) |
94
+
95
+ ## Supported today
96
+
97
+ - **Text generation** (language modeling) from `.txt`/`.md` files or JSON/JSONL with a `text` field
98
+ - **Text classification** from a directory of class subfolders (`positive/`, `negative/`, ...) or labeled JSON/JSONL
99
+ - **Tabular classification and regression** from CSV/TSV/JSON/JSONL with a target column
100
+
101
+ ## Project status
102
+
103
+ Tensorless is an early-stage, actively developed framework. The core
104
+ loop — inspect, auto-configure, train, checkpoint, save, reload, infer —
105
+ is real and tested end-to-end (see [tests/](tests/)). See
106
+ [docs/limitations.md](docs/limitations.md) for what's intentionally out
107
+ of scope right now, and [docs/roadmap.md](docs/roadmap.md) for what's next.
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,38 @@
1
+ tensorless/__init__.py,sha256=iNj7LjFkWkVXaDR9SIwDitpOwyXzKxcZKLDDih2VWt0,701
2
+ tensorless/_version.py,sha256=po3YK2JiKEcipFrlKt1aaEDfkSF_VT1s5zH4SW3ta8E,224
3
+ tensorless/api.py,sha256=zoEh4nKFa8XayVFapaaSAgD9YHFvxFl96xwTyWC8GA4,8707
4
+ tensorless/config.py,sha256=muhxE11UMdFkw1DDh846_0w4tM2BofT1zJ5L4t05T3M,3703
5
+ tensorless/errors.py,sha256=txzlMyNC_R7FdVxX3wbu46vYC02eGaWzFXPuTw7Pqk8,902
6
+ tensorless/runtime.py,sha256=ZNSrTIDf9A3W8OP8s3bsaFNwNnJnmgbV53DMi6fk-P8,6858
7
+ tensorless/auto/__init__.py,sha256=ZkV-4-VQul6gfkwiX90y6miW-ISIkXdK8uV5rfUSXZQ,114
8
+ tensorless/auto/config.py,sha256=zi9L4o7KuzU6iFL-Lhwadta1MldzAhbeu29zY3zTMiw,3905
9
+ tensorless/auto/detector.py,sha256=o_IPLT-M6uPG6pQAfNRES_z-DNe9SiQX9-92wgOy7Ik,3186
10
+ tensorless/checkpoint/__init__.py,sha256=pyEWGR1upD_HTjG90dW1Bosbk4vbKm_8Fpn1JLdl2WA,72
11
+ tensorless/checkpoint/manager.py,sha256=OOyExS6Mi2pWVSmrPX59jVcEBWbDSLAZTjmrif7hrQY,2343
12
+ tensorless/cli/__init__.py,sha256=9EVxmFiWsAoqWJ6br1bc3BxlA71JyOQP28fUHhX2k7E,43
13
+ tensorless/cli/main.py,sha256=iKBllAKmRkR6MjaC4o4DUq3Xr8TwWT4qKh43QOYZIOQ,4399
14
+ tensorless/data/__init__.py,sha256=737HGk7mGC1qjJNte51YFFnBuelhC3HhctO0mehzJwg,256
15
+ tensorless/data/fingerprint.py,sha256=UdnDB-Ii5Vm31QO2pO26cuMPFd4ROfxbklBcBBu-ors,2449
16
+ tensorless/data/inspector.py,sha256=HUfx0M0TkQDDwAavsfOAxSqkrtwCn3VcX3Yy-ZckaWA,5406
17
+ tensorless/data/loader.py,sha256=2zCmI9mXO0lkdPHfaTAGa8qfnbmAz2xy7CuuiJj3978,9201
18
+ tensorless/data/tabular.py,sha256=0FQ1dE9Uz1K9HTu_N1QzzKfalu-2bkoy8DGKpbfDmjI,6953
19
+ tensorless/devices/__init__.py,sha256=pxVve4VnSZn3T6ou31lbap7NDXokkdN9HUkMSxAbwPE,111
20
+ tensorless/devices/device.py,sha256=3zxuV-tjyWJ3ffMgEOTii4ucS3xH-Jkr5KQijsQmXas,3150
21
+ tensorless/models/__init__.py,sha256=_yOv4b3szXxd5a05BNvYS8oKIn4bOMVekKsz1ise_XE,163
22
+ tensorless/models/mlp.py,sha256=FML511OOEeFvD5eA08kSVgumn4HOjWp4RyiLYC2yImY,2019
23
+ tensorless/models/registry.py,sha256=ZrNARaQEgyvACiVPYKdT9JMXjtxia42wE9MeEJao6PY,1862
24
+ tensorless/models/transformer.py,sha256=IZr3x-iM87qRNiET0512QzGX23KE16GestuZBk-0Lks,6669
25
+ tensorless/serialization/__init__.py,sha256=jsPMigSc4vU3P2DJBy1nbw762LcDNmqFenXv1iMTtfo,74
26
+ tensorless/serialization/tl_format.py,sha256=Yop7VpVej2Lc0rsaRRwySwlMLzcCZB5g9cLZBZctAG8,2930
27
+ tensorless/tokenization/__init__.py,sha256=MNnWY_R9RjIGx_WkzjhS_isUKxVQWq3SB8VBKXiS5z4,71
28
+ tensorless/tokenization/char_tokenizer.py,sha256=sJdlt4MQfpNdFTapX6IH2d7QCel3ppnFHIcK3Oko_rU,2601
29
+ tensorless/training/__init__.py,sha256=p7DBWI6lYwQJ7EnSBFUf2cerAqDDqDmIzXBqyzBDYbk,121
30
+ tensorless/training/data_prep.py,sha256=jJ3t8CZ6eYj7CSZ25lXJNJC4N7wS_qIwSFE0g8oBZmY,7384
31
+ tensorless/training/early_stopping.py,sha256=kqaeU_L2jQLtaBgWfzxeYemofwLxUe7GWiSCGEyhz9E,999
32
+ tensorless/training/trainer.py,sha256=99fkkL0EKGP5Q13sz2wE0R2ATtzR0DgjlFen9HbrSQ4,9469
33
+ tensorless-0.1.0.dist-info/licenses/LICENSE,sha256=REl9_4muewZT_xO8osRhVQi4anHcbxh4C2hYMMLYnAE,1080
34
+ tensorless-0.1.0.dist-info/METADATA,sha256=66Q1GZ6FISDysqWkRB50APV_bFYP0CRo5fk0KmtLTqg,4108
35
+ tensorless-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
36
+ tensorless-0.1.0.dist-info/entry_points.txt,sha256=LaYKVgR59khkVHc_EUi8Ls_OxrjI-oxwsL3UduwoQDc,56
37
+ tensorless-0.1.0.dist-info/top_level.txt,sha256=hyLdNLPR3-nmOST5BedRzsOVWVYc24GVsUCkjBKsTuY,11
38
+ tensorless-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tensorless = tensorless.cli.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tensorless Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ tensorless