Delta-FlareAI commited on
Commit
a006332
·
verified ·
1 Parent(s): 5a52ac5

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -1,35 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
  *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.safetensors filter=lfs diff=lfs merge=lfs -text
2
+ *.pt filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flame Corporation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
MODEL_CARD.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ language:
4
+ - pt
5
+ - en
6
+ pipeline_tag: text-generation
7
+ tags:
8
+ - pytorch
9
+ - causal-lm
10
+ - decoder-only
11
+ - educational
12
+ - small-language-model
13
+ ---
14
+
15
+ # Delta Ultra Mini
16
+
17
+ Delta Ultra Mini is a compact decoder-only language model created by Flame Corporation. It is intended as an educational small LLM release for studying tokenizer training, causal language modeling, checkpoints, and local generation.
18
+
19
+ ## Model Details
20
+
21
+ - Architecture: decoder-only causal Transformer
22
+ - Parameters: about 50M
23
+ - Context length: 512 tokens
24
+ - Tokenizer: BPE
25
+ - License: MIT
26
+
27
+ ## Intended Use
28
+
29
+ - Learning how small language models are structured and trained
30
+ - Running local inference experiments
31
+ - Building and testing custom small datasets
32
+ - Fine-tuning from a compact baseline
33
+
34
+ ## Not Intended For
35
+
36
+ - Production assistant use without evaluation
37
+ - High-stakes decision making
38
+ - Security, medical, legal, or financial advice
39
+ - Claims of strong general reasoning
40
+
41
+ ## Training Data
42
+
43
+ The seed dataset is small and conversational. It is designed to validate the training pipeline, not to produce a high-quality assistant on its own. Users are encouraged to create larger, cleaner datasets and train new checkpoints.
44
+
45
+ ## Limitations
46
+
47
+ Delta Ultra Mini can hallucinate, mix examples from the seed dataset, and answer incorrectly. The included checkpoint should be treated as an experimental baseline.
48
+
49
+ ## Quick Start
50
+
51
+ ```bash
52
+ pip install -r requirements.txt
53
+ python scripts/generate_delta.py --prompt "Quem e voce?" --checkpoint_path delta_checkpoint.pt --tokenizer_path tokenizer.json
54
+ ```
55
+
56
+ ## Citation
57
+
58
+ If you publish work using this model, cite the repository or model page where you downloaded it.
README.md CHANGED
@@ -1,3 +1,80 @@
1
- ---
2
- license: mit
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Delta Ultra Mini
2
+
3
+ Delta Ultra Mini is a compact decoder-only language model created by Flame Corporation. This model-only release contains the neural model code, tokenizer wrapper, training utilities, seed dataset, and simple local inference tools.
4
+
5
+ This release intentionally does not include the REST API, API key server, browser SDK, or Python HTTP SDK.
6
+
7
+ ## Model
8
+
9
+ - Architecture: decoder-only causal Transformer
10
+ - Parameters: about 50M
11
+ - Context length: 512 tokens
12
+ - Tokenizer: BPE with chat tokens
13
+ - License: MIT
14
+
15
+ Delta Ultra Mini is an educational and experimental small LLM. It is useful for learning how a compact language model is structured, trained, checkpointed, and sampled. It is not a strong general assistant yet.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install -r requirements.txt
21
+ ```
22
+
23
+ ## Files
24
+
25
+ - `delta/model.py`: Transformer model
26
+ - `delta/tokenizer.py`: tokenizer training/loading and chat formatting
27
+ - `delta/generator.py`: local autoregressive generation
28
+ - `delta/dataset.py`: text/jsonl dataset loader
29
+ - `delta/trainer.py`: HuggingFace Trainer integration
30
+ - `configs/ultra_mini.json`: model configuration
31
+ - `tokenizer.json`: trained tokenizer
32
+ - `data/`: small MIT-licensed seed dataset
33
+ - `scripts/train_tokenizer.py`: tokenizer training entrypoint
34
+ - `scripts/train_delta.py`: model training entrypoint
35
+ - `scripts/generate_delta.py`: local inference entrypoint
36
+
37
+ ## Local Inference
38
+
39
+ ```bash
40
+ python scripts/generate_delta.py --prompt "O que e PyTorch?" --checkpoint_path runs/delta-ultra-mini/delta_checkpoint.pt --tokenizer_path tokenizer.json
41
+ ```
42
+
43
+ If your checkpoint is at the release root, use:
44
+
45
+ ```bash
46
+ python scripts/generate_delta.py --prompt "Quem e voce?" --checkpoint_path delta_checkpoint.pt --tokenizer_path tokenizer.json
47
+ ```
48
+
49
+ ## Train Tokenizer
50
+
51
+ ```bash
52
+ python scripts/train_tokenizer.py --corpus_files data/tokenizer_corpus.txt --output_path tokenizer.json
53
+ ```
54
+
55
+ ## Train Model
56
+
57
+ ```bash
58
+ python scripts/train_delta.py --data_path data --output_dir runs/delta-ultra-mini --epochs 1 --batch_size 2 --tokenizer_path tokenizer.json
59
+ ```
60
+
61
+ ## Dataset
62
+
63
+ The included dataset is a small seed dataset. It is meant to bootstrap experimentation and verify the pipeline. For better quality, create a larger dataset with varied examples, clean answers, validation splits, and careful review.
64
+
65
+ Recommended format:
66
+
67
+ ```jsonl
68
+ {"text":"[SYS] You are Delta. [SEP]\n[USR] Question [SEP]\n[ASS] Answer [SEP]"}
69
+ ```
70
+
71
+ ## Limitations
72
+
73
+ - The seed checkpoint may memorize examples and generalize poorly.
74
+ - The model is not safety-aligned like large production assistants.
75
+ - It can produce incorrect or mixed answers.
76
+ - It should be evaluated before any real use.
77
+
78
+ ## License
79
+
80
+ MIT.
config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vocab_size": 32000,
3
+ "d_model": 512,
4
+ "n_heads": 8,
5
+ "n_layers": 8,
6
+ "d_ff": 2048,
7
+ "max_seq_len": 512,
8
+ "dropout": 0.1,
9
+ "tie_embeddings": true,
10
+ "pad_token_id": 0,
11
+ "bos_token_id": 2,
12
+ "eos_token_id": 3,
13
+ "use_cache": false
14
+ }
configs/ultra_mini.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vocab_size": 32000,
3
+ "d_model": 512,
4
+ "n_heads": 8,
5
+ "n_layers": 8,
6
+ "d_ff": 2048,
7
+ "max_seq_len": 512,
8
+ "dropout": 0.1,
9
+ "tie_embeddings": true,
10
+ "pad_token_id": 0,
11
+ "bos_token_id": 2,
12
+ "eos_token_id": 3,
13
+ "use_cache": true
14
+ }
data/README.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Delta Seed Dataset
2
+
3
+ This directory contains the small seed dataset used to test and bootstrap Delta Ultra Mini.
4
+
5
+ - `delta_seed.jsonl`: conversational JSONL records with a `text` field.
6
+ - `tokenizer_corpus.txt`: plain text corpus for BPE tokenizer training.
7
+ - `corpus.txt`: training text in the same chat-token format.
8
+
9
+ This dataset is intentionally small. It is useful for validating the tokenizer, dataset loader, training loop, checkpoint saving, and local inference. It is not enough to create a strong general assistant.
10
+
11
+ ## License
12
+
13
+ MIT, same as the project.
14
+
15
+ ## Format
16
+
17
+ ```jsonl
18
+ {"text":"[SYS] You are Delta. [SEP]\n[USR] Question [SEP]\n[ASS] Answer [SEP]"}
19
+ ```
20
+
21
+ ## Train Tokenizer
22
+
23
+ ```powershell
24
+ python scripts/train_tokenizer.py --corpus_files data/tokenizer_corpus.txt --output_path tokenizer.json
25
+ ```
26
+
27
+ ## Train Model
28
+
29
+ ```powershell
30
+ python scripts/train_delta.py --data_path data --output_dir runs/delta-ultra-mini --epochs 1 --batch_size 2 --tokenizer_path tokenizer.json
31
+ ```
data/corpus.txt ADDED
The diff for this file is too large to render. See raw diff
 
data/delta_seed.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
data/tokenizer_corpus.txt ADDED
The diff for this file is too large to render. See raw diff
 
delta/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """Delta Ultra Mini package."""
2
+
3
+ from delta.identity import get_identity
4
+ from delta.model import DeltaConfig, DeltaModel
5
+
6
+ __all__ = ["DeltaConfig", "DeltaModel", "get_identity"]
delta/dataset.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dataset and collator for Delta causal language modeling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import torch
12
+ from torch.utils.data import Dataset
13
+
14
+ from delta.tokenizer import DeltaTokenizer
15
+
16
+ logging.basicConfig(level=os.getenv("DELTA_LOG_LEVEL", "INFO").upper())
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def _read_texts(path: Path) -> list[str]:
21
+ """Read .txt files or .jsonl files containing a text field."""
22
+
23
+ files = [path] if path.is_file() else sorted(path.rglob("*"))
24
+ texts: list[str] = []
25
+ for file_path in files:
26
+ if file_path.suffix.lower() == ".txt":
27
+ texts.append(file_path.read_text(encoding="utf-8"))
28
+ elif file_path.suffix.lower() == ".jsonl":
29
+ with file_path.open("r", encoding="utf-8") as handle:
30
+ for line in handle:
31
+ line = line.strip()
32
+ if not line:
33
+ continue
34
+ record = json.loads(line)
35
+ if "text" in record:
36
+ texts.append(str(record["text"]))
37
+ return texts
38
+
39
+
40
+ class DeltaDataset(Dataset[dict[str, torch.Tensor]]):
41
+ """Sliding-window token dataset for language modeling."""
42
+
43
+ def __init__(
44
+ self,
45
+ data_path: str | Path,
46
+ tokenizer: DeltaTokenizer,
47
+ max_seq_len: int = 512,
48
+ stride: int = 256,
49
+ ) -> None:
50
+ self.data_path = Path(data_path)
51
+ self.tokenizer = tokenizer
52
+ self.max_seq_len = max_seq_len
53
+ self.stride = stride
54
+ texts = _read_texts(self.data_path)
55
+ if not texts:
56
+ raise ValueError(f"No .txt or .jsonl text records found in {self.data_path}")
57
+ self.windows: list[list[int]] = []
58
+ for text in texts:
59
+ ids = tokenizer.encode(text, add_special_tokens=True)
60
+ for start in range(0, max(1, len(ids) - 1), stride):
61
+ window = ids[start : start + max_seq_len]
62
+ if len(window) >= 2:
63
+ self.windows.append(window)
64
+ if start + max_seq_len >= len(ids):
65
+ break
66
+ logger.info("Loaded %s training windows from %s", len(self.windows), self.data_path)
67
+
68
+ def __len__(self) -> int:
69
+ """Return the number of windows."""
70
+
71
+ return len(self.windows)
72
+
73
+ def __getitem__(self, index: int) -> dict[str, torch.Tensor]:
74
+ """Return one token window."""
75
+
76
+ ids = torch.tensor(self.windows[index], dtype=torch.long)
77
+ return {"input_ids": ids, "labels": ids.clone()}
78
+
79
+
80
+ class DeltaDataCollator:
81
+ """Dynamic padding collator for causal language modeling."""
82
+
83
+ def __init__(self, pad_token_id: int = 0) -> None:
84
+ self.pad_token_id = pad_token_id
85
+
86
+ def __call__(self, features: list[dict[str, torch.Tensor]]) -> dict[str, torch.Tensor]:
87
+ """Pad input ids and labels to the longest sample in the batch."""
88
+
89
+ max_len = max(feature["input_ids"].size(0) for feature in features)
90
+ input_ids = torch.full((len(features), max_len), self.pad_token_id, dtype=torch.long)
91
+ labels = torch.full((len(features), max_len), -100, dtype=torch.long)
92
+ attention_mask = torch.zeros((len(features), max_len), dtype=torch.long)
93
+ for row, feature in enumerate(features):
94
+ ids = feature["input_ids"]
95
+ length = ids.size(0)
96
+ input_ids[row, :length] = ids
97
+ labels[row, :length] = feature["labels"]
98
+ pad_positions = ids == self.pad_token_id
99
+ labels[row, :length][pad_positions] = -100
100
+ attention_mask[row, :length] = 1
101
+ return {"input_ids": input_ids, "labels": labels, "attention_mask": attention_mask}
delta/generator.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Text generation and chat helpers for Delta Ultra Mini."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Any, Generator
9
+
10
+ import torch
11
+ from torch.nn import functional as F
12
+
13
+ from delta.identity import identity_response
14
+ from delta.model import DeltaConfig, DeltaModel
15
+ from delta.tokenizer import DEFAULT_SYSTEM_PROMPT, DeltaTokenizer
16
+
17
+ logging.basicConfig(level=os.getenv("DELTA_LOG_LEVEL", "INFO").upper())
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class DeltaGenerator:
22
+ """Autoregressive generator for Delta Ultra Mini."""
23
+
24
+ def __init__(self, model: DeltaModel, tokenizer: DeltaTokenizer, device: str | torch.device | None = None) -> None:
25
+ self.device = torch.device(device or ("cuda" if torch.cuda.is_available() else "cpu"))
26
+ self.model = model.to(self.device)
27
+ self.model.eval()
28
+ self.tokenizer = tokenizer
29
+
30
+ @classmethod
31
+ def from_files(
32
+ cls,
33
+ checkpoint_path: str | Path,
34
+ tokenizer_path: str | Path,
35
+ config_path: str | Path | None = None,
36
+ device: str | torch.device | None = None,
37
+ ) -> "DeltaGenerator":
38
+ """Create a generator from checkpoint, tokenizer, and optional config."""
39
+
40
+ checkpoint = torch.load(checkpoint_path, map_location="cpu")
41
+ config_data = checkpoint.get("config")
42
+ config = DeltaConfig.from_dict(config_data) if config_data else DeltaConfig.from_json(config_path or "configs/ultra_mini.json")
43
+ model = DeltaModel(config)
44
+ state = checkpoint.get("model_state_dict", checkpoint)
45
+ model.load_state_dict(state)
46
+ return cls(model=model, tokenizer=DeltaTokenizer(tokenizer_path), device=device)
47
+
48
+ def _filter_logits(
49
+ self,
50
+ logits: torch.Tensor,
51
+ temperature: float,
52
+ top_k: int,
53
+ top_p: float,
54
+ ) -> torch.Tensor:
55
+ """Apply temperature, top-k, and nucleus filtering."""
56
+
57
+ if temperature <= 0:
58
+ return logits
59
+ logits = logits / temperature
60
+ if top_k > 0:
61
+ values, _ = torch.topk(logits, min(top_k, logits.size(-1)))
62
+ logits = logits.masked_fill(logits < values[:, [-1]], torch.finfo(logits.dtype).min)
63
+ if 0.0 < top_p < 1.0:
64
+ sorted_logits, sorted_indices = torch.sort(logits, descending=True)
65
+ sorted_probs = F.softmax(sorted_logits, dim=-1)
66
+ cumulative = sorted_probs.cumsum(dim=-1)
67
+ remove = cumulative > top_p
68
+ remove[..., 1:] = remove[..., :-1].clone()
69
+ remove[..., 0] = False
70
+ indices_to_remove = remove.scatter(1, sorted_indices, remove)
71
+ logits = logits.masked_fill(indices_to_remove, torch.finfo(logits.dtype).min)
72
+ return logits
73
+
74
+ def _apply_repetition_penalty(
75
+ self,
76
+ logits: torch.Tensor,
77
+ generated: torch.Tensor,
78
+ repetition_penalty: float,
79
+ ) -> torch.Tensor:
80
+ """Penalize tokens that already appeared in the generated sequence."""
81
+
82
+ if repetition_penalty == 1.0:
83
+ return logits
84
+ for token_id in set(generated[0].tolist()):
85
+ score = logits[:, token_id]
86
+ logits[:, token_id] = torch.where(score < 0, score * repetition_penalty, score / repetition_penalty)
87
+ return logits
88
+
89
+ def _clean_completion_text(self, text: str) -> str:
90
+ """Trim leaked prompt/chat markers from decoded assistant text."""
91
+
92
+ cut_markers = ("[SYS]", "[USR]", "[ASS]", "[SEP]", DEFAULT_SYSTEM_PROMPT)
93
+ clean = text
94
+ for marker in cut_markers:
95
+ index = clean.find(marker)
96
+ if index >= 0:
97
+ clean = clean[:index]
98
+ return clean.strip()
99
+
100
+ @torch.inference_mode()
101
+ def generate(
102
+ self,
103
+ input_ids: list[int] | torch.Tensor,
104
+ max_new_tokens: int = 256,
105
+ temperature: float = 0.2,
106
+ top_k: int = 20,
107
+ top_p: float = 0.9,
108
+ repetition_penalty: float = 1.08,
109
+ ) -> list[int]:
110
+ """Generate token ids using KV cache and manual sampling."""
111
+
112
+ ids = torch.tensor([input_ids], dtype=torch.long, device=self.device) if isinstance(input_ids, list) else input_ids.to(self.device)
113
+ if ids.dim() == 1:
114
+ ids = ids.unsqueeze(0)
115
+ ids = ids[:, -(self.model.config.max_seq_len - 1) :]
116
+ max_new_tokens = min(max_new_tokens, self.model.config.max_seq_len - ids.size(1))
117
+ generated = ids.clone()
118
+ past_key_values = None
119
+ next_input = ids
120
+ stop_token_ids = self.tokenizer.chat_stop_token_ids
121
+ for _ in range(max_new_tokens):
122
+ outputs = self.model(next_input, past_key_values=past_key_values, use_cache=True)
123
+ logits = outputs["logits"][:, -1, :]
124
+ past_key_values = outputs["past_key_values"]
125
+ logits = self._apply_repetition_penalty(logits, generated, repetition_penalty)
126
+ if temperature <= 0:
127
+ next_token = torch.argmax(logits, dim=-1, keepdim=True)
128
+ else:
129
+ filtered = self._filter_logits(logits, temperature=temperature, top_k=top_k, top_p=top_p)
130
+ next_token = torch.multinomial(F.softmax(filtered, dim=-1), num_samples=1)
131
+ if int(next_token.item()) in stop_token_ids:
132
+ break
133
+ generated = torch.cat((generated, next_token), dim=1)
134
+ next_input = next_token
135
+ return generated[0].tolist()
136
+
137
+ def chat(self, messages: list[dict[str, Any]], persona: str | None = None, **gen_kwargs: Any) -> str:
138
+ """Generate an assistant response for chat messages."""
139
+
140
+ latest_user = next((str(m.get("content", "")) for m in reversed(messages) if m.get("role") == "user"), "")
141
+ intercepted = identity_response(latest_user)
142
+ if intercepted is not None:
143
+ return intercepted
144
+ prompt = self.tokenizer.format_chat(messages, persona=persona)
145
+ input_ids = self.tokenizer.encode(prompt, add_special_tokens=False)
146
+ input_ids = input_ids[-(self.model.config.max_seq_len - 1) :]
147
+ output_ids = self.generate(input_ids, **gen_kwargs)
148
+ new_ids = output_ids[len(input_ids) :]
149
+ text = self.tokenizer.decode(new_ids, skip_special_tokens=True)
150
+ return self._clean_completion_text(text)
151
+
152
+ def stream_chat(
153
+ self,
154
+ messages: list[dict[str, Any]],
155
+ persona: str | None = None,
156
+ **gen_kwargs: Any,
157
+ ) -> Generator[str, None, None]:
158
+ """Yield generated text token by token."""
159
+
160
+ latest_user = next((str(m.get("content", "")) for m in reversed(messages) if m.get("role") == "user"), "")
161
+ intercepted = identity_response(latest_user)
162
+ if intercepted is not None:
163
+ yield intercepted
164
+ return
165
+
166
+ prompt = self.tokenizer.format_chat(messages, persona=persona)
167
+ input_ids = self.tokenizer.encode(prompt, add_special_tokens=False)
168
+ input_ids = input_ids[-(self.model.config.max_seq_len - 1) :]
169
+ ids = torch.tensor([input_ids], dtype=torch.long, device=self.device)
170
+ generated = ids.clone()
171
+ past_key_values = None
172
+ next_input = ids
173
+ max_new_tokens = int(gen_kwargs.get("max_new_tokens", 256))
174
+ max_new_tokens = min(max_new_tokens, self.model.config.max_seq_len - ids.size(1))
175
+ temperature = float(gen_kwargs.get("temperature", 0.2))
176
+ top_k = int(gen_kwargs.get("top_k", 20))
177
+ top_p = float(gen_kwargs.get("top_p", 0.9))
178
+ repetition_penalty = float(gen_kwargs.get("repetition_penalty", 1.08))
179
+ stop_token_ids = self.tokenizer.chat_stop_token_ids
180
+ with torch.inference_mode():
181
+ for _ in range(max_new_tokens):
182
+ outputs = self.model(next_input, past_key_values=past_key_values, use_cache=True)
183
+ logits = outputs["logits"][:, -1, :]
184
+ past_key_values = outputs["past_key_values"]
185
+ logits = self._apply_repetition_penalty(logits, generated, repetition_penalty)
186
+ if temperature <= 0:
187
+ next_token = torch.argmax(logits, dim=-1, keepdim=True)
188
+ else:
189
+ filtered = self._filter_logits(logits, temperature=temperature, top_k=top_k, top_p=top_p)
190
+ next_token = torch.multinomial(F.softmax(filtered, dim=-1), num_samples=1)
191
+ if int(next_token.item()) in stop_token_ids:
192
+ return
193
+ generated = torch.cat((generated, next_token), dim=1)
194
+ next_input = next_token
195
+ text = self.tokenizer.decode([int(next_token.item())], skip_special_tokens=True)
196
+ if text:
197
+ yield text
delta/identity.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hardcoded identity helpers for Delta Ultra Mini.
2
+
3
+ This module keeps the immutable public identity of the model in one place and
4
+ provides a small interceptor for identity questions. The interceptor is used
5
+ before neural generation so the model never has to infer facts about itself.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import os
12
+ import re
13
+ from typing import Any
14
+
15
+ logging.basicConfig(level=os.getenv("DELTA_LOG_LEVEL", "INFO").upper())
16
+ logger = logging.getLogger(__name__)
17
+
18
+ _IDENTITY: dict[str, str] = {
19
+ "name": "Delta",
20
+ "version": "Ultra Mini",
21
+ "created_by": "Flame Corporation",
22
+ "parameters": "~50 milhões",
23
+ "purpose": "Assistente conversacional inteligente via API",
24
+ }
25
+
26
+ _IDENTITY_PATTERNS: tuple[re.Pattern[str], ...] = tuple(
27
+ re.compile(pattern, flags=re.IGNORECASE)
28
+ for pattern in (
29
+ r"qual.*seu nome",
30
+ r"who are you",
31
+ r"quem.*voc[eê]",
32
+ r"sua vers[aã]o",
33
+ r"your version",
34
+ r"quem.*criou",
35
+ r"who made you",
36
+ r"quantos par[aâ]metros",
37
+ r"how many parameters",
38
+ )
39
+ )
40
+
41
+
42
+ def get_identity() -> dict[str, str]:
43
+ """Return Delta's immutable identity.
44
+
45
+ Returns:
46
+ A copy of the identity dictionary.
47
+ """
48
+
49
+ return dict(_IDENTITY)
50
+
51
+
52
+ def is_identity_question(text: str) -> bool:
53
+ """Check whether a user message asks about Delta's identity.
54
+
55
+ Args:
56
+ text: User-provided text.
57
+
58
+ Returns:
59
+ True when the text matches one of the hardcoded identity regexes.
60
+ """
61
+
62
+ return any(pattern.search(text) for pattern in _IDENTITY_PATTERNS)
63
+
64
+
65
+ def identity_response(text: str) -> str | None:
66
+ """Return a deterministic identity answer when the input asks for it.
67
+
68
+ Args:
69
+ text: User-provided text.
70
+
71
+ Returns:
72
+ A precise identity answer, or None when the input should go to the
73
+ neural model.
74
+ """
75
+
76
+ if not is_identity_question(text):
77
+ return None
78
+
79
+ identity: dict[str, Any] = get_identity()
80
+ lowered = text.lower()
81
+ if re.search(r"quantos par[aâ]metros|how many parameters", lowered):
82
+ return f"Eu sou {identity['name']} {identity['version']} e tenho {identity['parameters']} de parâmetros."
83
+ if re.search(r"sua vers[aã]o|your version", lowered):
84
+ return f"Minha versão é {identity['version']}."
85
+ if re.search(r"quem.*criou|who made you", lowered):
86
+ return f"Fui criada pela {identity['created_by']}."
87
+ if re.search(r"qual.*seu nome", lowered):
88
+ return f"Meu nome é {identity['name']}."
89
+ return (
90
+ f"Eu sou {identity['name']} {identity['version']}, criada pela "
91
+ f"{identity['created_by']}. Meu propósito é ser um "
92
+ f"{identity['purpose'].lower()}."
93
+ )
delta/model.py ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """PyTorch implementation of the Delta Ultra Mini decoder-only Transformer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import math
8
+ import os
9
+ from dataclasses import asdict, dataclass
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ import torch
14
+ from torch import nn
15
+ from torch.nn import functional as F
16
+
17
+ logging.basicConfig(level=os.getenv("DELTA_LOG_LEVEL", "INFO").upper())
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass(slots=True)
22
+ class DeltaConfig:
23
+ """Configuration for Delta Ultra Mini.
24
+
25
+ Attributes:
26
+ vocab_size: Token vocabulary size.
27
+ d_model: Embedding and hidden size.
28
+ n_heads: Number of attention heads.
29
+ n_layers: Number of decoder blocks.
30
+ d_ff: Feed-forward hidden size.
31
+ max_seq_len: Maximum context length.
32
+ dropout: Dropout probability.
33
+ tie_embeddings: Whether output projection shares token embedding weight.
34
+ pad_token_id: Padding token id.
35
+ bos_token_id: Beginning-of-sequence token id.
36
+ eos_token_id: End-of-sequence token id.
37
+ """
38
+
39
+ vocab_size: int = 32000
40
+ d_model: int = 512
41
+ n_heads: int = 8
42
+ n_layers: int = 8
43
+ d_ff: int = 2048
44
+ max_seq_len: int = 512
45
+ dropout: float = 0.1
46
+ tie_embeddings: bool = True
47
+ pad_token_id: int = 0
48
+ bos_token_id: int = 2
49
+ eos_token_id: int = 3
50
+ use_cache: bool = True
51
+
52
+ @classmethod
53
+ def from_dict(cls, data: dict[str, Any]) -> "DeltaConfig":
54
+ """Build a config from a dictionary."""
55
+
56
+ valid = {field for field in cls.__dataclass_fields__}
57
+ return cls(**{key: value for key, value in data.items() if key in valid})
58
+
59
+ @classmethod
60
+ def from_json(cls, path: str | Path) -> "DeltaConfig":
61
+ """Load a config from a JSON file."""
62
+
63
+ with Path(path).open("r", encoding="utf-8") as handle:
64
+ return cls.from_dict(json.load(handle))
65
+
66
+ def to_dict(self) -> dict[str, Any]:
67
+ """Serialize config to a dictionary."""
68
+
69
+ return asdict(self)
70
+
71
+
72
+ class RMSNorm(nn.Module):
73
+ """Root Mean Square normalization without mean-centering."""
74
+
75
+ def __init__(self, dim: int, eps: float = 1e-6) -> None:
76
+ super().__init__()
77
+ self.eps = eps
78
+ self.weight = nn.Parameter(torch.ones(dim))
79
+
80
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
81
+ """Normalize the last dimension of x."""
82
+
83
+ normed = x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps)
84
+ return normed * self.weight
85
+
86
+
87
+ class RotaryEmbedding(nn.Module):
88
+ """Rotary positional embedding cache for attention heads."""
89
+
90
+ def __init__(self, dim: int, max_seq_len: int = 512, base: float = 10000.0) -> None:
91
+ super().__init__()
92
+ inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
93
+ positions = torch.arange(max_seq_len, dtype=torch.float)
94
+ freqs = torch.outer(positions, inv_freq)
95
+ emb = torch.cat((freqs, freqs), dim=-1)
96
+ self.register_buffer("cos_cached", emb.cos()[None, None, :, :], persistent=False)
97
+ self.register_buffer("sin_cached", emb.sin()[None, None, :, :], persistent=False)
98
+
99
+ def forward(self, seq_len: int, offset: int = 0) -> tuple[torch.Tensor, torch.Tensor]:
100
+ """Return cosine and sine caches for a sequence span."""
101
+
102
+ end = offset + seq_len
103
+ return self.cos_cached[:, :, offset:end, :], self.sin_cached[:, :, offset:end, :]
104
+
105
+
106
+ def _rotate_half(x: torch.Tensor) -> torch.Tensor:
107
+ """Rotate pairs of hidden dimensions for RoPE."""
108
+
109
+ x1, x2 = x.chunk(2, dim=-1)
110
+ return torch.cat((-x2, x1), dim=-1)
111
+
112
+
113
+ def apply_rotary(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
114
+ """Apply rotary embedding to q or k tensors."""
115
+
116
+ return (x * cos) + (_rotate_half(x) * sin)
117
+
118
+
119
+ class CausalSelfAttention(nn.Module):
120
+ """Multi-head causal self-attention with optional KV cache."""
121
+
122
+ def __init__(self, config: DeltaConfig) -> None:
123
+ super().__init__()
124
+ if config.d_model % config.n_heads != 0:
125
+ raise ValueError("d_model must be divisible by n_heads")
126
+ self.n_heads = config.n_heads
127
+ self.head_dim = config.d_model // config.n_heads
128
+ self.qkv_proj = nn.Linear(config.d_model, 3 * config.d_model, bias=False)
129
+ self.out_proj = nn.Linear(config.d_model, config.d_model, bias=False)
130
+ self.dropout = nn.Dropout(config.dropout)
131
+ self.rope = RotaryEmbedding(self.head_dim, config.max_seq_len)
132
+ mask = torch.tril(torch.ones(config.max_seq_len, config.max_seq_len, dtype=torch.bool))
133
+ self.register_buffer("causal_mask", mask, persistent=False)
134
+
135
+ def forward(
136
+ self,
137
+ x: torch.Tensor,
138
+ past_key_value: tuple[torch.Tensor, torch.Tensor] | None = None,
139
+ use_cache: bool = False,
140
+ ) -> tuple[torch.Tensor, tuple[torch.Tensor, torch.Tensor] | None]:
141
+ """Run attention.
142
+
143
+ Args:
144
+ x: Input tensor of shape (batch, seq, hidden).
145
+ past_key_value: Optional cached key and value tensors.
146
+ use_cache: Whether to return a new cache.
147
+
148
+ Returns:
149
+ Attention output and optional key/value cache.
150
+ """
151
+
152
+ batch_size, seq_len, hidden_size = x.shape
153
+ qkv = self.qkv_proj(x)
154
+ q, k, v = qkv.split(hidden_size, dim=-1)
155
+ q = q.view(batch_size, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
156
+ k = k.view(batch_size, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
157
+ v = v.view(batch_size, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
158
+
159
+ past_len = 0 if past_key_value is None else past_key_value[0].size(2)
160
+ cos, sin = self.rope(seq_len, offset=past_len)
161
+ q = apply_rotary(q, cos.to(q.device, q.dtype), sin.to(q.device, q.dtype))
162
+ k = apply_rotary(k, cos.to(k.device, k.dtype), sin.to(k.device, k.dtype))
163
+
164
+ if past_key_value is not None:
165
+ past_k, past_v = past_key_value
166
+ k = torch.cat((past_k, k), dim=2)
167
+ v = torch.cat((past_v, v), dim=2)
168
+
169
+ present = (k, v) if use_cache else None
170
+ attn_scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
171
+ total_len = k.size(2)
172
+ if past_len == 0:
173
+ mask = self.causal_mask[:seq_len, :total_len]
174
+ attn_scores = attn_scores.masked_fill(~mask[None, None, :, :], torch.finfo(attn_scores.dtype).min)
175
+ attn_weights = F.softmax(attn_scores, dim=-1)
176
+ attn_weights = self.dropout(attn_weights)
177
+ y = torch.matmul(attn_weights, v)
178
+ y = y.transpose(1, 2).contiguous().view(batch_size, seq_len, hidden_size)
179
+ return self.out_proj(y), present
180
+
181
+
182
+ class SwiGLUFeedForward(nn.Module):
183
+ """SwiGLU feed-forward network."""
184
+
185
+ def __init__(self, config: DeltaConfig) -> None:
186
+ super().__init__()
187
+ self.gate_proj = nn.Linear(config.d_model, config.d_ff, bias=False)
188
+ self.up_proj = nn.Linear(config.d_model, config.d_ff, bias=False)
189
+ self.down_proj = nn.Linear(config.d_ff, config.d_model, bias=False)
190
+ self.dropout = nn.Dropout(config.dropout)
191
+
192
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
193
+ """Apply SwiGLU transformation."""
194
+
195
+ return self.down_proj(self.dropout(F.silu(self.gate_proj(x)) * self.up_proj(x)))
196
+
197
+
198
+ class DeltaDecoderBlock(nn.Module):
199
+ """One Delta decoder block: RMSNorm, attention, RMSNorm, SwiGLU FFN."""
200
+
201
+ def __init__(self, config: DeltaConfig) -> None:
202
+ super().__init__()
203
+ self.attn_norm = RMSNorm(config.d_model)
204
+ self.attn = CausalSelfAttention(config)
205
+ self.ffn_norm = RMSNorm(config.d_model)
206
+ self.ffn = SwiGLUFeedForward(config)
207
+
208
+ def forward(
209
+ self,
210
+ x: torch.Tensor,
211
+ past_key_value: tuple[torch.Tensor, torch.Tensor] | None = None,
212
+ use_cache: bool = False,
213
+ ) -> tuple[torch.Tensor, tuple[torch.Tensor, torch.Tensor] | None]:
214
+ """Run one decoder block."""
215
+
216
+ attn_out, present = self.attn(self.attn_norm(x), past_key_value=past_key_value, use_cache=use_cache)
217
+ x = x + attn_out
218
+ x = x + self.ffn(self.ffn_norm(x))
219
+ return x, present
220
+
221
+
222
+ class DeltaModel(nn.Module):
223
+ """Delta Ultra Mini causal language model."""
224
+
225
+ def __init__(self, config: DeltaConfig | dict[str, Any] | None = None) -> None:
226
+ super().__init__()
227
+ self.config = DeltaConfig.from_dict(config) if isinstance(config, dict) else (config or DeltaConfig())
228
+ self.embed_tokens = nn.Embedding(self.config.vocab_size, self.config.d_model)
229
+ self.drop = nn.Dropout(self.config.dropout)
230
+ self.layers = nn.ModuleList(DeltaDecoderBlock(self.config) for _ in range(self.config.n_layers))
231
+ self.norm = RMSNorm(self.config.d_model)
232
+ self.lm_head = nn.Linear(self.config.d_model, self.config.vocab_size, bias=False)
233
+ if self.config.tie_embeddings:
234
+ self.lm_head.weight = self.embed_tokens.weight
235
+ self.apply(self._init_weights)
236
+ total_params = self.num_parameters()
237
+ logger.info("DeltaModel initialized with %s parameters", f"{total_params:,}")
238
+ print(f"DeltaModel parameters: {total_params:,}")
239
+ if not 48_000_000 <= total_params <= 55_000_000:
240
+ raise ValueError(f"Delta Ultra Mini must have 48M-55M parameters, got {total_params:,}")
241
+
242
+ def _init_weights(self, module: nn.Module) -> None:
243
+ """Initialize weights with GPT-style normal initialization."""
244
+
245
+ if isinstance(module, nn.Linear):
246
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
247
+ if module.bias is not None:
248
+ nn.init.zeros_(module.bias)
249
+ elif isinstance(module, nn.Embedding):
250
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
251
+
252
+ def num_parameters(self, only_trainable: bool = True, exclude_embeddings: bool = False) -> int:
253
+ """Return the number of model parameters.
254
+
255
+ Args:
256
+ only_trainable: Count only parameters with requires_grad.
257
+ exclude_embeddings: Exclude embedding parameters for Trainer FLOPs estimates.
258
+ """
259
+
260
+ total = 0
261
+ for name, parameter in self.named_parameters():
262
+ if only_trainable and not parameter.requires_grad:
263
+ continue
264
+ if exclude_embeddings and "embed_tokens" in name:
265
+ continue
266
+ total += parameter.numel()
267
+ return total
268
+
269
+ def forward(
270
+ self,
271
+ input_ids: torch.Tensor,
272
+ labels: torch.Tensor | None = None,
273
+ past_key_values: list[tuple[torch.Tensor, torch.Tensor]] | None = None,
274
+ use_cache: bool = False,
275
+ **_: Any,
276
+ ) -> dict[str, torch.Tensor | list[tuple[torch.Tensor, torch.Tensor]] | None]:
277
+ """Run causal language modeling forward pass."""
278
+
279
+ if input_ids.size(1) > self.config.max_seq_len:
280
+ input_ids = input_ids[:, -self.config.max_seq_len :]
281
+ if labels is not None:
282
+ labels = labels[:, -self.config.max_seq_len :]
283
+ x = self.drop(self.embed_tokens(input_ids))
284
+ next_cache: list[tuple[torch.Tensor, torch.Tensor]] = []
285
+ for index, layer in enumerate(self.layers):
286
+ past = None if past_key_values is None else past_key_values[index]
287
+ x, present = layer(x, past_key_value=past, use_cache=use_cache)
288
+ if present is not None:
289
+ next_cache.append(present)
290
+ logits = self.lm_head(self.norm(x))
291
+ loss = None
292
+ if labels is not None:
293
+ shift_logits = logits[:, :-1, :].contiguous()
294
+ shift_labels = labels[:, 1:].contiguous()
295
+ loss = F.cross_entropy(
296
+ shift_logits.view(-1, shift_logits.size(-1)),
297
+ shift_labels.view(-1),
298
+ ignore_index=-100,
299
+ )
300
+ return {"loss": loss, "logits": logits, "past_key_values": next_cache if use_cache else None}
301
+
302
+ def save_checkpoint(
303
+ self,
304
+ path: str | Path,
305
+ optimizer: torch.optim.Optimizer | None = None,
306
+ scheduler: Any | None = None,
307
+ step: int = 0,
308
+ ) -> None:
309
+ """Save a full training checkpoint."""
310
+
311
+ checkpoint: dict[str, Any] = {
312
+ "model_state_dict": self.state_dict(),
313
+ "step": step,
314
+ "config": self.config.to_dict(),
315
+ }
316
+ if optimizer is not None:
317
+ checkpoint["optimizer_state_dict"] = optimizer.state_dict()
318
+ if scheduler is not None:
319
+ checkpoint["scheduler_state_dict"] = scheduler.state_dict()
320
+ path = Path(path)
321
+ path.parent.mkdir(parents=True, exist_ok=True)
322
+ torch.save(checkpoint, path)
323
+
324
+ @classmethod
325
+ def load_checkpoint(cls, path: str | Path, map_location: str | torch.device = "cpu") -> "DeltaModel":
326
+ """Load a model from a checkpoint file."""
327
+
328
+ checkpoint = torch.load(path, map_location=map_location)
329
+ model = cls(DeltaConfig.from_dict(checkpoint["config"]))
330
+ model.load_state_dict(checkpoint["model_state_dict"])
331
+ return model
delta/tokenizer.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """BPE tokenizer training and chat formatting for Delta Ultra Mini."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from tokenizers import Tokenizer
11
+ from tokenizers.decoders import ByteLevel as ByteLevelDecoder
12
+ from tokenizers.models import BPE
13
+ from tokenizers.pre_tokenizers import ByteLevel
14
+ from tokenizers.processors import TemplateProcessing
15
+ from tokenizers.trainers import BpeTrainer
16
+
17
+ logging.basicConfig(level=os.getenv("DELTA_LOG_LEVEL", "INFO").upper())
18
+ logger = logging.getLogger(__name__)
19
+
20
+ SPECIAL_TOKENS: list[str] = ["[PAD]", "[UNK]", "[BOS]", "[EOS]", "[SYS]", "[USR]", "[ASS]", "[SEP]"]
21
+ DEFAULT_SYSTEM_PROMPT = (
22
+ "Você é Delta, assistente criada pela Flame Corporation. "
23
+ "Responda de forma clara, útil e amigável."
24
+ )
25
+
26
+
27
+ def train_tokenizer(corpus_files: list[str] | list[Path], output_path: str | Path) -> None:
28
+ """Train a BPE tokenizer from raw text files.
29
+
30
+ Args:
31
+ corpus_files: Paths to corpus files.
32
+ output_path: Destination tokenizer JSON path.
33
+ """
34
+
35
+ tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
36
+ tokenizer.pre_tokenizer = ByteLevel(add_prefix_space=False)
37
+ tokenizer.decoder = ByteLevelDecoder()
38
+ trainer = BpeTrainer(vocab_size=32000, special_tokens=SPECIAL_TOKENS, show_progress=True)
39
+ tokenizer.train([str(path) for path in corpus_files], trainer)
40
+ tokenizer.post_processor = TemplateProcessing(
41
+ single="[BOS] $A [EOS]",
42
+ pair="[BOS] $A [SEP] $B [EOS]",
43
+ special_tokens=[
44
+ ("[BOS]", tokenizer.token_to_id("[BOS]")),
45
+ ("[EOS]", tokenizer.token_to_id("[EOS]")),
46
+ ("[SEP]", tokenizer.token_to_id("[SEP]")),
47
+ ],
48
+ )
49
+ output = Path(output_path)
50
+ output.parent.mkdir(parents=True, exist_ok=True)
51
+ tokenizer.save(str(output))
52
+ logger.info("Tokenizer saved to %s", output)
53
+
54
+
55
+ def load_tokenizer(path: str | Path) -> "DeltaTokenizer":
56
+ """Load a DeltaTokenizer from disk."""
57
+
58
+ return DeltaTokenizer(path)
59
+
60
+
61
+ class DeltaTokenizer:
62
+ """Thin wrapper around HuggingFace tokenizers.Tokenizer."""
63
+
64
+ def __init__(self, path: str | Path) -> None:
65
+ self.path = Path(path)
66
+ self.tokenizer = Tokenizer.from_file(str(self.path))
67
+ self.pad_token_id = self.tokenizer.token_to_id("[PAD]")
68
+ self.unk_token_id = self.tokenizer.token_to_id("[UNK]")
69
+ self.bos_token_id = self.tokenizer.token_to_id("[BOS]")
70
+ self.eos_token_id = self.tokenizer.token_to_id("[EOS]")
71
+ self.sys_token_id = self.tokenizer.token_to_id("[SYS]")
72
+ self.usr_token_id = self.tokenizer.token_to_id("[USR]")
73
+ self.ass_token_id = self.tokenizer.token_to_id("[ASS]")
74
+ self.sep_token_id = self.tokenizer.token_to_id("[SEP]")
75
+ self.special_tokens = set(SPECIAL_TOKENS)
76
+
77
+ @property
78
+ def chat_stop_token_ids(self) -> set[int]:
79
+ """Token ids that should end an assistant completion."""
80
+
81
+ return {
82
+ token_id
83
+ for token_id in (
84
+ self.eos_token_id,
85
+ self.sep_token_id,
86
+ self.sys_token_id,
87
+ self.usr_token_id,
88
+ self.ass_token_id,
89
+ self.bos_token_id,
90
+ self.pad_token_id,
91
+ )
92
+ if token_id is not None
93
+ }
94
+
95
+ def encode(self, text: str, add_special_tokens: bool = True) -> list[int]:
96
+ """Encode a string into token ids."""
97
+
98
+ return self.tokenizer.encode(text, add_special_tokens=add_special_tokens).ids
99
+
100
+ def decode(self, ids: list[int], skip_special_tokens: bool = True) -> str:
101
+ """Decode token ids into text."""
102
+
103
+ return self.tokenizer.decode(ids, skip_special_tokens=skip_special_tokens)
104
+
105
+ def batch_encode(self, texts: list[str], add_special_tokens: bool = True) -> list[list[int]]:
106
+ """Encode a batch of strings."""
107
+
108
+ return [encoding.ids for encoding in self.tokenizer.encode_batch(texts, add_special_tokens=add_special_tokens)]
109
+
110
+ def format_chat(self, messages: list[dict[str, Any]], persona: str | None = None) -> str:
111
+ """Format a multi-turn conversation for Delta.
112
+
113
+ Args:
114
+ messages: Conversation turns with role and content.
115
+ persona: Optional system prompt.
116
+
117
+ Returns:
118
+ Prompt text ending with an assistant tag for continuation.
119
+ """
120
+
121
+ system = persona or DEFAULT_SYSTEM_PROMPT
122
+ parts = [f"[SYS] {system} [SEP]"]
123
+ for message in messages:
124
+ role = str(message.get("role", "")).lower()
125
+ content = str(message.get("content", "")).strip()
126
+ if role == "user":
127
+ parts.append(f"[USR] {content} [SEP]")
128
+ elif role == "assistant":
129
+ parts.append(f"[ASS] {content} [SEP]")
130
+ elif role == "system":
131
+ parts[0] = f"[SYS] {content} [SEP]"
132
+ if not parts[-1].startswith("[ASS]"):
133
+ parts.append("[ASS]")
134
+ else:
135
+ parts.append("[USR] [SEP]")
136
+ parts.append("[ASS]")
137
+ return "\n".join(parts)
delta/trainer.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Training utilities for Delta Ultra Mini using HuggingFace Trainer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import torch
12
+ from transformers import Trainer, TrainingArguments
13
+ from transformers.utils import SAFE_WEIGHTS_NAME
14
+
15
+ from delta.dataset import DeltaDataCollator, DeltaDataset
16
+ from delta.model import DeltaConfig, DeltaModel
17
+ from delta.tokenizer import load_tokenizer
18
+
19
+ logging.basicConfig(level=os.getenv("DELTA_LOG_LEVEL", "INFO").upper())
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class DeltaTrainer(Trainer):
24
+ """Trainer override that saves tied weights safely with safetensors."""
25
+
26
+ def _save(self, output_dir: str | None = None, state_dict: dict[str, torch.Tensor] | None = None) -> None:
27
+ output_dir = output_dir or self.args.output_dir
28
+ os.makedirs(output_dir, exist_ok=True)
29
+
30
+ state_dict = state_dict or self.model.state_dict()
31
+ weights_path = os.path.join(output_dir, SAFE_WEIGHTS_NAME)
32
+
33
+ try:
34
+ import safetensors.torch
35
+
36
+ safetensors.torch.save_file(state_dict, weights_path, metadata={"format": "pt"})
37
+ except RuntimeError as exc:
38
+ # When embeddings are tied, tensors share the same underlying storage.
39
+ # `save_file(state_dict, ...)` refuses that; `save_model(model, ...)` handles it.
40
+ if "share memory" not in str(exc):
41
+ raise
42
+ import safetensors.torch
43
+
44
+ safetensors.torch.save_model(self.model, weights_path, metadata={"format": "pt"})
45
+
46
+
47
+ def train(config_dict: dict[str, Any]) -> Trainer:
48
+ """Train Delta Ultra Mini.
49
+
50
+ Args:
51
+ config_dict: Training and model configuration.
52
+
53
+ Returns:
54
+ The configured Trainer after training.
55
+ """
56
+
57
+ data_path = Path(config_dict["data_path"])
58
+ output_dir = Path(config_dict["output_dir"])
59
+ tokenizer_path = Path(config_dict.get("tokenizer_path", output_dir / "tokenizer.json"))
60
+ epochs = float(config_dict.get("epochs", 1))
61
+ batch_size = int(config_dict.get("batch_size", 2))
62
+ resume_from_checkpoint = config_dict.get("resume_from_checkpoint")
63
+ model_config = DeltaConfig.from_dict(config_dict.get("model", config_dict))
64
+ tokenizer = load_tokenizer(tokenizer_path)
65
+ dataset = DeltaDataset(
66
+ data_path=data_path,
67
+ tokenizer=tokenizer,
68
+ max_seq_len=model_config.max_seq_len,
69
+ stride=int(config_dict.get("stride", 256)),
70
+ )
71
+ model = DeltaModel(model_config)
72
+ training_args = TrainingArguments(
73
+ output_dir=str(output_dir),
74
+ num_train_epochs=epochs,
75
+ per_device_train_batch_size=batch_size,
76
+ learning_rate=float(config_dict.get("learning_rate", 3e-4)),
77
+ weight_decay=float(config_dict.get("weight_decay", 0.01)),
78
+ warmup_steps=int(config_dict.get("warmup_steps", 500)),
79
+ lr_scheduler_type="cosine",
80
+ max_grad_norm=float(config_dict.get("max_grad_norm", 1.0)),
81
+ fp16=torch.cuda.is_available(),
82
+ save_steps=int(config_dict.get("save_steps", 1000)),
83
+ logging_steps=int(config_dict.get("logging_steps", 50)),
84
+ report_to=[],
85
+ remove_unused_columns=False,
86
+ dataloader_pin_memory=torch.cuda.is_available(),
87
+ )
88
+ trainer = DeltaTrainer(
89
+ model=model,
90
+ args=training_args,
91
+ train_dataset=dataset,
92
+ data_collator=DeltaDataCollator(tokenizer.pad_token_id),
93
+ )
94
+ trainer.train(resume_from_checkpoint=resume_from_checkpoint)
95
+ trainer.save_model(str(output_dir))
96
+ output_dir.mkdir(parents=True, exist_ok=True)
97
+ model.save_checkpoint(
98
+ output_dir / "delta_checkpoint.pt",
99
+ optimizer=trainer.optimizer,
100
+ scheduler=trainer.lr_scheduler,
101
+ step=int(trainer.state.global_step),
102
+ )
103
+ with (output_dir / "config.json").open("w", encoding="utf-8") as handle:
104
+ json.dump(model_config.to_dict(), handle, ensure_ascii=False, indent=2)
105
+ logger.info("Saved Delta checkpoint to %s", output_dir / "delta_checkpoint.pt")
106
+ return trainer
delta_checkpoint.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6ff56db700e129603ddb8d75ed058a2aa6d707c59f176e643238913229e2ee10
3
+ size 599439053
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:37ae14167b33ca2943e5a665a2e04acc6d7718694cff13900544e1b1fd42ac4f
3
+ size 199794416
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ torch
2
+ tokenizers
3
+ transformers
4
+ accelerate>=1.1.0
scripts/generate_delta.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Command line local inference for Delta Ultra Mini without the REST API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import logging
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
12
+ if str(PROJECT_ROOT) not in sys.path:
13
+ sys.path.insert(0, str(PROJECT_ROOT))
14
+
15
+ from delta.generator import DeltaGenerator
16
+
17
+ logging.basicConfig(level=os.getenv("DELTA_LOG_LEVEL", "INFO").upper())
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def parse_args() -> argparse.Namespace:
22
+ """Parse command line arguments."""
23
+
24
+ parser = argparse.ArgumentParser(description="Generate text with Delta Ultra Mini.")
25
+ parser.add_argument("--prompt", required=True, help="User prompt.")
26
+ parser.add_argument("--checkpoint_path", default="runs/delta-ultra-mini/delta_checkpoint.pt")
27
+ parser.add_argument("--tokenizer_path", default="tokenizer.json")
28
+ parser.add_argument("--config_path", default="configs/ultra_mini.json")
29
+ parser.add_argument("--max_new_tokens", type=int, default=128)
30
+ parser.add_argument("--temperature", type=float, default=0.2)
31
+ parser.add_argument("--top_k", type=int, default=20)
32
+ parser.add_argument("--top_p", type=float, default=0.9)
33
+ parser.add_argument("--repetition_penalty", type=float, default=1.08)
34
+ return parser.parse_args()
35
+
36
+
37
+ def main() -> None:
38
+ """Load the checkpoint and print one assistant response."""
39
+
40
+ args = parse_args()
41
+ generator = DeltaGenerator.from_files(
42
+ checkpoint_path=args.checkpoint_path,
43
+ tokenizer_path=args.tokenizer_path,
44
+ config_path=args.config_path,
45
+ )
46
+ response = generator.chat(
47
+ [{"role": "user", "content": args.prompt}],
48
+ max_new_tokens=args.max_new_tokens,
49
+ temperature=args.temperature,
50
+ top_k=args.top_k,
51
+ top_p=args.top_p,
52
+ repetition_penalty=args.repetition_penalty,
53
+ )
54
+ print(response)
55
+
56
+
57
+ if __name__ == "__main__":
58
+ main()
scripts/train_delta.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Command line training entrypoint for Delta Ultra Mini."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import logging
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
14
+ if str(PROJECT_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(PROJECT_ROOT))
16
+
17
+ from delta.trainer import train
18
+
19
+ logging.basicConfig(level=os.getenv("DELTA_LOG_LEVEL", "INFO").upper())
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def parse_args() -> argparse.Namespace:
24
+ """Parse training arguments."""
25
+
26
+ parser = argparse.ArgumentParser(description="Train Delta Ultra Mini.")
27
+ parser.add_argument("--data_path", required=True, help="Directory or file containing .txt/.jsonl training data.")
28
+ parser.add_argument("--output_dir", required=True, help="Output directory for checkpoints.")
29
+ parser.add_argument("--epochs", type=float, default=1.0, help="Number of training epochs.")
30
+ parser.add_argument("--batch_size", type=int, default=2, help="Per-device train batch size.")
31
+ parser.add_argument("--resume_from_checkpoint", default=None, help="Checkpoint path or true to resume latest.")
32
+ parser.add_argument("--tokenizer_path", default=None, help="Path to tokenizer.json.")
33
+ parser.add_argument("--config_path", default="configs/ultra_mini.json", help="Model config JSON.")
34
+ return parser.parse_args()
35
+
36
+
37
+ def main() -> None:
38
+ """Run Trainer-based model training."""
39
+
40
+ args = parse_args()
41
+ with Path(args.config_path).open("r", encoding="utf-8") as handle:
42
+ model_config: dict[str, Any] = json.load(handle)
43
+ output_dir = Path(args.output_dir)
44
+ config = {
45
+ "data_path": args.data_path,
46
+ "output_dir": str(output_dir),
47
+ "epochs": args.epochs,
48
+ "batch_size": args.batch_size,
49
+ "resume_from_checkpoint": args.resume_from_checkpoint,
50
+ "tokenizer_path": args.tokenizer_path or str(output_dir / "tokenizer.json"),
51
+ "model": model_config,
52
+ }
53
+ train(config)
54
+ logger.info("Training complete.")
55
+
56
+
57
+ if __name__ == "__main__":
58
+ main()
scripts/train_tokenizer.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Command line tokenizer trainer for Delta Ultra Mini."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import logging
7
+ import os
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
12
+ if str(PROJECT_ROOT) not in sys.path:
13
+ sys.path.insert(0, str(PROJECT_ROOT))
14
+
15
+ from delta.tokenizer import train_tokenizer
16
+
17
+ logging.basicConfig(level=os.getenv("DELTA_LOG_LEVEL", "INFO").upper())
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def parse_args() -> argparse.Namespace:
22
+ """Parse command line arguments."""
23
+
24
+ parser = argparse.ArgumentParser(description="Train Delta BPE tokenizer.")
25
+ parser.add_argument("--corpus_files", nargs="+", required=True, help="Input .txt corpus files.")
26
+ parser.add_argument("--output_path", required=True, help="Output tokenizer JSON path.")
27
+ return parser.parse_args()
28
+
29
+
30
+ def main() -> None:
31
+ """Train and save a tokenizer."""
32
+
33
+ args = parse_args()
34
+ train_tokenizer([Path(path) for path in args.corpus_files], Path(args.output_path))
35
+ logger.info("Tokenizer training complete.")
36
+
37
+
38
+ if __name__ == "__main__":
39
+ main()
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff