
Pre-processing Pipelines in Python
The Production Foundry. Build a complete, end-to-end Python pipeline to transform raw JSONL into fully tokenized, masked, and GPU-ready tensors.
Pre-processing Pipelines in Python: The Production Foundry
We have learned all the individual pieces:
- Tokenization: Turning text into numbers.
- Special Tokens: Managing BOS/EOS boundaries.
- Truncation: Cutting data to fit the context window.
- Padding & Masking: Formatting batches and focusing model learning.
Now, it’s time to build the Pipeline. In a real-world scenario, you don't do these steps manually for every example. You use a pipeline that processes thousands of rows of data in parallel, saving the results to a "Cache" so you don't have to repeat the work every time you start a training job.
In this final lesson of Module 7, we will build a production-ready pre-processing pipeline using the Hugging Face datasets library.
1. The Pipeline Architecture
A good pipeline is Idempotent (doing it twice gives the same result) and Efficient (it uses all your CPU cores).
graph LR
A["Raw JSONL File"] --> B["Mapped Processing Function"]
B --> C["Tokenization"]
B --> D["Prompt Masking"]
B --> E["Length Validation"]
D & E --> F["Pre-processed Dataset (PyTorch Tensors)"]
F --> G["Disk Cache / Training Loop"]
subgraph "The 'Worker' Core"
B
C
D
end
2. Implementation: The tokenize_and_mask Function
This is the "Brain" of your pipeline. It takes a raw conversation and produces the input_ids, attention_mask, and labels.
from transformers import AutoTokenizer
from datasets import load_dataset
import torch
# 1. Setup
MODEL_NAME = "mistralai/Mistral-7B-v0.1"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
tokenizer.pad_token = tokenizer.eos_token # Standard practice for Mistral/Llama
def preprocess_function(examples):
"""
Standard SFT Preprocessing:
- Combines messages into a single string
- Tokenizes
- Masks the USER portion of the loss
"""
# a. Combine messages into text (Simplified ChatML)
texts = []
for msgs in examples["messages"]:
text = f"USER: {msgs[0]['content']}\nASSISTANT: {msgs[1]['content']}"
texts.append(text)
# b. Tokenize everything
tokenized = tokenizer(
texts,
truncation=True,
max_length=1024,
padding="max_length"
)
# c. Create labels (Clone of input_ids)
labels = []
for i, input_id in enumerate(tokenized["input_ids"]):
label = list(input_id)
# d. FIND the boundary of the Assistant response
# In a real scenario, you'd find the index of the 'ASSISTANT:' token
# For this demo, we assume the first 50 tokens are the prompt
prompt_len = 50
for j in range(prompt_len):
label[j] = -100 # Mask the prompt
labels.append(label)
tokenized["labels"] = labels
return tokenized
# 3. Running the Pipeline at Scale
dataset = load_dataset("json", data_files="my_data.jsonl", split="train")
# batched=True allows processing 1000s of rows in parallel on CPU
processed_dataset = dataset.map(
preprocess_function,
batched=True,
num_proc=4, # Use 4 CPU cores
remove_columns=dataset.column_names # Keep only tensors
)
print(f"Dataset Ready: {len(processed_dataset)} examples")
3. The Performance Bottleneck: num_proc
Fine-tuning is a GPU-intensive task, but Pre-processing is a CPU-intensive task.
If you have 100,000 examples, tokenizing them on a single CPU core will take hours. By using the num_proc argument in the map function, you can distribute the work across all available cores, reducing processing time from hours to minutes.
4. Saving the "Artifacts"
Once your pipeline finishes, you should save the processed data to a binary format like Apache Arrow. This allows the training loop to instantly "memory-map" the data without re-tokenizing.
processed_dataset.save_to_disk("./data/tokenized_v1")
# Next time, just load it:
# from datasets import load_from_disk
# dataset = load_from_disk("./data/tokenized_v1")
Summary and Key Takeaways
- Pipelines automate the complex dance of mapping text to model-ready tensors.
- Batch Processing: Use
batched=Trueto significantly speed up your data preparation. - Tensors Only: The final output of your pipeline should only contain
input_ids,attention_mask, andlabels. No raw text strings should remain in the GPU samples. - Caching: Always save your pre-processed data to disk to avoid repeated computation.
Congratulations! You have completed Module 7. You have moved from "Raw Data" to "GPU-Ready Binary Data."
In Module 8, we will finally start the training: Supervised Fine-Tuning Workflow (End to End), where we look at the hyperparameters that turn these tensors into intelligence.
Reflection Exercise
- Why is it better to tokenize your data before you start the training loop, rather than inside the training loop? (Hint: Think about GPU idle time).
- If you change your tokenizer (e.g., from Llama 2 to Llama 3), do you need to rerun your entire pipeline? Why?
SEO Metadata & Keywords
Focus Keywords: Hugging Face dataset map tutorial, Python fine-tuning pre-processing, tokenize and mask LLM, efficient data preparation for AI, scaling SFT pipelines. Meta Description: Build a production-grade data pipeline for AI fine-tuning. Learn how to use the Hugging Face datasets library to tokenize, mask, and cache your data for high-performance training.