Every way to train a model on this dataset

Nine proven recipes with runnable code. The full guide with setup, hardware guidance, and evaluation sits in docs/TRAINING.md — this page is the interactive summary.

Choose your inputWhich file do you need?

Training inputRowsBest used for
qa.jsonl317,907SFT, RAG readers, preference tuning
instructions.jsonl235,395SFT, LoRA/QLoRA, chat models
descriptions.jsonl63,396domain continual pretraining
rag_chunks.jsonl206,906retrieval, embeddings, RAG
anime.jsonl35,766classification, ranking, KG
relationships.jsonl39,226relation extraction, KG
entity_links.jsonl77,681entity linking
Always filter by split. Every record carries train/val/test, assigned per anime work. Never train on val or test rows — they exist to measure you honestly.

SetupEnvironment & hardware

python -m venv .venv
source .venv/bin/activate      # bash / zsh
.venv\Scripts\activate         # Windows PowerShell / CMD
pip install -r requirements.txt
pip install -r examples/training-requirements.txt
Model sizeMethodMinimum GPU
0.5B–3Bfull fine-tune16 GB
3B–7BLoRA8–16 GB
7B–13BQLoRA 4-bit8–12 GB
13B–70BQLoRA 4-bit24–48 GB

Rule of thumb: LoRA/QLoRA gives ~90% of full fine-tuning's benefit at ~10% of the cost. This 35,766-work corpus fine-tunes fast.

Recipe 1Instruction fine-tuning (SFT)

Teach a chat model to answer anime questions using qa.jsonl.

from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, \
    TrainingArguments, DataCollatorForLanguageModeling
from datasets import Dataset
import json

tok = AutoTokenizer.from_pretrained("HuggingFaceTB/SmolLM2-1.7B-Instruct")
tok.pad_token = tok.eos_token

rows = []
for line in open("exports/qa.jsonl", encoding="utf-8"):
    d = json.loads(line)
    if d.get("split") != "train":
        continue
    chat = [{"role": "user", "content": d["question"]},
            {"role": "assistant", "content": d["answer"]}]
    rows.append({"text": tok.apply_chat_template(chat, tokenize=False)})
ds = Dataset.from_list(rows[:50_000])

def tok_fn(b): return tok(b["text"], truncation=True, max_length=1024)
ds = ds.map(tok_fn, batched=True, remove_columns=["text"])

model = AutoModelForCausalLM.from_pretrained("HuggingFaceTB/SmolLM2-1.7B-Instruct")
args = TrainingArguments(output_dir="runs/sft-qa", per_device_train_batch_size=4,
                         num_train_epochs=2, learning_rate=2e-5, report_to="none")
Trainer(model=model, args=args, train_dataset=ds,
        data_collator=DataCollatorForLanguageModeling(tok, mlm=False)).train()

Recipe 2LoRA / QLoRA (small GPUs)

Fine-tune a 7–13B model on 8–16GB VRAM with peft adapters.

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer, SFTConfig

MODEL = "mistralai/Mistral-7B-v0.1"
tok = AutoTokenizer.from_pretrained(MODEL); tok.pad_token = tok.eos_token

quant = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4",
                           bnb_4bit_compute_dtype="float16")   # QLoRA
model = AutoModelForCausalLM.from_pretrained(MODEL, quantization_config=quant,
                                             device_map="auto")
model = get_peft_model(model, LoraConfig(r=16, lora_alpha=32,
        target_modules=["q_proj","k_proj","v_proj","o_proj"], lora_dropout=0.05,
        task_type="CAUSAL_LM"))
model.print_trainable_parameters()   # ~0.5-2% of total

cfg = SFTConfig(output_dir="runs/lora-anime", per_device_train_batch_size=2,
                num_train_epochs=1, learning_rate=2e-4, report_to="none")
SFTTrainer(model=model, args=cfg, train_dataset=ds, processing_class=tok).train()
model.save_pretrained("runs/lora-anime/final")   # adapters only

Recipe 3Preference tuning (DPO)

Train the model to prefer the grounded answer over a weak one.

from trl import DPOTrainer, DPOConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import Dataset
import json

pairs = []
for line in open("exports/qa.jsonl", encoding="utf-8"):
    d = json.loads(line)
    if d.get("split") != "train":
        continue
    pairs.append({
      "prompt":   [{"role": "user", "content": d["question"]}],
      "chosen":   [{"role": "assistant", "content": d["answer"]}],
      "rejected": [{"role": "assistant", "content": "I don't know."}],
    })
    if len(pairs) >= 20_000: break

# Frozen reference: a second copy of the SFT model, never trained.
model = AutoModelForCausalLM.from_pretrained("runs/lora-anime/merged")
ref   = AutoModelForCausalLM.from_pretrained("runs/lora-anime/merged")
tok   = AutoTokenizer.from_pretrained("runs/lora-anime/merged")

cfg = DPOConfig(output_dir="runs/dpo-anime", beta=0.1,
                per_device_train_batch_size=1, learning_rate=1e-5,
                max_length=1024, report_to="none")
DPOTrainer(model=model, ref_model=ref, args=cfg,
           train_dataset=Dataset.from_list(pairs), processing_class=tok).train()

Order matters: SFT first (Recipe 1/2), DPO on top. Logging real base-model generations as rejected gives the best separation.

Recipe 4RAG & retrieval

Index rag_chunks.jsonl with FAISS, then fine-tune a reader on retrieved context.

import json, faiss, numpy as np
from sentence_transformers import SentenceTransformer

