跳至主要内容

Tinker 教程:使用 Thinking Machines Lab 微调大型语言模型

通过 Tinker AI 实现 LoRA 的实践指南,用于在链式思维(CoT)金融数据上训练 Qwen3-8B,并优化其泛化能力。
· 15分钟 阅读

使用 AI 探索

微调大型语言模型通常意味着要处理分布式 GPU 基础设施、管理集群故障,以及调试复杂的训练脚本。Tinker 正是为解决这些问题而来。

由 Mira Murati 的 Thinking Machines Lab 于 2025 年 10 月 1 日发布的 Tinker,是一个训练 API,能够处理所有与基础设施相关的复杂性,同时让您完全掌控算法与数据。它支持用四个核心函数编写简洁的 Python 脚本,并能将其运行在分布式 GPU 上,覆盖 Llama 70B、Qwen 235B 等多种开源模型。

该平台采用 低秩适配(LoRA) 微调以降低成本,支持从监督学习到强化学习的各种场景。普林斯顿、斯坦福和伯克利的研究团队已在使用它开展研究工作。

在本教程中,我将带您完成 Tinker 的安装、API 的基本用法,并基于 Qwen3-8B 训练一个完整的金融问答模型。如果您想通过实操进一步了解微调,建议查看这门关于使用 Llama 3 进行微调的课程。

什么是 Tinker?

Thinking Machines Lab 构建 Tinker,是为了让希望自定义模型学习方式的人无需成为基础设施专家。

谁会使用 Tinker?

该平台面向三类用户: 

  • 探索新训练方法的研究人员
  • 构建需要定制行为的 AI 产品的开发者
  • 希望在没有企业级资源的情况下获得生产级效果的构建者

这三类用户的共同点是:他们清楚自己希望模型完成什么,但标准的微调接口往往无法提供足够的控制力。

例如,学术团队可以用它来测试新颖的算法。化学实验室可能希望在不符合典型指令微调模板的领域推理模式上训练模型。打造金融顾问机器人的初创公司可以让模型遵循特定的输出格式与推理链。

这些用例的共同点在于,它们需要修改训练过程本身,而不仅仅是更换数据集。

Tinker 有何不同?

大多数平台在易用性与灵活性之间二选一。Tinker 的理念是二者并不必然冲突。平台通过四个核心操作为您提供训练的低层访问权,同时自动处理其余一切:

  • 训练循环控制:自行编写损失函数、梯度累积策略或采样模式。
  • 模型更新:精确定义在优化过程中权重应如何变化。
  • 评估:在训练过程的任意时刻生成输出或计算概率。
  • 状态管理:保存和恢复训练,并完全控制需要持久化的内容。

Tinker 通过四个 API 原语暴露这些能力,您将在实操环节学到它们。

之所以可行,是因为训练的主要复杂性来自基础设施,而非算法本身。在笔记本电脑上运行自定义训练循环并不难;而将其扩展到 100 块 GPU,并具备故障恢复与资源调度能力则很难。Tinker 负责后者,让您专注于前者。

Tinker 的定位

AI 工具生态大致分为三层:提供原始 GPU 的云计算服务商、运行预定义工作流的托管平台、以及帮助您从零构建训练系统的框架。Tinker 介于托管平台和框架之间。与 Hugging Face AutoTrain 这类平台相比,您能获得更多控制权;与在 Google Cloud Platform(GCP)上自建集群相比,所需的基础设施工作又更少。

Tinker 定价与额度

Tinker 采用透明的按 token 计费方式,费用随模型大小与操作类型而变化。训练 Qwen3-8B 的成本为每百万 token 0.40 美元。好消息是:新用户在通过候补名单审核后可获得 150 美元的代金券,足以覆盖包括本教程在内的多次实验性训练。

Tinker 定价

来源:Thinking Machines Lab

Tinker 示例:为金融问答微调 Qwen3-8B

本节将带您通过 Tinker 的 API 构建完整的微调流程。我们将用 LoRA 在金融问答数据上微调 Qwen3-8B,在掌握四个核心 API 原语的同时,了解生产实践中的最佳做法。

想要了解该模型的更详细概览,请务必查看这篇介绍 Qwen3 的文章。

