Skip to main content

Fine-tune a model

Serverless fine-tuning lets you fine-tune open-weight models on your own data without provisioning or managing GPU infrastructure. You upload a training dataset, choose a base model, and configure hyperparameters. Crusoe handles the rest and returns LoRA adapter checkpoints you can serve from your own inference stack.

For API-specific information, refer to the Serverless Fine-Tuning API.

Prerequisites

  • An Intelligence API key generated from the API Keys page in Intelligence Foundry. See Managing API Keys for more information.
  • The OpenAI Python client (pip install openai httpx), if you use the Python examples.
  • A training dataset in JSON Lines (JSONL) or Parquet format, 3 GB or smaller.

1. Authenticate against the API

All API calls require an Intelligence API key and the API base URL.

  1. Export the token and base URL in your shell:

    export API_TOKEN='<your token>'
    export URL='https://api.intelligence.crusoecloud.com'
  2. Construct an OpenAI client pointed at the Crusoe gateway. Every Python example on this page assumes you have this openai_client in scope:

    from openai import OpenAI
    import httpx, os

    openai_client = OpenAI(
    api_key=os.environ["API_TOKEN"],
    base_url=f"{os.environ['URL']}/v1",
    http_client=httpx.Client(proxy=None, trust_env=False),
    )

2. Prepare a dataset

You must upload training data as a single file that's 3GB or smaller with a .jsonl or .parquet extension. Each line must be a JSON object containing a messages array of chat turns.

Dataset format

Each line is a chat conversation. The example below classifies banking customer messages into intent labels:

{
"messages": [
{
"role": "system",
"content": "You are a banking customer service intent classifier. Given a customer message, classify it into exactly one of these intents: activate_my_card, country_support, ..."
},
{
"role": "user",
"content": "Which countries are represented?"
},
{
"role": "assistant",
"content": "country_support"
}
]
}

How loss is computed

Loss is applied to assistant turns and masked on every other role.

Multi-turn reasoning isn't supported yet

Single-turn reasoning and multi-turn non-reasoning both work. Contact support if you need multi-turn reasoning.

Model-specific dataset quirks

Some base models have quirks that affect how you structure assistant turns:

  • llama-3-instruct: When an assistant turn contains both a tool call and content, llama-3-instruct doesn't render the content. The model wasn't pretrained to emit content before a tool call, only one or the other. Keep this format in your fine-tuning data; don't expect the model to emit content alongside a tool call.

  • gemma-4: When an assistant turn contains both a tool call and content, gemma-4 renders the content before the tool call, which often reads unnaturally. To get content after the tool call, split it into a separate assistant turn that follows the tool call.

  • gpt-oss: gpt-oss is pretrained to emit analysis before final. If your dataset focuses only on final content, include an empty thinking field so the model doesn't fight its pretrained format:

    {
    "messages": [
    {
    "role": "system",
    "content": "..."
    },
    {
    "role": "user",
    "content": "Which countries are represented?"
    },
    {
    "role": "assistant",
    "content": "country_support",
    "thinking": ""
    }
    ]
    }

3. Upload a dataset

Upload your prepared .jsonl file with purpose=fine-tune. The response includes a file ID you'll pass in the job creation step:

with open("train.jsonl", "rb") as f:
file_obj = openai_client.files.create(file=f, purpose="fine-tune")

print(file_obj.id) # save this for the job creation step

Manage uploaded datasets

After upload, you can list every dataset in your account, download a dataset's contents, or delete a dataset you no longer need. The examples below show all three operations:

# List
files = openai_client.files.list()

# Download
file_content = openai_client.files.content(file_id)

# Delete
openai_client.files.delete(file_obj.id)

4. Launch a fine-tuning job

A job requires a training dataset and accepts an optional validation dataset. If you don't supply validation data, Crusoe holds out 10% of the training data automatically.

Create a job from either the console or the API. Select the tab for your preferred interface.

  1. Sign in to the Crusoe Console and switch to Intelligence Foundry using the workspace menu in the top-right corner.

  2. Select Fine-Tuning in the left navigation. From this page, you can view all jobs in your project with their current status, base model, and checkpoint count.

  3. Click Start a fine-tuning job and complete the form by adding a base model, training data, optional validation data, and hyperparameters.

  4. Click any row on the Fine-Tuning page to see live training and validation loss, ETA, and the list of saved checkpoints.

Hyperparameter reference

The job creation API exposes more training configuration than the console. The table below explains the parameters that aren't self-explanatory:

ParameterDefaultWhat it does
learning_rate_multiplier1Scales the learning rate. Present mainly for OpenAI compatibility. Unless you need to scale the configured learning rate, leave it empty or set to 1.
checkpoint_steps100How often a checkpoint is saved. Each checkpoint is a usable LoRA adapter, and the best one can be selected after the run. If you use more frequent checkpoints, the job takes longer because each save takes time.
eval_steps_per_epoch4The number of evaluation passes per epoch. Higher values produce finer-grained loss curves but can slow the job significantly when the eval dataset is large.
early_stopping_patiencenullThe number of evaluation calls without improvement before training stops. Set to null (default) to disable early stopping.
lora_variant"lora"Defines which LoRA variant to use: "lora" or "rslora".
lr_scheduler"cosine"How learning rate decays over training: "cosine" (smooth decay to zero), "linear" (straight-line decay), "constant" (no decay), or "constant_with_warmup".
warmup_ratio0.0Fraction of training steps where learning rate linearly ramps from 0 to the target. Prevents early instability when gradients are noisy; typical values are 0.03 to 0.1.
overlong_row_behavior"error"What to do when a sample exceeds the maximum sequence length: "error" (fail the job), "drop" (skip the sample), or "exact" (truncate or pad to fit).

Check job status

After you launch a job, retrieve its current status by job ID or list every job in your project. Use retrieval to poll a specific run; use the list endpoint to find a job ID you don't already have.

# Retrieve a single job
retrieved_job = openai_client.fine_tuning.jobs.retrieve(job.id)
print(retrieved_job.status)

# Paginate through previous jobs
page = openai_client.fine_tuning.jobs.list(limit=20, after=after)
for j in page.data:
print(j.id)

Interpret a successful job response

When a job succeeds, the response includes a result_files array. Each entry is a checkpoint ID you can pass to the download endpoint in the next step. Save an ID from the response before moving on:

{
"id": "ftjob-82d5c693a9c84eb2907309225797e6f3",
"status": "succeeded",
"result_files": [
"adapter:checkpoint-1-40a614f42c3d423ab314bd691691c2c0:e32cddf8-72b6-4ea3-be48-74eece3554fe:..."
]
}

5. (Optional) Download a checkpoint

When a job finishes, every saved checkpoint is exposed as a downloadable LoRA adapter. You can list the checkpoints emitted by a job and download one to disk. See the following examples for reference:

# List the checkpoints emitted by a finished job
checkpoints = openai_client.fine_tuning.jobs.checkpoints.list(job.id).data

# Download a checkpoint's contents
content = openai_client.files.content(checkpoints[-1].id)
with open("checkpoint-best.zip", "wb") as f:
f.write(content.read())

6. Self-serve a deployment for your checkpoint

From the console's Fine-tuning jobs page, select your model to open its job details page. From the details page, find the checkpoint you want to deploy, click the corresponding three-dot menu, and select Deploy. For more information, see Self-Serve Deployments.