A tuning run inherits every flaw in the data behind it. Skip the cleaning and you pay for it later, in answers that quote page headers, dates that contradict each other, and a model that sounds sure about the wrong revision of a policy. The tuning we did in Part 11 and the InstructLab alignment in Part 12 both assumed a dataset was already sitting there, clean and ready. This part is about earning that assumption.
Where a tuning set comes from
A tuning set is a file of examples that shows the model the behaviour you want. For a QnA tune it is question and answer pairs. For a tool-using agent it is a prompt plus the tool call the model should have made. Those examples come from three places, and most real projects use all three. You extract them from documents and logs you already own. You write a handful by hand to set the standard. Then you synthesize more from that handful when you do not have enough. The order matters, and I put the hand-written and extracted data first for a reason I come back to below.
watsonx.data is where all of this sits. It is an open data lakehouse, which means a single store that holds both structured tables and unstructured files, queried through open formats like Apache Iceberg rather than locked inside one vendor engine. In practice you point watsonx.data at your object storage, register the data, and query it with a fit-for-purpose engine such as Presto or Spark. For a data prep workflow that gives you one governed place to land raw PDFs, clean them into tables, and hand the result to a tune, without copying files across four systems and losing track of which copy is current.
flowchart LR
A[Raw sources] --> B[watsonx.data lakehouse]
B --> C[Clean and normalize]
C --> D[Chunk]
D --> E[Curate and label]
E --> F{Enough real data?}
F -->|Yes| H[Tuning set JSONL]
F -->|No| G[Synthetic Data Generator]
G --> I[Validate]
I --> H
Load raw documents into watsonx.data
Most enterprise knowledge starts as unstructured files, PDFs, Word documents, wiki exports, support tickets. watsonx.data added connectors and document ingestion so you can pull from source systems like FileNet, Box, and shared drives, then turn those files into something a model can learn from. The 2025 releases lean on document processing that extracts text, tables, and layout, and can produce both vectorized embeddings for retrieval and structured derivatives you can query. Embeddings we covered when we built RAG in Part 9; here the same extracted text becomes seed material for tuning rather than retrieval context.
There is a related product worth naming so you do not confuse the two. watsonx.data integration handles the ingestion and preparation pipelines, structured and unstructured, batch and streaming, from one control plane, while watsonx.data itself is the lakehouse those pipelines write into. For document-heavy sources IBM leans on a document-processing partnership with Unstructured, which parses layout, tables, and metadata out of messy files and syncs only new or changed documents so you are not reprocessing the whole corpus on every run. You do not have to use it, a Python script and the cleaning step below will carry a small project, but once you are ingesting thousands of files from several systems, a managed pipeline stops being optional.
How much cleaning is enough?
Enough is when the noise that survives will not teach the model a habit you have to untrain. That is a lower bar than perfect and a higher bar than most people set. Three passes cover the common cases. Remove structural furniture, headers, footers, page numbers, navigation text. Normalize the content, collapse whitespace, fix encoding artifacts, make dates and units consistent. Then deduplicate, because near-identical paragraphs from twenty versions of the same template will dominate a small set and skew the tune toward them.
Chunking sits at the end of cleaning. A chunk is a unit of text small enough for the model to handle and large enough to carry a complete thought. For QnA generation I keep chunks to a single topic, roughly a few hundred tokens, and I never split a table across two chunks. The split you choose here shows up directly in the questions a knowledge builder produces later, so it is worth getting right before you generate anything.
Notice where the time goes. People assume synthetic generation is the hard part because it involves a model. It is the small slice. The expensive, unglamorous work is cleaning and curation, and no generator rescues a set built on bad extraction. That is the whole argument for doing real data first.
Here is a cleaning step you can run today. It takes an exported JSONL of QnA pairs, drops stubs and near-duplicates, and writes a clean file ready for the generator or a tune.
import re
import pandas as pd
# One record per line, e.g. {'question': ..., 'answer': ...}
df = pd.read_json('seed_pairs.jsonl', lines=True)
before = len(df)
def norm(text):
return re.sub(r's+', ' ', str(text)).strip().lower()
df['q_key'] = df['question'].map(norm)
df = df.dropna(subset=['question', 'answer'])
df = df[df['answer'].str.len().between(20, 1200)] # drop stubs and essays
df = df.drop_duplicates(subset='q_key')
after = len(df)
df[['question', 'answer']].to_json('seed_clean.jsonl',
orient='records', lines=True)
print(f'kept {after} of {before} pairs')
kept 182 of 240 pairs and a new seed_clean.jsonl. Failure mode: a KeyError on question means your export used different column names, for example input and output; print df.columns first and rename. A ValueError on read means the file is not valid line-delimited JSON.When do you need synthetic data at all?
Synthetic data is generated by a model to look like your real examples, so you can reach a training set size that hand labelling would never afford. You need it when you have a clear pattern but too few examples, a few dozen good QnA pairs when a tune wants a few thousand. You do not need it when you already have thousands of real, varied examples, or when the pattern itself is still unsettled. Generating ten thousand rows off a confused seed set just gives you ten thousand confused rows, faster.
Two routes exist on watsonx and they solve different problems. InstructLab, from Part 12, drives generation from a taxonomy, a tree of skills and knowledge you curate, and is aimed at teaching a model new capabilities through its LAB method. The Synthetic Data Generator service on watsonx.ai is a more direct tool, you give it seed examples and a data builder, and it produces a dataset in the shape that builder targets. Reach for InstructLab when you are aligning behaviour across a curated skill tree. Reach for the Synthetic Data Generator when you want a specific dataset, tool calls, SQL, or QnA, without standing up a taxonomy.
| Data builder | What it generates | Seed or knowledge input | Good for |
|---|---|---|---|
| Tool calling | Prompts paired with the correct tool or API call | Seed examples as YAML | Teaching a model to call your own tools |
| Text to SQL | A natural-language request plus the equivalent SQL | Seed examples as YAML | Query assistants over a known schema |
| Knowledge | Question and answer pairs from domain documents | PDF or Markdown knowledge base | Domain QnA where the base model is thin |
Generate synthetic data on watsonx.ai
The service works the same way whichever builder you pick. You provide seed data as project assets, YAML for tool calling and text to SQL, PDF or Markdown for the knowledge builder. A foundation model on watsonx.ai generates candidates in the target shape. Validators built into each builder check the output against quality rules, and only what passes lands as a project asset in JSONL. You are charged for the tokens the generating model produces, so a run that targets thousands of examples is a real cost, not a rounding error, and it belongs in the FinOps view we set up in the series guide.
Worked example
Say you have 50 hand-written QnA pairs for one policy area and you want a knowledge dataset for a tune. You ask the builder for 30 candidates per seed, so 1,500 candidates. The validators reject about a fifth for being off-format or ungrounded, leaving roughly 1,200. Deduplication trims near-identical questions down to about 1,080, and you cut to a clean 1,000 for the tune. At an average of 500 generated tokens per pair, the 1,500 candidates cost you about 750,000 generated tokens. That token figure, not the final 1,000, is what shows up on the bill.
The lesson: budget on candidates generated, because you pay for rejects too.
| Stage | Pairs | Note |
|---|---|---|
| Seed pairs | 50 | Hand-written, one policy area |
| Candidates generated | 1,500 | 30 per seed, billed in tokens |
| After validators | 1,200 | About a fifth rejected |
| After dedup | 1,080 | Near-duplicate questions removed |
| Final tuning set | 1,000 | Trimmed to a clean round number |
Run an SDG job in Python
The Synthetic Data Generator exposes an API as well as the studio UI. You need an Admin or Editor role on a project that has a Watson Machine Learning instance attached, an IBM Cloud IAM token, and a task credential. The job takes your seed asset and a data builder, runs generation and validation, and writes JSONL back to the project. The call below is the shape of that request; confirm the exact endpoint path against the watsonx.ai API reference for your region and version before you wire it into a pipeline.
import os
import requests
# Exchange an IBM Cloud API key for a short-lived IAM token
iam = requests.post(
'https://iam.cloud.ibm.com/identity/token',
data={
'grant_type': 'urn:ibm:params:oauth:grant-type:apikey',
'apikey': os.environ['IBM_CLOUD_APIKEY'],
},
).json()['access_token']
# Submit a knowledge-builder synthetic data job
resp = requests.post(
'https://us-south.ml.cloud.ibm.com/ml/v1/synthetic_data_generation'
'?version=2025-06-01',
headers={'Authorization': f'Bearer {iam}'},
json={
'project_id': os.environ['WX_PROJECT_ID'],
'data_builder': 'knowledge',
'seed_asset': 'policy_seed.yaml',
'target_count': 1500,
},
)
print(resp.status_code, resp.json().get('metadata', {}).get('id'))
202 and a job id you can poll for status. Failure mode: 401 means the IAM token expired, they last about an hour, so fetch one per run; 403 means your account lacks the Editor role or a task credential on the project.Validate before you tune
The builders validate format and grounding, but they do not know your business. Two checks earn their keep every time. First, hold out a sample and read it. Not a metric, actual reading, fifty rows, by a person who knows the domain. Synthetic sets fail in ways that look fine to a script and wrong to an expert in one glance. Second, check the distribution. If 40 percent of your generated questions are about one subtopic because that is where the seed leaned, your tune will over-index on it. Rebalance the seed and regenerate rather than shipping a skewed set.
There is a governance angle here too, and it is one reason IBM customers land data in watsonx.data rather than a loose bucket. Every synthetic set has a lineage question attached, which seed produced it, which model generated it, which validators it passed. When an auditor or a regulator asks how a tuned model learned a given behaviour, that trail is the answer, and it is far easier to keep if the data never left a governed store. This is the same factsheet-and-lineage thinking that watsonx.governance formalizes later in the series, applied at the data layer before a tune ever runs.
What a data prep pass costs
Cost lands in two places. Generation tokens are the visible one, and the worked example shows how candidates, not keepers, drive that number. The larger cost is human, the curation and reading that Figure 2 puts at more than half the effort. A team that treats synthetic generation as free and skips the reading spends less on tokens and far more on a tune that has to be redone. Storing and querying the data in watsonx.data is the cheap part by comparison, and the governance you get, lineage and access policy across the same store, is worth more than the compute it costs. The full Resource Unit picture sat in Part 5, and the same accounting applies here.
The prepared set you finish here is exactly the input the next Part assumes. Part 14 moves to training infrastructure and scaling on OpenShift, where this JSONL becomes a job on real hardware. For a view of how another platform frames the same prep step, the Google Cloud series covered data prep and embeddings on Vertex AI, and the pattern of clean-first, generate-second holds on both.
Curate the real data first, synthesize only the gaps
If you take one working rule from this, take the order. Land your real data in watsonx.data, clean it hard, curate a small set you would stake your name on, and only then generate synthetic examples to reach the size a tune needs. Synthetic data is a multiplier on quality you already have, not a substitute for quality you skipped. Teams that invert the order generate first and clean never, and the tune shows it. Start your next prep job by curating 50 examples you fully trust, then let the generator scale that trust, not paper over its absence.
References
- IBM Documentation, Generating unstructured synthetic data
- IBM, AI-ready unstructured and structured data on watsonx.data
- Red Hat, How InstructLab synthetic data generation enhances LLMs


DrJha