Build / AI /

Model Fine-Tuning

Prepare formatted training datasets, execute fine-tuning jobs, and evaluate specialized models with OpenAI and LoRA.

TL;DR

  1. Format training examples into JSONL files using standard messages structures.
  2. Upload validated datasets to OpenAI using the files.create() storage API.
  3. Launch managed model fine-tuning jobs utilizing fineTuning.jobs.create() calls.

JSONL Training Dataset Formatting

    Chat Format Training Sample

    Format 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 & Splitter

    Split 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 Validator

    Validate 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 Storage

    Upload 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 Job

    Launch 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 Poller

    Poll 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 Model

    Direct 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 Pricing

    Account 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 tokens
    Model Checkpoint Retirement

    Safely 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 Configuration

    Key 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 Hook

    Compare 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 Benchmark

    Verify fine-tuned model outputs desired format without prompting.

    function testFormatAdherence(output: string) {
      const isSelect = output.startsWith('SELECT');
      return isSelect && output.endsWith(';');
    }

Tips

  1. Reserve at least ten percent of your curated examples as an isolated validation_file to detect model overfitting early in training runs.
  2. Fine-tune smaller models like gpt-4o-mini to master narrow formatting styles rather than attempting to teach models new general knowledge.

Warnings

  1. Never use fine-tuning when simple few-shot examples or RAG retrieval can solve the problem with zero training downtime.
  2. Avoid noisy, contradictory, or uncurated training samples because low quality records severely degrade downstream model accuracy.

In Practice

FAQ