Twinkle Client
Twinkle Client is the native client, designed with the philosophy: Change from twinkle import to from twinkle_client import, and you can migrate local training code to remote calls without modifying the original training logic.
Initialization
from twinkle_client import init_twinkle_client
# Initialize client, connect to Twinkle Server
client = init_twinkle_client(
base_url='http://127.0.0.1:8000', # Server address
api_key='your-api-key' # Authentication token (can be set via environment variable TWINKLE_SERVER_TOKEN)
)
After initialization, the client object (TwinkleClient) provides the following management functions:
# Health check
client.health_check()
# List current user's training runs
runs = client.list_training_runs(limit=20)
# Get specific training run details
run = client.get_training_run(run_id='xxx')
# List checkpoints
checkpoints = client.list_checkpoints(run_id='xxx')
# Get checkpoint path (for resuming training)
path = client.get_checkpoint_path(run_id='xxx', checkpoint_id='yyy')
# Get latest checkpoint path
latest_path = client.get_latest_checkpoint_path(run_id='xxx')
Migrating from Local Code to Remote
Migration is very simple, just replace the import path from twinkle to twinkle_client:
# Local training code (original)
from twinkle.dataloader import DataLoader
from twinkle.dataset import Dataset
from twinkle.model import MultiLoraTransformersModel
# Remote training code (after migration)
# DataLoader and Dataset can be imported from either local twinkle or remote twinkle_client
from twinkle.dataloader import DataLoader # or: from twinkle_client.dataloader import DataLoader
from twinkle.dataset import Dataset # or: from twinkle_client.dataset import Dataset
from twinkle_client.model import MultiLoraTransformersModel
Training loops, data processing, and other logic do not need any modifications.
Complete Training Example (Transformers Backend)
import dotenv
dotenv.load_dotenv('.env')
from peft import LoraConfig
from twinkle import get_logger
from twinkle.dataset import DatasetMeta
from twinkle_client import init_twinkle_client
# DataLoader and Dataset can be imported from either local twinkle or remote twinkle_client
from twinkle.dataloader import DataLoader
from twinkle.dataset import Dataset
from twinkle_client.model import MultiLoraTransformersModel
logger = get_logger()
base_model = 'Qwen/Qwen3.5-4B'
base_url = 'http://localhost:8000'
api_key = 'EMPTY_API_KEY'
# Step 1: Initialize client
client = init_twinkle_client(base_url=base_url, api_key=api_key)
# List available models on the server
print('Available models:')
for item in client.get_server_capabilities().supported_models:
print('- ' + item.model_name)
# Step 2: Query existing training runs (optional, for resuming training)
runs = client.list_training_runs()
resume_path = None
for run in runs:
logger.info(run.model_dump_json(indent=2))
checkpoints = client.list_checkpoints(run.training_run_id)
for checkpoint in checkpoints:
logger.info(checkpoint.model_dump_json(indent=2))
# Uncomment to resume from checkpoint:
# resume_path = checkpoint.twinkle_path
# Step 3: Prepare dataset
# data_slice limits the number of samples loaded
dataset = Dataset(dataset_meta=DatasetMeta('ms://swift/self-cognition', data_slice=range(500)))
# Set chat template to match model's input format
dataset.set_template('Qwen3_5Template', model_id=f'ms://{base_model}', max_length=512)
# Data preprocessing: Replace placeholders with custom names
dataset.map('SelfCognitionProcessor',
init_args={'model_name': 'twinkle model', 'model_author': 'ModelScope Team'})
# Encode dataset into tokens usable by the model
dataset.encode(batched=True)
# Create DataLoader
dataloader = DataLoader(dataset=dataset, batch_size=4)
# Step 4: Configure model
model = MultiLoraTransformersModel(model_id=f'ms://{base_model}')
# Configure LoRA: apply low-rank adapters to all linear layers
lora_config = LoraConfig(target_modules='all-linear')
# gradient_accumulation_steps=2: accumulate gradients over 2 micro-batches before each optimizer step
model.add_adapter_to_model('default', lora_config, gradient_accumulation_steps=2)
# Set template, processor, loss function
model.set_template('Qwen3_5Template')
model.set_processor('InputProcessor', padding_side='right')
model.set_loss('CrossEntropyLoss')
# Set optimizer (only Adam is supported if the server uses Megatron backend)
model.set_optimizer('Adam', lr=1e-4)
# Set LR scheduler (not supported if the server uses Megatron backend)
# model.set_lr_scheduler('LinearLR')
# Step 5: Resume training (optional)
start_step = 0
if resume_path:
logger.info(f'Resuming from checkpoint {resume_path}')
progress = model.resume_from_checkpoint(resume_path)
dataloader.resume_from_checkpoint(progress['consumed_train_samples'])
start_step = progress['cur_step']
# Step 6: Training loop
logger.info(model.get_train_configs().model_dump())
for epoch in range(3):
logger.info(f'Starting epoch {epoch}')
for cur_step, batch in enumerate(dataloader, start=start_step + 1):
# Forward propagation + backward propagation
model.forward_backward(inputs=batch)
# Gradient clipping + optimizer update (equivalent to calling clip_grad_norm / step / zero_grad / lr_step in sequence)
model.clip_grad_and_step()
# Print metric every 2 steps (aligned with gradient_accumulation_steps)
if cur_step % 2 == 0:
metric = model.calculate_metric(is_training=True)
logger.info(f'Current is step {cur_step} of {len(dataloader)}, metric: {metric.result}')
# Step 7: Save checkpoint
twinkle_path = model.save(
name=f'twinkle-epoch-{epoch}',
save_optimizer=True,
consumed_train_samples=dataloader.get_state()['consumed_train_samples'],
)
logger.info(f'Saved checkpoint: {twinkle_path}')
# Step 8: Upload to ModelScope Hub (optional)
# YOUR_USER_NAME = "your_username"
# hub_model_id = f'{YOUR_USER_NAME}/twinkle-self-cognition'
# model.upload_to_hub(
# checkpoint_dir=twinkle_path,
# hub_model_id=hub_model_id,
# async_upload=False
# )
For checkpoint resumption, the recommended client-side flow is:
Query the server for an existing checkpoint path with
client.list_checkpoints(...)orclient.get_latest_checkpoint_path(...).Call
model.resume_from_checkpoint(resume_path)to restore weights, optimizer, scheduler, RNG, and progress metadata.Call
dataloader.resume_from_checkpoint(progress['consumed_train_samples'])to skip already-consumed samples.
This matches the end-to-end example in cookbook/client/twinkle/self_host/self_cognition.py.
Differences with Megatron Backend
When using the Megatron backend, the main differences in client code:
# Megatron backend does not need explicit loss setting (computed internally by Megatron)
# model.set_loss('CrossEntropyLoss') # Not needed
# Optimizer and LR scheduler use Megatron built-in defaults
model.set_optimizer('default', lr=1e-4)
model.set_lr_scheduler('default', lr_decay_steps=1000, max_lr=1e-4)
The rest of the data processing, training loop, checkpoint saving, and other code remains exactly the same.