1. TRIO介绍
TRIO官网地址:pytrio.cn
TRIO文档地址:docs.pytrio.cn
Tinker官网地址:thinkingmachines.ai/tinker
TRIO 是一个面向大模型后训练的 AI Infra 产品。你可以在本地用 Python 写训练循环,把数据处理、loss 设计、reward 计算、评估逻辑都掌握在自己手里;真正复杂的分布式训练、GPU 调度、资源管理和执行过程,则交给 TRIO 云端完成。

如果你了解 Thinking Machines Lab 的 Tinker,可以把 TRIO 理解为一种更适合国内开发者快速使用的同类训练 API:它不是传统意义上的“一键微调平台”,也不是单纯租一台 GPU 让你自己配环境,而是在保留训练控制权的同时,把底层训练基础设施抽象成几个清晰的 API。

简单来说:
- 你写:数据、prompt、loss、reward、训练循环、评估逻辑
- TRIO 负责:模型加载、LoRA 后训练、分布式执行、采样、权重保存、断点续训
- 结果是:不用搭训练集群,也能写出可控的 SFT/RL 后训练实验
TRIO 的核心特性包括:
-
本地 CPU 开发,大模型训练在云端执行
你只需要在自己的机器上运行 Python 脚本,TRIO 会把计算请求调度到云端 GPU。 -
支持 LoRA 后训练
TRIO 当前采用 LoRA 方式更新模型,相比全量微调更轻量,也更适合快速迭代多个实验。 -
支持 SFT 和 RL
既可以做监督微调,也可以做强化学习式的后训练,例如基于采样结果计算 reward,再通过forward_backward和optim_step更新模型。 -
API 足够底层,训练逻辑足够自由
TRIO 的核心不是一个黑盒train(),而是几个训练原语:sample、forward_backward、optim_step、save_state、save_weights_for_sampler。 -
支持异步调用
API 返回 Future,你可以把采样、前反向、优化器更新组织成更高吞吐的 pipeline。 -
支持权重保存、续训与下载
训练中可以保存 sampler 权重,也可以保存带优化器状态的 train 权重,之后继续训练或下载 LoRA adapter 到本地部署。 -
兼容 OpenAI API
训练后的模型可以通过 OpenAI 兼容接口接入应用,方便替换现有推理调用链。
TRIO 和常见方案的区别
相比“黑盒微调平台”,TRIO 给你的控制权更多。你可以自己组织 batch、自己定义 loss 或 reward,也可以在训练中穿插采样和评估。
相比“自建 GPU 集群”,TRIO 省去了环境配置、分布式训练、显存规划、任务调度等大量工程工作。
相比海外的 Tinker,TRIO 的文档、控制台和使用路径对国内开发者更友好,也更适合在国内网络环境下做快速实验。
2. 快速上手
参考链接:TRIO快速开始
2.1 安装 pytrio
在 Python 环境中安装 TRIO 的 SDK:
pip install pytrio
如果安装速度较慢,可以使用国内源:
pip install pytrio -i https://mirrors.cernet.edu.cn/pypi/web/simple
后续示例会用到 tokenizer、数组计算和下载能力,建议一并安装:
pip install transformers modelscope numpy requests
2.2 登录账号
如果你还没有 TRIO 账号,可以先访问 pytrio.cn 注册。
注册完成后,在命令行输入:
trio login
命令行会提示你前往控制台复制 API Key:
trio: You can find your API key at: https://pytrio.cn
trio: Paste an API key from your profile and hit enter, or press 'Ctrl+C' to quit:
复制 API Key 后粘贴到命令行,按回车即可完成登录。粘贴时终端通常不会显示 API Key,这是正常现象。
登录成功后,TRIO 会把凭证保存到本地,之后运行脚本时无需重复登录。
2.3 查看支持的模型列表
先运行一个最小脚本,确认你已经能够连接到 TRIO:
import pytrio as trio
client = trio.ServiceClient()
supported_models = client.get_supported_models()
print("Supported models:")
for index, model_name in enumerate(supported_models, start=1):
print(f"{index}. {model_name}")
如果能够看到模型列表,说明本地 SDK、账号登录和云端连接都已经正常。
官方文档中的示例模型包括:
Qwen/Qwen3.6-27B
Qwen/Qwen3.5-4B
实际可用模型以 TRIO 控制台和官方模型列表为准。下面为了便于快速体验,统一使用 Qwen/Qwen3.5-4B。
3. 完成一次推理
先从最简单的推理开始。流程是:
- 创建
ServiceClient - 创建
SamplingClient - 获取 tokenizer
- 把 prompt 转成 token ids
- 调用
sample - 读取生成结果
新建 sample_trio.py:
import pytrio as trio
service_client = trio.ServiceClient()
sampling_client = service_client.create_sampling_client(
base_model="Qwen/Qwen3.5-4B",
)
print("Loading tokenizer...")
tokenizer = sampling_client.get_tokenizer()
messages = [
{"role": "user", "content": "请用一句话介绍 TRIO。"},
]
prompt_text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
input_ids = tokenizer.encode(prompt_text)
params = trio.SamplingParams(
max_tokens=120,
temperature=0.7,
seed=42,
)
response = sampling_client.sample(
prompt=trio.ModelInput.from_ints(input_ids),
num_samples=1,
sampling_params=params,
).result()
print(response.sequences[0].text)
运行:
python sample_trio.py
如果一切正常,你会在终端看到模型生成的回答。
这段代码里最重要的几个对象是:
ServiceClient:连接 TRIO 云端计算服务create_sampling_client:创建推理客户端SamplingParams:控制生成参数,例如最大 token 数、温度、随机种子ModelInput.from_ints:把 token ids 封装成 TRIO 输入格式sample:执行一次采样推理
4. 训练一个“认识 TRIO”的 SFT 模型
接下来完成一次监督微调。这个例子故意做得很小:让模型学会回答“TRIO 是什么”。
为什么要这样设计?因为英文里的 trio 本来有“三重奏、三人组”的意思,基础模型很可能会往普通词义上回答。我们用几条样本做 SFT,让它把 TRIO 识别为一个 AI Infra 产品。
新建 train_trio_sft.py:
import numpy as np
import pytrio as trio
service_client = trio.ServiceClient()
base_model = "Qwen/Qwen3.5-4B"
training_client = service_client.create_lora_training_client(
base_model=base_model,
rank=32,
)
examples = [
{
"input": "TRIO是什么?",
"output": "TRIO 是一个面向大模型后训练的 AI Infra 产品,可以让开发者在本地写训练循环,并把分布式训练交给云端执行。",
},
{
"input": "TRIO适合做什么?",
"output": "TRIO 适合做大模型监督微调、强化学习后训练、训练中采样评估,以及可控的算法实验。",
},
{
"input": "TRIO和普通微调平台有什么区别?",
"output": "TRIO 不是黑盒微调平台,它提供 sample、forward_backward、optim_step 等底层 API,让开发者保留训练循环和算法细节的控制权。",
},
]
print("Loading tokenizer...")
tokenizer = training_client.get_tokenizer()
print("Tokenizer loaded")
def process_example(example: dict) -> trio.Datum:
prompt = f"问题:{example['input']}\n回答:"
prompt_tokens = tokenizer.encode(prompt, add_special_tokens=True)
completion_tokens = tokenizer.encode(
example["output"] + "\n",
add_special_tokens=False,
)
tokens = prompt_tokens + completion_tokens
# prompt 部分不算 loss,answer 部分计算 loss。
weights = [0] * len(prompt_tokens) + [1] * len(completion_tokens)
input_tokens = tokens[:-1]
target_tokens = tokens[1:]
loss_weights = weights[1:]
return trio.Datum(
model_input=trio.ModelInput.from_ints(tokens=input_tokens),
loss_fn_inputs={
"weights": np.asarray(loss_weights, dtype=np.float32),
"target_tokens": np.asarray(target_tokens, dtype=np.int32),
},
)
processed_examples = [process_example(example) for example in examples]
print("Start training")
for step in range(15):
fwdbwd_future = training_client.forward_backward(
processed_examples,
loss_fn="cross_entropy",
)
optim_future = training_client.optim_step(
trio.AdamParams(learning_rate=1e-4),
)
fwdbwd_result = fwdbwd_future.result()
optim_future.result()
logprobs = np.concatenate(
[np.asarray(output["logprobs"]) for output in fwdbwd_result.loss_fn_outputs]
)
weights = np.concatenate(
[example.loss_fn_inputs["weights"] for example in processed_examples]
)
loss = -np.dot(logprobs, weights) / weights.sum()
print(f"Step {step + 1:02d} | loss per token: {loss:.4f}")
print("Saving LoRA weights")
sft_weights = training_client.save_weights_for_sampler(
name="trio-intro-sft",
).result()
base_sampler = service_client.create_sampling_client(base_model=base_model)
sft_sampler = service_client.create_sampling_client(
base_model=base_model,
model_path=sft_weights.path,
)
prompt = trio.ModelInput.from_ints(
tokenizer.encode("问题:TRIO是什么?\n回答:")
)
params = trio.SamplingParams(max_tokens=100, temperature=0.0)
base_response = base_sampler.sample(
prompt=prompt,
sampling_params=params,
num_samples=1,
).result()
sft_response = sft_sampler.sample(
prompt=prompt,
sampling_params=params,
num_samples=1,
).result()
print("\nBase model:")
print(repr(base_response.sequences[0].text))
print("\nSFT model:")
print(repr(sft_response.sequences[0].text))
运行:
python train_trio_sft.py
你会看到类似这样的训练日志:
Loading tokenizer...
Tokenizer loaded
Start training
Step 01 | loss per token: 5.1234
Step 02 | loss per token: 3.8921
...
Step 15 | loss per token: 0.0123
Saving LoRA weights
最后,脚本会分别用 base model 和 SFT 后的模型回答同一个问题。正常情况下,SFT 后的模型会更稳定地回答“TRIO 是一个面向大模型后训练的 AI Infra 产品”,而不是把 trio 解释成“三重奏”。
这段训练代码的核心 API 是:
create_lora_training_client:创建 LoRA 训练客户端Datum:封装一条训练样本,包括模型输入和 loss 所需参数forward_backward:在云端执行前向和反向计算,并累积梯度optim_step:根据累积梯度更新 LoRA 权重save_weights_for_sampler:保存可用于推理的 LoRA 权重create_sampling_client(..., model_path=...):加载训练后的权重进行采样
5. 保存、续训与下载权重
TRIO 支持两类保存方式:
training_client.save_weights_for_sampler(name="sampler")
这个方式保存的是推理用权重,适合训练完成后做采样和部署。
training_client.save_state(name="train")
这个方式会保存权重和优化器状态,适合后续继续训练。
如果想从某个训练状态继续训练,可以在 TRIO 控制台的权重页面复制权重路径,然后:
import pytrio as trio
service_client = trio.ServiceClient()
training_client = service_client.create_training_client_from_state_with_optimizer(
path="YOUR_MODEL_PATH",
)
如果只想加载权重,不加载优化器状态,可以使用:
training_client = service_client.create_training_client_from_state(
path="YOUR_MODEL_PATH",
)
下载权重也可以通过 API 完成。先在 WebUI 的“权重”页面复制 checkpoint id,然后运行:
import os
import requests
import pytrio as trio
client = trio.ServiceClient()
rest_client = client.create_rest_client()
checkpoint_id = "YOUR_CHECKPOINT_ID"
result = rest_client.get_checkpoint_archive_url(checkpoint_id).result()
save_filename = f"{checkpoint_id}.zip"
with requests.get(result.url, stream=True) as response:
response.raise_for_status()
with open(save_filename, "wb") as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
print(f"File download complete: {os.path.abspath(save_filename)}")
下载得到的是 LoRA adapter,不是完整 base model。部署时需要搭配对应的 base model,例如 Qwen/Qwen3.5-4B。
6. 用 OpenAI API 调用训练后的模型
如果你想把 TRIO 训练后的模型接入已有应用,可以使用 OpenAI 兼容接口。
先安装:
pip install openai
然后调用:
from openai import OpenAI
client = OpenAI(
base_url="https://pytrio.cn/api/openai/v1",
api_key="YOUR_TRIO_API_KEY",
)
response = client.chat.completions.create(
model="YOUR_MODEL_PATH_OR_BASE_MODEL_NAME",
messages=[
{"role": "user", "content": "请介绍一下 TRIO。"},
],
max_tokens=120,
temperature=0.7,
top_p=0.9,
)
print(response.choices[0].message.content)
这里的 model 可以填基础模型名称,也可以填你在 TRIO 控制台复制到的训练后权重路径。
7. 下一步可以做什么
到这里,你已经完成了 TRIO 的最小闭环:
- 安装 SDK
- 登录账号
- 查看模型列表
- 完成一次推理
- 完成一次 SFT 训练
- 保存并加载训练后的权重
- 通过 OpenAI API 接入应用
接下来可以继续尝试:
- 把示例数据换成自己的业务数据,做一个小规模 SFT
- 在训练循环里加入验证集,观察 loss 和准确率变化
- 用
sample生成答案,再写 reward 函数做 RL 实验 - 使用异步 API 提升采样和训练 pipeline 的吞吐
- 下载 LoRA adapter,在本地用 Transformers + PEFT 验证效果
- 将训练日志接入 SwanLab,记录 loss、reward、评估分数和超参数
TRIO 最有价值的地方不在于“把微调按钮化”,而在于让你用接近研究代码的方式表达训练算法,同时不用被分布式训练工程拖住。对于想快速尝试 SFT、RLHF、GRPO、Agent 后训练或者自定义 reward 的开发者来说,它是一个很适合上手的中国版 Tinker。
134

被折叠的 条评论
为什么被折叠?



