tensorless 0.1.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.
Files changed (81) hide show
  1. tensorless-0.3.0/PKG-INFO +77 -0
  2. tensorless-0.3.0/README.md +63 -0
  3. {tensorless-0.1.0 → tensorless-0.3.0}/docs/architecture.md +2 -1
  4. {tensorless-0.1.0 → tensorless-0.3.0}/docs/automatic_mode.md +10 -1
  5. {tensorless-0.1.0 → tensorless-0.3.0}/docs/checkpointing.md +4 -0
  6. {tensorless-0.1.0 → tensorless-0.3.0}/docs/configuration.md +3 -1
  7. {tensorless-0.1.0 → tensorless-0.3.0}/docs/installation.md +0 -20
  8. {tensorless-0.1.0 → tensorless-0.3.0}/docs/limitations.md +4 -6
  9. {tensorless-0.1.0 → tensorless-0.3.0}/docs/roadmap.md +0 -13
  10. {tensorless-0.1.0 → tensorless-0.3.0}/docs/training.md +10 -3
  11. {tensorless-0.1.0 → tensorless-0.3.0}/examples/text_generation_example.py +6 -6
  12. {tensorless-0.1.0 → tensorless-0.3.0}/pyproject.toml +4 -1
  13. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/__init__.py +2 -1
  14. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/api.py +17 -1
  15. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/auto/config.py +31 -9
  16. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/config.py +4 -0
  17. tensorless-0.3.0/tensorless/data/english_grammar.txt +262 -0
  18. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/data/tabular.py +48 -14
  19. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/runtime.py +2 -2
  20. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/serialization/tl_format.py +31 -1
  21. tensorless-0.3.0/tensorless/tokenization/__init__.py +11 -0
  22. tensorless-0.3.0/tensorless/tokenization/bpe_tokenizer.py +128 -0
  23. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/tokenization/char_tokenizer.py +1 -1
  24. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/training/data_prep.py +55 -32
  25. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/training/early_stopping.py +1 -1
  26. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/training/trainer.py +2 -2
  27. tensorless-0.3.0/tensorless.egg-info/PKG-INFO +77 -0
  28. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless.egg-info/SOURCES.txt +2 -0
  29. {tensorless-0.1.0 → tensorless-0.3.0}/tests/test_checkpoint_resume.py +18 -0
  30. {tensorless-0.1.0 → tensorless-0.3.0}/tests/test_serialization.py +42 -0
  31. {tensorless-0.1.0 → tensorless-0.3.0}/tests/test_train_tabular.py +39 -0
  32. {tensorless-0.1.0 → tensorless-0.3.0}/tests/test_train_text_generation.py +47 -0
  33. tensorless-0.1.0/PKG-INFO +0 -111
  34. tensorless-0.1.0/README.md +0 -97
  35. tensorless-0.1.0/tensorless/tokenization/__init__.py +0 -3
  36. tensorless-0.1.0/tensorless.egg-info/PKG-INFO +0 -111
  37. {tensorless-0.1.0 → tensorless-0.3.0}/LICENSE +0 -0
  38. {tensorless-0.1.0 → tensorless-0.3.0}/MANIFEST.in +0 -0
  39. {tensorless-0.1.0 → tensorless-0.3.0}/docs/api_reference.md +0 -0
  40. {tensorless-0.1.0 → tensorless-0.3.0}/docs/cli.md +0 -0
  41. {tensorless-0.1.0 → tensorless-0.3.0}/docs/contributing.md +0 -0
  42. {tensorless-0.1.0 → tensorless-0.3.0}/docs/examples.md +0 -0
  43. {tensorless-0.1.0 → tensorless-0.3.0}/docs/inference.md +0 -0
  44. {tensorless-0.1.0 → tensorless-0.3.0}/docs/quickstart.md +0 -0
  45. {tensorless-0.1.0 → tensorless-0.3.0}/docs/tl_format.md +0 -0
  46. {tensorless-0.1.0 → tensorless-0.3.0}/docs/troubleshooting.md +0 -0
  47. {tensorless-0.1.0 → tensorless-0.3.0}/docs/tutorial.md +0 -0
  48. {tensorless-0.1.0 → tensorless-0.3.0}/examples/tabular_classification_example.py +0 -0
  49. {tensorless-0.1.0 → tensorless-0.3.0}/examples/tabular_regression_example.py +0 -0
  50. {tensorless-0.1.0 → tensorless-0.3.0}/examples/text_classification_example.py +0 -0
  51. {tensorless-0.1.0 → tensorless-0.3.0}/setup.cfg +0 -0
  52. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/_version.py +0 -0
  53. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/auto/__init__.py +0 -0
  54. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/auto/detector.py +0 -0
  55. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/checkpoint/__init__.py +0 -0
  56. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/checkpoint/manager.py +0 -0
  57. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/cli/__init__.py +0 -0
  58. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/cli/main.py +0 -0
  59. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/data/__init__.py +0 -0
  60. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/data/fingerprint.py +0 -0
  61. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/data/inspector.py +0 -0
  62. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/data/loader.py +0 -0
  63. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/devices/__init__.py +0 -0
  64. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/devices/device.py +0 -0
  65. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/errors.py +0 -0
  66. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/models/__init__.py +0 -0
  67. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/models/mlp.py +0 -0
  68. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/models/registry.py +0 -0
  69. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/models/transformer.py +0 -0
  70. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/serialization/__init__.py +0 -0
  71. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless/training/__init__.py +0 -0
  72. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless.egg-info/dependency_links.txt +0 -0
  73. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless.egg-info/entry_points.txt +0 -0
  74. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless.egg-info/requires.txt +0 -0
  75. {tensorless-0.1.0 → tensorless-0.3.0}/tensorless.egg-info/top_level.txt +0 -0
  76. {tensorless-0.1.0 → tensorless-0.3.0}/tests/test_auto_detection.py +0 -0
  77. {tensorless-0.1.0 → tensorless-0.3.0}/tests/test_cli.py +0 -0
  78. {tensorless-0.1.0 → tensorless-0.3.0}/tests/test_data_loading.py +0 -0
  79. {tensorless-0.1.0 → tensorless-0.3.0}/tests/test_end_to_end.py +0 -0
  80. {tensorless-0.1.0 → tensorless-0.3.0}/tests/test_fingerprint.py +0 -0
  81. {tensorless-0.1.0 → tensorless-0.3.0}/tests/test_train_text_classification.py +0 -0
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.4
2
+ Name: tensorless
3
+ Version: 0.3.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
+ Tensorless trains small PyTorch models with sensible defaults. It supports
18
+ text generation, text classification, tabular classification, and regression.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install -e .
24
+ ```
25
+
26
+ ## Train on your data
27
+
28
+ ```python
29
+ import tensorless as tl
30
+
31
+ model = tl.train("./corpus.txt", task="text-generation")
32
+ print(model.generate("The", max_new_tokens=40))
33
+ ```
34
+
35
+ Text files are trained as next-token language models. BPE is the default
36
+ tokenizer; use `tokenizer="char"` for a character-level model. Tensorless
37
+ derives model size, batch size, epochs, validation, device, and BPE vocabulary
38
+ size from the data, while every setting can be overridden.
39
+
40
+ Long text is tokenized lazily and fed through PyTorch in fixed-size batches.
41
+ The automatic batch size uses a token budget; reduce `batch_size` if your
42
+ available memory is limited.
43
+
44
+ ## English starter pretraining
45
+
46
+ ```python
47
+ import tensorless as tl
48
+
49
+ model = tl.pretrain(out="english.tl", epochs=20, max_seq_len=128)
50
+ print(model.generate("A complete sentence", max_new_tokens=30))
51
+ ```
52
+
53
+ This offline starter corpus contains English prose and grammar examples. It is
54
+ for demos and smoke tests, not a replacement for a large language dataset. For
55
+ real pretraining, pass your own `.txt` corpus to `tl.train()` and increase the
56
+ training settings as your hardware allows.
57
+
58
+ ## Other tasks
59
+
60
+ ```python
61
+ tl.train("reviews/", task="text-classification")
62
+ tl.train("housing.csv", task="regression")
63
+ ```
64
+
65
+ Tabular preprocessing automatically handles numeric values, ISO dates, and
66
+ high-cardinality categories. Missing and rare values are handled using the
67
+ fitted training data, and the same preprocessing is stored in the `.tl` file.
68
+
69
+ Models are saved as `.tl` files and can be loaded later:
70
+
71
+ ```python
72
+ model = tl.load("model.tl")
73
+ print(model.info())
74
+ ```
75
+
76
+ See the [documentation](docs/quickstart.md) for data formats, configuration,
77
+ checkpointing, and the command-line interface.
@@ -0,0 +1,63 @@
1
+ # Tensorless
2
+
3
+ Tensorless trains small PyTorch models with sensible defaults. It supports
4
+ text generation, text classification, tabular classification, and regression.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pip install -e .
10
+ ```
11
+
12
+ ## Train on your data
13
+
14
+ ```python
15
+ import tensorless as tl
16
+
17
+ model = tl.train("./corpus.txt", task="text-generation")
18
+ print(model.generate("The", max_new_tokens=40))
19
+ ```
20
+
21
+ Text files are trained as next-token language models. BPE is the default
22
+ tokenizer; use `tokenizer="char"` for a character-level model. Tensorless
23
+ derives model size, batch size, epochs, validation, device, and BPE vocabulary
24
+ size from the data, while every setting can be overridden.
25
+
26
+ Long text is tokenized lazily and fed through PyTorch in fixed-size batches.
27
+ The automatic batch size uses a token budget; reduce `batch_size` if your
28
+ available memory is limited.
29
+
30
+ ## English starter pretraining
31
+
32
+ ```python
33
+ import tensorless as tl
34
+
35
+ model = tl.pretrain(out="english.tl", epochs=20, max_seq_len=128)
36
+ print(model.generate("A complete sentence", max_new_tokens=30))
37
+ ```
38
+
39
+ This offline starter corpus contains English prose and grammar examples. It is
40
+ for demos and smoke tests, not a replacement for a large language dataset. For
41
+ real pretraining, pass your own `.txt` corpus to `tl.train()` and increase the
42
+ training settings as your hardware allows.
43
+
44
+ ## Other tasks
45
+
46
+ ```python
47
+ tl.train("reviews/", task="text-classification")
48
+ tl.train("housing.csv", task="regression")
49
+ ```
50
+
51
+ Tabular preprocessing automatically handles numeric values, ISO dates, and
52
+ high-cardinality categories. Missing and rare values are handled using the
53
+ fitted training data, and the same preprocessing is stored in the `.tl` file.
54
+
55
+ Models are saved as `.tl` files and can be loaded later:
56
+
57
+ ```python
58
+ model = tl.load("model.tl")
59
+ print(model.info())
60
+ ```
61
+
62
+ See the [documentation](docs/quickstart.md) for data formats, configuration,
63
+ checkpointing, and the command-line interface.
@@ -22,7 +22,8 @@ tensorless/
22
22
  │ ├── detector.py Dataset -> task string
