|
|
| """Implementation derived from https://github.com/tloen/alpaca-lora"""
|
|
|
| import json
|
| from dataclasses import dataclass, field
|
| from pathlib import Path
|
|
|
| import torch
|
| from torch.utils.data import DataLoader, random_split
|
|
|
| from litgpt.constants import _REQUESTS_AVAILABLE
|
| from litgpt.data.base import DataModule, SFTDataset, get_sft_collate_fn
|
| from litgpt.prompts import PromptStyle
|
| from litgpt.tokenizer import Tokenizer
|
|
|
| _URL = "https://raw.githubusercontent.com/tloen/alpaca-lora/main/alpaca_data_cleaned_archive.json"
|
|
|
|
|
| @dataclass
|
| class Alpaca(DataModule):
|
| """Alpaca data module for supervised finetuning."""
|
|
|
| mask_prompt: bool = False
|
| """Whether to mask the prompt section from the label (with ``ignore_index``)."""
|
| val_split_fraction: float = 0.03865
|
| """The fraction of the dataset to use for the validation dataset. The rest is used for training."""
|
| prompt_style: str | PromptStyle = "alpaca"
|
| """The style to apply to instruction prompts. See `litgpt.prompts` for a list of available styles."""
|
| ignore_index: int = -100
|
| """The index to use for elements to be ignored in the label."""
|
| seed: int = 42
|
| """The random seed for creating the train/val splits and shuffling the dataset."""
|
| num_workers: int = 4
|
| """How many DataLoader processes to use for loading."""
|
| download_dir: Path = Path("./data/alpaca")
|
| """The directory in which the downloaded dataset gets saved."""
|
| file_url: str = field(repr=False, default=_URL)
|
| """The URL from where to download the dataset."""
|
| file_name: str = field(repr=False, default="alpaca_data_cleaned_archive.json")
|
| """The name of the dataset file to download."""
|
|
|
| tokenizer: Tokenizer | None = field(default=None, init=False, repr=False)
|
| batch_size: int = field(default=1, init=False, repr=False)
|
| max_seq_length: int = field(default=-1, init=False, repr=False)
|
| train_dataset: SFTDataset | None = field(default=None, init=False, repr=False)
|
| test_dataset: SFTDataset | None = field(default=None, init=False, repr=False)
|
|
|
| def __post_init__(self) -> None:
|
| super().__init__()
|
| if isinstance(self.prompt_style, str):
|
| self.prompt_style = PromptStyle.from_name(self.prompt_style)
|
|
|
| def connect(
|
| self, tokenizer: Tokenizer | None = None, batch_size: int = 1, max_seq_length: int | None = None
|
| ) -> None:
|
| self.tokenizer = tokenizer
|
| self.batch_size = batch_size
|
| self.max_seq_length = -1 if max_seq_length is None else max_seq_length
|
|
|
| def prepare_data(self) -> None:
|
| self.download_dir.mkdir(parents=True, exist_ok=True)
|
| download_if_missing(self.download_dir / self.file_name, self.file_url)
|
|
|
| def setup(self, stage: str = "") -> None:
|
| with open(self.download_dir / self.file_name, encoding="utf-8") as file:
|
| data = json.load(file)
|
|
|
|
|
| train_data, test_data = random_split(
|
| data,
|
| [1.0 - self.val_split_fraction, self.val_split_fraction],
|
| generator=torch.Generator().manual_seed(self.seed),
|
| )
|
| train_data, test_data = list(train_data), list(test_data)
|
|
|
| self.train_dataset = SFTDataset(
|
| data=train_data,
|
| tokenizer=self.tokenizer,
|
| prompt_style=self.prompt_style,
|
| max_seq_length=self.max_seq_length,
|
| mask_prompt=self.mask_prompt,
|
| ignore_index=self.ignore_index,
|
| )
|
| self.test_dataset = SFTDataset(
|
| data=test_data,
|
| tokenizer=self.tokenizer,
|
| prompt_style=self.prompt_style,
|
| max_seq_length=self.max_seq_length,
|
| mask_prompt=self.mask_prompt,
|
| ignore_index=self.ignore_index,
|
| )
|
|
|
| def train_dataloader(self) -> DataLoader:
|
| return DataLoader(
|
| self.train_dataset,
|
| batch_size=self.batch_size,
|
| shuffle=True,
|
| generator=torch.Generator().manual_seed(self.seed),
|
| num_workers=self.num_workers,
|
| collate_fn=get_sft_collate_fn(max_seq_length=self.max_seq_length, ignore_index=self.ignore_index),
|
| )
|
|
|
| def val_dataloader(self) -> DataLoader:
|
| return DataLoader(
|
| self.test_dataset,
|
| batch_size=self.batch_size,
|
| shuffle=False,
|
| num_workers=self.num_workers,
|
| collate_fn=get_sft_collate_fn(max_seq_length=self.max_seq_length, ignore_index=self.ignore_index),
|
| )
|
|
|
|
|
| def download_if_missing(file_path: Path, file_url: str, mode: str = "w", stream: bool = False) -> None:
|
| """Downloads the raw json data file and saves it in the given destination."""
|
| if file_path.exists() and file_path.stat().st_size > 0:
|
| return
|
| if not _REQUESTS_AVAILABLE:
|
| raise ModuleNotFoundError(str(_REQUESTS_AVAILABLE))
|
| import requests
|
|
|
| response = requests.get(file_url, stream=stream)
|
| with open(file_path, mode, encoding=None if mode == "wb" else "utf-8") as f:
|
| if stream:
|
|
|
| from tqdm import tqdm
|
|
|
| pbar = tqdm(
|
| desc=str(file_path),
|
| total=int(response.headers.get("content-length", 0)),
|
| unit="iB",
|
| unit_scale=True,
|
| unit_divisor=1024,
|
| )
|
| for data in response.iter_content(chunk_size=1024):
|
| size = f.write(data)
|
| pbar.update(size)
|
| pbar.close()
|
| else:
|
| f.write(response.text)
|
|
|