Skip to content

Model Tuning

Gunpal Jain edited this page Nov 15, 2026 · 2 revisions

Model Tuning

Tuning adapts a base model to your data. TuningClient (namespace GenerativeAI.Clients) mirrors client.tunings of the google-genai SDK and works on both platforms, but they use different resources:

Gemini API Vertex AI
Resource tunedModels projects/{project}/locations/{location}/tuningJobs
Training data Inline TuningExamples (TextInput / Output) JSONL in Cloud Storage (GcsUri) or a Vertex AI dataset (VertexDatasetResource)
Methods Supervised tuning Supervised fine tuning, preference tuning, distillation, reinforcement tuning (TuningMethod)
TuneAsync, GetAsync, CancelAsync, WaitForCompletionAsync Yes Yes
ListAsync / ListAllAsync No (NotSupportedException) Yes
ValidateRewardAsync No (NotSupportedException) Yes
Hyperparameters EpochCount, LearningRateMultiplier, BatchSize, LearningRate All CreateTuningJobConfig options

On the Gemini API, tuned models are reported as TuningJob objects so the same code works on both platforms.

Prerequisites

  • NuGet package: Google_GenerativeAI
  • Gemini API: an API key. Vertex AI: a project, credentials, and a regional location (for example us-central1); tuning is a regional service. See Authentication.
using GenerativeAI;
using GenerativeAI.Clients;
using GenerativeAI.Types;

// Vertex AI
var vertexAi = new VertexAI(projectId, "us-central1");
TuningClient tuning = vertexAi.CreateTuningClient();

// Gemini API
var googleAi = new GoogleAi(apiKey);
TuningClient geminiTuning = googleAi.CreateTuningClient();

Create a tuning job on Vertex AI

TuningJob job = await tuning.TuneAsync(
    "gemini-2.5-flash",
    new TuningDataset { GcsUri = "gs://my-bucket/tuning/train.jsonl" },
    new CreateTuningJobConfig
    {
        TunedModelDisplayName = "support-bot",
        ValidationDataset = new TuningValidationDataset { GcsUri = "gs://my-bucket/tuning/validation.jsonl" },
        EpochCount = 3,
        LearningRateMultiplier = 1.0,
        AdapterSize = AdapterSize.ADAPTER_SIZE_FOUR,
        Labels = new Dictionary<string, string> { ["team"] = "support" }
    });

Console.WriteLine($"{job.Name} {job.State}");

Pick a base model that supports tuning in your region (see Google's list of tunable models).

Other CreateTuningJobConfig options include Method (TuningMethod.SUPERVISED_FINE_TUNING, PREFERENCE_TUNING, DISTILLATION, REINFORCEMENT_TUNING), Description, ExportLastCheckpointOnly, TuningMode, BatchSize, LearningRate, EvaluationConfig, OutputUri, EncryptionSpec, Beta (preference tuning), BaseTeacherModel / TunedTeacherModelSource (distillation), and RewardConfig / CompositeRewardConfig / SamplesPerPrompt (reinforcement tuning).

To continue tuning from an existing tuned model, pass its resource name (projects/.../models/...) as the base model and set PreTunedModelCheckpointId.

Create a tuned model on the Gemini API

The Gemini API takes inline examples:

TuningJob tuned = await geminiTuning.TuneAsync(
    "models/your-tunable-base-model",
    new TuningDataset
    {
        Examples = new List<TuningExample>
        {
            new() { TextInput = "1", Output = "2" },
            new() { TextInput = "3", Output = "4" },
            new() { TextInput = "-3", Output = "-2" }
        }
    },
    new CreateTuningJobConfig
    {
        TunedModelDisplayName = "number generator",
        EpochCount = 5,
        BatchSize = 4,
        LearningRate = 0.001
    });

Console.WriteLine(tuned.Name);   // "tunedModels/..."; State is JOB_STATE_QUEUED

Only Name and State are set in the create response on the Gemini API. Vertex-only options (GcsUri, VertexDatasetResource, ValidationDataset, Description, AdapterSize, Labels, reward settings and so on) throw NotSupportedException.

Get, wait, cancel

var current = await tuning.GetAsync(job.Name!);
Console.WriteLine(current.State);

// Polls until succeeded, partially succeeded, failed, cancelled or expired.
var finished = await tuning.WaitForCompletionAsync(job.Name!,
    pollInterval: TimeSpan.FromMinutes(1),   // default 30 seconds
    timeout: TimeSpan.FromHours(4));         // default: no limit

if (finished.State == JobState.JOB_STATE_SUCCEEDED)
{
    Console.WriteLine(finished.TunedModel?.Model);     // Vertex AI: the tuned model resource
    Console.WriteLine(finished.TunedModel?.Endpoint);  // Vertex AI: the endpoint to call
}
else
{
    Console.WriteLine(finished.Error?.Message);
}

await tuning.CancelAsync(job.Name!);

On Vertex AI the name can be the full resource name, tuningJobs/{id} or just the ID. On the Gemini API it is the tuned model name (tunedModels/...) or its ID.

List jobs (Vertex AI only)

var page = await tuning.ListAsync(pageSize: 10);
foreach (var j in page.TuningJobs ?? new List<TuningJob>())
    Console.WriteLine($"{j.Name} {j.State} {j.BaseModel}");

await foreach (var j in tuning.ListAllAsync(filter: null))
    Console.WriteLine(j.Name);

Validate a reward (Vertex AI only)

ValidateRewardAsync(sampleResponse, example, singleRewardConfig, compositeRewardConfig, ...) checks a reward configuration against a sample response before you start a reinforcement tuning job.

Error handling

  • API errors are thrown as GenerativeAI.Exceptions.ApiException (ErrorCode, ErrorStatus, ErrorMessage). On Vertex AI, a FAILED_PRECONDITION or a message mentioning the region/location usually means the location doesn't support tuning (for example global); use a regional location.
  • NotSupportedException: an option or method that the current platform doesn't support.
  • ArgumentException: empty base model or job name, or an invalid configuration.
try
{
    var job = await tuning.TuneAsync(baseModel, dataset, config);
}
catch (NotSupportedException ex)
{
    Console.WriteLine($"Not available on this platform: {ex.Message}");
}
catch (ApiException ex)
{
    Console.WriteLine($"{ex.ErrorCode} {ex.ErrorStatus}: {ex.ErrorMessage}");
}

Related pages

Clone this wiki locally