Text Generation
Transformers
Safetensors
taonet
trust-remote-code
sentencepiece
custom-architecture
custom_code
Instructions to use TaoTern/TaoNet-mini-A2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use TaoTern/TaoNet-mini-A2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="TaoTern/TaoNet-mini-A2", trust_remote_code=True)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("TaoTern/TaoNet-mini-A2", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use TaoTern/TaoNet-mini-A2 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "TaoTern/TaoNet-mini-A2" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "TaoTern/TaoNet-mini-A2", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/TaoTern/TaoNet-mini-A2
- SGLang
How to use TaoTern/TaoNet-mini-A2 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "TaoTern/TaoNet-mini-A2" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "TaoTern/TaoNet-mini-A2", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "TaoTern/TaoNet-mini-A2" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "TaoTern/TaoNet-mini-A2", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use TaoTern/TaoNet-mini-A2 with Docker Model Runner:
docker model run hf.co/TaoTern/TaoNet-mini-A2
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +98 -0
- added_tokens.json +6 -0
- config.json +59 -0
- configuration_taonet.py +72 -0
- embeddings.py +23 -0
- export_to_hf.py +84 -0
- generation_config.json +9 -0
- mla_components.py +213 -0
- model.safetensors +3 -0
- modeling_taonet.py +86 -0
- special_tokens_map.json +13 -0
- src/taoTrain/__init__.py +8 -0
- src/taoTrain/__pycache__/__init__.cpython-310.pyc +0 -0
- src/taoTrain/__pycache__/__init__.cpython-312.pyc +0 -0
- src/taoTrain/__pycache__/__init__.cpython-314.pyc +0 -0
- src/taoTrain/__pycache__/cli.cpython-310.pyc +0 -0
- src/taoTrain/__pycache__/cli.cpython-312.pyc +0 -0
- src/taoTrain/__pycache__/cli.cpython-314.pyc +0 -0
- src/taoTrain/__pycache__/config.cpython-310.pyc +0 -0
- src/taoTrain/__pycache__/config.cpython-312.pyc +0 -0
- src/taoTrain/__pycache__/config.cpython-314.pyc +0 -0
- src/taoTrain/benchmarks/__init__.py +5 -0
- src/taoTrain/benchmarks/__pycache__/__init__.cpython-310.pyc +0 -0
- src/taoTrain/benchmarks/__pycache__/__init__.cpython-312.pyc +0 -0
- src/taoTrain/benchmarks/__pycache__/__init__.cpython-314.pyc +0 -0
- src/taoTrain/benchmarks/__pycache__/runner.cpython-310.pyc +0 -0
- src/taoTrain/benchmarks/__pycache__/runner.cpython-312.pyc +0 -0
- src/taoTrain/benchmarks/__pycache__/runner.cpython-314.pyc +0 -0
- src/taoTrain/benchmarks/runner.py +221 -0
- src/taoTrain/checkpointing/__init__.py +5 -0
- src/taoTrain/checkpointing/__pycache__/__init__.cpython-310.pyc +0 -0
- src/taoTrain/checkpointing/__pycache__/__init__.cpython-312.pyc +0 -0
- src/taoTrain/checkpointing/__pycache__/__init__.cpython-314.pyc +0 -0
- src/taoTrain/checkpointing/__pycache__/checkpoint.cpython-310.pyc +0 -0
- src/taoTrain/checkpointing/__pycache__/checkpoint.cpython-312.pyc +0 -0
- src/taoTrain/checkpointing/__pycache__/checkpoint.cpython-314.pyc +0 -0
- src/taoTrain/checkpointing/checkpoint.py +194 -0
- src/taoTrain/cli.py +470 -0
- src/taoTrain/config.py +871 -0
- src/taoTrain/core/__init__.py +5 -0
- src/taoTrain/core/__pycache__/__init__.cpython-310.pyc +0 -0
- src/taoTrain/core/__pycache__/__init__.cpython-312.pyc +0 -0
- src/taoTrain/core/__pycache__/__init__.cpython-314.pyc +0 -0
- src/taoTrain/core/__pycache__/base.cpython-310.pyc +0 -0
- src/taoTrain/core/__pycache__/base.cpython-312.pyc +0 -0
- src/taoTrain/core/__pycache__/base.cpython-314.pyc +0 -0
- src/taoTrain/core/base.py +279 -0
- src/taoTrain/data/__init__.py +47 -0
- src/taoTrain/data/__pycache__/__init__.cpython-310.pyc +0 -0
- src/taoTrain/data/__pycache__/__init__.cpython-312.pyc +0 -0
README.md
CHANGED
|
@@ -1,3 +1,101 @@
|
|
| 1 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
license: mit
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
library_name: transformers
|
| 3 |
+
tags:
|
| 4 |
+
- text-generation
|
| 5 |
+
- trust-remote-code
|
| 6 |
+
- sentencepiece
|
| 7 |
+
- custom-architecture
|
| 8 |
+
pipeline_tag: text-generation
|
| 9 |
license: mit
|
| 10 |
---
|
| 11 |
+
|
| 12 |
+
# TaoNet-mini-A2
|
| 13 |
+
|
| 14 |
+
`TaoNet-mini-A2` is packaged as a Hugging Face `trust_remote_code` repository for causal text generation.
|
| 15 |
+
|
| 16 |
+
## Files
|
| 17 |
+
|
| 18 |
+
The repo root contains:
|
| 19 |
+
|
| 20 |
+
- `configuration_taonet.py`
|
| 21 |
+
- `modeling_taonet.py`
|
| 22 |
+
- `tokenization_taonet.py`
|
| 23 |
+
- `taonet_model.py`
|
| 24 |
+
- `mla_components.py`
|
| 25 |
+
- `embeddings.py`
|
| 26 |
+
|
| 27 |
+
The checkpoint export helper is:
|
| 28 |
+
|
| 29 |
+
- `export_to_hf.py`
|
| 30 |
+
|
| 31 |
+
Run that script once on your machine to convert `checkpoints/sft/final_model.pt` into a standard Hugging Face weight file such as `pytorch_model.bin`.
|
| 32 |
+
|
| 33 |
+
## Usage
|
| 34 |
+
|
| 35 |
+
```python
|
| 36 |
+
import time
|
| 37 |
+
import torch
|
| 38 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 39 |
+
|
| 40 |
+
MODEL_NAME = "TaoTern/TaoNet-mini-A2"
|
| 41 |
+
|
| 42 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 43 |
+
dtype = torch.bfloat16 if device == "cuda" else torch.float32
|
| 44 |
+
|
| 45 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
|
| 46 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 47 |
+
MODEL_NAME,
|
| 48 |
+
trust_remote_code=True,
|
| 49 |
+
torch_dtype=dtype,
|
| 50 |
+
).to(device)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def generate_text(prompt, max_new_tokens=64, temperature=0.7, top_p=0.85):
|
| 54 |
+
inputs = tokenizer(prompt, return_tensors="pt")
|
| 55 |
+
inputs = {key: value.to(device) for key, value in inputs.items()}
|
| 56 |
+
|
| 57 |
+
start_time = time.time()
|
| 58 |
+
with torch.inference_mode():
|
| 59 |
+
outputs = model.generate(
|
| 60 |
+
**inputs,
|
| 61 |
+
max_new_tokens=max_new_tokens,
|
| 62 |
+
temperature=temperature,
|
| 63 |
+
top_p=top_p,
|
| 64 |
+
repetition_penalty=1.2,
|
| 65 |
+
do_sample=True,
|
| 66 |
+
use_cache=False,
|
| 67 |
+
pad_token_id=tokenizer.pad_token_id,
|
| 68 |
+
eos_token_id=tokenizer.eos_token_id,
|
| 69 |
+
)
|
| 70 |
+
elapsed_time = time.time() - start_time
|
| 71 |
+
|
| 72 |
+
new_tokens = outputs.shape[1] - inputs["input_ids"].shape[1]
|
| 73 |
+
tokens_per_second = new_tokens / elapsed_time if elapsed_time > 0 else 0.0
|
| 74 |
+
completion = tokenizer.decode(
|
| 75 |
+
outputs[0][inputs["input_ids"].shape[1] :],
|
| 76 |
+
skip_special_tokens=True,
|
| 77 |
+
)
|
| 78 |
+
return completion, tokens_per_second
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
if __name__ == "__main__":
|
| 82 |
+
text, tps = generate_text("Fruit is now expensive so we should")
|
| 83 |
+
print(text)
|
| 84 |
+
print(f"\\nTokens per second: {tps:.2f}")
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
## Export
|
| 88 |
+
|
| 89 |
+
When you are ready to package the trained checkpoint into standard HF weights, run:
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
python export_to_hf.py
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
That script will:
|
| 96 |
+
|
| 97 |
+
- load `checkpoints/sft/final_model.pt`
|
| 98 |
+
- rebuild the TaoNet config from the checkpoint metadata
|
| 99 |
+
- load the weights into the HF wrapper
|
| 100 |
+
- write standard Hugging Face model files at repo root
|
| 101 |
+
- copy `tokenizer.model`, `tokenizer.vocab`, and `tokenizer.special_tokens.json` to repo root
|
added_tokens.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"<BOS>": 8192,
|
| 3 |
+
"<EOS>": 8193,
|
| 4 |
+
"<PAD>": 8195,
|
| 5 |
+
"<UNK>": 8194
|
| 6 |
+
}
|
config.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"TaoNetForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"bos_token_id": 1,
|
| 6 |
+
"cnn_channels": [
|
| 7 |
+
32,
|
| 8 |
+
64,
|
| 9 |
+
128
|
| 10 |
+
],
|
| 11 |
+
"cnn_kernel_size": 3,
|
| 12 |
+
"d_embed_rank": 96,
|
| 13 |
+
"d_latent_kv": 768,
|
| 14 |
+
"d_rope": 128,
|
| 15 |
+
"dropout": 0.02,
|
| 16 |
+
"dtype": "float32",
|
| 17 |
+
"eos_token_id": 2,
|
| 18 |
+
"gamma_activation": "gelu",
|
| 19 |
+
"gamma_discretization": "bilinear",
|
| 20 |
+
"gamma_dt_init": 0.01,
|
| 21 |
+
"gamma_dt_max": 0.1,
|
| 22 |
+
"gamma_dt_min": 0.001,
|
| 23 |
+
"gamma_gate": true,
|
| 24 |
+
"gamma_gate_bias": 2.0,
|
| 25 |
+
"gamma_hidden_dim": 1536,
|
| 26 |
+
"gamma_input_gate": true,
|
| 27 |
+
"gamma_input_gate_bias": 2.0,
|
| 28 |
+
"gamma_kernel_mode": "auto",
|
| 29 |
+
"gamma_kernel_threshold": 64,
|
| 30 |
+
"gamma_layer_scale_init": 0.1,
|
| 31 |
+
"gamma_prenorm": true,
|
| 32 |
+
"gamma_residual_scale": 1.0,
|
| 33 |
+
"gamma_use_D": true,
|
| 34 |
+
"gamma_use_output_linear": true,
|
| 35 |
+
"gqa_groups": 1,
|
| 36 |
+
"head_dim": 128,
|
| 37 |
+
"hidden_dim": 1024,
|
| 38 |
+
"hidden_dim_ff": 3072,
|
| 39 |
+
"image_size": 224,
|
| 40 |
+
"image_token": "<image>",
|
| 41 |
+
"init_std": 0.02,
|
| 42 |
+
"intermediate_dim": 4096,
|
| 43 |
+
"max_seq_length": 1024,
|
| 44 |
+
"model_type": "taonet",
|
| 45 |
+
"num_heads": 8,
|
| 46 |
+
"num_layers": 16,
|
| 47 |
+
"pad_token_id": 3,
|
| 48 |
+
"rope_scale": 40.0,
|
| 49 |
+
"transformers_version": "5.1.0",
|
| 50 |
+
"unk_token_id": 0,
|
| 51 |
+
"use_factorized_embedding": true,
|
| 52 |
+
"vision_encoder_type": "cnn",
|
| 53 |
+
"vision_output_dim": 256,
|
| 54 |
+
"vision_prefix_tokens": 10,
|
| 55 |
+
"vocab_size": 8192,
|
| 56 |
+
"yarn_alpha": 1.0,
|
| 57 |
+
"yarn_enabled": false,
|
| 58 |
+
"yarn_original_max_seq_length": null
|
| 59 |
+
}
|
configuration_taonet.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hugging Face configuration for TaoNet."""
|
| 2 |
+
|
| 3 |
+
from transformers import PretrainedConfig
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class TaoNetConfig(PretrainedConfig):
|
| 7 |
+
"""Configuration for the TaoNet causal language model."""
|
| 8 |
+
|
| 9 |
+
model_type = "taonet"
|
| 10 |
+
|
| 11 |
+
def __init__(
|
| 12 |
+
self,
|
| 13 |
+
vocab_size=8192,
|
| 14 |
+
hidden_dim=768,
|
| 15 |
+
num_layers=12,
|
| 16 |
+
num_heads=12,
|
| 17 |
+
head_dim=None,
|
| 18 |
+
intermediate_dim=None,
|
| 19 |
+
dropout=0.1,
|
| 20 |
+
max_seq_length=2048,
|
| 21 |
+
d_latent_kv=None,
|
| 22 |
+
d_rope=None,
|
| 23 |
+
gqa_groups=1,
|
| 24 |
+
hidden_dim_ff=None,
|
| 25 |
+
use_factorized_embedding=False,
|
| 26 |
+
d_embed_rank=96,
|
| 27 |
+
rope_scale=40.0,
|
| 28 |
+
yarn_enabled=False,
|
| 29 |
+
yarn_original_max_seq_length=None,
|
| 30 |
+
yarn_alpha=1.0,
|
| 31 |
+
init_std=0.02,
|
| 32 |
+
pad_token_id=3,
|
| 33 |
+
bos_token_id=1,
|
| 34 |
+
eos_token_id=2,
|
| 35 |
+
unk_token_id=0,
|
| 36 |
+
**kwargs,
|
| 37 |
+
):
|
| 38 |
+
super().__init__(
|
| 39 |
+
pad_token_id=pad_token_id,
|
| 40 |
+
bos_token_id=bos_token_id,
|
| 41 |
+
eos_token_id=eos_token_id,
|
| 42 |
+
unk_token_id=unk_token_id,
|
| 43 |
+
**kwargs,
|
| 44 |
+
)
|
| 45 |
+
self.vocab_size = vocab_size
|
| 46 |
+
self.hidden_dim = hidden_dim
|
| 47 |
+
self.num_layers = num_layers
|
| 48 |
+
self.num_heads = num_heads
|
| 49 |
+
self.head_dim = head_dim if head_dim is not None else hidden_dim // num_heads
|
| 50 |
+
self.intermediate_dim = intermediate_dim if intermediate_dim is not None else hidden_dim * 4
|
| 51 |
+
self.dropout = dropout
|
| 52 |
+
self.max_seq_length = max_seq_length
|
| 53 |
+
self.d_latent_kv = d_latent_kv
|
| 54 |
+
self.d_rope = d_rope
|
| 55 |
+
self.gqa_groups = gqa_groups
|
| 56 |
+
self.hidden_dim_ff = hidden_dim_ff
|
| 57 |
+
self.use_factorized_embedding = use_factorized_embedding
|
| 58 |
+
self.d_embed_rank = d_embed_rank
|
| 59 |
+
self.rope_scale = rope_scale
|
| 60 |
+
self.yarn_enabled = yarn_enabled
|
| 61 |
+
self.yarn_original_max_seq_length = yarn_original_max_seq_length
|
| 62 |
+
self.yarn_alpha = yarn_alpha
|
| 63 |
+
self.init_std = init_std
|
| 64 |
+
|
| 65 |
+
@classmethod
|
| 66 |
+
def from_taotrain_model_config(cls, model_config, **extra_kwargs):
|
| 67 |
+
"""Build an HF config from a TaoTrain model-config dictionary."""
|
| 68 |
+
data = dict(model_config)
|
| 69 |
+
data.pop("architecture_type", None)
|
| 70 |
+
data.pop("llm_architecture_type", None)
|
| 71 |
+
data.update(extra_kwargs)
|
| 72 |
+
return cls(**data)
|
embeddings.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Embedding layers used by TaoNet."""
|
| 2 |
+
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class FactorizedEmbedding(nn.Module):
|
| 7 |
+
"""Low-rank factorized embedding."""
|
| 8 |
+
|
| 9 |
+
def __init__(self, vocab_size, d_model, d_embed_rank=96):
|
| 10 |
+
super().__init__()
|
| 11 |
+
self.vocab_size = vocab_size
|
| 12 |
+
self.d_model = d_model
|
| 13 |
+
self.d_embed_rank = d_embed_rank
|
| 14 |
+
|
| 15 |
+
self.embed = nn.Embedding(vocab_size, d_embed_rank)
|
| 16 |
+
self.proj = nn.Linear(d_embed_rank, d_model, bias=False)
|
| 17 |
+
|
| 18 |
+
nn.init.normal_(self.embed.weight, mean=0.0, std=0.02)
|
| 19 |
+
nn.init.normal_(self.proj.weight, mean=0.0, std=0.02)
|
| 20 |
+
|
| 21 |
+
def forward(self, input_ids):
|
| 22 |
+
x = self.embed(input_ids)
|
| 23 |
+
return self.proj(x)
|
export_to_hf.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Convert TaoTrain checkpoint + tokenizer assets into a standard HF package."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import shutil
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def normalize_checkpoint(checkpoint):
|
| 9 |
+
if isinstance(checkpoint, dict):
|
| 10 |
+
if "model_state" in checkpoint:
|
| 11 |
+
return checkpoint["model_state"], checkpoint.get("config", {})
|
| 12 |
+
if "model_state_dict" in checkpoint:
|
| 13 |
+
return checkpoint["model_state_dict"], checkpoint.get("config", {})
|
| 14 |
+
return checkpoint, {}
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def infer_special_token_paths(repo_dir):
|
| 18 |
+
subdir_metadata = repo_dir / "tokenizer" / "tokenizer.special_tokens.json"
|
| 19 |
+
root_metadata = repo_dir / "tokenizer.special_tokens.json"
|
| 20 |
+
subdir_model = repo_dir / "tokenizer" / "tokenizer.model"
|
| 21 |
+
subdir_vocab = repo_dir / "tokenizer" / "tokenizer.vocab"
|
| 22 |
+
if subdir_metadata.exists() and subdir_model.exists() and subdir_vocab.exists():
|
| 23 |
+
return subdir_model, subdir_metadata, subdir_vocab
|
| 24 |
+
if root_metadata.exists():
|
| 25 |
+
return repo_dir / "tokenizer.model", root_metadata, repo_dir / "tokenizer.vocab"
|
| 26 |
+
return repo_dir / "tokenizer" / "tokenizer.model", subdir_metadata, repo_dir / "tokenizer" / "tokenizer.vocab"
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def main():
|
| 30 |
+
import torch
|
| 31 |
+
|
| 32 |
+
from configuration_taonet import TaoNetConfig
|
| 33 |
+
from modeling_taonet import TaoNetForCausalLM
|
| 34 |
+
from tokenization_taonet import TaoNetTokenizer
|
| 35 |
+
|
| 36 |
+
repo_dir = Path(__file__).resolve().parent
|
| 37 |
+
checkpoint_path = repo_dir / "checkpoints" / "sft" / "final_model.pt"
|
| 38 |
+
|
| 39 |
+
checkpoint = torch.load(checkpoint_path, map_location="cpu")
|
| 40 |
+
model_state, train_config = normalize_checkpoint(checkpoint)
|
| 41 |
+
model_config = dict(train_config.get("model", {}))
|
| 42 |
+
|
| 43 |
+
metadata_model_path, metadata_path, vocab_path = infer_special_token_paths(repo_dir)
|
| 44 |
+
with open(metadata_path, "r", encoding="utf-8") as handle:
|
| 45 |
+
metadata = json.load(handle)
|
| 46 |
+
|
| 47 |
+
special_tokens = metadata.get("special_tokens", {})
|
| 48 |
+
hf_config = TaoNetConfig.from_taotrain_model_config(
|
| 49 |
+
model_config,
|
| 50 |
+
vocab_size=model_config.get("vocab_size", sum(1 for _ in open(vocab_path, "r", encoding="utf-8"))),
|
| 51 |
+
pad_token_id=special_tokens.get("<PAD>", 3),
|
| 52 |
+
bos_token_id=special_tokens.get("<BOS>", 1),
|
| 53 |
+
eos_token_id=special_tokens.get("<EOS>", 2),
|
| 54 |
+
unk_token_id=special_tokens.get("<UNK>", 0),
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
model = TaoNetForCausalLM(hf_config)
|
| 58 |
+
missing, unexpected = model.model.load_state_dict(model_state, strict=False)
|
| 59 |
+
model.tie_weights()
|
| 60 |
+
model.save_pretrained(repo_dir, safe_serialization=False)
|
| 61 |
+
|
| 62 |
+
tokenizer = TaoNetTokenizer(
|
| 63 |
+
vocab_file=str(metadata_model_path),
|
| 64 |
+
special_tokens_file=str(metadata_path),
|
| 65 |
+
)
|
| 66 |
+
tokenizer.save_pretrained(repo_dir)
|
| 67 |
+
|
| 68 |
+
shutil.copyfile(metadata_model_path, repo_dir / "tokenizer.model")
|
| 69 |
+
shutil.copyfile(metadata_path, repo_dir / "tokenizer.special_tokens.json")
|
| 70 |
+
shutil.copyfile(vocab_path, repo_dir / "tokenizer.vocab")
|
| 71 |
+
|
| 72 |
+
if missing:
|
| 73 |
+
print("Missing keys while loading model state:")
|
| 74 |
+
for key in missing:
|
| 75 |
+
print(f" - {key}")
|
| 76 |
+
if unexpected:
|
| 77 |
+
print("Unexpected keys while loading model state:")
|
| 78 |
+
for key in unexpected:
|
| 79 |
+
print(f" - {key}")
|
| 80 |
+
print(f"Saved Hugging Face package to: {repo_dir}")
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
if __name__ == "__main__":
|
| 84 |
+
main()
|
generation_config.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_from_model_config": true,
|
| 3 |
+
"bos_token_id": 1,
|
| 4 |
+
"eos_token_id": 2,
|
| 5 |
+
"output_attentions": false,
|
| 6 |
+
"output_hidden_states": false,
|
| 7 |
+
"pad_token_id": 3,
|
| 8 |
+
"transformers_version": "5.1.0"
|
| 9 |
+
}
|
mla_components.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MLA attention blocks used by TaoNet."""
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class RotaryEmbedding(nn.Module):
|
| 11 |
+
"""Rotary position embedding with optional YaRN scaling."""
|
| 12 |
+
|
| 13 |
+
def __init__(
|
| 14 |
+
self,
|
| 15 |
+
dim,
|
| 16 |
+
rope_scale=40.0,
|
| 17 |
+
max_seq_length=1024,
|
| 18 |
+
yarn_enabled=False,
|
| 19 |
+
yarn_original_max_seq_length=None,
|
| 20 |
+
yarn_alpha=1.0,
|
| 21 |
+
):
|
| 22 |
+
super().__init__()
|
| 23 |
+
if dim % 2 != 0:
|
| 24 |
+
raise ValueError("RotaryEmbedding requires an even dimension")
|
| 25 |
+
self.dim = dim
|
| 26 |
+
self.rope_scale = rope_scale
|
| 27 |
+
self.max_seq_length = max_seq_length
|
| 28 |
+
self.yarn_enabled = yarn_enabled
|
| 29 |
+
self.yarn_original_max_seq_length = (
|
| 30 |
+
yarn_original_max_seq_length if yarn_original_max_seq_length is not None else max_seq_length
|
| 31 |
+
)
|
| 32 |
+
self.yarn_alpha = yarn_alpha
|
| 33 |
+
|
| 34 |
+
inv_freq = 1.0 / (10000 ** (torch.arange(0, dim, 2).float() / dim))
|
| 35 |
+
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
| 36 |
+
|
| 37 |
+
def _apply_yarn_scaling(self, freqs, seq_len):
|
| 38 |
+
if not self.yarn_enabled:
|
| 39 |
+
return freqs
|
| 40 |
+
original_max_seq_length = self.yarn_original_max_seq_length
|
| 41 |
+
if seq_len <= original_max_seq_length:
|
| 42 |
+
return freqs
|
| 43 |
+
target_scale = self.max_seq_length / original_max_seq_length
|
| 44 |
+
current_ratio = seq_len / original_max_seq_length
|
| 45 |
+
progress = min(current_ratio / target_scale, 1.0)
|
| 46 |
+
scale_factor = 1.0 + (target_scale - 1.0) * (progress ** (1.0 / self.yarn_alpha))
|
| 47 |
+
return freqs / scale_factor
|
| 48 |
+
|
| 49 |
+
def forward(self, seq_len, device):
|
| 50 |
+
t = torch.arange(seq_len, device=device).type_as(self.inv_freq) / self.rope_scale
|
| 51 |
+
freqs = torch.einsum("i,j->ij", t, self.inv_freq)
|
| 52 |
+
freqs = self._apply_yarn_scaling(freqs, seq_len)
|
| 53 |
+
return torch.cat((freqs, freqs), dim=-1)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def rotate_half(x):
|
| 57 |
+
x1, x2 = x.chunk(2, dim=-1)
|
| 58 |
+
return torch.cat((-x2, x1), dim=-1)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def apply_rotary(x, cos, sin):
|
| 62 |
+
if cos.dim() == 2:
|
| 63 |
+
cos = cos.unsqueeze(0).unsqueeze(0)
|
| 64 |
+
sin = sin.unsqueeze(0).unsqueeze(0)
|
| 65 |
+
cos = cos[..., : x.shape[-1]]
|
| 66 |
+
sin = sin[..., : x.shape[-1]]
|
| 67 |
+
x_rot = x[..., : cos.shape[-1]]
|
| 68 |
+
x_base = x[..., cos.shape[-1] :]
|
| 69 |
+
x_rot = (x_rot * cos) + (rotate_half(x_rot) * sin)
|
| 70 |
+
if x_base.shape[-1] > 0:
|
| 71 |
+
return torch.cat([x_rot, x_base], dim=-1)
|
| 72 |
+
return x_rot
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class DeepSeekMLA(nn.Module):
|
| 76 |
+
"""DeepSeek-style multi-head latent attention."""
|
| 77 |
+
|
| 78 |
+
def __init__(
|
| 79 |
+
self,
|
| 80 |
+
d_model,
|
| 81 |
+
d_latent_kv,
|
| 82 |
+
n_heads,
|
| 83 |
+
d_rope,
|
| 84 |
+
dropout=0.1,
|
| 85 |
+
gqa_groups=1,
|
| 86 |
+
rope_scale=40.0,
|
| 87 |
+
max_seq_length=1024,
|
| 88 |
+
yarn_enabled=False,
|
| 89 |
+
yarn_original_max_seq_length=None,
|
| 90 |
+
yarn_alpha=1.0,
|
| 91 |
+
):
|
| 92 |
+
super().__init__()
|
| 93 |
+
self.d_model = d_model
|
| 94 |
+
self.d_latent_kv = d_latent_kv
|
| 95 |
+
self.n_heads = n_heads
|
| 96 |
+
self.d_rope = d_rope
|
| 97 |
+
self.gqa_groups = gqa_groups
|
| 98 |
+
|
| 99 |
+
if d_model % n_heads != 0:
|
| 100 |
+
raise ValueError("d_model must be divisible by n_heads")
|
| 101 |
+
if d_latent_kv % n_heads != 0:
|
| 102 |
+
raise ValueError("d_latent_kv must be divisible by n_heads")
|
| 103 |
+
|
| 104 |
+
self.d_head_full = d_model // n_heads
|
| 105 |
+
self.d_head_latent = d_latent_kv // n_heads
|
| 106 |
+
|
| 107 |
+
self.norm = nn.LayerNorm(d_model)
|
| 108 |
+
self.q_proj = nn.Linear(d_model, d_model, bias=False)
|
| 109 |
+
self.k_proj = nn.Linear(d_model, d_latent_kv, bias=False)
|
| 110 |
+
self.v_proj = nn.Linear(d_model, d_latent_kv, bias=False)
|
| 111 |
+
self.rotary = RotaryEmbedding(
|
| 112 |
+
d_rope,
|
| 113 |
+
rope_scale=rope_scale,
|
| 114 |
+
max_seq_length=max_seq_length,
|
| 115 |
+
yarn_enabled=yarn_enabled,
|
| 116 |
+
yarn_original_max_seq_length=yarn_original_max_seq_length,
|
| 117 |
+
yarn_alpha=yarn_alpha,
|
| 118 |
+
)
|
| 119 |
+
self.out_proj = nn.Linear(d_latent_kv, d_model, bias=False)
|
| 120 |
+
self.head_weights = nn.Parameter(torch.ones(n_heads))
|
| 121 |
+
self.attn_dropout = nn.Dropout(dropout)
|
| 122 |
+
self.proj_dropout = nn.Dropout(dropout)
|
| 123 |
+
|
| 124 |
+
def forward(self, x, attention_mask=None):
|
| 125 |
+
batch_size, seq_len, _ = x.shape
|
| 126 |
+
x_norm = self.norm(x)
|
| 127 |
+
|
| 128 |
+
q = self.q_proj(x_norm)
|
| 129 |
+
k = self.k_proj(x_norm)
|
| 130 |
+
v = self.v_proj(x_norm)
|
| 131 |
+
|
| 132 |
+
q = q.view(batch_size, seq_len, self.n_heads, self.d_head_full).transpose(1, 2)
|
| 133 |
+
k = k.view(batch_size, seq_len, self.n_heads, self.d_head_latent).transpose(1, 2)
|
| 134 |
+
v = v.view(batch_size, seq_len, self.n_heads, self.d_head_latent).transpose(1, 2)
|
| 135 |
+
|
| 136 |
+
if self.d_rope > 0:
|
| 137 |
+
rotary_emb = self.rotary(seq_len, x.device)
|
| 138 |
+
cos = torch.cos(rotary_emb).unsqueeze(0).unsqueeze(0)
|
| 139 |
+
sin = torch.sin(rotary_emb).unsqueeze(0).unsqueeze(0)
|
| 140 |
+
q_rope = apply_rotary(q[..., : self.d_rope], cos, sin)
|
| 141 |
+
q = torch.cat([q_rope, q[..., self.d_rope :]], dim=-1)
|
| 142 |
+
k_rope = apply_rotary(k[..., : self.d_rope], cos, sin)
|
| 143 |
+
k = torch.cat([k_rope, k[..., self.d_rope :]], dim=-1)
|
| 144 |
+
|
| 145 |
+
q_for_attn = q[..., : self.d_head_latent]
|
| 146 |
+
attn_mask_bool = None
|
| 147 |
+
if attention_mask is not None:
|
| 148 |
+
if attention_mask.dim() == 2:
|
| 149 |
+
attn_mask_bool = attention_mask.bool().unsqueeze(1).unsqueeze(1)
|
| 150 |
+
else:
|
| 151 |
+
attn_mask_bool = attention_mask.bool()
|
| 152 |
+
|
| 153 |
+
dropout_p = self.attn_dropout.p if self.training else 0.0
|
| 154 |
+
out_heads = F.scaled_dot_product_attention(
|
| 155 |
+
q_for_attn,
|
| 156 |
+
k,
|
| 157 |
+
v,
|
| 158 |
+
attn_mask=attn_mask_bool,
|
| 159 |
+
dropout_p=dropout_p,
|
| 160 |
+
scale=None,
|
| 161 |
+
)
|
| 162 |
+
out_concat = out_heads.transpose(1, 2).reshape(batch_size, seq_len, self.d_latent_kv)
|
| 163 |
+
out = self.out_proj(out_concat)
|
| 164 |
+
return self.proj_dropout(out)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
class AttentionBlock(nn.Module):
|
| 168 |
+
"""Attention block with SwiGLU feed-forward network."""
|
| 169 |
+
|
| 170 |
+
def __init__(
|
| 171 |
+
self,
|
| 172 |
+
d_model,
|
| 173 |
+
d_latent_kv,
|
| 174 |
+
n_heads,
|
| 175 |
+
d_rope,
|
| 176 |
+
d_ff,
|
| 177 |
+
dropout=0.1,
|
| 178 |
+
gqa_groups=1,
|
| 179 |
+
rope_scale=40.0,
|
| 180 |
+
max_seq_length=1024,
|
| 181 |
+
yarn_enabled=False,
|
| 182 |
+
yarn_original_max_seq_length=None,
|
| 183 |
+
yarn_alpha=1.0,
|
| 184 |
+
):
|
| 185 |
+
super().__init__()
|
| 186 |
+
self.mla = DeepSeekMLA(
|
| 187 |
+
d_model,
|
| 188 |
+
d_latent_kv,
|
| 189 |
+
n_heads,
|
| 190 |
+
d_rope,
|
| 191 |
+
dropout,
|
| 192 |
+
gqa_groups,
|
| 193 |
+
rope_scale=rope_scale,
|
| 194 |
+
max_seq_length=max_seq_length,
|
| 195 |
+
yarn_enabled=yarn_enabled,
|
| 196 |
+
yarn_original_max_seq_length=yarn_original_max_seq_length,
|
| 197 |
+
yarn_alpha=yarn_alpha,
|
| 198 |
+
)
|
| 199 |
+
self.ff_norm = nn.LayerNorm(d_model)
|
| 200 |
+
self.ff_gate = nn.Linear(d_model, d_ff, bias=False)
|
| 201 |
+
self.ff_value = nn.Linear(d_model, d_ff, bias=False)
|
| 202 |
+
self.ff_out = nn.Linear(d_ff, d_model, bias=False)
|
| 203 |
+
self.dropout = nn.Dropout(dropout)
|
| 204 |
+
|
| 205 |
+
def forward(self, x, attention_mask=None):
|
| 206 |
+
attn_out = self.mla(x, attention_mask)
|
| 207 |
+
x = x + self.dropout(attn_out)
|
| 208 |
+
ff_norm = self.ff_norm(x)
|
| 209 |
+
ff_gate = self.ff_gate(ff_norm)
|
| 210 |
+
ff_value = self.ff_value(ff_norm)
|
| 211 |
+
ff_out = ff_value * F.silu(ff_gate)
|
| 212 |
+
ff_out = self.ff_out(ff_out)
|
| 213 |
+
return x + self.dropout(ff_out)
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9dcf558286bb14902875884b2a18858a2cab7d0341ced85297814b822a369c77
|
| 3 |
+
size 859468256
|
modeling_taonet.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hugging Face model wrapper for TaoNet."""
|
| 2 |
+
|
| 3 |
+
from torch import nn
|
| 4 |
+
from transformers import GenerationMixin, PreTrainedModel
|
| 5 |
+
from transformers.modeling_outputs import CausalLMOutput
|
| 6 |
+
|
| 7 |
+
from configuration_taonet import TaoNetConfig
|
| 8 |
+
from taonet_model import SimpleLLM, build_runtime_config
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class TaoNetForCausalLM(PreTrainedModel, GenerationMixin):
|
| 12 |
+
"""Transformers-compatible TaoNet causal LM."""
|
| 13 |
+
|
| 14 |
+
config_class = TaoNetConfig
|
| 15 |
+
base_model_prefix = "model"
|
| 16 |
+
supports_gradient_checkpointing = False
|
| 17 |
+
|
| 18 |
+
def __init__(self, config):
|
| 19 |
+
super().__init__(config)
|
| 20 |
+
runtime_config = build_runtime_config(config)
|
| 21 |
+
self.model = SimpleLLM(runtime_config)
|
| 22 |
+
self.post_init()
|
| 23 |
+
self.tie_weights()
|
| 24 |
+
|
| 25 |
+
def get_input_embeddings(self):
|
| 26 |
+
if getattr(self.model, "use_factorized_embedding", False):
|
| 27 |
+
return self.model.token_embedding.embed
|
| 28 |
+
return self.model.token_embedding
|
| 29 |
+
|
| 30 |
+
def set_input_embeddings(self, value):
|
| 31 |
+
if getattr(self.model, "use_factorized_embedding", False):
|
| 32 |
+
self.model.token_embedding.embed = value
|
| 33 |
+
else:
|
| 34 |
+
self.model.token_embedding = value
|
| 35 |
+
|
| 36 |
+
def get_output_embeddings(self):
|
| 37 |
+
return self.model.output_head
|
| 38 |
+
|
| 39 |
+
def set_output_embeddings(self, new_embeddings):
|
| 40 |
+
self.model.output_head = new_embeddings
|
| 41 |
+
|
| 42 |
+
def tie_weights(self, *args, **kwargs):
|
| 43 |
+
del args, kwargs
|
| 44 |
+
if not getattr(self.model, "use_factorized_embedding", False):
|
| 45 |
+
self.model.output_head.weight = self.get_input_embeddings().weight
|
| 46 |
+
|
| 47 |
+
def _init_weights(self, module):
|
| 48 |
+
if isinstance(module, nn.Linear):
|
| 49 |
+
nn.init.normal_(module.weight, mean=0.0, std=self.config.init_std)
|
| 50 |
+
if module.bias is not None:
|
| 51 |
+
nn.init.zeros_(module.bias)
|
| 52 |
+
elif isinstance(module, nn.Embedding):
|
| 53 |
+
nn.init.normal_(module.weight, mean=0.0, std=self.config.init_std)
|
| 54 |
+
|
| 55 |
+
def forward(
|
| 56 |
+
self,
|
| 57 |
+
input_ids=None,
|
| 58 |
+
attention_mask=None,
|
| 59 |
+
labels=None,
|
| 60 |
+
inputs_embeds=None,
|
| 61 |
+
return_dict=None,
|
| 62 |
+
**kwargs,
|
| 63 |
+
):
|
| 64 |
+
del kwargs
|
| 65 |
+
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
| 66 |
+
outputs = self.model(
|
| 67 |
+
input_ids=input_ids,
|
| 68 |
+
attention_mask=attention_mask,
|
| 69 |
+
labels=None,
|
| 70 |
+
inputs_embeds=inputs_embeds,
|
| 71 |
+
)
|
| 72 |
+
loss = None
|
| 73 |
+
if labels is not None:
|
| 74 |
+
shift_logits = outputs["logits"][..., :-1, :].contiguous()
|
| 75 |
+
shift_labels = labels[..., 1:].contiguous()
|
| 76 |
+
loss_fct = nn.CrossEntropyLoss(ignore_index=-100)
|
| 77 |
+
loss = loss_fct(
|
| 78 |
+
shift_logits.view(-1, shift_logits.size(-1)),
|
| 79 |
+
shift_labels.view(-1),
|
| 80 |
+
)
|
| 81 |
+
if not return_dict:
|
| 82 |
+
return (loss, outputs["logits"])
|
| 83 |
+
return CausalLMOutput(loss=loss, logits=outputs["logits"])
|
| 84 |
+
|
| 85 |
+
def prepare_inputs_for_generation(self, input_ids, attention_mask=None, **kwargs):
|
| 86 |
+
return {"input_ids": input_ids, "attention_mask": attention_mask}
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"unk_token": "<UNK>",
|
| 3 |
+
"bos_token": "<BOS>",
|
| 4 |
+
"eos_token": "<EOS>",
|
| 5 |
+
"pad_token": "<PAD>",
|
| 6 |
+
"additional_special_tokens": [
|
| 7 |
+
"\n",
|
| 8 |
+
"<think>",
|
| 9 |
+
"<user>",
|
| 10 |
+
"<assistant>",
|
| 11 |
+
"<image>"
|
| 12 |
+
]
|
| 13 |
+
}
|
src/taoTrain/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
TaoTrain: A clean, modular PyTorch LLM training framework.
|
| 3 |
+
|
| 4 |
+
For quick usage, see: https://github.com/your-org/taoTrain
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
__version__ = "0.1.0"
|
| 8 |
+
__author__ = "Felix"
|
src/taoTrain/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (329 Bytes). View file
|
|
|
src/taoTrain/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (362 Bytes). View file
|
|
|
src/taoTrain/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (360 Bytes). View file
|
|
|
src/taoTrain/__pycache__/cli.cpython-310.pyc
ADDED
|
Binary file (12.5 kB). View file
|
|
|
src/taoTrain/__pycache__/cli.cpython-312.pyc
ADDED
|
Binary file (22.7 kB). View file
|
|
|
src/taoTrain/__pycache__/cli.cpython-314.pyc
ADDED
|
Binary file (25 kB). View file
|
|
|
src/taoTrain/__pycache__/config.cpython-310.pyc
ADDED
|
Binary file (28.6 kB). View file
|
|
|
src/taoTrain/__pycache__/config.cpython-312.pyc
ADDED
|
Binary file (37.8 kB). View file
|
|
|
src/taoTrain/__pycache__/config.cpython-314.pyc
ADDED
|
Binary file (43.7 kB). View file
|
|
|
src/taoTrain/benchmarks/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Benchmarking suite."""
|
| 2 |
+
|
| 3 |
+
from .runner import BenchmarkRunner
|
| 4 |
+
|
| 5 |
+
__all__ = ["BenchmarkRunner"]
|
src/taoTrain/benchmarks/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (261 Bytes). View file
|
|
|
src/taoTrain/benchmarks/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (246 Bytes). View file
|
|
|
src/taoTrain/benchmarks/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (243 Bytes). View file
|
|
|
src/taoTrain/benchmarks/__pycache__/runner.cpython-310.pyc
ADDED
|
Binary file (5.81 kB). View file
|
|
|
src/taoTrain/benchmarks/__pycache__/runner.cpython-312.pyc
ADDED
|
Binary file (9.39 kB). View file
|
|
|
src/taoTrain/benchmarks/__pycache__/runner.cpython-314.pyc
ADDED
|
Binary file (10.5 kB). View file
|
|
|
src/taoTrain/benchmarks/runner.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Benchmarking suite for evaluating trained models."""
|
| 2 |
+
|
| 3 |
+
import time
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import Optional, Dict
|
| 6 |
+
import torch
|
| 7 |
+
from torch.utils.data import DataLoader
|
| 8 |
+
|
| 9 |
+
from taoTrain.core import BaseModel
|
| 10 |
+
from taoTrain.config import TrainingConfig
|
| 11 |
+
from taoTrain.data.loaders import get_dataloader
|
| 12 |
+
from taoTrain.inference import Inferencer
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class BenchmarkRunner:
|
| 16 |
+
"""Run benchmarks on a trained model."""
|
| 17 |
+
|
| 18 |
+
def __init__(
|
| 19 |
+
self,
|
| 20 |
+
model: BaseModel,
|
| 21 |
+
device: torch.device,
|
| 22 |
+
dtype: torch.dtype = torch.float32,
|
| 23 |
+
):
|
| 24 |
+
"""
|
| 25 |
+
Initialize benchmark runner.
|
| 26 |
+
|
| 27 |
+
Args:
|
| 28 |
+
model: Trained model
|
| 29 |
+
device: Device for inference
|
| 30 |
+
dtype: Data type
|
| 31 |
+
"""
|
| 32 |
+
self.model = model.to(device)
|
| 33 |
+
self.model.eval()
|
| 34 |
+
self.device = device
|
| 35 |
+
self.dtype = dtype
|
| 36 |
+
|
| 37 |
+
@staticmethod
|
| 38 |
+
def load_from_checkpoint(
|
| 39 |
+
checkpoint_path: str | Path,
|
| 40 |
+
device: Optional[torch.device] = None,
|
| 41 |
+
) -> "BenchmarkRunner":
|
| 42 |
+
"""Load model from checkpoint."""
|
| 43 |
+
if device is None:
|
| 44 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 45 |
+
|
| 46 |
+
checkpoint = torch.load(checkpoint_path, map_location=device)
|
| 47 |
+
|
| 48 |
+
# Reconstruct model config
|
| 49 |
+
from taoTrain.config import ModelConfig
|
| 50 |
+
from taoTrain.models import get_model
|
| 51 |
+
|
| 52 |
+
model_config = ModelConfig(**checkpoint.get("config", {}).get("model", {}))
|
| 53 |
+
model = get_model(model_config, device=device)
|
| 54 |
+
model.load_state_dict(checkpoint["model_state_dict"])
|
| 55 |
+
|
| 56 |
+
return BenchmarkRunner(model, device)
|
| 57 |
+
|
| 58 |
+
def benchmark_perplexity(
|
| 59 |
+
self,
|
| 60 |
+
dataset: "DataLoader",
|
| 61 |
+
num_batches: Optional[int] = None,
|
| 62 |
+
) -> float:
|
| 63 |
+
"""
|
| 64 |
+
Compute perplexity on a dataset.
|
| 65 |
+
|
| 66 |
+
Args:
|
| 67 |
+
dataset: DataLoader for evaluation
|
| 68 |
+
num_batches: Limit evaluation to N batches
|
| 69 |
+
|
| 70 |
+
Returns:
|
| 71 |
+
Perplexity (exp of average loss)
|
| 72 |
+
"""
|
| 73 |
+
total_loss = 0.0
|
| 74 |
+
total_tokens = 0
|
| 75 |
+
|
| 76 |
+
with torch.no_grad():
|
| 77 |
+
for batch_idx, batch in enumerate(dataset):
|
| 78 |
+
if num_batches and batch_idx >= num_batches:
|
| 79 |
+
break
|
| 80 |
+
|
| 81 |
+
# Move to device
|
| 82 |
+
input_ids = batch["input_ids"].to(self.device)
|
| 83 |
+
attention_mask = batch.get("attention_mask")
|
| 84 |
+
if attention_mask is not None:
|
| 85 |
+
attention_mask = attention_mask.to(self.device)
|
| 86 |
+
labels = batch.get("labels")
|
| 87 |
+
if labels is not None:
|
| 88 |
+
labels = labels.to(self.device)
|
| 89 |
+
|
| 90 |
+
# Forward pass
|
| 91 |
+
with torch.autocast(
|
| 92 |
+
device_type="cuda" if self.device.type == "cuda" else "cpu",
|
| 93 |
+
dtype=torch.bfloat16 if self.dtype == torch.bfloat16 else torch.float32,
|
| 94 |
+
):
|
| 95 |
+
outputs = self.model(
|
| 96 |
+
input_ids=input_ids,
|
| 97 |
+
attention_mask=attention_mask,
|
| 98 |
+
labels=labels,
|
| 99 |
+
)
|
| 100 |
+
loss = outputs.get("loss")
|
| 101 |
+
|
| 102 |
+
if loss is not None:
|
| 103 |
+
total_loss += loss.item() * input_ids.shape[0]
|
| 104 |
+
total_tokens += input_ids.shape[0]
|
| 105 |
+
|
| 106 |
+
avg_loss = total_loss / total_tokens if total_tokens > 0 else float('inf')
|
| 107 |
+
perplexity = torch.exp(torch.tensor(avg_loss)).item()
|
| 108 |
+
|
| 109 |
+
return perplexity
|
| 110 |
+
|
| 111 |
+
def benchmark_throughput(
|
| 112 |
+
self,
|
| 113 |
+
batch_size: int = 32,
|
| 114 |
+
seq_length: int = 1024,
|
| 115 |
+
num_iters: int = 10,
|
| 116 |
+
) -> Dict[str, float]:
|
| 117 |
+
"""
|
| 118 |
+
Benchmark forward pass throughput.
|
| 119 |
+
|
| 120 |
+
Args:
|
| 121 |
+
batch_size: Batch size
|
| 122 |
+
seq_length: Sequence length
|
| 123 |
+
num_iters: Number of iterations
|
| 124 |
+
|
| 125 |
+
Returns:
|
| 126 |
+
Dict with throughput metrics
|
| 127 |
+
"""
|
| 128 |
+
# Create dummy batch
|
| 129 |
+
dummy_input = torch.randint(
|
| 130 |
+
0, self.model.config.vocab_size,
|
| 131 |
+
(batch_size, seq_length)
|
| 132 |
+
).to(self.device)
|
| 133 |
+
|
| 134 |
+
# Warmup
|
| 135 |
+
with torch.no_grad():
|
| 136 |
+
for _ in range(2):
|
| 137 |
+
_ = self.model(dummy_input)
|
| 138 |
+
|
| 139 |
+
torch.cuda.synchronize() if torch.cuda.is_available() else None
|
| 140 |
+
|
| 141 |
+
# Benchmark forward pass
|
| 142 |
+
start = time.time()
|
| 143 |
+
|
| 144 |
+
with torch.no_grad():
|
| 145 |
+
for _ in range(num_iters):
|
| 146 |
+
_ = self.model(dummy_input)
|
| 147 |
+
|
| 148 |
+
torch.cuda.synchronize() if torch.cuda.is_available() else None
|
| 149 |
+
|
| 150 |
+
elapsed = time.time() - start
|
| 151 |
+
|
| 152 |
+
total_tokens = batch_size * seq_length * num_iters
|
| 153 |
+
tokens_per_sec = total_tokens / elapsed
|
| 154 |
+
|
| 155 |
+
return {
|
| 156 |
+
"throughput_tokens_per_sec": tokens_per_sec,
|
| 157 |
+
"throughput_samples_per_sec": (batch_size * num_iters) / elapsed,
|
| 158 |
+
"avg_time_per_iter_ms": (elapsed / num_iters) * 1000,
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
def benchmark_memory(self) -> Dict[str, float]:
|
| 162 |
+
"""
|
| 163 |
+
Benchmark peak GPU memory usage.
|
| 164 |
+
|
| 165 |
+
Returns:
|
| 166 |
+
Dict with memory stats
|
| 167 |
+
"""
|
| 168 |
+
if not torch.cuda.is_available():
|
| 169 |
+
return {"peak_memory_gb": 0.0}
|
| 170 |
+
|
| 171 |
+
torch.cuda.reset_peak_memory_stats()
|
| 172 |
+
torch.cuda.synchronize()
|
| 173 |
+
|
| 174 |
+
# Create dummy batch
|
| 175 |
+
dummy_input = torch.randint(
|
| 176 |
+
0, self.model.config.vocab_size,
|
| 177 |
+
(16, 1024)
|
| 178 |
+
).to(self.device)
|
| 179 |
+
|
| 180 |
+
with torch.no_grad():
|
| 181 |
+
_ = self.model(dummy_input)
|
| 182 |
+
|
| 183 |
+
torch.cuda.synchronize()
|
| 184 |
+
|
| 185 |
+
peak_memory = torch.cuda.max_memory_allocated() / (1024 ** 3) # GB
|
| 186 |
+
|
| 187 |
+
return {"peak_memory_gb": peak_memory}
|
| 188 |
+
|
| 189 |
+
def run_all_benchmarks(
|
| 190 |
+
self,
|
| 191 |
+
dataset: Optional["DataLoader"] = None,
|
| 192 |
+
batch_size: int = 32,
|
| 193 |
+
seq_length: int = 1024,
|
| 194 |
+
) -> Dict[str, float]:
|
| 195 |
+
"""
|
| 196 |
+
Run all benchmarks.
|
| 197 |
+
|
| 198 |
+
Args:
|
| 199 |
+
dataset: DataLoader for perplexity benchmark
|
| 200 |
+
batch_size: Batch size for throughput benchmark
|
| 201 |
+
seq_length: Sequence length for throughput benchmark
|
| 202 |
+
|
| 203 |
+
Returns:
|
| 204 |
+
Dict with all benchmark results
|
| 205 |
+
"""
|
| 206 |
+
results = {}
|
| 207 |
+
|
| 208 |
+
if dataset is not None:
|
| 209 |
+
print("Running perplexity benchmark...")
|
| 210 |
+
ppl = self.benchmark_perplexity(dataset, num_batches=10)
|
| 211 |
+
results["perplexity"] = ppl
|
| 212 |
+
|
| 213 |
+
print("Running throughput benchmark...")
|
| 214 |
+
throughput = self.benchmark_throughput(batch_size, seq_length)
|
| 215 |
+
results.update(throughput)
|
| 216 |
+
|
| 217 |
+
print("Running memory benchmark...")
|
| 218 |
+
memory = self.benchmark_memory()
|
| 219 |
+
results.update(memory)
|
| 220 |
+
|
| 221 |
+
return results
|
src/taoTrain/checkpointing/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Checkpoint management."""
|
| 2 |
+
|
| 3 |
+
from .checkpoint import CheckpointManager
|
| 4 |
+
|
| 5 |
+
__all__ = ["CheckpointManager"]
|
src/taoTrain/checkpointing/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (273 Bytes). View file
|
|
|
src/taoTrain/checkpointing/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (258 Bytes). View file
|
|
|
src/taoTrain/checkpointing/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (255 Bytes). View file
|
|
|
src/taoTrain/checkpointing/__pycache__/checkpoint.cpython-310.pyc
ADDED
|
Binary file (5.96 kB). View file
|
|
|
src/taoTrain/checkpointing/__pycache__/checkpoint.cpython-312.pyc
ADDED
|
Binary file (7.55 kB). View file
|
|
|
src/taoTrain/checkpointing/__pycache__/checkpoint.cpython-314.pyc
ADDED
|
Binary file (8.72 kB). View file
|
|
|
src/taoTrain/checkpointing/checkpoint.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Checkpoint management utilities.
|
| 2 |
+
|
| 3 |
+
Canonical Checkpoint Format (new):
|
| 4 |
+
{
|
| 5 |
+
'step': int, # Training step number
|
| 6 |
+
'model_state': Dict[str, Tensor], # Model state dict
|
| 7 |
+
'optimizer_state': Dict, # Optimizer state dict (optional)
|
| 8 |
+
'config': Dict, # TrainingConfig as dict
|
| 9 |
+
'metrics': Dict[str, float], # Training metrics
|
| 10 |
+
'global_step': int, # (deprecated, kept for compat) same as step
|
| 11 |
+
'current_epoch': int, # (optional) current epoch number
|
| 12 |
+
'best_loss': float, # (optional) best validation loss
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
Legacy Checkpoint Format (old, from BaseTrainer):
|
| 16 |
+
{
|
| 17 |
+
'global_step': int,
|
| 18 |
+
'current_epoch': int,
|
| 19 |
+
'best_loss': float,
|
| 20 |
+
'model_state_dict': Dict[str, Tensor], # ← Note: uses '_dict' suffix
|
| 21 |
+
'optimizer_state_dict': Dict,
|
| 22 |
+
'config': Dict,
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
The load() function auto-detects and migrates legacy format to canonical format.
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
from pathlib import Path
|
| 29 |
+
from typing import Dict, Any, Optional
|
| 30 |
+
import torch
|
| 31 |
+
from taoTrain.config import TrainingConfig
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class CheckpointManager:
|
| 35 |
+
"""Manage model checkpoints with versioning."""
|
| 36 |
+
|
| 37 |
+
def __init__(
|
| 38 |
+
self,
|
| 39 |
+
checkpoint_dir: str | Path,
|
| 40 |
+
keep_last_n: int = 3,
|
| 41 |
+
track_best: bool = True,
|
| 42 |
+
):
|
| 43 |
+
"""
|
| 44 |
+
Initialize checkpoint manager.
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
checkpoint_dir: Directory to save checkpoints
|
| 48 |
+
keep_last_n: Number of recent checkpoints to keep
|
| 49 |
+
track_best: Whether to track best model
|
| 50 |
+
"""
|
| 51 |
+
self.checkpoint_dir = Path(checkpoint_dir)
|
| 52 |
+
self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
|
| 53 |
+
|
| 54 |
+
self.keep_last_n = keep_last_n
|
| 55 |
+
self.track_best = track_best
|
| 56 |
+
|
| 57 |
+
self.best_metric = None
|
| 58 |
+
self.best_metric_name = None
|
| 59 |
+
self.saved_checkpoints = []
|
| 60 |
+
|
| 61 |
+
def save(
|
| 62 |
+
self,
|
| 63 |
+
step: int,
|
| 64 |
+
model_state: Dict[str, Any],
|
| 65 |
+
optimizer_state: Optional[Dict[str, Any]] = None,
|
| 66 |
+
config: Optional[TrainingConfig] = None,
|
| 67 |
+
metrics: Optional[Dict[str, float]] = None,
|
| 68 |
+
is_best: bool = False,
|
| 69 |
+
) -> Path:
|
| 70 |
+
"""
|
| 71 |
+
Save a checkpoint.
|
| 72 |
+
|
| 73 |
+
Args:
|
| 74 |
+
step: Training step
|
| 75 |
+
model_state: Model state dict
|
| 76 |
+
optimizer_state: Optimizer state dict
|
| 77 |
+
config: Training config
|
| 78 |
+
metrics: Metrics dict
|
| 79 |
+
is_best: Whether this is the best model so far
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
Path to saved checkpoint
|
| 83 |
+
"""
|
| 84 |
+
checkpoint = {
|
| 85 |
+
"step": step,
|
| 86 |
+
"model_state": model_state,
|
| 87 |
+
"optimizer_state": optimizer_state,
|
| 88 |
+
"config": config.to_dict() if config else None,
|
| 89 |
+
"metrics": metrics or {},
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
filename = f"checkpoint_step_{step:06d}.pt"
|
| 93 |
+
if is_best:
|
| 94 |
+
filename = "best_model.pt"
|
| 95 |
+
|
| 96 |
+
path = self.checkpoint_dir / filename
|
| 97 |
+
torch.save(checkpoint, path)
|
| 98 |
+
|
| 99 |
+
# Track saved checkpoints
|
| 100 |
+
if not is_best:
|
| 101 |
+
self.saved_checkpoints.append((step, path))
|
| 102 |
+
|
| 103 |
+
# Clean up old checkpoints
|
| 104 |
+
if len(self.saved_checkpoints) > self.keep_last_n:
|
| 105 |
+
_, old_path = self.saved_checkpoints.pop(0)
|
| 106 |
+
if old_path.exists():
|
| 107 |
+
old_path.unlink()
|
| 108 |
+
|
| 109 |
+
return path
|
| 110 |
+
|
| 111 |
+
def load(
|
| 112 |
+
self,
|
| 113 |
+
checkpoint_path: str | Path,
|
| 114 |
+
device: Optional[torch.device] = None,
|
| 115 |
+
) -> Dict[str, Any]:
|
| 116 |
+
"""
|
| 117 |
+
Load a checkpoint with backward-compatible format handling.
|
| 118 |
+
|
| 119 |
+
Auto-detects checkpoint format (canonical or legacy) and normalizes
|
| 120 |
+
to canonical format in-memory. Legacy checkpoints are migrated without
|
| 121 |
+
modifying the file.
|
| 122 |
+
|
| 123 |
+
Args:
|
| 124 |
+
checkpoint_path: Path to checkpoint
|
| 125 |
+
device: Device to load to
|
| 126 |
+
|
| 127 |
+
Returns:
|
| 128 |
+
Checkpoint dict in canonical format with 'model_state' key
|
| 129 |
+
"""
|
| 130 |
+
if device is None:
|
| 131 |
+
device = torch.device("cpu")
|
| 132 |
+
|
| 133 |
+
checkpoint = torch.load(checkpoint_path, map_location=device)
|
| 134 |
+
|
| 135 |
+
# Auto-detect and migrate legacy format to canonical format
|
| 136 |
+
checkpoint = self._normalize_checkpoint_format(checkpoint)
|
| 137 |
+
|
| 138 |
+
return checkpoint
|
| 139 |
+
|
| 140 |
+
def _normalize_checkpoint_format(self, checkpoint: Dict[str, Any]) -> Dict[str, Any]:
|
| 141 |
+
"""
|
| 142 |
+
Normalize checkpoint to canonical format.
|
| 143 |
+
|
| 144 |
+
Detects if checkpoint is in legacy format (from BaseTrainer with 'model_state_dict')
|
| 145 |
+
and migrates it to canonical format (with 'model_state').
|
| 146 |
+
|
| 147 |
+
Args:
|
| 148 |
+
checkpoint: Raw checkpoint dict
|
| 149 |
+
|
| 150 |
+
Returns:
|
| 151 |
+
Normalized checkpoint dict with canonical keys
|
| 152 |
+
"""
|
| 153 |
+
# Check if this is a legacy checkpoint (has 'model_state_dict' but not 'model_state')
|
| 154 |
+
if "model_state_dict" in checkpoint and "model_state" not in checkpoint:
|
| 155 |
+
# Migrate legacy format to canonical
|
| 156 |
+
migrated = {
|
| 157 |
+
"step": checkpoint.get("global_step", 0),
|
| 158 |
+
"model_state": checkpoint["model_state_dict"],
|
| 159 |
+
"optimizer_state": checkpoint.get("optimizer_state_dict"),
|
| 160 |
+
"config": checkpoint.get("config"),
|
| 161 |
+
"metrics": {},
|
| 162 |
+
# Keep legacy keys for backward compatibility in code that uses them
|
| 163 |
+
"global_step": checkpoint.get("global_step", 0),
|
| 164 |
+
"current_epoch": checkpoint.get("current_epoch", 0),
|
| 165 |
+
"best_loss": checkpoint.get("best_loss", float('inf')),
|
| 166 |
+
}
|
| 167 |
+
print(f"\n✓ [CheckpointManager] Detected legacy checkpoint format. Auto-migrated to canonical format.")
|
| 168 |
+
return migrated
|
| 169 |
+
|
| 170 |
+
# Already in canonical format or unknown format
|
| 171 |
+
if "model_state" not in checkpoint:
|
| 172 |
+
# If neither format detected, ensure model_state is accessible
|
| 173 |
+
# (might be a raw state_dict)
|
| 174 |
+
print(f"\n⚠ [CheckpointManager] Checkpoint format unclear. Assuming raw state_dict format.")
|
| 175 |
+
checkpoint["model_state"] = checkpoint
|
| 176 |
+
|
| 177 |
+
return checkpoint
|
| 178 |
+
|
| 179 |
+
def get_latest(self) -> Optional[Path]:
|
| 180 |
+
"""Get path to latest checkpoint."""
|
| 181 |
+
if not self.saved_checkpoints:
|
| 182 |
+
return None
|
| 183 |
+
return self.saved_checkpoints[-1][1]
|
| 184 |
+
|
| 185 |
+
def get_best(self) -> Optional[Path]:
|
| 186 |
+
"""Get path to best checkpoint."""
|
| 187 |
+
best_path = self.checkpoint_dir / "best_model.pt"
|
| 188 |
+
if best_path.exists():
|
| 189 |
+
return best_path
|
| 190 |
+
return None
|
| 191 |
+
|
| 192 |
+
def list_checkpoints(self) -> list[Path]:
|
| 193 |
+
"""List all saved checkpoints."""
|
| 194 |
+
return sorted(self.checkpoint_dir.glob("checkpoint_step_*.pt"))
|
src/taoTrain/cli.py
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Main CLI entry point."""
|
| 2 |
+
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import Optional
|
| 6 |
+
import click
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
from taoTrain.config import (
|
| 10 |
+
load_config,
|
| 11 |
+
load_tokenizer_config,
|
| 12 |
+
TrainingModeEnum,
|
| 13 |
+
TrainingConfig,
|
| 14 |
+
PretrainConfig,
|
| 15 |
+
SFTConfig,
|
| 16 |
+
RLConfig,
|
| 17 |
+
VLMConfig,
|
| 18 |
+
VLMSFTConfig,
|
| 19 |
+
)
|
| 20 |
+
from taoTrain.utils import set_seed, get_device
|
| 21 |
+
from taoTrain.core import BaseModel, create_model, create_datasets
|
| 22 |
+
from taoTrain.data import get_dataloader
|
| 23 |
+
from taoTrain.training import PretrainTrainer, SFTTrainer, RLTrainer, VLMTrainer, VLMSFTTrainer
|
| 24 |
+
from taoTrain.benchmarks import BenchmarkRunner
|
| 25 |
+
from taoTrain.tokenizers import TokenizerTrainer
|
| 26 |
+
from taoTrain.data.tokenizer import load_tokenizer_runtime_info
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@click.group()
|
| 30 |
+
def main():
|
| 31 |
+
"""TaoTrain: A clean, modular PyTorch LLM training framework."""
|
| 32 |
+
pass
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@main.command()
|
| 36 |
+
@click.option(
|
| 37 |
+
"--config",
|
| 38 |
+
type=click.Path(exists=True),
|
| 39 |
+
required=True,
|
| 40 |
+
help="Path to training config file (YAML or JSON)",
|
| 41 |
+
)
|
| 42 |
+
def pretrain(config: str):
|
| 43 |
+
"""Pretrain a language model."""
|
| 44 |
+
_train_command(config, TrainingModeEnum.PRETRAIN)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@main.command()
|
| 48 |
+
@click.option(
|
| 49 |
+
"--config",
|
| 50 |
+
type=click.Path(exists=True),
|
| 51 |
+
required=True,
|
| 52 |
+
help="Path to training config file (YAML or JSON)",
|
| 53 |
+
)
|
| 54 |
+
def sft(config: str):
|
| 55 |
+
"""Supervised fine-tune a language model."""
|
| 56 |
+
_train_command(config, TrainingModeEnum.SFT)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
@main.command()
|
| 60 |
+
@click.option(
|
| 61 |
+
"--config",
|
| 62 |
+
type=click.Path(exists=True),
|
| 63 |
+
required=True,
|
| 64 |
+
help="Path to training config file (YAML or JSON)",
|
| 65 |
+
)
|
| 66 |
+
def rl(config: str):
|
| 67 |
+
"""Train with reinforcement learning."""
|
| 68 |
+
_train_command(config, TrainingModeEnum.RL)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@main.command()
|
| 72 |
+
@click.option(
|
| 73 |
+
"--config",
|
| 74 |
+
type=click.Path(exists=True),
|
| 75 |
+
required=True,
|
| 76 |
+
help="Path to multimodal VLM config file (YAML or JSON)",
|
| 77 |
+
)
|
| 78 |
+
def vlm(config: str):
|
| 79 |
+
"""Train a multimodal vision-language connector."""
|
| 80 |
+
_train_command(config, TrainingModeEnum.VLM)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
@main.command(name="vlm-sft")
|
| 84 |
+
@click.option(
|
| 85 |
+
"--config",
|
| 86 |
+
type=click.Path(exists=True),
|
| 87 |
+
required=True,
|
| 88 |
+
help="Path to multimodal VLM SFT config file (YAML or JSON)",
|
| 89 |
+
)
|
| 90 |
+
def vlm_sft(config: str):
|
| 91 |
+
"""Run end-to-end multimodal supervised fine-tuning."""
|
| 92 |
+
_train_command(config, TrainingModeEnum.VLM_SFT)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
@main.command()
|
| 96 |
+
@click.option(
|
| 97 |
+
"--config",
|
| 98 |
+
type=click.Path(exists=True),
|
| 99 |
+
required=True,
|
| 100 |
+
help="Path to tokenizer config file (YAML or JSON)",
|
| 101 |
+
)
|
| 102 |
+
def train_tokenizer(config: str):
|
| 103 |
+
"""Train a SentencePiece tokenizer from a YAML/JSON config file."""
|
| 104 |
+
try:
|
| 105 |
+
click.echo("🚀 TaoTrain Tokenizer Trainer")
|
| 106 |
+
click.echo(f"{'=' * 50}")
|
| 107 |
+
|
| 108 |
+
# Load tokenizer config
|
| 109 |
+
click.echo(f"Loading config from {config}...")
|
| 110 |
+
tokenizer_config = load_tokenizer_config(config)
|
| 111 |
+
|
| 112 |
+
# Train tokenizer from config
|
| 113 |
+
result = TokenizerTrainer.train_from_config(tokenizer_config)
|
| 114 |
+
|
| 115 |
+
# Display results
|
| 116 |
+
click.echo(f"\n{'=' * 50}")
|
| 117 |
+
click.echo("✅ Tokenizer Training Complete!")
|
| 118 |
+
click.echo(f"\n📊 Configuration:")
|
| 119 |
+
click.echo(f" - Input file: {tokenizer_config.jsonl_path}")
|
| 120 |
+
click.echo(f" - Samples: {tokenizer_config.max_samples or 'all'}")
|
| 121 |
+
click.echo(f" - Output dir: {result['output_dir']}")
|
| 122 |
+
click.echo(f" - Vocab size: {result['vocab_size']}")
|
| 123 |
+
click.echo(f" - Model type: {result['model_type']}")
|
| 124 |
+
|
| 125 |
+
if tokenizer_config.special_tokens:
|
| 126 |
+
click.echo(f" - Special tokens: {tokenizer_config.special_tokens}")
|
| 127 |
+
|
| 128 |
+
click.echo(f"\n📁 Generated Files:")
|
| 129 |
+
click.echo(f" - Model: {result['model_file']}")
|
| 130 |
+
click.echo(f" - Vocab: {result['vocab_file']}")
|
| 131 |
+
if "special_tokens_file" in result:
|
| 132 |
+
click.echo(f" - Special token metadata: {result['special_tokens_file']}")
|
| 133 |
+
|
| 134 |
+
click.echo(f"\n📝 Next Steps:")
|
| 135 |
+
click.echo(f" 1. Use this tokenizer in your pretraining config:")
|
| 136 |
+
click.echo(f" dataset:")
|
| 137 |
+
click.echo(f" local: true")
|
| 138 |
+
click.echo(f" jsonl_path: {tokenizer_config.jsonl_path}")
|
| 139 |
+
click.echo(f" tokenizer_path: {result['model_file']}")
|
| 140 |
+
click.echo(f"")
|
| 141 |
+
click.echo(f" 2. Run pretraining with:")
|
| 142 |
+
click.echo(f" train pretrain --config your_config.yaml")
|
| 143 |
+
|
| 144 |
+
except ImportError as e:
|
| 145 |
+
click.echo(f"❌ Error: {e}", err=True)
|
| 146 |
+
sys.exit(1)
|
| 147 |
+
except FileNotFoundError as e:
|
| 148 |
+
click.echo(f"❌ File Error: {e}", err=True)
|
| 149 |
+
sys.exit(1)
|
| 150 |
+
except ValueError as e:
|
| 151 |
+
click.echo(f"❌ Validation Error: {e}", err=True)
|
| 152 |
+
sys.exit(1)
|
| 153 |
+
except Exception as e:
|
| 154 |
+
click.echo(f"❌ Unexpected Error: {e}", err=True)
|
| 155 |
+
click.echo(f" Please report this issue.", err=True)
|
| 156 |
+
sys.exit(1)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
@click.command()
|
| 160 |
+
@click.option(
|
| 161 |
+
"--jsonl-path",
|
| 162 |
+
type=click.Path(exists=True),
|
| 163 |
+
required=True,
|
| 164 |
+
help="Path to JSONL file containing training data",
|
| 165 |
+
)
|
| 166 |
+
@click.option(
|
| 167 |
+
"--output-dir",
|
| 168 |
+
type=click.Path(),
|
| 169 |
+
default="tokenizers",
|
| 170 |
+
help="Directory to save tokenizer files",
|
| 171 |
+
)
|
| 172 |
+
@click.option(
|
| 173 |
+
"--vocab-size",
|
| 174 |
+
type=int,
|
| 175 |
+
default=50000,
|
| 176 |
+
help="Vocabulary size for the tokenizer",
|
| 177 |
+
)
|
| 178 |
+
@click.option(
|
| 179 |
+
"--model-type",
|
| 180 |
+
type=click.Choice(["unigram", "bpe", "char", "word"]),
|
| 181 |
+
default="unigram",
|
| 182 |
+
help="SentencePiece model type",
|
| 183 |
+
)
|
| 184 |
+
@click.option(
|
| 185 |
+
"--character-coverage",
|
| 186 |
+
type=float,
|
| 187 |
+
default=0.9995,
|
| 188 |
+
help="Character coverage for SentencePiece",
|
| 189 |
+
)
|
| 190 |
+
@click.option(
|
| 191 |
+
"--tokenizer-prefix",
|
| 192 |
+
type=str,
|
| 193 |
+
default=None,
|
| 194 |
+
help="Prefix for tokenizer output files (default: model_type)",
|
| 195 |
+
)
|
| 196 |
+
def train_tokenizer_command(
|
| 197 |
+
jsonl_path: str,
|
| 198 |
+
output_dir: str,
|
| 199 |
+
vocab_size: int,
|
| 200 |
+
model_type: str,
|
| 201 |
+
character_coverage: float,
|
| 202 |
+
tokenizer_prefix: Optional[str],
|
| 203 |
+
):
|
| 204 |
+
"""Train a SentencePiece tokenizer from JSONL data."""
|
| 205 |
+
try:
|
| 206 |
+
click.echo("🚀 TaoTrain Tokenizer Trainer")
|
| 207 |
+
click.echo(f"{'=' * 50}")
|
| 208 |
+
|
| 209 |
+
# Train tokenizer
|
| 210 |
+
result = TokenizerTrainer.train_sentencepiece(
|
| 211 |
+
jsonl_path=jsonl_path,
|
| 212 |
+
output_dir=output_dir,
|
| 213 |
+
vocab_size=vocab_size,
|
| 214 |
+
model_type=model_type,
|
| 215 |
+
character_coverage=character_coverage,
|
| 216 |
+
tokenizer_prefix=tokenizer_prefix,
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
# Display results
|
| 220 |
+
click.echo(f"\n{'=' * 50}")
|
| 221 |
+
click.echo("✅ Tokenizer Training Complete!")
|
| 222 |
+
click.echo(f"\n📊 Configuration:")
|
| 223 |
+
click.echo(f" - Input file: {jsonl_path}")
|
| 224 |
+
click.echo(f" - Output dir: {result['output_dir']}")
|
| 225 |
+
click.echo(f" - Vocab size: {result['vocab_size']}")
|
| 226 |
+
click.echo(f" - Model type: {result['model_type']}")
|
| 227 |
+
|
| 228 |
+
click.echo(f"\n📁 Generated Files:")
|
| 229 |
+
click.echo(f" - Model: {result['model_file']}")
|
| 230 |
+
click.echo(f" - Vocab: {result['vocab_file']}")
|
| 231 |
+
if "special_tokens_file" in result:
|
| 232 |
+
click.echo(f" - Special token metadata: {result['special_tokens_file']}")
|
| 233 |
+
|
| 234 |
+
click.echo(f"\n📝 Next Steps:")
|
| 235 |
+
click.echo(f" 1. Use this tokenizer in your pretraining config:")
|
| 236 |
+
click.echo(f" dataset:")
|
| 237 |
+
click.echo(f" local: true")
|
| 238 |
+
click.echo(f" jsonl_path: {jsonl_path}")
|
| 239 |
+
click.echo(f" tokenizer_path: {result['model_file']}")
|
| 240 |
+
click.echo(f"")
|
| 241 |
+
click.echo(f" 2. Run pretraining with:")
|
| 242 |
+
click.echo(f" train pretrain --config your_config.yaml")
|
| 243 |
+
|
| 244 |
+
except ImportError as e:
|
| 245 |
+
click.echo(f"❌ Error: {e}", err=True)
|
| 246 |
+
sys.exit(1)
|
| 247 |
+
except FileNotFoundError as e:
|
| 248 |
+
click.echo(f"❌ File Error: {e}", err=True)
|
| 249 |
+
sys.exit(1)
|
| 250 |
+
except ValueError as e:
|
| 251 |
+
click.echo(f"❌ Validation Error: {e}", err=True)
|
| 252 |
+
sys.exit(1)
|
| 253 |
+
except Exception as e:
|
| 254 |
+
click.echo(f"❌ Unexpected Error: {e}", err=True)
|
| 255 |
+
click.echo(f" Please report this issue.", err=True)
|
| 256 |
+
sys.exit(1)
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
# Keep legacy CLI command as train-tokenizer-legacy for backward compatibility
|
| 260 |
+
main.add_command(train_tokenizer_command, name="train-tokenizer-legacy")
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def _train_command(config_path: str, mode: TrainingModeEnum):
|
| 264 |
+
"""Internal training command."""
|
| 265 |
+
try:
|
| 266 |
+
# Load config
|
| 267 |
+
click.echo(f"Loading config from {config_path}...")
|
| 268 |
+
train_config = load_config(config_path, mode)
|
| 269 |
+
|
| 270 |
+
_synchronize_model_vocab_with_tokenizer(train_config)
|
| 271 |
+
|
| 272 |
+
# Set seed
|
| 273 |
+
set_seed(train_config.seed)
|
| 274 |
+
|
| 275 |
+
# Get device
|
| 276 |
+
device = get_device(train_config.device)
|
| 277 |
+
click.echo(f"Using device: {device}")
|
| 278 |
+
|
| 279 |
+
# Create model
|
| 280 |
+
click.echo("Creating model...")
|
| 281 |
+
model = create_model(train_config, device)
|
| 282 |
+
total_params, trainable_params = _count_params(model)
|
| 283 |
+
click.echo(f" - Total parameters: {total_params:,}")
|
| 284 |
+
click.echo(f" - Trainable parameters: {trainable_params:,}")
|
| 285 |
+
|
| 286 |
+
# Load pretrained checkpoint if provided (for SFT/RL)
|
| 287 |
+
if train_config.checkpoint_path:
|
| 288 |
+
click.echo(f"Loading pretrained checkpoint from {train_config.checkpoint_path}...")
|
| 289 |
+
from taoTrain.checkpointing.checkpoint import CheckpointManager
|
| 290 |
+
checkpoint_manager = CheckpointManager(train_config.checkpoint_dir)
|
| 291 |
+
checkpoint = checkpoint_manager.load(train_config.checkpoint_path, device=device)
|
| 292 |
+
|
| 293 |
+
# CheckpointManager.load() normalizes format and ensures 'model_state' key exists
|
| 294 |
+
if "model_state" in checkpoint:
|
| 295 |
+
load_result = model.load_state_dict(checkpoint["model_state"], strict=False)
|
| 296 |
+
click.echo(" ✓ Checkpoint loaded successfully")
|
| 297 |
+
if load_result.missing_keys:
|
| 298 |
+
click.echo(f" Missing keys: {len(load_result.missing_keys)}")
|
| 299 |
+
for key in load_result.missing_keys[:10]:
|
| 300 |
+
click.echo(f" - {key}")
|
| 301 |
+
if load_result.unexpected_keys:
|
| 302 |
+
click.echo(f" Unexpected keys: {len(load_result.unexpected_keys)}")
|
| 303 |
+
for key in load_result.unexpected_keys[:10]:
|
| 304 |
+
click.echo(f" - {key}")
|
| 305 |
+
else:
|
| 306 |
+
raise KeyError(f"Invalid checkpoint format: 'model_state' key not found. "
|
| 307 |
+
f"Available keys: {list(checkpoint.keys())}")
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
# Create datasets
|
| 312 |
+
click.echo("Loading datasets...")
|
| 313 |
+
train_dataset, val_dataset = create_datasets(train_config)
|
| 314 |
+
click.echo(f" - Train samples: {len(train_dataset)}")
|
| 315 |
+
if val_dataset:
|
| 316 |
+
click.echo(f" - Val samples: {len(val_dataset)}")
|
| 317 |
+
|
| 318 |
+
# Select trainer
|
| 319 |
+
if mode == TrainingModeEnum.PRETRAIN:
|
| 320 |
+
trainer_class = PretrainTrainer
|
| 321 |
+
elif mode == TrainingModeEnum.SFT:
|
| 322 |
+
trainer_class = SFTTrainer
|
| 323 |
+
elif mode == TrainingModeEnum.RL:
|
| 324 |
+
trainer_class = RLTrainer
|
| 325 |
+
elif mode == TrainingModeEnum.VLM:
|
| 326 |
+
trainer_class = VLMTrainer
|
| 327 |
+
elif mode == TrainingModeEnum.VLM_SFT:
|
| 328 |
+
trainer_class = VLMSFTTrainer
|
| 329 |
+
else:
|
| 330 |
+
raise ValueError(f"Unknown training mode: {mode}")
|
| 331 |
+
|
| 332 |
+
# Create trainer
|
| 333 |
+
click.echo("Setting up trainer...")
|
| 334 |
+
trainer = trainer_class(
|
| 335 |
+
model=model,
|
| 336 |
+
train_dataset=train_dataset,
|
| 337 |
+
val_dataset=val_dataset,
|
| 338 |
+
config=train_config,
|
| 339 |
+
device=device,
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
# Training loop
|
| 343 |
+
click.echo("\nStarting training...\n")
|
| 344 |
+
for epoch in range(train_config.num_epochs):
|
| 345 |
+
if train_config.max_steps and trainer.global_step >= train_config.max_steps:
|
| 346 |
+
break
|
| 347 |
+
|
| 348 |
+
epoch_metrics = trainer.train_epoch()
|
| 349 |
+
click.echo(f"\nEpoch {epoch + 1} complete")
|
| 350 |
+
click.echo(f" - Loss: {epoch_metrics.get('loss', 'N/A')}")
|
| 351 |
+
click.echo(f" - Learning rate: {epoch_metrics.get('lr', 'N/A')}")
|
| 352 |
+
|
| 353 |
+
# Final checkpoint
|
| 354 |
+
final_path = Path(train_config.checkpoint_dir) / "final_model.pt"
|
| 355 |
+
trainer.save_checkpoint(final_path)
|
| 356 |
+
click.echo(f"\nTraining complete! Final model saved to {final_path}")
|
| 357 |
+
|
| 358 |
+
# Log finish
|
| 359 |
+
trainer.logger.finish()
|
| 360 |
+
|
| 361 |
+
except Exception as e:
|
| 362 |
+
click.echo(f"Error during training: {e}", err=True)
|
| 363 |
+
sys.exit(1)
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def _synchronize_model_vocab_with_tokenizer(train_config: TrainingConfig) -> None:
|
| 367 |
+
"""Validate tokenizer runtime IDs and align model vocab size with tokenizer artifacts."""
|
| 368 |
+
dataset_config = train_config.dataset
|
| 369 |
+
tokenizer_path = dataset_config.tokenizer_path
|
| 370 |
+
if not tokenizer_path:
|
| 371 |
+
return
|
| 372 |
+
|
| 373 |
+
tokenizer_info = load_tokenizer_runtime_info(tokenizer_path, dataset_config.tokenizer_type)
|
| 374 |
+
invalid_builtin_ids = {
|
| 375 |
+
token_name: token_id
|
| 376 |
+
for token_name, token_id in tokenizer_info.items()
|
| 377 |
+
if token_name.endswith("_id") and token_id < 0
|
| 378 |
+
}
|
| 379 |
+
if invalid_builtin_ids:
|
| 380 |
+
raise ValueError(
|
| 381 |
+
f"Tokenizer `{tokenizer_path}` is missing required built-in IDs: {invalid_builtin_ids}. "
|
| 382 |
+
"Retrain the tokenizer so PAD/BOS/EOS/UNK are real tokenizer IDs before training."
|
| 383 |
+
)
|
| 384 |
+
|
| 385 |
+
tokenizer_vocab_size = tokenizer_info["vocab_size"]
|
| 386 |
+
model_vocab_size = train_config.model.vocab_size
|
| 387 |
+
if model_vocab_size != tokenizer_vocab_size:
|
| 388 |
+
click.echo(
|
| 389 |
+
"Tokenizer/model vocab mismatch detected. "
|
| 390 |
+
f"Updating model.vocab_size from {model_vocab_size} to {tokenizer_vocab_size} "
|
| 391 |
+
f"based on tokenizer `{tokenizer_path}`."
|
| 392 |
+
)
|
| 393 |
+
train_config.model.vocab_size = tokenizer_vocab_size
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
@main.command()
|
| 397 |
+
@click.option(
|
| 398 |
+
"--model",
|
| 399 |
+
type=click.Path(exists=True),
|
| 400 |
+
required=True,
|
| 401 |
+
help="Path to model checkpoint",
|
| 402 |
+
)
|
| 403 |
+
@click.option(
|
| 404 |
+
"--benchmark-type",
|
| 405 |
+
type=click.Choice(["all", "perplexity", "throughput", "memory"]),
|
| 406 |
+
default="all",
|
| 407 |
+
help="Type of benchmark to run",
|
| 408 |
+
)
|
| 409 |
+
@click.option(
|
| 410 |
+
"--batch-size",
|
| 411 |
+
type=int,
|
| 412 |
+
default=32,
|
| 413 |
+
help="Batch size for benchmarking",
|
| 414 |
+
)
|
| 415 |
+
@click.option(
|
| 416 |
+
"--seq-length",
|
| 417 |
+
type=int,
|
| 418 |
+
default=1024,
|
| 419 |
+
help="Sequence length for benchmarking",
|
| 420 |
+
)
|
| 421 |
+
def benchmark(model: str, benchmark_type: str, batch_size: int, seq_length: int):
|
| 422 |
+
"""Benchmark a trained model."""
|
| 423 |
+
try:
|
| 424 |
+
click.echo(f"Loading model from {model}...")
|
| 425 |
+
device = get_device("cuda")
|
| 426 |
+
runner = BenchmarkRunner.load_from_checkpoint(model, device=device)
|
| 427 |
+
|
| 428 |
+
click.echo("Running benchmarks...\n")
|
| 429 |
+
|
| 430 |
+
if benchmark_type == "throughput" or benchmark_type == "all":
|
| 431 |
+
click.echo("Throughput benchmark:")
|
| 432 |
+
results = runner.benchmark_throughput(batch_size, seq_length)
|
| 433 |
+
for key, val in results.items():
|
| 434 |
+
click.echo(f" {key}: {val:.2f}")
|
| 435 |
+
|
| 436 |
+
if benchmark_type == "memory" or benchmark_type == "all":
|
| 437 |
+
click.echo("\nMemory benchmark:")
|
| 438 |
+
results = runner.benchmark_memory()
|
| 439 |
+
for key, val in results.items():
|
| 440 |
+
click.echo(f" {key}: {val:.2f}")
|
| 441 |
+
|
| 442 |
+
click.echo("\nBenchmarking complete!")
|
| 443 |
+
|
| 444 |
+
except Exception as e:
|
| 445 |
+
click.echo(f"Error during benchmarking: {e}", err=True)
|
| 446 |
+
sys.exit(1)
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
@main.command()
|
| 450 |
+
@click.option("--repo", type=str, default=".aim", help="AimStack repository path")
|
| 451 |
+
def view_logs(repo: str):
|
| 452 |
+
"""View training logs with AimStack."""
|
| 453 |
+
try:
|
| 454 |
+
import subprocess
|
| 455 |
+
click.echo(f"Opening AimStack dashboard for repo: {repo}")
|
| 456 |
+
subprocess.run(["aim", "up", "--repo", repo])
|
| 457 |
+
except FileNotFoundError:
|
| 458 |
+
click.echo("Error: 'aim' command not found. Install with: pip install aim", err=True)
|
| 459 |
+
sys.exit(1)
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def _count_params(model: BaseModel) -> tuple[int, int]:
|
| 463 |
+
"""Count model parameters."""
|
| 464 |
+
total = sum(p.numel() for p in model.parameters())
|
| 465 |
+
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 466 |
+
return total, trainable
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
if __name__ == "__main__":
|
| 470 |
+
main()
|
src/taoTrain/config.py
ADDED
|
@@ -0,0 +1,871 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Pydantic configuration schemas for TaoTrain."""
|
| 2 |
+
|
| 3 |
+
from enum import Enum
|
| 4 |
+
from typing import Optional, Literal
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
import json
|
| 7 |
+
from pydantic import BaseModel as PydanticBaseModel, Field, validator
|
| 8 |
+
import yaml
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
# ============================================================================
|
| 12 |
+
# Enums
|
| 13 |
+
# ============================================================================
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class DataTypeEnum(str, Enum):
|
| 17 |
+
"""Data types for training."""
|
| 18 |
+
FLOAT32 = "float32"
|
| 19 |
+
FLOAT16 = "float16"
|
| 20 |
+
BFLOAT16 = "bfloat16"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class OptimizerEnum(str, Enum):
|
| 24 |
+
"""Supported optimizers."""
|
| 25 |
+
ADAM = "adam"
|
| 26 |
+
ADAMW = "adamw"
|
| 27 |
+
SGD = "sgd"
|
| 28 |
+
HYBRID_MUON_ADAMW = "hybrid_muon_adamw"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class ModelArchitectureEnum(str, Enum):
|
| 32 |
+
"""Built-in model architectures."""
|
| 33 |
+
TRANSFORMER = "transformer"
|
| 34 |
+
TAONET = "taonet"
|
| 35 |
+
GAMMA_NET = "gamma_net"
|
| 36 |
+
MULTIMODAL_WRAPPER = "multimodal_wrapper"
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class SchedulerEnum(str, Enum):
|
| 40 |
+
"""Supported learning rate schedulers."""
|
| 41 |
+
LINEAR_WARMUP = "linearWarmup"
|
| 42 |
+
COSINE_WARMUP = "cosineWarmup"
|
| 43 |
+
CONSTANT = "constant"
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class RLMethodEnum(str, Enum):
|
| 47 |
+
"""Supported RL training methods."""
|
| 48 |
+
PPO = "ppo"
|
| 49 |
+
DPO = "dpo"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class TrainingModeEnum(str, Enum):
|
| 53 |
+
"""Training stages."""
|
| 54 |
+
PRETRAIN = "pretrain"
|
| 55 |
+
SFT = "sft"
|
| 56 |
+
RL = "rl"
|
| 57 |
+
VLM = "vlm"
|
| 58 |
+
VLM_SFT = "vlm_sft"
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# ============================================================================
|
| 62 |
+
# Base Configs
|
| 63 |
+
# ============================================================================
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class BaseConfig(PydanticBaseModel):
|
| 67 |
+
"""Base Pydantic model with utility methods."""
|
| 68 |
+
|
| 69 |
+
class Config:
|
| 70 |
+
"""Pydantic config."""
|
| 71 |
+
arbitrary_types_allowed = True
|
| 72 |
+
|
| 73 |
+
def to_dict(self) -> dict:
|
| 74 |
+
"""Convert to dictionary."""
|
| 75 |
+
data = self.model_dump(mode='json') # Enums -> strings
|
| 76 |
+
return data
|
| 77 |
+
|
| 78 |
+
def to_json_str(self) -> str:
|
| 79 |
+
"""Convert to JSON string."""
|
| 80 |
+
return json.dumps(self.to_dict(), indent=2)
|
| 81 |
+
|
| 82 |
+
def save_yaml(self, path: str | Path) -> None:
|
| 83 |
+
"""Save config to YAML file."""
|
| 84 |
+
path = Path(path)
|
| 85 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 86 |
+
with open(path, 'w') as f:
|
| 87 |
+
yaml.dump(self.to_dict(), f, default_flow_style=False, sort_keys=False)
|
| 88 |
+
|
| 89 |
+
def save_json(self, path: str | Path) -> None:
|
| 90 |
+
"""Save config to JSON file."""
|
| 91 |
+
path = Path(path)
|
| 92 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 93 |
+
with open(path, 'w') as f:
|
| 94 |
+
f.write(self.to_json_str())
|
| 95 |
+
|
| 96 |
+
@classmethod
|
| 97 |
+
def load_yaml(cls, path: str | Path) -> "BaseConfig":
|
| 98 |
+
"""Load config from YAML file."""
|
| 99 |
+
with open(path) as f:
|
| 100 |
+
data = yaml.safe_load(f)
|
| 101 |
+
return cls(**data)
|
| 102 |
+
|
| 103 |
+
@classmethod
|
| 104 |
+
def load_json(cls, path: str | Path) -> "BaseConfig":
|
| 105 |
+
"""Load config from JSON file."""
|
| 106 |
+
with open(path) as f:
|
| 107 |
+
data = json.load(f)
|
| 108 |
+
return cls(**data)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
# ============================================================================
|
| 112 |
+
# Model Config
|
| 113 |
+
# ============================================================================
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class ModelConfig(BaseConfig):
|
| 117 |
+
"""Configuration for model architecture."""
|
| 118 |
+
|
| 119 |
+
architecture_type: ModelArchitectureEnum = Field(
|
| 120 |
+
default=ModelArchitectureEnum.TRANSFORMER,
|
| 121 |
+
description="Type of model architecture"
|
| 122 |
+
)
|
| 123 |
+
llm_architecture_type: Optional[ModelArchitectureEnum] = Field(
|
| 124 |
+
default=None,
|
| 125 |
+
description="Base language-model architecture when using a multimodal wrapper."
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
# Transformer-specific
|
| 129 |
+
vocab_size: int = Field(default=50257, description="Vocabulary size")
|
| 130 |
+
hidden_dim: int = Field(default=768, description="Hidden dimension")
|
| 131 |
+
num_layers: int = Field(default=12, description="Number of transformer blocks")
|
| 132 |
+
num_heads: int = Field(default=12, description="Number of attention heads")
|
| 133 |
+
head_dim: Optional[int] = Field(
|
| 134 |
+
default=None,
|
| 135 |
+
description="Head dimension (defaults to hidden_dim // num_heads)"
|
| 136 |
+
)
|
| 137 |
+
intermediate_dim: Optional[int] = Field(
|
| 138 |
+
default=None,
|
| 139 |
+
description="FFN intermediate dimension (defaults to 4 * hidden_dim)"
|
| 140 |
+
)
|
| 141 |
+
dropout: float = Field(default=0.1, description="Dropout rate")
|
| 142 |
+
max_seq_length: int = Field(default=2048, description="Maximum sequence length")
|
| 143 |
+
|
| 144 |
+
# TaoNet (DeepSeek MLA) specific
|
| 145 |
+
d_latent_kv: Optional[int] = Field(
|
| 146 |
+
default=None,
|
| 147 |
+
description="KV compression dimension for MLA (defaults to 3/4 * hidden_dim). Only used for taonet architecture."
|
| 148 |
+
)
|
| 149 |
+
d_rope: Optional[int] = Field(
|
| 150 |
+
default=None,
|
| 151 |
+
description="RoPE dimension per head (defaults to hidden_dim // num_heads). Only used for taonet architecture."
|
| 152 |
+
)
|
| 153 |
+
gqa_groups: int = Field(
|
| 154 |
+
default=1,
|
| 155 |
+
description="Grouped Query Attention groups (1 = standard MLA, >1 = GQA). Only used for taonet architecture."
|
| 156 |
+
)
|
| 157 |
+
hidden_dim_ff: Optional[int] = Field(
|
| 158 |
+
default=None,
|
| 159 |
+
description="Feed-forward intermediate dimension (defaults to 4 * hidden_dim)."
|
| 160 |
+
)
|
| 161 |
+
use_factorized_embedding: bool = Field(
|
| 162 |
+
default=False,
|
| 163 |
+
description="Use low-rank factorized embedding instead of standard embedding (reduces params). Only for taonet."
|
| 164 |
+
)
|
| 165 |
+
d_embed_rank: int = Field(
|
| 166 |
+
default=96,
|
| 167 |
+
description="Rank dimension for factorized embedding. Only used if use_factorized_embedding=True."
|
| 168 |
+
)
|
| 169 |
+
vision_encoder_type: str = Field(
|
| 170 |
+
default="cnn",
|
| 171 |
+
description="Vision encoder type for multimodal models."
|
| 172 |
+
)
|
| 173 |
+
image_size: int = Field(
|
| 174 |
+
default=224,
|
| 175 |
+
description="Square image size for multimodal image preprocessing."
|
| 176 |
+
)
|
| 177 |
+
vision_output_dim: int = Field(
|
| 178 |
+
default=256,
|
| 179 |
+
description="Output feature dimension from the vision encoder before projection."
|
| 180 |
+
)
|
| 181 |
+
vision_prefix_tokens: int = Field(
|
| 182 |
+
default=10,
|
| 183 |
+
description="Number of visual prefix tokens projected into the LLM sequence."
|
| 184 |
+
)
|
| 185 |
+
image_token: str = Field(
|
| 186 |
+
default="<image>",
|
| 187 |
+
description="Tokenizer special token reserved for image placeholders."
|
| 188 |
+
)
|
| 189 |
+
cnn_channels: list[int] = Field(
|
| 190 |
+
default_factory=lambda: [32, 64, 128],
|
| 191 |
+
description="Per-stage channel sizes for the in-repo CNN encoder."
|
| 192 |
+
)
|
| 193 |
+
cnn_kernel_size: int = Field(
|
| 194 |
+
default=3,
|
| 195 |
+
description="Kernel size for CNN encoder convolutions."
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
# GammaSpaceModel-specific
|
| 199 |
+
gamma_hidden_dim: int = Field(
|
| 200 |
+
default=1536,
|
| 201 |
+
description="GammaSpaceBlock hidden_dim (state-space hidden size). Only used for gamma_net."
|
| 202 |
+
)
|
| 203 |
+
gamma_dt_min: float = Field(
|
| 204 |
+
default=1e-3,
|
| 205 |
+
description="GammaSpaceBlock dt_min. Only used for gamma_net."
|
| 206 |
+
)
|
| 207 |
+
gamma_dt_max: float = Field(
|
| 208 |
+
default=1e-1,
|
| 209 |
+
description="GammaSpaceBlock dt_max. Only used for gamma_net."
|
| 210 |
+
)
|
| 211 |
+
gamma_dt_init: float = Field(
|
| 212 |
+
default=1e-2,
|
| 213 |
+
description="GammaSpaceBlock dt_init. Only used for gamma_net."
|
| 214 |
+
)
|
| 215 |
+
gamma_discretization: str = Field(
|
| 216 |
+
default="bilinear",
|
| 217 |
+
description="GammaSpaceBlock discretization mode: bilinear, zoh, or euler. Only used for gamma_net."
|
| 218 |
+
)
|
| 219 |
+
gamma_prenorm: bool = Field(
|
| 220 |
+
default=True,
|
| 221 |
+
description="GammaSpaceBlock prenorm flag. Only used for gamma_net."
|
| 222 |
+
)
|
| 223 |
+
gamma_residual_scale: float = Field(
|
| 224 |
+
default=1.0,
|
| 225 |
+
description="GammaSpaceBlock residual_scale. Only used for gamma_net."
|
| 226 |
+
)
|
| 227 |
+
gamma_activation: str = Field(
|
| 228 |
+
default="gelu",
|
| 229 |
+
description="GammaSpaceBlock activation. Only used for gamma_net."
|
| 230 |
+
)
|
| 231 |
+
gamma_gate: bool = Field(
|
| 232 |
+
default=True,
|
| 233 |
+
description="GammaSpaceBlock gate flag. Only used for gamma_net."
|
| 234 |
+
)
|
| 235 |
+
gamma_use_D: bool = Field(
|
| 236 |
+
default=True,
|
| 237 |
+
description="GammaSpaceBlock use_D flag. Only used for gamma_net."
|
| 238 |
+
)
|
| 239 |
+
gamma_kernel_mode: str = Field(
|
| 240 |
+
default="auto",
|
| 241 |
+
description="GammaSpaceBlock kernel_mode: auto, recurrent, or conv. Only used for gamma_net."
|
| 242 |
+
)
|
| 243 |
+
gamma_kernel_threshold: int = Field(
|
| 244 |
+
default=64,
|
| 245 |
+
description="GammaSpaceBlock kernel_threshold. Only used for gamma_net."
|
| 246 |
+
)
|
| 247 |
+
gamma_use_output_linear: bool = Field(
|
| 248 |
+
default=True,
|
| 249 |
+
description="GammaSpaceBlock use_output_linear flag. Only used for gamma_net."
|
| 250 |
+
)
|
| 251 |
+
gamma_gate_bias: float = Field(
|
| 252 |
+
default=2.0,
|
| 253 |
+
description="GammaSpaceBlock gate_bias. Only used for gamma_net."
|
| 254 |
+
)
|
| 255 |
+
gamma_input_gate: bool = Field(
|
| 256 |
+
default=True,
|
| 257 |
+
description="GammaSpaceBlock input_gate flag. Only used for gamma_net."
|
| 258 |
+
)
|
| 259 |
+
gamma_input_gate_bias: float = Field(
|
| 260 |
+
default=2.0,
|
| 261 |
+
description="GammaSpaceBlock input_gate_bias. Only used for gamma_net."
|
| 262 |
+
)
|
| 263 |
+
gamma_layer_scale_init: float = Field(
|
| 264 |
+
default=0.1,
|
| 265 |
+
description="GammaSpaceBlock layer_scale_init. Only used for gamma_net."
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
# YaRN (Yet another RoPE eXtension) for context length extension
|
| 269 |
+
rope_scale: float = Field(
|
| 270 |
+
default=40.0,
|
| 271 |
+
description="Base RoPE scale factor (default: 40.0). Controls position frequency base."
|
| 272 |
+
)
|
| 273 |
+
yarn_enabled: bool = Field(
|
| 274 |
+
default=False,
|
| 275 |
+
description="Enable YaRN (Yet another RoPE eXtension) for context length interpolation."
|
| 276 |
+
)
|
| 277 |
+
yarn_original_max_seq_length: Optional[int] = Field(
|
| 278 |
+
default=None,
|
| 279 |
+
description="Original trained context length that YaRN extends from. Defaults to max_seq_length when unset."
|
| 280 |
+
)
|
| 281 |
+
yarn_alpha: float = Field(
|
| 282 |
+
default=1.0,
|
| 283 |
+
description="YaRN interpolation smoothness (1.0=smooth, <1.0=aggressive, >1.0=conservative). Only used if yarn_enabled=True."
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
# Initializations
|
| 287 |
+
init_std: float = Field(default=0.02, description="Weight initialization standard deviation")
|
| 288 |
+
|
| 289 |
+
@validator("head_dim", always=True)
|
| 290 |
+
def validate_head_dim(cls, v, values):
|
| 291 |
+
"""Validate head dimension."""
|
| 292 |
+
if v is None and 'hidden_dim' in values:
|
| 293 |
+
return values['hidden_dim'] // values.get('num_heads', 12)
|
| 294 |
+
return v
|
| 295 |
+
|
| 296 |
+
@validator("intermediate_dim", always=True)
|
| 297 |
+
def validate_intermediate_dim(cls, v, values):
|
| 298 |
+
"""Validate intermediate dimension."""
|
| 299 |
+
if v is None and 'hidden_dim' in values:
|
| 300 |
+
return 4 * values['hidden_dim']
|
| 301 |
+
return v
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
# ============================================================================
|
| 305 |
+
# Dataset Config
|
| 306 |
+
# ============================================================================
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
class DatasetConfig(BaseConfig):
|
| 310 |
+
"""Configuration for dataset loading."""
|
| 311 |
+
|
| 312 |
+
# Local vs HuggingFace dataset selection
|
| 313 |
+
local: bool = Field(default=False, description="Use local JSONL dataset instead of HuggingFace")
|
| 314 |
+
|
| 315 |
+
# HuggingFace dataset fields
|
| 316 |
+
dataset_name: Optional[str] = Field(default=None, description="HuggingFace dataset name (e.g., 'wikitext', 'openwebtext')")
|
| 317 |
+
split: str = Field(default="train", description="Dataset split to use")
|
| 318 |
+
config: Optional[str] = Field(default=None, description="Dataset config if multi-config (e.g., 'wikitext-103')")
|
| 319 |
+
|
| 320 |
+
# Local JSONL dataset fields
|
| 321 |
+
jsonl_path: Optional[str] = Field(default=None, description="Path to local JSONL dataset file")
|
| 322 |
+
text_field: str = Field(default="text", description="Name of text field in JSONL")
|
| 323 |
+
image_path_column: str = Field(
|
| 324 |
+
default="image_path",
|
| 325 |
+
description="Column containing a local image path for multimodal JSONL datasets."
|
| 326 |
+
)
|
| 327 |
+
image_path_aliases: list[str] = Field(
|
| 328 |
+
default_factory=lambda: ["image", "image_path", "image_file", "file_name"],
|
| 329 |
+
description="Fallback column names to try when a multimodal JSONL record does not contain image_path_column."
|
| 330 |
+
)
|
| 331 |
+
caption_prompt: str = Field(
|
| 332 |
+
default="Describe the image.",
|
| 333 |
+
description="Prompt paired with caption-style multimodal records that only provide image + text."
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
# Text column name varies by dataset
|
| 337 |
+
text_column: str = Field(default="text", description="Name of text column in dataset")
|
| 338 |
+
|
| 339 |
+
# Preprocessing
|
| 340 |
+
max_samples: Optional[int] = Field(
|
| 341 |
+
default=None,
|
| 342 |
+
description="Limit dataset to N samples (useful for debugging)"
|
| 343 |
+
)
|
| 344 |
+
cache_dir: str = Field(default=".cache/datasets", description="HuggingFace cache directory")
|
| 345 |
+
|
| 346 |
+
# For SFT/RL datasets with instruction-response format
|
| 347 |
+
instruction_column: Optional[str] = Field(default=None, description="Instruction column for SFT")
|
| 348 |
+
response_column: Optional[str] = Field(default=None, description="Response column for SFT")
|
| 349 |
+
prompt_column: Optional[str] = Field(default=None, description="Prompt column for RL")
|
| 350 |
+
|
| 351 |
+
# Instruction template
|
| 352 |
+
instruction_template: Optional[str] = Field(
|
| 353 |
+
default=None,
|
| 354 |
+
description="Template for combining instruction and response. E.g., '{instruction}\\n{response}'"
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
# Tokenizer configuration
|
| 358 |
+
tokenizer_type: Optional[str] = Field(
|
| 359 |
+
default=None,
|
| 360 |
+
description="Tokenizer type: 'huggingface' or 'sentencepiece'. If None, defaults based on tokenizer_path."
|
| 361 |
+
)
|
| 362 |
+
tokenizer_path: Optional[str] = Field(
|
| 363 |
+
default=None,
|
| 364 |
+
description="Path to saved tokenizer (for SentencePiece: .model file, for HuggingFace: model name or local path)"
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
# Chunked loading for large JSONL files
|
| 368 |
+
enable_streaming: bool = Field(
|
| 369 |
+
default=True,
|
| 370 |
+
description="Enable streaming/chunked loading for large JSONL files to reduce memory usage"
|
| 371 |
+
)
|
| 372 |
+
chunk_size_gb: float = Field(
|
| 373 |
+
default=5.0,
|
| 374 |
+
description="Approximate chunk size in GB (ignored if samples_per_chunk is set)"
|
| 375 |
+
)
|
| 376 |
+
samples_per_chunk: Optional[int] = Field(
|
| 377 |
+
default=1000,
|
| 378 |
+
description="Number of samples per chunk (takes precedence over chunk_size_gb). Default: 1000 samples"
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
# Chunk caching
|
| 382 |
+
enable_chunk_metadata_cache: bool = Field(
|
| 383 |
+
default=True,
|
| 384 |
+
description="Enable caching of chunk metadata (file scan results) to avoid re-scanning large JSONL files"
|
| 385 |
+
)
|
| 386 |
+
enable_chunk_data_cache: bool = Field(
|
| 387 |
+
default=False,
|
| 388 |
+
description="Enable caching of actual chunk data as separate files for faster loading (uses more disk space)"
|
| 389 |
+
)
|
| 390 |
+
chunk_cache_dir: str = Field(
|
| 391 |
+
default=".cache/chunks",
|
| 392 |
+
description="Directory to store chunk metadata and data cache files"
|
| 393 |
+
)
|
| 394 |
+
|
| 395 |
+
# Tokenization parallelization
|
| 396 |
+
tokenizer_threads: int = Field(
|
| 397 |
+
default=1,
|
| 398 |
+
description="Number of background threads for tokenization (1-32 recommended). Higher values speed up tokenization but increase memory usage."
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
@validator('jsonl_path', always=True)
|
| 402 |
+
def validate_dataset_source(cls, v, values):
|
| 403 |
+
"""Validate that either local JSONL or HuggingFace dataset is specified."""
|
| 404 |
+
local = values.get('local', False)
|
| 405 |
+
dataset_name = values.get('dataset_name')
|
| 406 |
+
|
| 407 |
+
if local and not v:
|
| 408 |
+
raise ValueError("jsonl_path must be provided when local=True")
|
| 409 |
+
if not local and not dataset_name:
|
| 410 |
+
raise ValueError("dataset_name must be provided when local=False (HuggingFace dataset)")
|
| 411 |
+
|
| 412 |
+
return v
|
| 413 |
+
|
| 414 |
+
@validator('tokenizer_threads')
|
| 415 |
+
def validate_tokenizer_threads(cls, v):
|
| 416 |
+
"""Validate tokenizer_threads is a positive integer."""
|
| 417 |
+
if v < 1:
|
| 418 |
+
raise ValueError("tokenizer_threads must be at least 1")
|
| 419 |
+
if v > 128:
|
| 420 |
+
raise ValueError("tokenizer_threads should not exceed 128 (recommended: 1-32)")
|
| 421 |
+
return v
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
# ============================================================================
|
| 425 |
+
# Tokenizer Config
|
| 426 |
+
# ============================================================================
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
class TokenizerConfig(BaseConfig):
|
| 430 |
+
"""Configuration for tokenizer training."""
|
| 431 |
+
|
| 432 |
+
# Dataset source
|
| 433 |
+
jsonl_path: str = Field(description="Path to JSONL file containing training data")
|
| 434 |
+
text_field: str = Field(default="text", description="Field name in JSONL for text data")
|
| 435 |
+
|
| 436 |
+
# Training configuration
|
| 437 |
+
vocab_size: int = Field(default=50000, description="Vocabulary size")
|
| 438 |
+
model_type: str = Field(default="unigram", description="SentencePiece model type (unigram, bpe, char, word)")
|
| 439 |
+
character_coverage: float = Field(
|
| 440 |
+
default=0.9995,
|
| 441 |
+
description="Character coverage for SentencePiece training"
|
| 442 |
+
)
|
| 443 |
+
output_dir: str = Field(default="tokenizers", description="Directory to save trained tokenizer")
|
| 444 |
+
tokenizer_prefix: Optional[str] = Field(
|
| 445 |
+
default=None,
|
| 446 |
+
description="Prefix for tokenizer output files (default: model_type)"
|
| 447 |
+
)
|
| 448 |
+
|
| 449 |
+
# Custom special tokens registered as SentencePiece user-defined symbols.
|
| 450 |
+
special_tokens: Optional[list[str]] = Field(
|
| 451 |
+
default=None,
|
| 452 |
+
description=(
|
| 453 |
+
"Custom special tokens such as <think>, <user>, <assistant>, or <image>. "
|
| 454 |
+
"Built-in SentencePiece tokens are managed by the tokenizer itself and should not be listed here."
|
| 455 |
+
)
|
| 456 |
+
)
|
| 457 |
+
|
| 458 |
+
# Data sampling
|
| 459 |
+
max_samples: Optional[int] = Field(
|
| 460 |
+
default=None,
|
| 461 |
+
description="Limit training to first N samples from JSONL (useful for quick testing)"
|
| 462 |
+
)
|
| 463 |
+
|
| 464 |
+
# Tokenizer metadata
|
| 465 |
+
tokenizer_name: Optional[str] = Field(
|
| 466 |
+
default=None,
|
| 467 |
+
description="Optional name for the tokenizer"
|
| 468 |
+
)
|
| 469 |
+
|
| 470 |
+
@validator("special_tokens", pre=True)
|
| 471 |
+
def normalize_special_tokens(cls, value):
|
| 472 |
+
"""Normalize special token config to a list of custom token strings."""
|
| 473 |
+
if value is None:
|
| 474 |
+
return None
|
| 475 |
+
|
| 476 |
+
if isinstance(value, dict):
|
| 477 |
+
return [str(token) for token in value.keys() if str(token) not in {"<UNK>", "<BOS>", "<EOS>", "<PAD>"}]
|
| 478 |
+
|
| 479 |
+
if isinstance(value, (list, tuple)):
|
| 480 |
+
return [str(token) for token in value]
|
| 481 |
+
|
| 482 |
+
raise ValueError("special_tokens must be a list of token strings")
|
| 483 |
+
|
| 484 |
+
@validator("special_tokens")
|
| 485 |
+
def validate_special_tokens(cls, value):
|
| 486 |
+
"""Validate custom special token registry."""
|
| 487 |
+
if value is None:
|
| 488 |
+
return value
|
| 489 |
+
|
| 490 |
+
builtin_tokens = {"<UNK>", "<BOS>", "<EOS>", "<PAD>"}
|
| 491 |
+
seen: set[str] = set()
|
| 492 |
+
normalized: list[str] = []
|
| 493 |
+
for token in value:
|
| 494 |
+
if token in builtin_tokens:
|
| 495 |
+
continue
|
| 496 |
+
if token in seen:
|
| 497 |
+
continue
|
| 498 |
+
seen.add(token)
|
| 499 |
+
normalized.append(token)
|
| 500 |
+
return normalized
|
| 501 |
+
|
| 502 |
+
|
| 503 |
+
# ============================================================================
|
| 504 |
+
# Training Config
|
| 505 |
+
# ============================================================================
|
| 506 |
+
|
| 507 |
+
|
| 508 |
+
class OptimizerConfig(BaseConfig):
|
| 509 |
+
"""Optimizer configuration."""
|
| 510 |
+
|
| 511 |
+
optimizer_type: OptimizerEnum = Field(default=OptimizerEnum.ADAMW, description="Optimizer type")
|
| 512 |
+
learning_rate: float = Field(default=1e-4, description="Peak learning rate (for Muon 2D weights)")
|
| 513 |
+
adamw_lr: Optional[float] = Field(
|
| 514 |
+
default=None,
|
| 515 |
+
description="Learning rate for AdamW (1D parameters). If None, defaults to learning_rate / 10. Used in hybrid_muon_adamw optimizer."
|
| 516 |
+
)
|
| 517 |
+
weight_decay: float = Field(default=1e-2, description="Weight decay (L2 regularization)")
|
| 518 |
+
betas: tuple[float, float] = Field(default=(0.9, 0.999), description="Adam betas")
|
| 519 |
+
eps: float = Field(default=1e-8, description="Optimizer epsilon")
|
| 520 |
+
|
| 521 |
+
@validator('adamw_lr', always=True)
|
| 522 |
+
def set_default_adamw_lr(cls, v, values):
|
| 523 |
+
"""Set default adamw_lr as 1/10 of learning_rate if not specified."""
|
| 524 |
+
if v is None and 'learning_rate' in values:
|
| 525 |
+
return values['learning_rate'] / 10
|
| 526 |
+
return v
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
class SchedulerConfig(BaseConfig):
|
| 530 |
+
"""Learning rate scheduler configuration."""
|
| 531 |
+
|
| 532 |
+
scheduler_type: SchedulerEnum = Field(default=SchedulerEnum.LINEAR_WARMUP, description="Scheduler type")
|
| 533 |
+
warmup_steps: int = Field(default=0, description="Number of warmup steps (takes precedence over warmup_ratio)")
|
| 534 |
+
warmup_ratio: float = Field(default=0.1, description="Warmup as fraction of total steps (used if warmup_steps=0)")
|
| 535 |
+
|
| 536 |
+
# Cosine scheduler specific
|
| 537 |
+
num_cycles: float = Field(default=0.5, description="Number of cycles for cosine schedule")
|
| 538 |
+
last_epoch: int = Field(default=-1, description="Last epoch for scheduler")
|
| 539 |
+
|
| 540 |
+
# TaoNet 3-phase scheduler (warmup -> steady -> cosine decay)
|
| 541 |
+
steady_ratio: float = Field(
|
| 542 |
+
default=0.0,
|
| 543 |
+
description="Fraction of training steps at peak LR before cosine decay (0.0 = no steady phase). Only for cosineWarmup."
|
| 544 |
+
)
|
| 545 |
+
min_lr_ratio: float = Field(
|
| 546 |
+
default=0.0,
|
| 547 |
+
description="Minimum LR as fraction of peak LR at end of training (0.0 = decay to 0). Only for cosineWarmup."
|
| 548 |
+
)
|
| 549 |
+
|
| 550 |
+
@validator('warmup_ratio')
|
| 551 |
+
def validate_warmup_ratio(cls, v):
|
| 552 |
+
"""Validate warmup ratio is between 0 and 1."""
|
| 553 |
+
if not 0 <= v <= 1:
|
| 554 |
+
raise ValueError("warmup_ratio must be between 0 and 1")
|
| 555 |
+
return v
|
| 556 |
+
|
| 557 |
+
@validator('steady_ratio')
|
| 558 |
+
def validate_steady_ratio(cls, v):
|
| 559 |
+
"""Validate steady ratio is between 0 and 1."""
|
| 560 |
+
if not 0 <= v <= 1:
|
| 561 |
+
raise ValueError("steady_ratio must be between 0 and 1")
|
| 562 |
+
return v
|
| 563 |
+
|
| 564 |
+
@validator('min_lr_ratio')
|
| 565 |
+
def validate_min_lr_ratio(cls, v):
|
| 566 |
+
"""Validate min_lr_ratio is between 0 and 1."""
|
| 567 |
+
if not 0 <= v <= 1:
|
| 568 |
+
raise ValueError("min_lr_ratio must be between 0 and 1")
|
| 569 |
+
return v
|
| 570 |
+
|
| 571 |
+
@validator('warmup_steps')
|
| 572 |
+
def validate_warmup_steps(cls, v):
|
| 573 |
+
"""Validate warmup steps is non-negative."""
|
| 574 |
+
if v < 0:
|
| 575 |
+
raise ValueError("warmup_steps must be non-negative")
|
| 576 |
+
return v
|
| 577 |
+
|
| 578 |
+
|
| 579 |
+
class TrainingConfig(BaseConfig):
|
| 580 |
+
"""Base training configuration shared across all modes."""
|
| 581 |
+
|
| 582 |
+
# Data and model
|
| 583 |
+
model: ModelConfig = Field(default_factory=ModelConfig, description="Model configuration")
|
| 584 |
+
dataset: DatasetConfig = Field(description="Dataset configuration")
|
| 585 |
+
|
| 586 |
+
# Training hyperparameters
|
| 587 |
+
batch_size: int = Field(default=32, description="Batch size per device")
|
| 588 |
+
num_epochs: int = Field(default=3, description="Number of training epochs")
|
| 589 |
+
max_steps: Optional[int] = Field(
|
| 590 |
+
default=None,
|
| 591 |
+
description="Maximum steps (overrides num_epochs if set)"
|
| 592 |
+
)
|
| 593 |
+
gradient_accumulation_steps: int = Field(
|
| 594 |
+
default=1,
|
| 595 |
+
description="Gradient accumulation steps"
|
| 596 |
+
)
|
| 597 |
+
max_grad_norm: float = Field(default=1.0, description="Gradient clipping max norm")
|
| 598 |
+
|
| 599 |
+
# Optimizer
|
| 600 |
+
optimizer: OptimizerConfig = Field(
|
| 601 |
+
default_factory=OptimizerConfig,
|
| 602 |
+
description="Optimizer configuration"
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
+
# Scheduler
|
| 606 |
+
scheduler: SchedulerConfig = Field(
|
| 607 |
+
default_factory=SchedulerConfig,
|
| 608 |
+
description="Learning rate scheduler configuration"
|
| 609 |
+
)
|
| 610 |
+
|
| 611 |
+
# Data type and device
|
| 612 |
+
dtype: DataTypeEnum = Field(
|
| 613 |
+
default=DataTypeEnum.BFLOAT16,
|
| 614 |
+
description="Training data type"
|
| 615 |
+
)
|
| 616 |
+
device: str = Field(default="cuda", description="Device to train on (cuda, cpu)")
|
| 617 |
+
seed: int = Field(default=42, description="Random seed")
|
| 618 |
+
|
| 619 |
+
# Checkpointing
|
| 620 |
+
checkpoint_dir: str = Field(default="checkpoints", description="Directory to save checkpoints")
|
| 621 |
+
checkpoint_path: Optional[str] = Field(
|
| 622 |
+
default=None,
|
| 623 |
+
description="Path to load pretrained checkpoint (for SFT/RL). If provided, loads weights before training starts."
|
| 624 |
+
)
|
| 625 |
+
save_every_steps: int = Field(default=500, description="Save checkpoint every N steps")
|
| 626 |
+
keep_last_n_checkpoints: int = Field(default=3, description="Keep only last N checkpoints")
|
| 627 |
+
save_best_model: bool = Field(default=True, description="Save best model based on validation loss")
|
| 628 |
+
|
| 629 |
+
# Validation
|
| 630 |
+
eval_every_steps: int = Field(default=500, description="Evaluate every N steps")
|
| 631 |
+
eval_samples: int = Field(default=1000, description="Number of validation samples")
|
| 632 |
+
|
| 633 |
+
# Logging
|
| 634 |
+
log_every_steps: int = Field(default=10, description="Log metrics every N steps")
|
| 635 |
+
aim_repo: str = Field(default=".aim", description="AimStack repository path")
|
| 636 |
+
|
| 637 |
+
# Misc
|
| 638 |
+
num_workers: int = Field(default=0, description="Number of DataLoader workers")
|
| 639 |
+
pin_memory: bool = Field(default=True, description="Pin memory for DataLoader")
|
| 640 |
+
use_compile: bool = Field(default=False, description="Use torch.compile (experimental)")
|
| 641 |
+
|
| 642 |
+
# Mode
|
| 643 |
+
mode: TrainingModeEnum = Field(default=TrainingModeEnum.PRETRAIN, description="Training mode")
|
| 644 |
+
|
| 645 |
+
|
| 646 |
+
# ============================================================================
|
| 647 |
+
# Stage-Specific Configs
|
| 648 |
+
# ============================================================================
|
| 649 |
+
|
| 650 |
+
|
| 651 |
+
class PretrainConfig(TrainingConfig):
|
| 652 |
+
"""Configuration for pretraining."""
|
| 653 |
+
|
| 654 |
+
mode: Literal[TrainingModeEnum.PRETRAIN] = TrainingModeEnum.PRETRAIN
|
| 655 |
+
|
| 656 |
+
# Pretraining-specific
|
| 657 |
+
sequence_length: int = Field(default=1024, description="Sequence length for pretraining")
|
| 658 |
+
|
| 659 |
+
|
| 660 |
+
class SFTConfig(TrainingConfig):
|
| 661 |
+
"""Configuration for supervised fine-tuning."""
|
| 662 |
+
|
| 663 |
+
mode: Literal[TrainingModeEnum.SFT] = TrainingModeEnum.SFT
|
| 664 |
+
|
| 665 |
+
# SFT-specific
|
| 666 |
+
response_loss_only: bool = Field(
|
| 667 |
+
default=True,
|
| 668 |
+
description="Only compute loss on response/assistant tokens (not instruction/user tokens). Uses -100 label masking."
|
| 669 |
+
)
|
| 670 |
+
|
| 671 |
+
# Multi-turn conversation role tokens
|
| 672 |
+
user_token: str = Field(
|
| 673 |
+
default="<user>",
|
| 674 |
+
description="Special token representing user/instruction role in conversations"
|
| 675 |
+
)
|
| 676 |
+
assistant_token: str = Field(
|
| 677 |
+
default="<assistant>",
|
| 678 |
+
description="Special token representing assistant/response role in conversations"
|
| 679 |
+
)
|
| 680 |
+
|
| 681 |
+
|
| 682 |
+
class RLConfig(TrainingConfig):
|
| 683 |
+
"""Configuration for reinforcement learning training."""
|
| 684 |
+
|
| 685 |
+
mode: Literal[TrainingModeEnum.RL] = TrainingModeEnum.RL
|
| 686 |
+
|
| 687 |
+
# RL-specific
|
| 688 |
+
rl_method: RLMethodEnum = Field(
|
| 689 |
+
default=RLMethodEnum.PPO,
|
| 690 |
+
description="RL training method (PPO or DPO)"
|
| 691 |
+
)
|
| 692 |
+
|
| 693 |
+
# Reward model
|
| 694 |
+
reward_model_path: str = Field(description="Path to trained reward model checkpoint")
|
| 695 |
+
|
| 696 |
+
# PPO-specific
|
| 697 |
+
ppo_epochs: int = Field(default=4, description="PPO inner epochs")
|
| 698 |
+
ppo_clip_ratio: float = Field(default=0.2, description="PPO clipping ratio")
|
| 699 |
+
entropy_coeff: float = Field(default=0.01, description="Entropy bonus coefficient")
|
| 700 |
+
value_loss_coeff: float = Field(default=1.0, description="Value function loss coefficient")
|
| 701 |
+
|
| 702 |
+
# DPO-specific (Direct Preference Optimization)
|
| 703 |
+
dpo_beta: float = Field(default=0.1, description="DPO inverse temperature (beta)")
|
| 704 |
+
|
| 705 |
+
# Prompt distribution
|
| 706 |
+
prompt_dataset: Optional[DatasetConfig] = Field(
|
| 707 |
+
default=None,
|
| 708 |
+
description="Separate dataset for prompts (if different from main dataset)"
|
| 709 |
+
)
|
| 710 |
+
generation_max_length: int = Field(
|
| 711 |
+
default=256,
|
| 712 |
+
description="Maximum length for generated responses during RL"
|
| 713 |
+
)
|
| 714 |
+
|
| 715 |
+
|
| 716 |
+
class VLMConfig(TrainingConfig):
|
| 717 |
+
"""Configuration for multimodal vision-language connector training."""
|
| 718 |
+
|
| 719 |
+
mode: Literal[TrainingModeEnum.VLM] = TrainingModeEnum.VLM
|
| 720 |
+
|
| 721 |
+
response_loss_only: bool = Field(
|
| 722 |
+
default=True,
|
| 723 |
+
description="Only compute loss on assistant/response tokens for multimodal samples."
|
| 724 |
+
)
|
| 725 |
+
user_token: str = Field(
|
| 726 |
+
default="<user>",
|
| 727 |
+
description="Special token representing user/instruction role in multimodal conversations."
|
| 728 |
+
)
|
| 729 |
+
assistant_token: str = Field(
|
| 730 |
+
default="<assistant>",
|
| 731 |
+
description="Special token representing assistant/response role in multimodal conversations."
|
| 732 |
+
)
|
| 733 |
+
freeze_llm: bool = Field(
|
| 734 |
+
default=True,
|
| 735 |
+
description="Freeze the LLM except for any explicitly unfrozen trailing layers."
|
| 736 |
+
)
|
| 737 |
+
unfreeze_last_n_layers: int = Field(
|
| 738 |
+
default=0,
|
| 739 |
+
description="Number of final LLM blocks to unfreeze during VLM connector training."
|
| 740 |
+
)
|
| 741 |
+
vision_learning_rate: float = Field(
|
| 742 |
+
default=1e-4,
|
| 743 |
+
description="Learning rate for the vision encoder and multimodal projector."
|
| 744 |
+
)
|
| 745 |
+
llm_learning_rate: float = Field(
|
| 746 |
+
default=5e-5,
|
| 747 |
+
description="Learning rate for the unfrozen LLM parameter subset."
|
| 748 |
+
)
|
| 749 |
+
vision_prefix_tokens: int = Field(
|
| 750 |
+
default=10,
|
| 751 |
+
description="Number of visual prefix tokens to inject in place of a single image placeholder token."
|
| 752 |
+
)
|
| 753 |
+
image_token: str = Field(
|
| 754 |
+
default="<image>",
|
| 755 |
+
description="Tokenizer token used as the multimodal image placeholder."
|
| 756 |
+
)
|
| 757 |
+
image_size: int = Field(
|
| 758 |
+
default=224,
|
| 759 |
+
description="Square image size used for multimodal preprocessing."
|
| 760 |
+
)
|
| 761 |
+
|
| 762 |
+
@validator("image_token")
|
| 763 |
+
def validate_image_token_matches_model_config(cls, v, values):
|
| 764 |
+
"""Validate duplicated image token settings stay aligned."""
|
| 765 |
+
model = values.get("model")
|
| 766 |
+
if model is not None and getattr(model, "image_token", None) != v:
|
| 767 |
+
raise ValueError(
|
| 768 |
+
f"image_token must match model.image_token for VLM configs. "
|
| 769 |
+
f"Got top-level `{v}` and model `{model.image_token}`."
|
| 770 |
+
)
|
| 771 |
+
return v
|
| 772 |
+
|
| 773 |
+
@validator("vision_prefix_tokens")
|
| 774 |
+
def validate_vision_prefix_tokens_match_model_config(cls, v, values):
|
| 775 |
+
"""Validate duplicated visual prefix settings stay aligned."""
|
| 776 |
+
model = values.get("model")
|
| 777 |
+
if model is not None and getattr(model, "vision_prefix_tokens", None) != v:
|
| 778 |
+
raise ValueError(
|
| 779 |
+
"vision_prefix_tokens must match model.vision_prefix_tokens for VLM configs. "
|
| 780 |
+
f"Got top-level `{v}` and model `{model.vision_prefix_tokens}`."
|
| 781 |
+
)
|
| 782 |
+
return v
|
| 783 |
+
|
| 784 |
+
@validator("image_size")
|
| 785 |
+
def validate_image_size_matches_model_config(cls, v, values):
|
| 786 |
+
"""Validate duplicated image size settings stay aligned."""
|
| 787 |
+
model = values.get("model")
|
| 788 |
+
if model is not None and getattr(model, "image_size", None) != v:
|
| 789 |
+
raise ValueError(
|
| 790 |
+
f"image_size must match model.image_size for VLM configs. "
|
| 791 |
+
f"Got top-level `{v}` and model `{model.image_size}`."
|
| 792 |
+
)
|
| 793 |
+
return v
|
| 794 |
+
|
| 795 |
+
@validator("unfreeze_last_n_layers")
|
| 796 |
+
def validate_unfreeze_last_n_layers(cls, v):
|
| 797 |
+
"""Validate unfrozen layer count."""
|
| 798 |
+
if v < 0:
|
| 799 |
+
raise ValueError("unfreeze_last_n_layers must be non-negative")
|
| 800 |
+
return v
|
| 801 |
+
|
| 802 |
+
@validator("vision_learning_rate", "llm_learning_rate")
|
| 803 |
+
def validate_multimodal_learning_rates(cls, v):
|
| 804 |
+
"""Validate multimodal learning rates."""
|
| 805 |
+
if v <= 0:
|
| 806 |
+
raise ValueError("multimodal learning rates must be greater than 0")
|
| 807 |
+
return v
|
| 808 |
+
|
| 809 |
+
@validator("vision_prefix_tokens")
|
| 810 |
+
def validate_vision_prefix_tokens(cls, v):
|
| 811 |
+
"""Validate visual prefix length."""
|
| 812 |
+
if v < 1:
|
| 813 |
+
raise ValueError("vision_prefix_tokens must be at least 1")
|
| 814 |
+
return v
|
| 815 |
+
|
| 816 |
+
@validator("image_size")
|
| 817 |
+
def validate_image_size(cls, v):
|
| 818 |
+
"""Validate image size."""
|
| 819 |
+
if v < 8:
|
| 820 |
+
raise ValueError("image_size must be at least 8")
|
| 821 |
+
return v
|
| 822 |
+
|
| 823 |
+
|
| 824 |
+
class VLMSFTConfig(VLMConfig):
|
| 825 |
+
"""Configuration for end-to-end multimodal supervised fine-tuning."""
|
| 826 |
+
|
| 827 |
+
mode: Literal[TrainingModeEnum.VLM_SFT] = TrainingModeEnum.VLM_SFT
|
| 828 |
+
freeze_llm: bool = Field(
|
| 829 |
+
default=False,
|
| 830 |
+
description="Whether to freeze the LLM during end-to-end multimodal SFT."
|
| 831 |
+
)
|
| 832 |
+
|
| 833 |
+
|
| 834 |
+
# ============================================================================
|
| 835 |
+
# Factory function
|
| 836 |
+
# ============================================================================
|
| 837 |
+
|
| 838 |
+
|
| 839 |
+
def load_config(path: str | Path, mode: TrainingModeEnum | str) -> TrainingConfig:
|
| 840 |
+
"""Load config file and return appropriate config class."""
|
| 841 |
+
if isinstance(mode, str):
|
| 842 |
+
mode = TrainingModeEnum(mode)
|
| 843 |
+
|
| 844 |
+
config_map = {
|
| 845 |
+
TrainingModeEnum.PRETRAIN: PretrainConfig,
|
| 846 |
+
TrainingModeEnum.SFT: SFTConfig,
|
| 847 |
+
TrainingModeEnum.RL: RLConfig,
|
| 848 |
+
TrainingModeEnum.VLM: VLMConfig,
|
| 849 |
+
TrainingModeEnum.VLM_SFT: VLMSFTConfig,
|
| 850 |
+
}
|
| 851 |
+
|
| 852 |
+
config_class = config_map[mode]
|
| 853 |
+
|
| 854 |
+
path = Path(path)
|
| 855 |
+
if path.suffix == '.yaml' or path.suffix == '.yml':
|
| 856 |
+
return config_class.load_yaml(path)
|
| 857 |
+
elif path.suffix == '.json':
|
| 858 |
+
return config_class.load_json(path)
|
| 859 |
+
else:
|
| 860 |
+
raise ValueError(f"Unsupported config file format: {path.suffix}")
|
| 861 |
+
|
| 862 |
+
|
| 863 |
+
def load_tokenizer_config(path: str | Path) -> TokenizerConfig:
|
| 864 |
+
"""Load tokenizer config from YAML or JSON file."""
|
| 865 |
+
path = Path(path)
|
| 866 |
+
if path.suffix == '.yaml' or path.suffix == '.yml':
|
| 867 |
+
return TokenizerConfig.load_yaml(path)
|
| 868 |
+
elif path.suffix == '.json':
|
| 869 |
+
return TokenizerConfig.load_json(path)
|
| 870 |
+
else:
|
| 871 |
+
raise ValueError(f"Unsupported config file format: {path.suffix}")
|
src/taoTrain/core/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Base classes for models, trainers, and datasets."""
|
| 2 |
+
|
| 3 |
+
from .base import BaseModel, BaseTrainer, BaseDataset, create_model, create_datasets
|
| 4 |
+
|
| 5 |
+
__all__ = ["BaseModel", "BaseTrainer", "BaseDataset", "create_model", "create_datasets"]
|
src/taoTrain/core/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (366 Bytes). View file
|
|
|
src/taoTrain/core/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (351 Bytes). View file
|
|
|
src/taoTrain/core/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (348 Bytes). View file
|
|
|
src/taoTrain/core/__pycache__/base.cpython-310.pyc
ADDED
|
Binary file (8.69 kB). View file
|
|
|
src/taoTrain/core/__pycache__/base.cpython-312.pyc
ADDED
|
Binary file (11.5 kB). View file
|
|
|
src/taoTrain/core/__pycache__/base.cpython-314.pyc
ADDED
|
Binary file (14.5 kB). View file
|
|
|
src/taoTrain/core/base.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Base classes for models, trainers, and datasets."""
|
| 2 |
+
|
| 3 |
+
from abc import ABC, abstractmethod
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import Optional, Any, Iterator
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
from torch.utils.data import Dataset as TorchDataset
|
| 9 |
+
from taoTrain.config import TrainingConfig, ModelConfig, TrainingModeEnum
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# ============================================================================
|
| 13 |
+
# Base Model
|
| 14 |
+
# ============================================================================
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class BaseModel(nn.Module, ABC):
|
| 18 |
+
"""Abstract base class for language models."""
|
| 19 |
+
|
| 20 |
+
def __init__(self, config: Any):
|
| 21 |
+
"""Initialize model with config."""
|
| 22 |
+
super().__init__()
|
| 23 |
+
self.config = config
|
| 24 |
+
|
| 25 |
+
@abstractmethod
|
| 26 |
+
def forward(
|
| 27 |
+
self,
|
| 28 |
+
input_ids: Optional[torch.Tensor] = None,
|
| 29 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 30 |
+
labels: Optional[torch.Tensor] = None,
|
| 31 |
+
inputs_embeds: Optional[torch.Tensor] = None,
|
| 32 |
+
pixel_values: Optional[torch.Tensor] = None,
|
| 33 |
+
) -> dict[str, torch.Tensor]:
|
| 34 |
+
"""
|
| 35 |
+
Forward pass.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
input_ids: Shape (batch_size, seq_length)
|
| 39 |
+
attention_mask: Shape (batch_size, seq_length), optional
|
| 40 |
+
labels: Shape (batch_size, seq_length), optional (for loss computation)
|
| 41 |
+
|
| 42 |
+
Returns:
|
| 43 |
+
Dict with keys:
|
| 44 |
+
- 'logits': Shape (batch_size, seq_length, vocab_size)
|
| 45 |
+
- 'loss': Scalar (if labels provided)
|
| 46 |
+
"""
|
| 47 |
+
pass
|
| 48 |
+
|
| 49 |
+
def count_parameters(self) -> int:
|
| 50 |
+
"""Count total trainable parameters."""
|
| 51 |
+
return sum(p.numel() for p in self.parameters() if p.requires_grad)
|
| 52 |
+
|
| 53 |
+
def get_num_layers(self) -> int:
|
| 54 |
+
"""Get number of layers (for model architecture)."""
|
| 55 |
+
return self.config.num_layers
|
| 56 |
+
|
| 57 |
+
def get_optimizer_param_groups(self, config: TrainingConfig) -> Optional[list[dict[str, Any]]]:
|
| 58 |
+
"""Optional custom optimizer param groups for advanced training setups."""
|
| 59 |
+
return None
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# ============================================================================
|
| 63 |
+
# Base Dataset
|
| 64 |
+
# ============================================================================
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class BaseDataset(TorchDataset, ABC):
|
| 68 |
+
"""Abstract base class for datasets."""
|
| 69 |
+
|
| 70 |
+
def __init__(self, config: "TrainingConfig"):
|
| 71 |
+
"""Initialize dataset."""
|
| 72 |
+
self.config = config
|
| 73 |
+
self.data = None
|
| 74 |
+
|
| 75 |
+
@abstractmethod
|
| 76 |
+
def __len__(self) -> int:
|
| 77 |
+
"""Return dataset size."""
|
| 78 |
+
pass
|
| 79 |
+
|
| 80 |
+
@abstractmethod
|
| 81 |
+
def __getitem__(self, idx: int) -> dict[str, torch.Tensor]:
|
| 82 |
+
"""
|
| 83 |
+
Get a single sample.
|
| 84 |
+
|
| 85 |
+
Returns:
|
| 86 |
+
Dict with keys:
|
| 87 |
+
- 'input_ids': 1D tensor of token IDs
|
| 88 |
+
- 'attention_mask': 1D tensor of attention mask
|
| 89 |
+
- 'labels': 1D tensor of labels (optional)
|
| 90 |
+
"""
|
| 91 |
+
pass
|
| 92 |
+
|
| 93 |
+
def load_dataset(self) -> None:
|
| 94 |
+
"""Load dataset from HuggingFace or other source."""
|
| 95 |
+
pass
|
| 96 |
+
|
| 97 |
+
def preprocess(self) -> None:
|
| 98 |
+
"""Preprocess dataset (tokenization, etc)."""
|
| 99 |
+
pass
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
# ============================================================================
|
| 103 |
+
# Base Trainer
|
| 104 |
+
# ============================================================================
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class BaseTrainer(ABC):
|
| 108 |
+
"""Abstract base class for trainers."""
|
| 109 |
+
|
| 110 |
+
def __init__(
|
| 111 |
+
self,
|
| 112 |
+
model: BaseModel,
|
| 113 |
+
train_dataset: BaseDataset,
|
| 114 |
+
val_dataset: Optional[BaseDataset],
|
| 115 |
+
config: TrainingConfig,
|
| 116 |
+
device: torch.device,
|
| 117 |
+
):
|
| 118 |
+
"""Initialize trainer."""
|
| 119 |
+
self.model = model.to(device)
|
| 120 |
+
self.train_dataset = train_dataset
|
| 121 |
+
self.val_dataset = val_dataset
|
| 122 |
+
self.config = config
|
| 123 |
+
self.device = device
|
| 124 |
+
|
| 125 |
+
# Training state
|
| 126 |
+
self.global_step = 0
|
| 127 |
+
self.current_epoch = 0
|
| 128 |
+
self.best_loss = float('inf')
|
| 129 |
+
|
| 130 |
+
# Logging
|
| 131 |
+
self.logger = None
|
| 132 |
+
|
| 133 |
+
# Optimizer and scheduler (to be set up by subclass)
|
| 134 |
+
self.optimizer = None
|
| 135 |
+
self.scheduler = None
|
| 136 |
+
|
| 137 |
+
@abstractmethod
|
| 138 |
+
def training_step(self, batch: dict[str, torch.Tensor]) -> dict[str, float]:
|
| 139 |
+
"""
|
| 140 |
+
Single training step.
|
| 141 |
+
|
| 142 |
+
Args:
|
| 143 |
+
batch: Training batch with input_ids, attention_mask, labels, etc.
|
| 144 |
+
|
| 145 |
+
Returns:
|
| 146 |
+
Dict with metrics (e.g., {'loss': 0.5, 'accuracy': 0.8})
|
| 147 |
+
"""
|
| 148 |
+
pass
|
| 149 |
+
|
| 150 |
+
@abstractmethod
|
| 151 |
+
def validation_step(self, batch: dict[str, torch.Tensor]) -> dict[str, float]:
|
| 152 |
+
"""
|
| 153 |
+
Single validation step.
|
| 154 |
+
|
| 155 |
+
Args:
|
| 156 |
+
batch: Validation batch
|
| 157 |
+
|
| 158 |
+
Returns:
|
| 159 |
+
Dict with validation metrics
|
| 160 |
+
"""
|
| 161 |
+
pass
|
| 162 |
+
|
| 163 |
+
@abstractmethod
|
| 164 |
+
def train_epoch(self) -> dict[str, float]:
|
| 165 |
+
"""
|
| 166 |
+
Train for one epoch.
|
| 167 |
+
|
| 168 |
+
Returns:
|
| 169 |
+
Dict with epoch-level metrics
|
| 170 |
+
"""
|
| 171 |
+
pass
|
| 172 |
+
|
| 173 |
+
@abstractmethod
|
| 174 |
+
def validate(self) -> dict[str, float]:
|
| 175 |
+
"""
|
| 176 |
+
Run validation on the entire validation set.
|
| 177 |
+
|
| 178 |
+
Returns:
|
| 179 |
+
Dict with validation metrics
|
| 180 |
+
"""
|
| 181 |
+
pass
|
| 182 |
+
|
| 183 |
+
def save_checkpoint(self, path: str | Path) -> None:
|
| 184 |
+
"""
|
| 185 |
+
Save checkpoint in canonical format.
|
| 186 |
+
|
| 187 |
+
Uses canonical checkpoint format:
|
| 188 |
+
{
|
| 189 |
+
'step': int,
|
| 190 |
+
'model_state': state_dict,
|
| 191 |
+
'optimizer_state': state_dict,
|
| 192 |
+
'config': dict,
|
| 193 |
+
'metrics': dict,
|
| 194 |
+
'global_step': int, # Legacy compat
|
| 195 |
+
'current_epoch': int, # Legacy compat
|
| 196 |
+
'best_loss': float, # Legacy compat
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
Args:
|
| 200 |
+
path: Path to save checkpoint
|
| 201 |
+
"""
|
| 202 |
+
path = Path(path)
|
| 203 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 204 |
+
|
| 205 |
+
# Save in canonical format
|
| 206 |
+
checkpoint = {
|
| 207 |
+
# Canonical format keys
|
| 208 |
+
'step': self.global_step,
|
| 209 |
+
'model_state': self.model.state_dict(),
|
| 210 |
+
'optimizer_state': self.optimizer.state_dict() if self.optimizer else None,
|
| 211 |
+
'config': self.config.to_dict(),
|
| 212 |
+
'metrics': {},
|
| 213 |
+
# Legacy format keys (for backward compatibility with code that reads them)
|
| 214 |
+
'global_step': self.global_step,
|
| 215 |
+
'current_epoch': self.current_epoch,
|
| 216 |
+
'best_loss': self.best_loss,
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
torch.save(checkpoint, path)
|
| 220 |
+
|
| 221 |
+
def load_checkpoint(self, path: str | Path) -> None:
|
| 222 |
+
"""
|
| 223 |
+
Load checkpoint (handles both canonical and legacy formats).
|
| 224 |
+
|
| 225 |
+
Args:
|
| 226 |
+
path: Path to checkpoint
|
| 227 |
+
"""
|
| 228 |
+
path = Path(path)
|
| 229 |
+
checkpoint = torch.load(path, map_location=self.device)
|
| 230 |
+
|
| 231 |
+
# Try canonical keys first, fall back to legacy keys
|
| 232 |
+
model_state_key = 'model_state' if 'model_state' in checkpoint else 'model_state_dict'
|
| 233 |
+
optimizer_state_key = 'optimizer_state' if 'optimizer_state' in checkpoint else 'optimizer_state_dict'
|
| 234 |
+
|
| 235 |
+
self.model.load_state_dict(checkpoint[model_state_key])
|
| 236 |
+
if self.optimizer and checkpoint.get(optimizer_state_key):
|
| 237 |
+
self.optimizer.load_state_dict(checkpoint[optimizer_state_key])
|
| 238 |
+
|
| 239 |
+
# Try canonical 'step' first, fall back to legacy 'global_step'
|
| 240 |
+
self.global_step = checkpoint.get('step', checkpoint.get('global_step', 0))
|
| 241 |
+
self.current_epoch = checkpoint.get('current_epoch', 0)
|
| 242 |
+
self.best_loss = checkpoint.get('best_loss', float('inf'))
|
| 243 |
+
|
| 244 |
+
def _get_lr(self) -> float:
|
| 245 |
+
"""Get current learning rate from optimizer."""
|
| 246 |
+
for param_group in self.optimizer.param_groups:
|
| 247 |
+
return param_group['lr']
|
| 248 |
+
return 0.0
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
# ============================================================================
|
| 252 |
+
# Utility functions
|
| 253 |
+
# ============================================================================
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def create_model(config: TrainingConfig, device: torch.device) -> BaseModel:
|
| 257 |
+
"""Create model from config (calls registry)."""
|
| 258 |
+
from taoTrain.models import get_model
|
| 259 |
+
if config.mode in {TrainingModeEnum.VLM, TrainingModeEnum.VLM_SFT}:
|
| 260 |
+
return get_model(config, device=device)
|
| 261 |
+
return get_model(config.model, device=device)
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
def create_datasets(
|
| 265 |
+
config: TrainingConfig,
|
| 266 |
+
) -> tuple[BaseDataset, Optional[BaseDataset]]:
|
| 267 |
+
"""Create train and validation datasets using factory pattern."""
|
| 268 |
+
# Import here to avoid circular imports
|
| 269 |
+
from taoTrain.data import DatasetFactory
|
| 270 |
+
|
| 271 |
+
# Create train dataset
|
| 272 |
+
train_dataset = DatasetFactory.create_dataset(config, split="train")
|
| 273 |
+
|
| 274 |
+
# Create validation dataset (only for HuggingFace datasets with explicit validation split)
|
| 275 |
+
val_dataset = None
|
| 276 |
+
if not config.dataset.local and hasattr(config.dataset, "validation_split"):
|
| 277 |
+
val_dataset = DatasetFactory.create_dataset(config, split="validation")
|
| 278 |
+
|
| 279 |
+
return train_dataset, val_dataset
|
src/taoTrain/data/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Dataset implementations and loaders."""
|
| 2 |
+
|
| 3 |
+
# HuggingFace-based datasets
|
| 4 |
+
from .hf_base import BaseHFDataset
|
| 5 |
+
from .hf_pretrain import PretrainDataset
|
| 6 |
+
from .hf_sft import SFTDataset
|
| 7 |
+
from .hf_rl import RLDataset
|
| 8 |
+
|
| 9 |
+
# JSONL-based datasets (async-only)
|
| 10 |
+
from .jsonl_base import BaseJSONLDataset
|
| 11 |
+
from .pretrain_jsonl import PretrainJSONLDataset
|
| 12 |
+
from .sft_jsonl import SFTJSONLDataset
|
| 13 |
+
from .rl_jsonl import RLJSONLDataset
|
| 14 |
+
from .vlm_jsonl import VLMJSONLDataset, VLMSFTJSONLDataset
|
| 15 |
+
|
| 16 |
+
# Utilities
|
| 17 |
+
from .tokenizer import SentencePieceTokenizerWrapper
|
| 18 |
+
from .sft_utils import parse_sft_record, build_sft_sequence_tokens, apply_response_masking
|
| 19 |
+
from .loaders import get_dataloader
|
| 20 |
+
from .async_loader import AsyncBatchIterator
|
| 21 |
+
from .tokenization_queue import TokenizationQueue
|
| 22 |
+
from .factory import DatasetFactory
|
| 23 |
+
|
| 24 |
+
__all__ = [
|
| 25 |
+
# HuggingFace datasets
|
| 26 |
+
"BaseHFDataset",
|
| 27 |
+
"PretrainDataset",
|
| 28 |
+
"SFTDataset",
|
| 29 |
+
"RLDataset",
|
| 30 |
+
# JSONL datasets
|
| 31 |
+
"BaseJSONLDataset",
|
| 32 |
+
"PretrainJSONLDataset",
|
| 33 |
+
"SFTJSONLDataset",
|
| 34 |
+
"RLJSONLDataset",
|
| 35 |
+
"VLMJSONLDataset",
|
| 36 |
+
"VLMSFTJSONLDataset",
|
| 37 |
+
# Utilities
|
| 38 |
+
"SentencePieceTokenizerWrapper",
|
| 39 |
+
"parse_sft_record",
|
| 40 |
+
"build_sft_sequence_tokens",
|
| 41 |
+
"apply_response_masking",
|
| 42 |
+
# Data loading
|
| 43 |
+
"get_dataloader",
|
| 44 |
+
"AsyncBatchIterator",
|
| 45 |
+
"TokenizationQueue",
|
| 46 |
+
"DatasetFactory",
|
| 47 |
+
]
|
src/taoTrain/data/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (1.16 kB). View file
|
|
|
src/taoTrain/data/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (1.17 kB). View file
|
|
|