Distilabel Tutorial: Synthetic Data Generation Framework for LLM Fine-Tuning

# Tutorial Distilabel: Framework Generasi Data Sintetis untuk Fine-Tuning LLM Dalam dunia pengembangan Large Language Model (LLM), kualitas data pelatihan merupakan faktor penentu keberhasilan model....

By Ruby Abdullah · · tutorial
DistilabelSynthetic DataLLMFine-TuningArgilla

Distilabel Tutorial: Synthetic Data Generation Framework for LLM Fine-Tuning

In the world of Large Language Model (LLM) development, training data quality is the single most important factor determining model success. Distilabel is an open-source framework from Argilla that enables you to build pipelines for generating high-quality synthetic data, evaluating models, and preparing datasets for fine-tuning. This tutorial covers how to use Distilabel from installation to building complex production pipelines.

What Is Distilabel?

Distilabel is a Python framework developed by the Argilla team for creating synthetic data generation and LLM evaluation pipelines. It is designed to address the primary challenge in AI development: obtaining large amounts of high-quality training data without the expensive costs of manual annotation.

Key features of Distilabel include:

  • DAG-based pipelines: Directed Acyclic Graph architecture enabling parallel and efficient data processing
  • Multi-provider LLM support: Integration with OpenAI, Anthropic, Google, Hugging Face, vLLM, Ollama, and more
  • Advanced generation techniques: Built-in implementations for Self-Instruct, Evol-Instruct, UltraFeedback, Magpie, and other techniques
  • Scalability: Ability to run pipelines on Ray clusters for large-scale data processing
  • Argilla and Hugging Face Hub integration: Direct data export to Argilla for human validation or to Hugging Face Hub

Installation

Prerequisites

Ensure you have Python 3.9 or newer installed on your system.

Basic Installation

pip install distilabel

Installation with Specific Providers

Distilabel uses an extras system to install LLM provider dependencies:

# For OpenAI

pip install "distilabel[openai]"

For Anthropic

pip install "distilabel[anthropic]"

For vLLM (local)

pip install "distilabel[vllm]"

For Hugging Face Inference Endpoints

pip install "distilabel[hf-inference-endpoints]"

For Ollama

pip install "distilabel[ollama]"

Full installation with all providers

pip install "distilabel[openai,anthropic,vllm,ollama]"

Installation with Ray for Scalability

pip install "distilabel[ray,openai]"

Verify Installation

import distilabel

print(distilabel.version)

Configure API Keys

Before using cloud-based LLM providers, configure your API keys:

export OPENAIAPIKEY="sk-your-api-key"

export ANTHROPICAPIKEY="your-anthropic-key"

export HFTOKEN="hfyour-token"

Basic Usage

Core Concepts