重要说明:Tinker 目前处于测试阶段,通过候补名单发放访问权限。如果您已有访问权限,可以跟着操作并运行代码;如果仍在候补名单中,也可以先阅读,了解使用 Tinker 微调有多么直接顺畅。一旦获得访问权限,您即可立刻上手。

训练结果与检查点选择

我们先看看训练结果。理解过程有助于您在自己运行微调任务时识别这些模式。

我们使用 LoRA(秩 32)在 FinCoT 数据集(链式思维的金融推理数据集)上微调了 Qwen3-8B。过滤掉长度超过 10,000 个 token 的序列后,我们得到 7,244 个训练样本和 500 个验证样本。

训练跨越 4 个 epoch,共运行 904 次迭代,大约耗时 3 小时。在此期间,Tinker 负责全部分布式 GPU 的协调。

一个 epoch 是对整个训练数据集的一次完整遍历。由于每个 epoch 有 226 个批次(7,244 个样本除以 32 的批量大小),四个 epoch 共计 904 次迭代。

训练结果分析

下图跟踪了三个指标:训练损失(蓝色)、验证损失(红色)和学习率(绿色)。

Tinker 训练进展

早期训练阶段(迭代 0-400)表现健康:训练和验证损失同步快速下降,从 1.4 以上降至 0.8 以下,二者相差约 0.1。

这一阶段表明模型正在有效学习数据中的可泛化模式。学习率(绿线)在前 200 次迭代中平滑上升,预热过程稳定了训练。

在迭代 400-600 左右,情况发生变化。训练损失继续下降,但验证损失停滞在 0.75-0.8 左右,差距扩大到 0.13,显示模型开始过拟合。到 600-900 次迭代时,分化愈发明显:训练损失骤降至 0.39,而验证损失上升到 0.8,说明模型从学习推理模式转向记忆样本。

在推理类模型的微调中,这种模式很常见。如果您对推理能力在训练过程中如何演化感兴趣,可以查看这篇关于微调 DeepSeek R1 的教程,探讨类似的动态。

在 checkpoint-400 保存的模型具有最佳泛化能力:训练损失 0.68、验证损失 0.73、差距仅 0.05。尽管训练损失高于最终检查点,它在处理新问题时表现更好。最低的训练损失很少意味着最优模型,这正是验证指标重要的原因。

我们稍后将通过 LLM 评审对比 checkpoint-400 与基座模型来验证这一点。现在,让我们一步步搭建这个微调流程。

步骤 1:安装 Tinker 并设置环境

说明:以下步骤拆解自一个约 300 行的脚本。由于讲解与代码片段交织,建议您在新标签页中打开完整脚本,便于通览全貌。

您需要 Python 3.11 或更高版本,以及稳定的网络连接。由于 Tinker 在其服务器上进行 GPU 训练,良好的网络连接比强大的本地电脑更重要。

首先登录 Tinker 控制台并创建一个 API 密钥。将该密钥保存在项目目录的 .env 文件中:

TINKER_API_KEY=your_key_here

设置完成后,Tinker 控制台将成为您监控训练任务和管理检查点的中心枢纽。

Tinker 训练运行

Tinker 控制台会跟踪所有训练运行的关键信息:唯一运行 ID、基座模型、LoRA 秩、最近请求时间等。您可以在此界面中搜索、筛选并管理多个实验。

安装所需软件包。Tinker 有同行依赖,不会自动安装,请显式指定:

pip install tinker transformers torch datasets python-dotenv numpy

transformers 与 torch 为 Tinker 内部使用的分词与张量运算提供支持。datasets 用于从 Hugging Face 加载训练数据。

现在设置导入与重试辅助函数。在远程服务器上训练时,您可能会遇到临时连接错误,但这并不一定表示出现实际问题;训练任务仍在 Tinker 的基础设施上正常运行。这个重试封装可自动处理这些瞬时故障:

import time
import numpy as np
from dotenv import load_dotenv
from datasets import load_dataset
import tinker
from tinker import types

def with_retry(future, max_attempts=3, delay=5):
    """Simple retry logic for API futures"""
    for attempt in range(max_attempts):
        try:
            return future.result()
        except Exception as e:
            if attempt == max_attempts - 1:
                raise
            time.sleep(delay)

