Here is a comprehensive, self-contained article based on the original tutorial content, now organized with an FAQ section and a conclusion section.
—
# From Raw Corpus to Fine-Tuned Reasoner: A Complete End-to-End Pipeline with SupraLabs and SmolLM2
In this article, we walk through an end-to-end workflow for building a reasoning-focused language model using the **SupraLabs Reasoning Corpus**. The pipeline covers scalable data streaming, exploratory analysis, dataset filtering, formatting for supervised fine-tuning (SFT), parameter-efficient adaptation of a small language model, and final evaluation. The end goal is to transform a large, multi-model reasoning dataset into a compact, high-quality training set and adapt it into a lightweight reasoning model suitable for deployment in constrained environments.
—
## 1. Environment Setup and Data Streaming
We begin by configuring the environment and installing necessary dependencies. Using Hugging Face’s `datasets` library, we stream the **SupraLabs Reasoning Corpus (4K–5M version)** to avoid downloading the full dataset, which is critical in environments like Google Colab with memory and storage constraints.
“`python
import subprocess, sys
def pip_install(pkgs):
subprocess.check_call([sys.executable, “-m”, “pip”, “install”, “-q”, *pkgs])
subprocess.call([sys.executable, “-m”, “pip”, “uninstall”, “-y”, “-q”, “torchao”])
pip_install([
“datasets>=3.0.0”,
“transformers>=4.46.0”,
“trl>=0.12.0”,
“peft>=0.13.0”,
“accelerate>=1.0.0”,
“bitsandbytes”,
“matplotlib”,
“pandas”,
])
“`
After installing dependencies, we detect compute availability (CPU or GPU), initialize a random seed for reproducibility, and start streaming the dataset:
“`python
import os, re, json, math, random, itertools, warnings
import pandas as pd
import matplotlib.pyplot as plt
import torch
from collections import Counter
from datasets import load_dataset, Dataset
warnings.filterwarnings(“ignore”)
random.seed(42)
torch.manual_seed(42)
DEVICE = “cuda” if torch.cuda.is_available() else “cpu”
print(f”Device: {DEVICE}”)
if DEVICE == “cuda”:
print(f”GPU: {torch.cuda.get_device_name(0)}”)
DATASET_ID = “SupraLabs/reasoning-corpus-4K-5M-v1″
SAMPLE_SIZE = 8_000
print(f”Streaming {DATASET_ID} …”)
stream = load_dataset(DATASET_ID, split=”train”, streaming=True)
stream = stream.shuffle(seed=42, buffer_size=30_000)
rows = list(itertools.islice(stream, SAMPLE_SIZE))
ds = Dataset.from_list(rows)
print(f”Materialized sample: {len(ds):,} rows”)
print(f”Columns: {ds.column_names}”)
“`
At this stage, we materialize a manageable sample and inspect an example row to understand the data structure.
—
## 2. Exploratory Data Analysis
The SupraLabs corpus contains heterogeneous data from many repositories and domains. We convert the sample into a Pandas DataFrame to analyze patterns:
“`python
df = ds.to_pandas()
print(“Top 15 source repos in sample:”)
src_counts = df[“repo_id”].value_counts()
print(src_counts.head(15).to_string())
“`
### Visualizations
We plot:
– Token-length distribution
– Top contributing repositories
– Reasoning-to-answer character ratios
– Correlation between token length and reasoning ratio
These visualizations help identify skewed distributions and ensure the dataset contains sufficient reasoning behavior.
—
## 3. Task Classification
To better understand the types of problems in the corpus, we apply lightweight heuristics to assign each example a task label:
“`python
def tag_task(row):
u = row[“user”].lower()
a = row[“assistant”]
if ““`” in a or re.search(r”b(def |class |import |function|#include)”, a):
return “code”
if re.search(r”(prove|equation|integral|theorem|frac|int|solve for)”, u):
return “math”
if re.search(r”b(patient|diagnosis|symptom|treatment|clinical)b”, u):
return “medical”
if re.search(r”b(which of the following|options?:|(a)|(b))”, u):
return “mcq/logic”
return “general”
df[“task”] = df.apply(tag_task, axis=1)
print(“Heuristic task mix:”)
print(df[“task”].value_counts(normalize=True).round(3).to_string())
“`
This helps ensure the dataset is balanced and appropriate for general reasoning training.
—
## 4. Quality Filtering
Large corpora often contain low-quality or degenerate examples. We define and apply four filtering steps:
1. **Length filtering** – Keep examples within a trainable token window.
2. **Degeneracy filtering** – Remove empty or near-empty thoughts/answers.
3. **Repetition filtering** – Detect and remove loops where the same line repeats too often.
4. **Reasoning ratio filtering** – Ensure examples contain both reasoning and answer text.
“`python
def filter_length(row, min_tok=200, max_tok=3000):
return min_tok <= row["tok_len"] <= max_tok
def filter_degenerate(row):
return len(row["thought_trace"]) > 100 and len(row[“assistant”]) > 20
def filter_repetition(row, max_line_repeat=0.30):
lines = [l.strip() for l in row[“thought_trace”].split(“n”) if l.strip()]
if len(lines) < 5:
return False
most_common = Counter(lines).most_common(1)[0][1]
return (most_common / len(lines)) <= max_line_repeat
def filter_reason_ratio(row, lo=0.15, hi=0.97):
t, a = len(row["thought_trace"]), len(row["assistant"])
r = t / (t + a + 1)
return lo <= r <= hi
n0 = len(ds)
ds_f = ds.filter(filter_length)
ds_f = ds_f.filter(filter_degenerate)
ds_f = ds_f.filter(filter_repetition)
ds_f = ds_f.filter(filter_reason_ratio)
print(f"Filtering: {n0:,} -> {len(ds_f):,} rows ({100 * len(ds_f) / n0:.1f}% retained)”)
“`
This reduces the dataset size but greatly improves quality and training stability.
—
## 5. Formatting for Supervised Fine-Tuning
We convert each example into a chat-based format with explicit reasoning tags. The model is instructed to separate reasoning from the final answer using `
“`python
from transformers import AutoTokenizer, AutoModelForCausalLM
MODEL_ID = “HuggingFaceTB/SmolLM2-135M-Instruct”
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
SYSTEM_PROMPT = (
“You are a careful reasoning assistant. Think step by step inside ”
“
)
def to_chat(row):
return {
“messages”: [
{“role”: “system”, “content”: SYSTEM_PROMPT},
{“role”: “user”, “content”: row[“user”]},
{“role”: “assistant”,
“content”: f”
]
}
train_ds = ds_f.map(to_chat, remove_columns=ds_f.column_names)
train_ds = train_ds.shuffle(seed=42)
N_TRAIN, N_EVAL = 1_500, 100
eval_ds = train_ds.select(range(N_TRAIN, min(N_TRAIN + N_EVAL, len(train_ds))))
train_ds = train_ds.select(range(min(N_TRAIN, len(train_ds))))
print(f”Train: {len(train_ds):,} | Eval: {len(eval_ds):,}”)
“`
—
## 6. Model Setup and Fine-Tuning
We load **SmolLM2-135M-Instruct**, apply LoRA for parameter-efficient tuning, and train using TRL’s `SFTTrainer`.
“`python
from trl import SFTTrainer, SFTConfig
from peft import LoraConfig
import torch
try:
import peft.import_utils as _piu
import peft.tuners.lora.torchao as _plt
_piu.is_torchao_available = lambda: False
_plt.is_torchao_available = lambda: False
except Exception:
pass
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
dtype=torch.bfloat16 if DEVICE == “cuda” else torch.float32,
).to(DEVICE)
peft_config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
bias=”none”,
task_type=”CAUSAL_LM”
)
sft_config = SFTConfig(
output_dir=”smollm2-reasoning-demo”,
max_length=2048,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
num_train_epochs=1,
learning_rate=2e-4,
lr_scheduler_type=”cosine”,
warmup_steps=10,
logging_steps=10,
eval_strategy=”steps”,
eval_steps=50,
save_strategy=”no”,
bf16=(DEVICE == “cuda”),
gradient_checkpointing=True,
report_to=”none”,
)
trainer = SFTTrainer(
model=model,
args=sft_config,
train_dataset=train_ds,
eval_dataset=eval_ds,
peft_config=peft_config,
processing_class=tokenizer,
)
print(“Starting fine-tune (≈10–20 min on a T4 with these settings)…”)
trainer.train()
print(“Done. Final eval loss:”, trainer.evaluate().get(“eval_loss”))
“`
This step adapts the base model to reason in the required format while keeping memory and compute usage low.
—
## 7. Inference and Dataset Export
After fine-tuning, we define a generation helper and test the model on a logic puzzle.
“`python
def generate(question, max_new_tokens=512, temperature=0.7):
msgs = [
{“role”: “system”, “content”: SYSTEM_PROMPT},
{“role”: “user”, “content”: question},
]
prompt = tokenizer.apply_chat_template(
msgs, tokenize=False, add_generation_prompt=True
)
inputs = tokenizer(prompt, return_tensors=”pt”).to(DEVICE)
with torch.no_grad():
out = trainer.model.generate(
**inputs,
max_new_tokens=max_new_tokens,
temperature=temperature,
top_p=0.9,
do_sample=True,
pad_token_id=tokenizer.pad_token_id,
)
text = tokenizer.decode(out[0][inputs[“input_ids”].shape[1]:], skip_special_tokens=True)
m = re.search(r”
if m:
print(“─” * 60, “nTHINKING:n”, m.group(1).strip()[:1500])
print(“─” * 60, “nANSWER:n”, m.group(2).strip())
else:
print(text)
print(“n### TEST 1: logic puzzle”)
generate(“If all bloops are razzies and all razzies are lazzies, are all bloops definitely lazzies? Explain briefly.”)
“`
Finally, we export the curated datasets for reuse:
“`python
train_ds.to_parquet(“reasoning_subset_train.parquet”)
eval_ds.to_parquet(“reasoning_subset_eval.parquet”)
print(“Saved: reasoning_subset_train.parquet / reasoning_subset_eval.parquet”)
“`
—
## FAQ
**What is the SupraLabs Reasoning Corpus?**
It is a large-scale dataset containing multi-turn reasoning traces and answers from various models and domains, hosted on Hugging Face under `SupraLabs/reasoning-corpus-4K-5M-v1`.
**Why stream the dataset instead of downloading it?**
Streaming allows us to work efficiently in memory-constrained environments (like Colab) by loading only a small random subset of the full dataset.
**What filtering criteria are applied?**
We filter by:
– Token length (200–3000 tokens)
– Non-degenerate thoughts and answers
– Moderate repetition in reasoning lines
– Balanced reasoning-to-answer ratio
**Which model is fine-tuned?**
We adapt **SmolLM2-135M-Instruct**, a compact 135M-parameter model, using LoRA for parameter-efficient training.
**How is reasoning structured in the output?**
The model is trained to produce reasoning enclosed in `
—
## Conclusion
This end-to-end pipeline demonstrates how to effectively transform a large, multi-model reasoning corpus into a focused, high-quality training set for a small language model. By combining scalable data access, thorough exploratory analysis, carefully designed quality filters, and parameter-efficient fine-tuning, we show that strong reasoning performance can be achieved without requiring massive compute resources or storing the full dataset locally. The resulting workflow is not only reproducible in a Colab environment but also serves as a robust foundation for future work in curriculum learning, multi-turn reasoning, and scaling to larger student models.
—