embed = SentenceTransformer("all-MiniLM-L6-v2")
texts  = [json.loads(l)["text"] for l in open("exports/rag_chunks.jsonl", encoding="utf-8")]
vecs   = embed.encode(texts, batch_size=64, show_progress_bar=True)

idx    = faiss.IndexFlatIP(vecs.shape[1]); idx.add(np.asarray(vecs, dtype=np.float32))
faiss.write_index(idx, "runs/rag.index")     # + save texts for reuse

# fine-tune a reader with retrieved context prepended to each QA prompt

Recipe 5Domain continued pretraining

Make a base model fluent in anime facts using descriptions.jsonl.

from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, \
    TrainingArguments, DataCollatorForLanguageModeling
from datasets import Dataset
import json

tok = AutoTokenizer.from_pretrained("microsoft/phi-2"); tok.pad_token = tok.eos_token
texts = [json.loads(l)["text"] for l in open("exports/descriptions.jsonl", encoding="utf-8")
         if json.loads(l).get("split") == "train"]
ds  = Dataset.from_list([{"text": t} for t in texts if t])
ds  = ds.map(lambda b: tok(b["text"], truncation=True, max_length=2048), batched=True,
             remove_columns=["text"])

Trainer(model=AutoModelForCausalLM.from_pretrained("microsoft/phi-2"),
        args=TrainingArguments(output_dir="runs/pretrain-anime",
                               per_device_train_batch_size=4, num_train_epochs=1,
                               learning_rate=1e-5, report_to="none"),
        train_dataset=ds,
        data_collator=DataCollatorForLanguageModeling(tok, mlm=False)).train()

Recipe 6Embedding training

Train a domain embedder for anime retrieval with QA pairs as (anchor, positive).

from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader
import json

model = SentenceTransformer("all-MiniLM-L6-v2")
examples = []
for line in open("exports/qa.jsonl", encoding="utf-8"):
    d = json.loads(line)
    if d.get("split") == "train":
        examples.append(InputExample(texts=[d["question"], d["answer"]]))

model.fit(train_objectives=[(DataLoader(examples, batch_size=64, shuffle=True),
                             losses.MultipleNegativesRankingLoss(model))],
          epochs=3, warmup_steps=500, output_path="runs/anime-embedder")

Recipe 7Classification & ranking

Multi-label genre classifier trained directly on anime.jsonl.

from transformers import AutoTokenizer, AutoModelForSequenceClassification, \
    Trainer, TrainingArguments
from datasets import Dataset
import json

GENRES = ["Action","Drama","Sci-Fi","Comedy","Romance","Fantasy","Horror",
          "Slice of Life","Mecha","Sports"]   # extend from genres.jsonl
tok = AutoTokenizer.from_pretrained("distilbert-base-uncased")
rows = []
for line in open("exports/anime.jsonl", encoding="utf-8"):
    d = json.loads(line)
    if d.get("split") != "train":
        continue
    rows.append({"title": d["title_canonical"],
                 "labels": [1.0 if g in (d.get("genres") or []) else 0.0 for g in GENRES]})
ds = Dataset.from_list(rows)
ds = ds.map(lambda b: {"labels": b["labels"],
                       **tok(b["title"], truncation=True, padding="max_length", max_length=64)},
            batched=True)

model = AutoModelForSequenceClassification.from_pretrained(
    "distilbert-base-uncased", num_labels=len(GENRES), problem_type="multi_label_classification")
Trainer(model=model,
        args=TrainingArguments(output_dir="runs/genre-clf", num_train_epochs=3,
                               per_device_train_batch_size=32, learning_rate=3e-5,
                               report_to="none"),
        train_dataset=ds).train()

Recipes 8–9Entity linking & evaluation

Entity linking

Use entity_links.jsonl to train a retriever that maps a source string to a canonical anime_id; score top-k accuracy on test rows. Use relationships.jsonl to train a typed relation classifier over description pairs.

Evaluation

Evaluate only on split == "test". ROUGE/BLEURT for generation, Recall@k/MRR for retrieval, macro-F1 for classification, Spearman ρ for ranking. Full metrics table in TRAINING.md.

LicensingFilter by tier before you publish

Every exported row carries a license_tier, so no join is needed to keep a training run inside one obligation.

TierContributing sourceWhat it permits
cc0_onlyWikidataReuse freely; attribution is courtesy.
permission_requiredAniList, KitsuAPI use is granted, redistribution is not — obtain written permission first.
unresolvedShikimoriNo permission basis located. Do not redistribute.
# train only on rows whose provenance is cleanest
import json
CLEAN = {"cc0_only"}
rows = (json.loads(l) for l in open("exports/instructions.jsonl", encoding="utf-8"))
train = [r for r in rows if r["split"] == "train" and r["license_tier"] in CLEAN]

The tier describes this dataset's provenance obligation, not the copyright status of the underlying work. Full definitions, counts and known gaps: license notes and the licence audit.

Upstream prose is retained with attribution, not relicensed. Plot synopses, character biographies, episode summaries and tag definitions are third-party expression: they carry prose_source and prose_license, are excluded from the project's CC-BY-4.0 grant, and remain subject to the named source's terms. Attribution is not permission. The artifact-by-artifact audit, including what the maintainer does not claim, is in LICENSE_AUDIT.md.

MemoryEfficiency cheat sheet

TechniqueVRAM saving
Gradient accumulationbatches larger than VRAM
gradient_checkpointing=True~30–50%
fp16 / bf16~50%
QLoRA 4-bit~4× vs full
Truncate to 1024 tokenslarge constant

Open the full guide (4000+ words, all code)   Command reference