23
23
  │ └── config.py Dataset + TrainConfig -> ResolvedConfig
24
24
  ├── tokenization/
25
- │ └── char_tokenizer.py CharTokenizer: vocab build/encode/decode/save/load
25
+ │ ├── char_tokenizer.py CharTokenizer: vocab build/encode/decode/save/load
26
+ │ └── bpe_tokenizer.py BPETokenizer: corpus-trained subword encoding
26
27
  ├── models/
27
28
  │ ├── transformer.py TinyTransformer (GPT-style decoder)
28
29
  │ ├── mlp.py TabularMLP
@@ -31,14 +31,23 @@ Once the task is known, `tensorless/auto/config.py` picks:
31
31
 
32
32
  - **model type**: `transformer` for text tasks, `mlp` for tabular tasks
33
33
  - **size** (`d_model`, `layers`, `heads`): scaled to dataset size, from
34
- a tiny 2-layer/64-dim model for a few hundred examples up to an
34
+ a tiny 2-layer/64-dim model for a few hundred effective text examples up to an
35
35
  8-layer/384-dim model for 50,000+ examples
36
36
 
37
+ For text corpora, effective examples include corpus character count, so a
38
+ single large `.txt` file is not treated like one training example. BPE
39
+ vocabulary size is also bounded from corpus character diversity rather than
40
+ always using a fixed oversized vocabulary.
41
+
37
42
  This is a heuristic, not a search — the goal is "a model that trains
