Current section
Files
Jump to
Current section
Files
examples/custom_loss_training.exs
# Custom Loss Training (Live API)
#
# Demonstrates `forward_backward_custom/4` training with user-defined loss and
# gradients that are sent back to the Tinker backend. Shows compatibility with
# `optim_step/2` after the custom loss pass.
#
# Run with:
# TINKER_API_KEY=your-key mix run examples/custom_loss_training.exs
#
# Optional environment:
# TINKER_BASE_URL - API endpoint (default: production)
# TINKER_BASE_MODEL - Base model id (default: Llama-3.1-8B)
defmodule Tinkex.Examples.CustomLossTraining do
@default_base_url "https://tinker.thinkingmachines.dev/services/tinker-prod"
@default_model "meta-llama/Llama-3.1-8B"
@await_timeout 120_000
alias Tinkex.Error
alias Tinkex.Types.{AdamParams, Datum, ModelInput, TensorData}
def run do
{:ok, _} = Application.ensure_all_started(:tinkex)
api_key = fetch_env!("TINKER_API_KEY")
base_url = System.get_env("TINKER_BASE_URL", @default_base_url)
base_model = System.get_env("TINKER_BASE_MODEL", @default_model)
IO.puts("""
================================================================================
Custom Loss Training (Live)
================================================================================
Base URL : #{base_url}
Base model : #{base_model}
""")
config = Tinkex.Config.new(api_key: api_key, base_url: base_url)
with {:ok, service} <- Tinkex.ServiceClient.start_link(config: config),
{:ok, training} <- create_training_client(service, base_model),
{:ok, datum} <- build_datum(training, base_model),
{:ok, output} <- run_custom_loss(training, datum),
:ok <- maybe_run_optim_step(training) do
display_results(output)
else
{:error, %Error{} = error} ->
halt_with_error("Failed to run custom loss training", error)
{:error, other} ->
halt("Failed to run custom loss training: #{inspect(other)}")
end
end
defp create_training_client(service, base_model) do
IO.puts("Creating training client...")
Tinkex.ServiceClient.create_lora_training_client(
service,
base_model,
lora_config: %Tinkex.Types.LoraConfig{rank: 16},
call_timeout: @await_timeout
)
end
defp build_datum(training, base_model) do
prompt = "Name three planets in the solar system."
IO.puts("Preparing training datum for prompt: #{prompt}")
with {:ok, model_input} <-
ModelInput.from_text(prompt, model_name: base_model, training_client: training) do
tokens = first_chunk_tokens(model_input)
datum =
Datum.new(%{
model_input: model_input,
loss_fn_inputs: %{
target_tokens: TensorData.from_nx(Nx.tensor(tokens, type: {:s, 64})),
weights:
TensorData.from_nx(Nx.tensor(List.duplicate(1.0, length(tokens)), type: {:f, 32}))
}
})
{:ok, datum}
end
end
defp run_custom_loss(training, datum) do
IO.puts("\nRunning forward_backward_custom...")
start_ms = System.monotonic_time(:millisecond)
loss_fn = fn _data, [logprobs] ->
# Simple negative log-likelihood; metrics include perplexity
nll = Nx.negate(Nx.mean(logprobs))
ppl = Nx.exp(nll)
{nll, %{"custom_perplexity" => ppl}}
end
{:ok, task} = Tinkex.TrainingClient.forward_backward_custom(training, [datum], loss_fn)
case Task.await(task, @await_timeout) do
{:ok, output} ->
duration = System.monotonic_time(:millisecond) - start_ms
IO.puts("Custom loss completed in #{duration} ms")
{:ok, output}
{:error, %Error{} = error} ->
{:error, error}
{:error, other} ->
{:error, Error.new(:request_failed, "Custom loss failed: #{inspect(other)}")}
end
end
defp maybe_run_optim_step(training) do
IO.puts("\nRunning optim_step...")
with {:ok, adam} <- AdamParams.new(learning_rate: 1.0e-4),
{:ok, task} <- Tinkex.TrainingClient.optim_step(training, adam),
{:ok, _resp} <- Task.await(task, @await_timeout) do
IO.puts("optim_step succeeded.")
:ok
else
{:error, %Error{} = error} ->
{:error, error}
{:error, other} ->
{:error, Error.new(:request_failed, "optim_step failed: #{inspect(other)}")}
end
end
defp display_results(%Tinkex.Types.ForwardBackwardOutput{} = output) do
IO.puts("\n=== ForwardBackwardOutput ===")
IO.puts("loss_fn_output_type: #{output.loss_fn_output_type}")
IO.puts("metrics: #{inspect(output.metrics)}")
if output.loss_fn_outputs != [] do
IO.puts("loss_fn_outputs (truncated):")
IO.inspect(Enum.take(output.loss_fn_outputs, 1))
end
IO.puts("\nSuccess! Gradients were sent to the backend and optim_step is ready.")
end
defp first_chunk_tokens(%ModelInput{} = model_input), do: ModelInput.to_ints(model_input)
defp fetch_env!(var) do
case System.get_env(var) do
nil -> halt("Set #{var} environment variable to run this example")
value -> value
end
end
defp halt_with_error(prefix, %Error{} = error) do
IO.puts(:stderr, "#{prefix}: #{Error.format(error)}")
if error.data, do: IO.puts(:stderr, "Error data: #{inspect(error.data)}")
System.halt(1)
end
defp halt(message) do
IO.puts(:stderr, message)
System.halt(1)
end
end
Tinkex.Examples.CustomLossTraining.run()