Model Fine-Tuning
Prepare formatted training datasets, execute fine-tuning jobs, and evaluate specialized models with OpenAI and LoRA.
TL;DR
- Format training examples into JSONL files using standard
messagesstructures. - Upload validated datasets to OpenAI using the
files.create()storage API. - Launch managed model fine-tuning jobs utilizing
fineTuning.jobs.create()calls.
JSONL Training Dataset Formatting
Chat Format Training SampleFormat individual fine-tuning example as standard messages array.
const sql = 'SELECT count(*) FROM users;';
const sample = {
messages: [
{ role: 'system', content: 'Style: terse SQL.' },
{ role: 'user', content: 'Active users count?' },
{ role: 'assistant', content: sql },
],
};
const jsonlLine = JSON.stringify(sample);JSONL File Writer & SplitterSplit dataset into training (90%) and validation (10%) sets.
import fs from 'node:fs';
function writeSplits(samples: any[], dir: string) {
const n = Math.floor(samples.length * 0.9);
const train = samples.slice(0, n);
const val = samples.slice(n);
const toL = (arr: any[]) => {
return arr.map(x => JSON.stringify(x)).join('\n');
};
fs.writeFileSync(`${dir}/train.jsonl`, toL(train));
fs.writeFileSync(`${dir}/val.jsonl`, toL(val));
}Dataset Schema ValidatorValidate presence of required roles before initiating upload.
function validateSample(sample: any) {
const msgs = sample.messages;
if (!Array.isArray(msgs) || msgs.length < 2) {
const err = 'Sample must have 2+ messages';
throw new Error(err);
}
const last = msgs[msgs.length - 1];
if (last.role !== 'assistant') {
const err = 'Final message must be assistant';
throw new Error(err);
}
}OpenAI Fine-Tuning Job Lifecycle
Dataset Upload to File StorageUpload JSONL file to OpenAI with fine-tune purpose.
import OpenAI from 'openai';
import fs from 'node:fs';
const client = new OpenAI();
const file = await client.files.create({
file: fs.createReadStream('train.jsonl'),
purpose: 'fine-tune',
});
console.log(`Uploaded file ID: ${file.id}`);Create Fine-Tuning JobLaunch fine-tuning training job on foundation model.
const job = await client.fineTuning.jobs.create({
training_file: file.id,
model: 'gpt-4o-mini-2024-07-18',
hyperparameters: { n_epochs: 3 },
});
console.log(`Job created: ${job.id}`);Job Progress PollerPoll fine-tuning job status until training resolves.
async function pollJob(jobId: string) {
let status = 'running';
while (
status !== 'succeeded' && status !== 'failed'
) {
await new Promise(r => setTimeout(r, 10000));
const j =
await client.fineTuning.jobs.retrieve(jobId);
status = j.status;
console.log(`Status: ${status}`);
if (status === 'succeeded') {
return j.fine_tuned_model;
}
}
throw new Error('Fine-tuning job failed');
}Inference with Fine-Tuned Models
Invoke Custom ModelDirect chat completion requests to custom model checkpoint.
const userMsg = 'Active users count?';
const res = await client.chat.completions.create({
model: 'ft:gpt-4o-mini-2024-07-18:org::id123',
messages: [{ role: 'user', content: userMsg }],
});
console.log(res.choices[0].message.content);Fine-Tuning Token PricingAccount for premium fine-tuned model inference costs.
// Base gpt-4o-mini: $0.15 / $0.60 per M tokens
// Fine-tuned gpt-4o-mini: $0.30 / $1.20 per M tokens
// Training: $3.00 per M training tokensModel Checkpoint RetirementSafely delete obsolete fine-tuned models from account.
const modelId = 'ft:gpt-4o-mini:org::old_v1';
await client.models.delete(modelId);LoRA & Parameter-Efficient Tuning
LoRA Hyperparameter ConfigurationKey settings for low-rank adapter fine-tuning.
const loraConfig = {
r: 16, // Rank of decomposition matrices
lora_alpha: 32, // Scaling factor
target_modules: ['q_proj', 'v_proj'],
lora_dropout: 0.05,
};Overfitting Detection HookCompare validation loss against training loss curves.
function checkOverfitting(
trainLoss: number, valLoss: number
) {
if (valLoss > trainLoss * 1.5) {
console.warn('Warning: Model is overfitting data');
}
}Format Enforcement BenchmarkVerify fine-tuned model outputs desired format without prompting.
function testFormatAdherence(output: string) {
const isSelect = output.startsWith('SELECT');
return isSelect && output.endsWith(';');
}Tips
- Reserve at least ten percent of your curated examples as an isolated
validation_fileto detect model overfitting early in training runs. - Fine-tune smaller models like
gpt-4o-minito master narrow formatting styles rather than attempting to teach models new general knowledge.
Warnings
- Never use fine-tuning when simple few-shot examples or
RAGretrieval can solve the problem with zero training downtime. - Avoid noisy, contradictory, or uncurated training samples because low quality records severely degrade downstream model
accuracy.
In Practice
Validates training samples, formats JSONL lines, and uploads file to OpenAI fine-tuning storage.
- Assemble curated training examples with system and user turns.
- Validate formatting consistency and serialize to JSONL.
- Write dataset to local disk for training verification.
- Upload validated file buffer to OpenAI fine-tune storage.
import OpenAI from 'openai';
import fs from 'node:fs';
const client = new OpenAI();
async function prepAndUpload(samples: any[]) {
const list = samples.map(s => JSON.stringify(s));
const lines = list.join('\n');
fs.writeFileSync('train.jsonl', lines);
const file = await client.files.create({
file: fs.createReadStream('train.jsonl'),
purpose: 'fine-tune',
});
return file.id;
}
const data = [{
messages: [
{ role: 'user', content: 'Ping' },
{ role: 'assistant', content: 'Pong' },
],
}];
console.log(await prepAndUpload(data));FAQ
Use RAG to inject dynamic knowledge, private facts, or current information. Use fine-tuning to teach a specific tone, style, complex formatting, or specialized syntax that prompts struggle to enforce consistently.
OpenAI recommends starting with 50-100 high-quality, targeted examples to see clear behavioral improvements. Complex styles may require 500-1000 examples.
LoRA freezes pre-trained model weights and injects small trainable rank-decomposition matrices into transformer layers, slashing memory requirements by 80% during fine-tuning.