该函数接收一个来自 Tinker API 的 future 对象(表示尚未完成的操作),最多尝试三次获取结果,每次间隔五秒。大多数临时性故障都会在这个时间窗内恢复。

步骤 2:初始化 ServiceClient 并加载数据集

ServiceClient 是进入 Tinker 的入口。它用于发现可用模型并处理认证:

load_dotenv()
service_client = tinker.ServiceClient()

之后您不会频繁使用它。它主要用于为特定模型创建训练与采样客户端。

现在加载 FinCoT 数据集。它包含金融问题、逐步推理链以及最终答案。与简单的问答数据集不同,FinCoT 让模型在给出答案前展示其推理过程,这对金融顾问类应用尤为重要,因为用户需要理解建议背后的论证。

dataset = load_dataset("TheFinAI/FinCoT")

Tinker 数据集

该数据集包含带完整推理链的 SFT(监督微调)划分,以及用于强化学习的 RL 划分。使用 SFT 划分进行训练,并从 RL 划分中抽样作为验证,确保模型能泛化到不同示例。

train_data_raw = dataset["SFT"]  # 7,686 examples with reasoning chains
val_data_raw = dataset["RL"].shuffle(seed=42).select(range(500))  # 500 validation examples

print(f"Loaded {len(train_data_raw)} training examples")
print(f"Loaded {len(val_data_raw)} validation examples")

步骤 3:创建 LoRA 训练客户端

现在为 Qwen3-8B 创建一个 LoRA 训练客户端:

training_client = service_client.create_lora_training_client(
    base_model="Qwen/Qwen3-8B",
    rank=32
)

逐条解释如下:

  • Qwen/Qwen3-8B:该模型具有 80 亿参数,足以胜任真实任务,同时训练仍较为高效,适合作为学习的平衡点。您可以在 Tinker 的模型列表中查看所有可用模型。
  • LoRA(低秩适配):LoRA 并非更新全部 80 亿参数,而是训练一些小型“适配器”层,以修改基座模型的行为。可以把它想象成给相机加装定制镜头,而不是重造整台相机。基座模型在 Tinker 服务器上保持冻结;您只训练这些小适配器。
  • rank=32:该参数控制 LoRA 适配器的可训练参数量。对于 5,000-10,000 个样本的数据集,秩 32 通常效果良好。更大的数据集可能需要 rank=64 或 rank=128 来获得更强的适配能力。

若想深入了解 LoRA 的数学原理及如何调参,请参阅 Tinker 的 LoRA 入门或 DataCamp 的LoRA 全面指南。

语言模型并不直接处理“词”,而是处理“token”,即文本片段。分词器会将文本拆分为这些片段。例如,“financial” 可能是一个 token,而 “understanding” 可能会被拆成 “under” 和 “standing”。

分词器将文本转换为模型可处理的数字(token ID)。通过从训练客户端获取分词器,您能确保使用与 Qwen3-8B 完全一致的分词方式。

tokenizer = training_client.get_tokenizer()

步骤 4:将数据转换为 Tinker 的格式

Tinker 需要将训练数据组织为 types.Datum 格式。该格式将模型输入与损失函数配置进行分离,让您能精确控制哪些 token 参与训练。

在微调语言模型时,通常希望模型学习如何生成优质答案,而不是去记忆问题。做法是为输入的不同部分分配不同的权重:

  • 提示 token(问题):权重 = 0.0(不从中学习)
  • 完成 token(答案):权重 = 1.0(从中学习)

让我们一步步构建数据转换函数。先定义函数签名:

def prepare_datum(example, max_length=10000):

用 Qwen3 的聊天模板格式化对话

不同模型对多轮对话的格式要求不同。Qwen3 使用 <|im_start|> 与 <|im_end|> 等特殊 token 标注消息边界。将每条消息拆分为“observation”(角色头)与“action”(实际内容)两部分:

   # Qwen3 chat format - split into observation and action parts
    user_ob = "<|im_start|>user\n"
    user_ac = f"{example['Question']}<|im_end|>"
    
    assistant_ob = "\n<|im_start|>assistant\n"
    # Include both reasoning process AND final answer
    assistant_ac = f"{example['Reasoning_process']}\n\nFinal Answer: {example['Final_response']}<|im_end|>"

