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 input | Rows | Best used for |
|---|---|---|
qa.jsonl | 317,907 | SFT, RAG readers, preference tuning |
instructions.jsonl | 235,395 | SFT, LoRA/QLoRA, chat models |
descriptions.jsonl | 63,396 | domain continual pretraining |
rag_chunks.jsonl | 206,906 | retrieval, embeddings, RAG |
anime.jsonl | 35,766 | classification, ranking, KG |
relationships.jsonl | 39,226 | relation extraction, KG |
entity_links.jsonl | 77,681 | entity linking |
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 size | Method | Minimum GPU |
|---|---|---|
| 0.5B–3B | full fine-tune | 16 GB |
| 3B–7B | LoRA | 8–16 GB |
| 7B–13B | QLoRA 4-bit | 8–12 GB |
| 13B–70B | QLoRA 4-bit | 24–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.
| Tier | Contributing source | What it permits |
|---|---|---|
cc0_only | Wikidata | Reuse freely; attribution is courtesy. |
permission_required | AniList, Kitsu | API use is granted, redistribution is not — obtain written permission first. |
unresolved | Shikimori | No 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
| Technique | VRAM saving |
|---|---|
| Gradient accumulation | batches larger than VRAM |
gradient_checkpointing=True | ~30–50% |
| fp16 / bf16 | ~50% |
| QLoRA 4-bit | ~4× vs full |
| Truncate to 1024 tokens | large constant |
Open the full guide (4000+ words, all code) Command reference