38
43
  quickly and doesn't wildly overfit or underfit for typical dataset
39
44
  sizes," not the best possible architecture. Override any of it:
40
45
  `tl.train("./data", d_model=512, layers=6)`.
41
46
 
47
+ For a packaged English grammar starter corpus, use
48
+ `tl.pretrain(out="english.tl")`. It is intended for demos and smoke tests;
49
+ larger local corpora should be passed to `tl.train()`.
50
+
42
51
  ## 3. Hyperparameter selection
43
52
 
44
53
  Batch size, epoch count, learning rate, warmup steps, and the
@@ -40,6 +40,10 @@ Writes are atomic: Tensorless writes to a temporary file in the same
40
40
  directory and renames it into place, so a crash mid-write never leaves a
41
41
  corrupt checkpoint that would block resumption.
42
42
 
43
+ When loading `.tl` files, Tensorless fills compatible fields introduced by
44
+ older versions with safe defaults. Files created by a newer unsupported format
45
+ version are rejected with an upgrade message instead of being partially read.
46
+
43
47
  ## How resumption works
44
48
 
45
49
  When `tl.train()` finds a checkpoint whose `dataset_fingerprint` matches
@@ -26,6 +26,8 @@ defaults.
26
26
  | `ff_mult` | auto | Feed-forward expansion multiplier (transformer only) |