FinCoT 数据集包含结构化推理,因此我们将 Reasoning_process 与 Final_response 字段拼接,教会模型在回答前展示其推理过程。

分别对各部分进行分词 

为准确标注权重的适用范围,需要分别对消息的各部分进行分词。

   # Tokenize each part separately to track weight boundaries
    user_ob_tokens = tokenizer.encode(user_ob, add_special_tokens=False)
    user_ac_tokens = tokenizer.encode(user_ac, add_special_tokens=False)
    assistant_ob_tokens = tokenizer.encode(assistant_ob, add_special_tokens=False)
    assistant_ac_tokens = tokenizer.encode(assistant_ac, add_special_tokens=False)
    
    # Combine all tokens
    all_tokens = user_ob_tokens + user_ac_tokens + assistant_ob_tokens + assistant_ac_tokens

过滤过长序列

部分 FinCoT 示例包含极其详尽的推理链,超出 Qwen3-8B 的“舒适”上下文窗口。尽管模型在技术上支持 32,768 个 token,过长序列可能导致训练不稳定:

   # Check if sequence exceeds max length
    if len(all_tokens) > max_length:
        return None  # Skip this example

以 10,000 token 为阈值进行过滤仅移除了 5-6% 的数据,却能显著提升训练可靠性。

分配权重以控制模型学习的内容

为问题部分设为 0.0(避免让模型学习预测问题),为答案部分设为 1.0(让模型学习生成优质回答):

   # Weights: only train on assistant's answer (action part)
    weights = np.array(
        [0.0] * len(user_ob_tokens) +
        [0.0] * len(user_ac_tokens) +
        [0.0] * len(assistant_ob_tokens) +
        [1.0] * len(assistant_ac_tokens)
    )

为下一个 token 预测对 tokens 与权重进行移位

语言模型会预测序列中的下一个 token。当模型看到第 0-9 位 token 时,它应该预测第 10 位。因此目标总是相对于输入向前移一位:

   # CRITICAL: Shift tokens AND weights for next-token prediction
    input_tokens_model = all_tokens[:-1]
    target_tokens = all_tokens[1:]
    weights_shifted = weights[1:]

务必同时移位权重与 token。若未移位权重,损失计算将错位:模型在预测答案的第一个 token,但该位置的权重仍是来自提示的 0.0。此类对齐错误会阻碍模型正确学习。

返回格式化数据

最后,prepare_datum() 函数返回格式化后的数据,供后续处理使用。

   return types.Datum(
        model_input=types.ModelInput.from_ints(tokens=input_tokens_model),
        loss_fn_inputs=dict(weights=weights_shifted, target_tokens=target_tokens),
    )

将定义好的函数应用到原始训练与验证子集,即可处理整个数据集:

# Process and filter training data
training_data_raw = [prepare_datum(example) for example in train_data_raw]
training_data = [d for d in training_data_raw if d is not None]
skipped = len(train_data_raw) - len(training_data)

print(f"Processed {len(training_data)} examples (skipped {skipped} too-long sequences)")
# Output: Processed 7,244 examples (skipped 442 too-long sequences)

# Process validation data
val_data_raw_processed = [prepare_datum(example) for example in val_data_raw]
val_data = [d for d in val_data_raw_processed if d is not None]

print(f"Validation set: {len(val_data)} examples")

步骤 5:使用训练循环训练模型

训练循环使用了 Tinker 的两个核心 API 原语:forward_backward 和 optim_step。我们将带着验证跟踪一步步构建它,以便及早捕捉过拟合。

定义验证损失函数

首先定义验证损失函数。与训练损失不同,验证损失衡量模型对未见数据的泛化能力。这对检测过拟合至关重要。

def compute_validation_loss(val_data, batch_size=100):
    """Compute loss on validation set (forward only, no backward)"""
    batch_indices = np.random.choice(
        len(val_data), size=min(batch_size, len(val_data)), replace=False
    )
    batch = [val_data[i] for i in batch_indices]
    
    # Forward pass only (no backward!)
    fwd_future = training_client.forward(batch, loss_fn="cross_entropy")
    fwd_result = with_retry(fwd_future)
    
    # Calculate per-token loss
    loss_sum = fwd_result.metrics["loss:sum"]
    total_completion_tokens = sum(
        np.sum(np.array(val_data[i].loss_fn_inputs["weights"].data) > 0)
        for i in batch_indices
    )
    return loss_sum / total_completion_tokens if total_completion_tokens > 0 else 0

