| """Dataset and collator for Delta causal language modeling.""" |
|
|
| from __future__ import annotations |
|
|
| import json |
| import logging |
| import os |
| from pathlib import Path |
| from typing import Any |
|
|
| import torch |
| from torch.utils.data import Dataset |
|
|
| from delta.tokenizer import DeltaTokenizer |
|
|
| logging.basicConfig(level=os.getenv("DELTA_LOG_LEVEL", "INFO").upper()) |
| logger = logging.getLogger(__name__) |
|
|
|
|
| def _read_texts(path: Path) -> list[str]: |
| """Read .txt files or .jsonl files containing a text field.""" |
|
|
| files = [path] if path.is_file() else sorted(path.rglob("*")) |
| texts: list[str] = [] |
| for file_path in files: |
| if file_path.suffix.lower() == ".txt": |
| texts.append(file_path.read_text(encoding="utf-8")) |
| elif file_path.suffix.lower() == ".jsonl": |
| with file_path.open("r", encoding="utf-8") as handle: |
| for line in handle: |
| line = line.strip() |
| if not line: |
| continue |
| record = json.loads(line) |
| if "text" in record: |
| texts.append(str(record["text"])) |
| return texts |
|
|
|
|
| class DeltaDataset(Dataset[dict[str, torch.Tensor]]): |
| """Sliding-window token dataset for language modeling.""" |
|
|
| def __init__( |
| self, |
| data_path: str | Path, |
| tokenizer: DeltaTokenizer, |
| max_seq_len: int = 512, |
| stride: int = 256, |
| ) -> None: |
| self.data_path = Path(data_path) |
| self.tokenizer = tokenizer |
| self.max_seq_len = max_seq_len |
| self.stride = stride |
| texts = _read_texts(self.data_path) |
| if not texts: |
| raise ValueError(f"No .txt or .jsonl text records found in {self.data_path}") |
| self.windows: list[list[int]] = [] |
| for text in texts: |
| ids = tokenizer.encode(text, add_special_tokens=True) |
| for start in range(0, max(1, len(ids) - 1), stride): |
| window = ids[start : start + max_seq_len] |
| if len(window) >= 2: |
| self.windows.append(window) |
| if start + max_seq_len >= len(ids): |
| break |
| logger.info("Loaded %s training windows from %s", len(self.windows), self.data_path) |
|
|
| def __len__(self) -> int: |
| """Return the number of windows.""" |
|
|
| return len(self.windows) |
|
|
| def __getitem__(self, index: int) -> dict[str, torch.Tensor]: |
| """Return one token window.""" |
|
|
| ids = torch.tensor(self.windows[index], dtype=torch.long) |
| return {"input_ids": ids, "labels": ids.clone()} |
|
|
|
|
| class DeltaDataCollator: |
| """Dynamic padding collator for causal language modeling.""" |
|
|
| def __init__(self, pad_token_id: int = 0) -> None: |
| self.pad_token_id = pad_token_id |
|
|
| def __call__(self, features: list[dict[str, torch.Tensor]]) -> dict[str, torch.Tensor]: |
| """Pad input ids and labels to the longest sample in the batch.""" |
|
|
| max_len = max(feature["input_ids"].size(0) for feature in features) |
| input_ids = torch.full((len(features), max_len), self.pad_token_id, dtype=torch.long) |
| labels = torch.full((len(features), max_len), -100, dtype=torch.long) |
| attention_mask = torch.zeros((len(features), max_len), dtype=torch.long) |
| for row, feature in enumerate(features): |
| ids = feature["input_ids"] |
| length = ids.size(0) |
| input_ids[row, :length] = ids |
| labels[row, :length] = feature["labels"] |
| pad_positions = ids == self.pad_token_id |
| labels[row, :length][pad_positions] = -100 |
| attention_mask[row, :length] = 1 |
| return {"input_ids": input_ids, "labels": labels, "attention_mask": attention_mask} |
|
|