27
27
  | `dropout` | `0.1` | Dropout probability |
28
28
  | `max_seq_len` | `256` (text) / `1` (tabular) | Max sequence length in tokens (text tasks) |
29
+ | `tokenizer` | `"bpe"` | Text tokenizer: `"bpe"` or `"char"` |
30
+ | `bpe_vocab_size` | `1000` | Maximum vocabulary size when `tokenizer="bpe"` |
29
31
 
30
32
  ## Optimization
31
33
 
@@ -45,7 +47,7 @@ defaults.
45
47
  | Field | Default | Description |
46
48
  |---|---|---|
47
49
  | `val_split` | `0.1` (if ≥50 examples, else `0`) | Fraction of data held out for validation |
48
- | `patience` | `5` | Epochs without improvement before stopping early |
50
+ | `patience` | `3` | Epochs without improvement before stopping early |
49
51
  | `min_delta` | `1e-4` | Minimum improvement to count as "improved" |
50
52
 
51
53
  ## Hardware
@@ -7,26 +7,6 @@
7
7
  - Optional: a CUDA-capable GPU, or a TPU with `torch_xla` installed, for
8
8
  faster training. Tensorless works fine on CPU-only machines too.
9
9
 
10
- ## Install from source
11
-
12
- Tensorless isn't published to PyPI yet. Install it directly from a
13
- checkout of this repository:
14
-
15
- ```bash
16
- git clone https://github.com/tensorless/tensorless.git
17
- cd tensorless
18
- pip install -e .
19
- ```
20
-
21
- The `-e` (editable) flag means changes to the source are picked up
22
- immediately without reinstalling — useful if you're also contributing.
23
-
24
- If you plan to run the test suite, install the dev extras too:
25
-
26
- ```bash
27
- pip install -e ".[dev]"
28
- ```
29
-
30
10
  ## Verify your installation
31
11
 
32
12
  ```bash
@@ -6,12 +6,10 @@ current limits:
6
6
 
7
7
  ## Models
8
8
 
9
- - The default text tokenizer is **character-level**, not subword/BPE.
10
- This means no vocabulary file dependency and it works on any language
11
- out of the box, but it's less token-efficient than production
12
- tokenizers (GPT-style BPE, SentencePiece), so very long documents take
13
- more sequence positions to represent, and generation quality per
14
- parameter is lower than a comparably-sized BPE-tokenized model.
9
+ - The default text tokenizer is the dependency-free **BPE** tokenizer, which
10
+ learns merges from the training corpus. It is more token-efficient than the
11
+ optional `tokenizer="char"` alternative, but still simpler than production
12
+ tokenizers such as GPT-style BPE or SentencePiece.
15
13
  - Models are trained **from scratch** every time — there's no
16
14
  fine-tuning of pretrained checkpoints. This keeps things dependency-
17
15
  free and fast to set up, but means text-generation quality on small
@@ -5,18 +5,7 @@ a commitment or timeline.
5
5
 
6
6
  ## Near-term
7
7
 
8
- - **Subword/BPE tokenization** as an alternative to the default
9
- character-level tokenizer, for better efficiency on larger text
10
- corpora
11
- - **`.tl` format migration** — forward-compatible loading of files
12
- written by older Tensorless versions
13
- - **Better tabular preprocessing** — handling of date/datetime columns,
14
- high-cardinality categorical columns, and more robust outlier handling
15
- for regression targets
16
8
  - **Progress bars** for training (currently plain print-based logging)
17
- - **`tl.train(..., resume=False)`** enforcement — currently `resume` is
18
- accepted in `TrainConfig` but the automatic resume decision doesn't
19
- yet fully respect an explicit `False` override in every code path
20
9
  - **Multi-GPU / distributed training** for larger datasets
21
10
 
22
11
  ## Medium-term
@@ -38,8 +27,6 @@ a commitment or timeline.
38
27
 
39
28
  - **Alternate backends** (JAX, a lightweight NumPy-only backend) behind
40
29
  the same `tl.train()`/`tl.load()` API
41
- - **Streaming/out-of-core training** for datasets too large to fit in
42
- memory
43
30
  - **Export to other formats** (ONNX, TorchScript) from a `.tl` file for
44
31
  deployment outside Python
45
32
 
@@ -38,6 +38,11 @@ A directory can mix multiple files of the *same* format (e.g. several
38
38
  plain text files with structured (JSON/CSV) files in the same directory
39
39
  raises a `DataError` asking you to separate them.
40
40
 
41
+ For tabular data, numeric columns are robustly scaled and missing values use
42
+ the training median. ISO-8601 date and datetime columns are converted to
43
+ numeric timestamps. Categorical columns are frequency-ranked and capped at
44
+ 1,000 learned values; rare or unseen values use the `<unk>` category.
45
+
41
46
  Tensorless never modifies, moves, or deletes files in your dataset
42
47
  directory. It only ever reads from `path`; all output goes to the `out`
43
48
  file and `checkpoint_dir`.
@@ -68,7 +73,9 @@ By default, Tensorless holds out `val_split` of the data (10% for
68
73
  datasets with 50+ examples, 0% for smaller ones where a held-out split
69
74
  wouldn't be meaningful) and tracks validation loss after each epoch. If
70
75
  validation loss doesn't improve by at least `min_delta` for `patience`
71
- consecutive epochs, training stops early.
76
+ consecutive epochs, training stops early. The automatic default is 3
77
+ consecutive epochs, and the completed model is still written to the `.tl`
78
+ output file.
72
79
 
73
80
  ## Checkpointing during training
74
81
 
@@ -81,8 +88,8 @@ for resumption.
81
88
 
82
89
  | Task | Model | What's learned |
83
90
  |---|---|---|
84
- | `text-generation` | Small GPT-style decoder transformer, char-level tokenizer | Next-character prediction over your text |
85
- | `text-classification` | Same transformer backbone, classification head on the final token | Text → one of your labeled classes |
91
+ | `text-generation` | Small GPT-style decoder transformer, BPE tokenizer by default | Next-token prediction over your text |
92
+ | `text-classification` | Same transformer backbone, BPE tokenizer by default, classification head on the final token | Text → one of your labeled classes |
86
93
  | `classification` | MLP with per-column categorical embeddings | Row of features → one of your labeled classes |
87
94
  | `regression` | Same MLP, single continuous output | Row of features → a number |
88
95
 
@@ -32,12 +32,12 @@ def main():
32
32
  model = tl.train(
33
33
  CORPUS_PATH,
34
34
  out=os.path.join(DATA_DIR, "text_gen_example.tl"),
35
- epochs=5,
36
- d_model=64,
37
- layers=2,
38
- heads=4,
39
- batch_size=16,
40
- max_seq_len=64,
35
+ # epochs=5,
36
+ # d_model=64,
37
+ # layers=2,
38
+ # heads=4,
39
+ # batch_size=16,
40
+ # max_seq_len=64,
41
41
  )
42
42
 
43
43
  print("\nGenerated text:")
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tensorless"
7
- version = "0.1.0"
7
+ version = "0.3.0"
8
8
  description = "ML with maximum automation and minimum setup."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -22,3 +22,6 @@ tensorless = "tensorless.cli.main:main"
22
22
 
23
23
  [tool.setuptools.packages.find]
24
24
  include = ["tensorless*"]
25
+
26
+ [tool.setuptools.package-data]
27
+ tensorless = ["data/*.txt"]
@@ -12,7 +12,7 @@ ML with maximum automation and minimum setup.
12
12
  See https://github.com/tensorless/tensorless for full documentation.
13
13
  """
14
14
 
15
- from .api import train, run, load, inspect
15
+ from .api import train, pretrain, run, load, inspect
16
16
  from .config import TrainConfig
17
17
  from .errors import (
18
18
  TensorlessError,
@@ -26,6 +26,7 @@ from ._version import __version__
26
26
 
27
27
  __all__ = [
28
28
  "train",
29
+ "pretrain",
29
30
  "run",
30
31
  "load",
31
32
  "inspect",
@@ -12,6 +12,7 @@
12
12
  from __future__ import annotations
13
13
 
14
14
  import dataclasses
15
+ import importlib.resources
15
16
  import os
16
17
  from typing import Any, Optional
17
18
 
@@ -92,7 +93,7 @@ def train(path: str, **kwargs: Any) -> LoadedModel:
92
93
  return LoadedModel(existing)
93
94
 
94
95
  # 2. Is there an interrupted / matching checkpoint to resume from?
95
- if checkpoint_mgr.exists():
96
+ if checkpoint_mgr.exists() and user_cfg.resume is not False:
96
97
  ckpt = checkpoint_mgr.load()
97
98
  if ckpt.get("dataset_fingerprint") == fingerprint:
98
99
  if ckpt.get("training_complete"):
@@ -119,6 +120,10 @@ def train(path: str, **kwargs: Any) -> LoadedModel:
119
120
  "checkpoint -- retraining from scratch."
120
121
  )
121
122
  checkpoint_mgr.clear()
123
+ elif checkpoint_mgr.exists():
124
+ if user_cfg.verbose:
125
+ print("[tensorless] resume=False -- ignoring the existing checkpoint.")
126
+ checkpoint_mgr.clear()
122
127
  else:
123
128
  checkpoint_mgr.clear()
124
129
 
@@ -162,6 +167,17 @@ def train(path: str, **kwargs: Any) -> LoadedModel:
162
167
  return LoadedModel(payload)
163
168
 
164
169
 
170
+ def pretrain(
171
+ out: str = "english_pretrained.tl", language: str = "english", **kwargs: Any
172
+ ) -> LoadedModel:
173
+ """Pretrain a small language model on the built-in starter corpus."""
174
+ if language.lower() != "english":
175
+ raise ValueError("The built-in pretraining corpus currently supports only 'english'.")
176
+ corpus = importlib.resources.files("tensorless.data").joinpath("english_grammar.txt")
177
+ options = {"task": "text-generation", "out": out, **kwargs}
178
+ return train(str(corpus), **options)
179
+
180
+
165
181
  def _finalize_from_checkpoint(ckpt: dict, out: str, verbose: bool) -> LoadedModel:
166
182
  payload = {
167
183
  "tl_format_version": _tl_format_version,
@@ -43,15 +43,17 @@ def _auto_model_size(n_examples: int, kind: str):
43
43
  return 128, 4, 1, 2
44
44
 
45
45
 
46
- def _auto_batch_size(n_examples: int) -> int:
46
+ def _auto_batch_size(n_examples: int, max_seq_len: int) -> int:
47
+ token_budget = 8192
48
+ sequence_batch = max(1, token_budget // max_seq_len)
47
49
  if n_examples < 200:
48
- return 8
50
+ return min(8, sequence_batch)
49
51
  elif n_examples < 2000:
50
- return 16
52
+ return min(16, sequence_batch)
51
53
  elif n_examples < 20000:
52
- return 32
54
+ return min(32, sequence_batch)
53
55
  else:
54
- return 64
56
+ return min(64, sequence_batch)
55
57
 
56
58
 
57
59
  def _auto_epochs(n_examples: int) -> int:
@@ -65,12 +67,29 @@ def _auto_epochs(n_examples: int) -> int:
65
67
  return 5
66
68
 
67
69
 
70
+ def _effective_text_size(ds: Dataset) -> int:
71
+ """Estimate useful training examples for raw corpora."""
72
+ if ds.kind in ("text", "text_labeled"):
73
+ return max(len(ds), sum(len(text) for text in ds.texts) // 200)
74
+ return len(ds)
75
+
76
+
77
+ def _auto_vocab_size(ds: Dataset) -> int:
78
+ if ds.kind not in ("text", "text_labeled"):
79
+ return 1000
80
+ unique_chars = len(set("".join(ds.texts)))
81
+ return min(4096, max(64, unique_chars * 8))
82
+
83
+
68
84
  def resolve_config(ds: Dataset, user: TrainConfig) -> ResolvedConfig:
69
- n = len(ds)
85
+ n = _effective_text_size(ds)
70
86
  task = user.task or detect_task(ds)
71
87
  model_type = user.model_type or (
72
88
  "transformer" if task in ("text-generation", "text-classification") else "mlp"
73
89
  )
90
+ tokenizer = user.tokenizer or "bpe"
91
+ if tokenizer not in ("char", "bpe"):
92
+ raise ValueError("tokenizer must be 'char' or 'bpe'")
74
93
 
75
94
  d_model, layers, heads, ff_mult = _auto_model_size(n, ds.kind)
76
95
 
@@ -79,6 +98,7 @@ def resolve_config(ds: Dataset, user: TrainConfig) -> ResolvedConfig:
79
98
  out = user.out or "model.tl"
80
99
  checkpoint_dir = user.checkpoint_dir or (out + ".ckpt")
81
100
 
101
+ max_seq_len = user.max_seq_len or (256 if ds.kind in ("text", "text_labeled") else 1)
82
102
  resolved = ResolvedConfig(
83
103
  out=out,
84
104
  force=bool(user.force),
@@ -91,17 +111,19 @@ def resolve_config(ds: Dataset, user: TrainConfig) -> ResolvedConfig:
91
111
  heads=user.heads or heads,
92
112
  ff_mult=user.ff_mult or ff_mult,
93
113
  dropout=user.dropout if user.dropout is not None else 0.1,
94
- max_seq_len=user.max_seq_len or (256 if ds.kind in ("text", "text_labeled") else 1),
114
+ max_seq_len=max_seq_len,
115
+ tokenizer=tokenizer,
116
+ bpe_vocab_size=user.bpe_vocab_size if user.bpe_vocab_size is not None else _auto_vocab_size(ds),
95
117
  optimizer=user.optimizer or "adamw",
96
118
  learning_rate=user.learning_rate or (3e-4 if model_type == "transformer" else 1e-3),
97
119
  weight_decay=user.weight_decay if user.weight_decay is not None else 0.01,
98
- batch_size=user.batch_size or _auto_batch_size(n),
120
+ batch_size=user.batch_size if user.batch_size is not None else _auto_batch_size(n, max_seq_len),
99
121
  epochs=user.epochs or _auto_epochs(n),
100
122
  max_steps=user.max_steps,
101
123
  grad_clip=user.grad_clip if user.grad_clip is not None else 1.0,
102
124
  warmup_steps=user.warmup_steps if user.warmup_steps is not None else min(100, max(1, n // 10)),
103
125
  val_split=user.val_split if user.val_split is not None else (0.1 if n >= 50 else 0.0),
104
- patience=user.patience or 5,
126
+ patience=user.patience if user.patience is not None else 3,
105
127
  min_delta=user.min_delta if user.min_delta is not None else 1e-4,
106
128
  device=device,
107
129
  precision=precision,
@@ -33,6 +33,8 @@ class TrainConfig:
33
33
  ff_mult: Optional[int] = None
34
34
  dropout: Optional[float] = None
35
35
  max_seq_len: Optional[int] = None
36
+ tokenizer: Optional[str] = None # "char" or "bpe"
37
+ bpe_vocab_size: Optional[int] = None
36
38
 
37
39
  # --- optimization ---
38
40
  optimizer: Optional[str] = None # "adamw", "adam", "sgd"
@@ -89,6 +91,8 @@ class ResolvedConfig:
89
91
  ff_mult: int
90
92
  dropout: float
91
93
  max_seq_len: int
94
+ tokenizer: str
95
+ bpe_vocab_size: int
92
96
 
93
97
  optimizer: str
94
98
  learning_rate: float