forward() 方法只计算损失而不计算梯度,非常适合在评估时使用,因为此时只需度量性能而不更新权重。计算验证损失的开销很小(仅前向传播,不计算梯度),但当训练与验证损失的差距超过 0.2 时,它能提供早期预警,提示模型正在记忆而非学习。

配置训练参数

我们需要定义 epoch 数量和学习率。与全参数微调相比,LoRA 需要显著更高的学习率,通常高出 10-20 倍。

# Training configuration
n_samples = len(training_data)  # 7,244
n_epochs = 4  # More epochs for smaller dataset
batch_size = 32

# Calculate optimal LR for Qwen3-8B with LoRA
# Formula: base_lr * lora_multiplier * (2000 / hidden_size) ** exponent
# For Qwen: base=5e-5, multiplier=10, hidden_size=4096, exponent=0.0775
learning_rate = 5e-5 * 10.0 * (2000 / 4096) ** 0.0775  # ≈ 4.7e-4

该公式考虑了模型大小(Qwen3-8B 的隐藏层维度为 4,096),并使用了基于经验的指数,不同模型族有所差异,详见 Tinker Cookbook。对于 Qwen 模型,指数为 0.0775;而 Llama 模型使用 0.781。本例得到约 4.7e-4 的学习率,既能稳定收敛,又避免梯度爆炸。

设置学习率预热

在训练初期直接使用完整学习率,可能在模型尚未适应任务时引发梯度爆炸。预热会在前 200 次迭代中将学习率从接近零逐步提升到目标值。您可以在此前的训练可视化中清楚看到,学习率(绿线)在前 200 次迭代平滑上升。

# Warmup configuration
warmup_steps = 200