Distilabel is built upon three core concepts:

  • Step: The smallest processing unit in a pipeline (GeneratorStep, Step, GlobalStep)
  • Task: A specialized type of Step that interacts with an LLM
  • Pipeline: The orchestrator that connects Steps in a DAG order
  • Running a Simple LLM Call

    from distilabel.llms import OpenAILLM
    
    

    llm = OpenAILLM(model="gpt-4o-mini")

    llm.load()

    result = llm.generateoutputs(

    inputs=[

    [

    {"role": "user", "content": "Explain what machine learning is in 2 sentences."}

    ]

    ]

    )

    print(result[0][0])

    Your First Pipeline: Simple Text Generation

    from distilabel.llms import OpenAILLM
    

    from distilabel.pipeline import Pipeline

    from distilabel.steps.tasks import TextGeneration

    from distilabel.steps import LoadDataFromDicts

    data = [

    {"instruction": "Explain the concept of neural networks for beginners."},

    {"instruction": "What is the difference between supervised and unsupervised learning?"},

    {"instruction": "How does gradient descent work?"},

    ]

    with Pipeline(name="basic-tutorial") as pipeline:

    loaddata = LoadDataFromDicts(data=data)

    textgen = TextGeneration(

    llm=OpenAILLM(model="gpt-4o-mini"),

    numgenerations=1,

    )

    loaddata >> textgen

    distiset = pipeline.run()

    df = distiset["default"]["train"].topandas()

    print(df[["instruction", "generation"]].tostring())

    Using Multiple LLMs

    One of Distilabel's strengths is the ability to use multiple LLMs within a single pipeline:

    from distilabel.llms import OpenAILLM, AnthropicLLM
    

    from distilabel.pipeline import Pipeline

    from distilabel.steps.tasks import TextGeneration

    from distilabel.steps import LoadDataFromDicts, CombineOutputs

    data = [

    {"instruction": "What is transfer learning?"},

    {"instruction": "Explain the attention mechanism concept."},

    ]

    with Pipeline(name="multi-llm") as pipeline:

    loaddata = LoadDataFromDicts(data=data)

    genopenai = TextGeneration(

    name="genopenai",

    llm=OpenAILLM(model="gpt-4o-mini"),

    numgenerations=1,

    outputmappings={"generation": "generationopenai"},

    )

    genanthropic = TextGeneration(

    name="genanthropic",

    llm=AnthropicLLM(model="claude-sonnet-4-6"),

    numgenerations=1,

    outputmappings={"generation": "generationanthropic"},

    )

    combine = CombineOutputs()

    loaddata >> [genopenai, genanthropic] >> combine

    distiset = pipeline.run()

    Advanced Usage

    Self-Instruct: Instruction Dataset Generation

    Self-Instruct is a technique that allows LLMs to generate new training instructions based on seed instructions. This is extremely useful for expanding fine-tuning datasets:

    from distilabel.llms import OpenAILLM
    

    from distilabel.pipeline import Pipeline

    from distilabel.steps.tasks import SelfInstruct

    from distilabel.steps import LoadDataFromDicts

    seeddata = [

    {

    "input": "You are an AI assistant specialized in data science and machine learning. "

    "Your task is to help users understand technical concepts in an accessible way."

    }

    ]

    with Pipeline(name="self-instruct-pipeline") as pipeline:

    loaddata = LoadDataFromDicts(data=seeddata)

    selfinstruct = SelfInstruct(

    llm=OpenAILLM(model="gpt-4o-mini"),

    numinstructions=5,

    criteriaforquerygeneration=(

    "Instructions should relate to data science, machine learning, or AI. "

    "Instructions should vary in difficulty level. "

    "Instructions should be practical and answerable concretely."

    ),

    )

    loaddata >> selfinstruct

    distiset = pipeline.run()

    df = distiset["default"]["train"].topandas()

    for idx, row in df.iterrows():

    print(f"\nGenerated instructions:")

    for instruction in row["instructions"]:

    print(f" - {instruction}")

    Evol-Instruct: Instruction Evolution

    Evol-Instruct is a technique from the WizardLM paper that evolves simple instructions into more complex and challenging ones:

    from distilabel.llms import OpenAILLM
    

    from distilabel.pipeline import Pipeline

    from distilabel.steps.tasks import EvolInstruct

    from distilabel.steps import LoadDataFromDicts

    data = [

    {"instruction": "Calculate the average of a list."},

    {"instruction": "Create a sorting function."},

    {"instruction": "Explain what an API is."},

    ]

    with Pipeline(name="evol-instruct-pipeline") as pipeline:

    loaddata = LoadDataFromDicts(data=data)

    evolinstruct = EvolInstruct(

    llm=OpenAILLM(model="gpt-4o-mini"),

    numevolutions=3,

    storeevolutions=True,

    includeoriginalinstruction=True,

    )

    loaddata >> evolinstruct

    distiset = pipeline.run()

    df = distiset["default"]["train"].topandas()

    for idx, row in df.iterrows():

    print(f"\nOriginal: {row['instruction']}")

    print(f"Evolved: {row['evolvedinstruction']}")

    UltraFeedback: Response Evaluation and Scoring

    UltraFeedback enables you to evaluate response quality from multiple models, which is useful for creating preference datasets for RLHF/DPO training:

    from distilabel.llms import OpenAILLM
    

    from distilabel.pipeline import Pipeline

    from distilabel.steps.tasks import TextGeneration, UltraFeedback

    from distilabel.steps import LoadDataFromDicts, CombineOutputs, FormatTextGenerationDPO

    data = [

    {"instruction": "Explain the difference between a list and tuple in Python."},

    {"instruction": "How do you optimize a slow SQL query?"},

    ]

    with Pipeline(name="ultrafeedback-pipeline") as pipeline:

    loaddata = LoadDataFromDicts(data=data)

    gen1 = TextGeneration(

    name="genmodela",

    llm=OpenAILLM(model="gpt-4o-mini"),

    outputmappings={"generation": "generationa"},

    )

    gen2 = TextGeneration(

    name="genmodelb",

    llm=OpenAILLM(model="gpt-4o"),

    outputmappings={"generation": "generationb"},

    )

    combine = CombineOutputs()

    feedback = UltraFeedback(

    llm=OpenAILLM(model="gpt-4o"),

    aspect="overall-quality",

    )

    formatdpo = FormatTextGenerationDPO()

    loaddata >> [gen1, gen2] >> combine >> feedback >> formatdpo

    distiset = pipeline.run()

    Magpie: Conversation Data Generation from Pre-trained LLMs

    Magpie is a technique that extracts instruction-following capabilities from aligned LLMs to generate high-quality instruction-response pairs:

    from distilabel.llms import OpenAILLM
    

    from distilabel.pipeline import Pipeline

    from distilabel.steps.tasks import Magpie

    with Pipeline(name="magpie-pipeline") as pipeline:

    magpie = Magpie(

    llm=OpenAILLM(model="gpt-4o-mini"),

    nturns=1,

    numrows=20,

    systemprompt=(

    "You are an AI assistant specialized in Python programming. "

    "Provide detailed answers with code examples."

    ),

    )

    distiset = pipeline.run()

    df = distiset["default"]["train"].topandas()

    for idx, row in df.iterrows():

    print(f"\n{'='*60}")

    print(f"Instruction: {row['instruction'][:100]}...")

    print(f"Response: {row['generation'][:100]}...")

    Creating Custom Tasks

    You can create custom tasks for specific needs:

    from typing import Any
    

    from distilabel.steps.tasks import Task

    from distilabel.steps.tasks.typing import ChatType

    class CodeReviewTask(Task):

    systemprompt: str = (

    "You are a senior code reviewer with extensive experience. "

    "Review the following code and provide constructive feedback."

    )

    @property

    def inputs(self) -> list[str]:

    return ["code"]

    @property

    def outputs(self) -> list[str]:

    return ["review", "score", "suggestions"]

    def formatinput(self, input: dict[str, Any]) -> ChatType:

    return [

    {"role": "system", "content": self.systemprompt},

    {

    "role": "user",

    "content": (

    f"Review the following code:\n\n

    python\n{input['code']}\n``\n\n"

    "Provide output in this format:\n"

    "REVIEW: [detailed review]\n"

    "SCORE: [1-10]\n"

    "SUGGESTIONS: [improvement suggestions]"

    ),

    },

    ]

    def formatoutput(

    self, output: str | None, input: dict[str, Any] | None = None

    ) -> dict[str, Any]:

    if output is None:

    return {"review": None, "score": None, "suggestions": None}

    parts = output.split("\n")

    review = ""

    score = ""

    suggestions = ""

    for part in parts:

    if part.startswith("REVIEW:"):

    review = part.replace("REVIEW:", "").strip()

    elif part.startswith("SCORE:"):

    score = part.replace("SCORE:", "").strip()

    elif part.startswith("SUGGESTIONS:"):

    suggestions = part.replace("SUGGESTIONS:", "").strip()

    return {"review": review, "score": score, "suggestions": suggestions}

    
    

    Structured Generation with JSON Schema

    Distilabel supports structured generation using JSON schemas:

    python

    from distilabel.llms import OpenAILLM

    from distilabel.pipeline import Pipeline

    from distilabel.steps.tasks import TextGeneration

    from distilabel.steps import LoadDataFromDicts

    structuredllm = OpenAILLM(

    model="gpt-4o-mini",

    structuredoutput={

    "format": "json",

    "schema": {

    "type": "object",

    "properties": {

    "topic": {"type": "string", "description": "Main topic"},

    "difficulty": {

    "type": "string",

    "enum": ["beginner", "intermediate", "advanced"],

    },

    "keyconcepts": {

    "type": "array",

    "items": {"type": "string"},

    "description": "Key concepts covered",

    },

    "explanation": {

    "type": "string",

    "description": "Detailed explanation",

    },

    },

    "required": ["topic", "difficulty", "keyconcepts", "explanation"],

    },

    },

    )

    data = [

    {"instruction": "Explain Retrieval-Augmented Generation (RAG)."},

    {"instruction": "What is Model Quantization in the context of LLMs?"},

    ]

    with Pipeline(name="structured-output") as pipeline:

    loaddata = LoadDataFromDicts(data=data)

    textgen = TextGeneration(

    llm=structuredllm,

    numgenerations=1,

    )

    loaddata >> textgen

    distiset = pipeline.run()

    
    

    Pipeline with Caching and Checkpoints

    Distilabel automatically saves cache to avoid reprocessing:

    python

    from distilabel.pipeline import Pipeline

    from distilabel.steps.tasks import TextGeneration

    from distilabel.steps import LoadDataFromDicts

    from distilabel.llms import OpenAILLM

    with Pipeline(

    name="cached-pipeline",

    cachedir="./pipelinecache",

    ) as pipeline:

    loaddata = LoadDataFromDicts(

    data=[{"instruction": f"Create an interesting fact about the number {i}"} for i in range(100)]

    )

    textgen = TextGeneration(

    llm=OpenAILLM(model="gpt-4o-mini"),

    numgenerations=1,

    inputbatchsize=10,

    )

    loaddata >> textgen

    distiset = pipeline.run()

    print(f"Total data: {len(distiset['default']['train'])}")

    
    

    Exporting to Hugging Face Hub and Argilla

    python

    distiset = pipeline.run()

    Push to Hugging Face Hub

    distiset.pushtohub(

    repoid="username/my-synthetic-dataset",

    private=True,

    token="hfyourtoken",

    )

    Or push to Argilla for human validation

    import argilla as rg

    client = rg.Argilla(apiurl="http://localhost:6900", apikey="your-key")

    dataset = rg.Dataset(

    name="synthetic-review",

    workspace="default",

    settings=rg.Settings(

    fields=[

    rg.TextField(name="instruction"),

    rg.TextField(name="generation"),

    ],

    questions=[

    rg.RatingQuestion(name="quality", values=[1, 2, 3, 4, 5]),

    rg.TextQuestion(name="feedback"),

    ],

    ),

    )

    dataset.create()

    records = [

    rg.Record(

    fields={

    "instruction": row["instruction"],

    "generation": row["generation"],

    }

    )

    for , row in df.iterrows()

    ]

    dataset.records.log(records)

    
    

    Best Practices

    1. Design Modular Pipelines

    Separate each processing stage into distinct steps. This makes debugging, reuse, and testing easier:

    python

    with Pipeline(name="modular-pipeline") as pipeline:

    loaddata = LoadDataFromDicts(data=data)

    generate = TextGeneration(llm=llm, name="generate")

    evaluate = UltraFeedback(llm=judgellm, name="evaluate")

    formatoutput = FormatTextGenerationDPO(name="format")

    loaddata >> generate >> evaluate >> formatoutput

    
    

    2. Use Appropriate Batch Sizes

    Adjust inputbatchsize based on API capacity and memory requirements:

    python

    textgen = TextGeneration(

    llm=OpenAILLM(model="gpt-4o-mini"),

    inputbatchsize=50,

    )

    
    

    For large datasets, use smaller batch sizes to avoid rate limiting. For small datasets, larger batch sizes improve throughput.

    3. Implement Error Handling

    Use the retries parameter and handle failures gracefully:

    python

    llm = OpenAILLM(

    model="gpt-4o-mini",

    maxretries=3,

    timeout=120,

    )

    
    

    4. Validate Data Quality

    Always validate output before using it for fine-tuning:

    python

    import json

    df = distiset["default"]["train"].topandas()

    Filter empty responses

    dfclean = df[df["generation"].notna() & (df["generation"] != "")]

    Filter by minimum length

    dfclean = dfclean[dfclean["generation"].str.len() > 50]

    Save cleaned dataset

    print(f"Valid data: {len(dfclean)} out of {len(df)} total")

    
    

    5. Use Specific System Prompts

    Detailed system prompts produce more consistent and higher-quality data:

    python

    textgen = TextGeneration(

    llm=llm,

    systemprompt=(

    "You are an experienced machine learning instructor with expertise "

    "in Python, TensorFlow, and PyTorch. Answer questions in detail, "

    "include runnable code examples, and explain each step in language "

    "that is accessible to beginners."

    ),

    )

    
    

    6. Diversify Data Sources

    Combine multiple techniques for richer datasets:

    python

    with Pipeline(name="diverse-pipeline") as pipeline:

    loadseeds = LoadDataFromDicts(data=seeddata)

    selfinstruct = SelfInstruct(

    name="expand",

    llm=llm,

    numinstructions=10,

    )

    evolinstruct = EvolInstruct(

    name="evolve",

    llm=llm,

    numevolutions=2,

    )

    generate = TextGeneration(

    name="answer",

    llm=llm,

    )

    loadseeds >> selfinstruct >> evolinstruct >> generate

    
    

    7. Monitor Token Usage

    Track token consumption to control costs:

    python

    distiset = pipeline.run()

    Check pipeline statistics

    print(f"Pipeline completed")

    print(f"Total rows processed: {len(distiset['default']['train'])}")

    
    

    8. Use Local LLMs for Fast Iteration

    When developing pipelines, use local models via Ollama to save costs:

    python

    from distilabel.llms import OllamaLLM

    devllm = OllamaLLM(model="llama3.2")

    prodllm = OpenAILLM(model="gpt-4o")

    llm = devllm # Switch to prodllm when ready for production

    ``

    Conclusion

    Distilabel is a powerful framework for building synthetic data generation and LLM evaluation pipelines. With its flexible pipeline-based architecture, multi-provider support, and built-in implementations of advanced techniques like Self-Instruct, Evol-Instruct, and UltraFeedback, Distilabel streamlines the process of creating high-quality datasets for model fine-tuning.

    Key takeaways to remember:

    • Start simple: Build a basic pipeline first, then add complexity incrementally
    • Always validate: Never use synthetic data without quality validation
    • Iterate with local models: Use Ollama or vLLM for development, then switch to production APIs
    • Leverage integrations: Use Argilla for human-in-the-loop validation and Hugging Face Hub for dataset sharing
    • Watch costs: Monitor token usage and use caching to avoid reprocessing

    By incorporating Distilabel into your AI development workflow, you can significantly reduce the cost and time required to prepare high-quality training data while maintaining full control over the data generation process.

    Related Articles

    LitGPT Tutorial: A Framework for Training and Fine-Tuning Large Language Models

    Tutorial LitGPT: Framework untuk Melatih dan Fine-Tuning Large Language Models LitGPT adalah framework open-source dari ...

    Fireworks AI: A Super Fast Inference Platform for Open LLMs and Multimodal Models

    Fireworks AI: Platform Inference Super Cepat buat Open LLM dan Model Multimodal Halo temen-temen, di tutorial kali ini a...

    Together AI: A Complete Guide to Inference and Fine-Tuning Open Source Models with One API

    Together AI: Panduan Lengkap Inference dan Fine-Tuning Model Open Source dengan Satu API Halo temen-temen! Kali ini aku ...

    TRL Tutorial: LLM Post-Training with SFT, DPO, and Reward Modeling

    Post-Training LLM dengan TRL: SFT, Reward Modeling, dan DPO Setelah sebuah base language model selesai dipretraining, mo...