# Calculate iterations from epochs
num_iterations = n_epochs * (n_samples // batch_size)  # 4 * 226 = 904
checkpoint_interval = 200
validation_interval = 50

启动训练循环

初始化用于跟踪损失的列表后,即可启动训练循环:

losses = []
per_token_losses = []
val_losses = []

for iteration in range(num_iterations):
    # Sample random batch
    batch_indices = np.random.choice(len(training_data), size=batch_size, replace=False)
    batch = [training_data[i] for i in batch_indices]
    
    # Apply learning rate warmup
    if iteration < warmup_steps:
        current_lr = learning_rate * (iteration + 1) / warmup_steps
    else:
        current_lr = learning_rate

预热逻辑在前 200 次迭代中逐步放大学习率,防止训练初期不稳定。

调用 Tinker 的核心训练原语

在每次迭代中,按照如下方式调用 Tinker 的核心原语:

   # API Primitive 1: forward_backward - compute gradients
    fwdbwd_future = training_client.forward_backward(batch, loss_fn="cross_entropy")

    # API Primitive 2: optim_step - update parameters
    optim_future = training_client.optim_step(types.AdamParams(learning_rate=current_lr))

    # Wait for results with retry logic
    fwdbwd_result = with_retry(fwdbwd_future)
    optim_result = with_retry(optim_future)

forward_backward 通过衡量模型预测与真实下一个 token 之间的偏差,并确定各参数应调整的方向与幅度来计算梯度。它使用交叉熵损失来衡量模型预测的概率分布与真实分布的匹配程度。optim_step 则使用 Adam 优化器 基于这些梯度实际更新模型参数。

注意,这两种方法都会立即返回“future”对象。这允许 Tinker 同时处理多个操作,从而加速整体流程。调用 with_retry(future) 则会等待特定操作完成。

跟踪并展示损失指标

我们计算总损失与每 token 损失,并将其分别追加到列表中:

   # Track loss
    loss_sum = fwdbwd_result.metrics["loss:sum"]
    total_completion_tokens = sum(
        np.sum(np.array(training_data[i].loss_fn_inputs["weights"].data) > 0)
        for i in batch_indices
    )
    per_token_loss = loss_sum / total_completion_tokens

    losses.append(loss_sum)
    per_token_losses.append(per_token_loss)

    if iteration % 10 == 0:
        warmup_indicator = "🔥" if iteration < warmup_steps else ""
        print(f"{warmup_indicator} Iteration {iteration} | Train: {per_token_loss:.4f} | LR: {current_lr:.6f}")

每 token 损失会以答案 token 的数量为归一化基准,便于在不同批量大小之间进行解释。一次良好的训练通常以约 1.2-1.5 的每 token 损失起步,并在 checkpoint-400 前降至 0.8 以下。如果您的损失起始高于 2.0,或在 50 次迭代后仍不下降,请检查数据准备与学习率设置。

添加定期验证与检查点

每 200 次迭代保存一次权重可创建用于对比的快照。正如先前的训练结果所示,尽管训练损失更高,checkpoint-400 的泛化表现仍好于最终检查点。务必保存多个检查点并在留出数据上进行评估。脚本结束后,检查点会保留在 Tinker 的服务器上。

   # Compute validation loss periodically
    if iteration % validation_interval == 0:
        val_loss = compute_validation_loss(val_data)
        val_losses.append((iteration, val_loss))
        gap = val_loss - per_token_loss
        print(f"  📊 Iteration {iteration} | Train: {per_token_loss:.4f} | Val: {val_loss:.4f} | Gap: {gap:+.4f}")

    # Checkpoint every 200 iterations
    if iteration > 0 and iteration % checkpoint_interval == 0:
        training_client.save_weights_for_sampler(name=f"fincot-checkpoint-{iteration}")

Tinker 检查点

Tinker 将检查点分为两类:用于恢复训练的全状态检查点,以及用于推理的轻量级采样器权重。注意 checkpoint-400 同时以这两种格式存在,便于继续训练与部署。

如果训练中断,您可以使用全状态权重从任意已保存的检查点恢复,继续训练而无需从头开始。

有关这些方法的完整 API 参考,请参见 Tinker 的训练与采样文档。

步骤 6:保存微调后的模型

训练完成后,保存最终模型权重:

sampling_client = training_client.save_weights_and_get_sampling_client(
    name="financial-qa-qwen3-8b-lora"
)

权重保存在 Tinker 的服务器而非本地。LoRA 适配器通常只需数十 MB。如果您希望在其基础设施之外部署模型,Tinker 也可选支持将权重导出到外部推理服务。

步骤 7:用示例问题测试模型

最后,使用 sample API 原语测试您的微调模型。首先配置采样参数:

sampling_params = types.SamplingParams(
    max_tokens=200,
    temperature=0.7,
    top_p=0.9,
    stop_sequences=["<|im_end|>"]
)

参数说明如下:

  • max_tokens:生成响应的最大长度(200 个 token 约合 150 个英文单词)。

  • temperature:控制生成的随机性。较低数值(0.1-0.5)使输出更集中、更确定;较高数值(0.7-1.0)更具创造性。0.7 在连贯性与多样性之间取得平衡。

  • top_p(核采样):仅考虑累计概率超过该值的最小 token 集合。0.9 表示“只考虑累计占 90% 概率质量的 token”。这可防止模型选择极不可能的 token。

  • stop_sequences:当遇到该序列时停止生成。<|im_end|> 是序列结束标记。

为测试问题生成响应:

questions = [
    "What are the main risks associated with investing in stocks?",
    "How does diversification help reduce portfolio risk?"
]

for question in questions:
    model_input = types.ModelInput.from_ints(tokenizer.encode(question))

    response = with_retry(
        sampling_client.sample(
            prompt=model_input,
            num_samples=1,
            sampling_params=sampling_params
        )
    )

    answer = tokenizer.decode(response.sequences[0].tokens)
    print(f"Q: {question}")
    print(f"A: {answer}\n")

以下是微调模型的两个示例输出(为便于展示已截断):

Q: What are the main risks associated with investing in stocks?
A: Investing in stocks carries several key risks. Market risk represents the potential for stock prices to decline due to economic downturns or market volatility. Company-specific risk arises from poor management decisions, competitive pressures, or operational failures that affect individual stocks. Liquidity risk occurs when you cannot sell shares quickly without significantly impacting the price. Additionally, investors face inflation risk, where returns may not keep pace with rising prices...

Q: How does diversification help reduce portfolio risk?
A: Diversification reduces portfolio risk by spreading investments across different asset classes, sectors, and geographic regions. When you hold multiple uncorrelated assets, losses in one investment can be offset by gains in others. For example, if technology stocks decline but healthcare stocks rise, a diversified portfolio experiences less volatility than one concentrated in technology alone. The key principle is that different assets respond differently to market conditions...

响应对象包含一个 sequences 列表,因为您可以为每个提示请求多个样本。通过 sequences[0].tokens 访问 token,并使用相同分词器将其解码回文本。

至此,您已成功演示四个核心 API 原语:

  1. forward_backward:从训练数据计算梯度

  2. optim_step:使用 Adam 优化器更新模型参数

  3. save_weights_and_get_sampling_client:持久化您的微调模型

  4. sample:从微调模型生成预测

同样的工作流可扩展至更高级的训练范式。对于强化学习或偏好优化,您将继续使用这些原语,但采用不同的损失函数与数据格式。如果您对让模型对齐人类偏好感兴趣,可查看偏好微调技术,其基础与本教程一致。Tinker Cookbook 提供了这些高级场景的示例。

但如何判断微调是否真的提升了性能?与其依赖主观印象,不如进行严格评估。下一节将展示 LLM 评审的结果:它对比了微调模型与基座 Qwen3-8B 在 10 个多样化金融问题上的表现。

用 LLM 评审评估微调模型

为客观衡量性能,我们使用 LLM 作为评审:GPT-4o 在 10 个多样化的金融问题上,对 checkpoint-400 与基座 Qwen3-8B 进行评测,并从准确性、清晰度、完整性与金融术语使用等维度打分。

微调模型得分 8.5/10,基座模型得分 6.5,且 10 场对比全部胜出。评审结论揭示了原因:微调模型能以自信、结构化的推理给出完整解释,而基座模型常以 “I think”“maybe” 等措辞犹疑不决,思路不够完整。

这验证了 checkpoint-400 在真实场景中的更佳表现。若选择第 900 次迭代的最低训练损失,得到的将是记忆而非推理能力。

查看用于运行类似评估的对比脚本与详细结果。

结论

您已经看到,Tinker 如何在不牺牲控制力的前提下简化微调。四个核心 API 原语 forward_backward、optim_step、save_weights_and_get_sampling_client 与 sample,为定制训练工作流提供了基石,而 Tinker 则负责分布式基础设施。

我们构建的金融问答模型体现了生产级最佳实践:验证跟踪及早捕捉了过拟合;checkpoint-400 因聚焦泛化而优于最终模型;LLM 评审验证其相较基座模型提升了 2 分。这些模式同样适用于 80 亿到 700 亿参数规模的模型。

该平台当前处于测试阶段,通过候补名单发放访问权限,但您学到的概念在拿到权限后可直接应用。本教程中我进行的训练(包括失败与实验性运行)共消耗了 150 美元的起始额度,因此仅凭初始额度,您就能将本教程重复实践 6 次并进行额外实验,足以为您自己的微调项目提供充足的学习与迭代空间。

接下来做什么?尝试在您的领域数据上进行微调,实验不同的 LoRA 秩与学习率,或探索如人类反馈强化学习等高级训练范式。Tinker Cookbook 为这些场景提供了示例。现在就开始思考您的用例——您希望模型学到怎样的行为?

如果您希望系统性提升训练与微调前沿 AI 模型以达成生产落地的能力,欢迎查看面向开发者的 AI 工程师(副级)职业路径。

Bexruz (Bex) Tuychiev

作者

Bexruz (Bex) Tuychiev

我是一名数据科学内容创作者,拥有超过 2 年的经验,并在 Medium 上拥有较大的读者群。我喜欢用略带讽刺的笔调撰写有关 AI 和机器学习的深度文章——毕竟总得想点办法让这些内容不那么枯燥。我已发表 130 多篇文章,并制作了一门 DataCamp 课程,另有一门正在筹备中。我的内容已被超过 500 万人次阅读,其中有 2 万人在 Medium 和 LinkedIn 上成为关注者。 

主题