Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Importante
Questa funzionalità è in versione beta. Gli amministratori dell'area di lavoro possono controllare l'accesso a questa funzionalità dalla pagina Anteprime . Vedere Gestire le anteprime di Azure Databricks.
Questo esempio esegue l'ottimizzazione parallela dei dati distribuita con TorchTrainer su 8 GPU H100 in un singolo nodo. Uno script bootstrap avvia un cluster Ray nel nodo, quindi il driver Ray Train avvia un ruolo di lavoro per GPU, esegue il wrapping del modello in DDP e partiziona automaticamente il set di dati tra i ruoli di lavoro.
Esegue la messa a punto di un modello pubblico (Qwen2.5-3B), quindi può essere eseguito così com’è senza bisogno di un token Hugging Face.
Il carico di lavoro esegue le operazioni seguenti:
- Carica il progetto locale con
code_source: snapshot. - Avvia una testa Ray con tutte e 8 le GPU, quindi esegue il driver Ray Train.
- Usa
ray.train.torch.prepare_modeleprepare_data_loaderper gestire il wrapping di DDP, il posizionamento dei dispositivi e il campionamento distribuito. - Registra le metriche in MLflow.
Prerequisites
- La
airCLI è installata e autenticata. Consulta Installazione dell'AI Runtime CLI.
Layout del progetto
Creare una directory con i file seguenti.
ray_train_distributed/
├── train.yaml # air workload config (inline dependencies + Ray bootstrap)
└── train_ray.py # Ray Train TorchTrainer driver + per-worker training
Passaggio 1: Scrivere il carico di lavoro YAML
train.yaml richiede un singolo GPU_8xH100 nodo. Le dipendenze vengono dichiarate direttamente sotto environment (con l'immagine client version) e command avvia un cluster Ray sul nodo, quindi esegue il driver, perciò il carico di lavoro non richiede un file delle dipendenze separato né uno script di avvio:
experiment_name: air-ray-train-distributed
environment:
version: '4'
dependencies:
- ray[default,train]>=2.30
- transformers>=4.45
- datasets>=3.0
- huggingface_hub>=0.34
# The base image ships fsspec 2023.5.0, which is too old for modern
# huggingface_hub and breaks dataset/model downloads. Pin a newer fsspec.
- fsspec>=2024.6.1
# 8 H100 on a single node. Ray Train launches one worker per GPU.
compute:
num_accelerators: 8
accelerator_type: GPU_8xH100
code_source:
type: snapshot
snapshot:
root_path: .
command: |
cd $CODE_SOURCE_PATH
RAY_HEAD_PORT=6379
GPUS_PER_NODE=${LOCAL_WORLD_SIZE:-8}
if [ "${NODE_RANK:-0}" = "0" ]; then
echo "NODE_RANK=0: starting Ray head with $GPUS_PER_NODE GPU(s)..."
ray start --head --port=$RAY_HEAD_PORT --num-gpus="$GPUS_PER_NODE" --dashboard-host=0.0.0.0
python train_ray.py
ray stop
else
echo "NODE_RANK=$NODE_RANK: connecting to Ray head at $MASTER_ADDR:$RAY_HEAD_PORT..."
for i in $(seq 1 12); do
if ray start --address="$MASTER_ADDR:$RAY_HEAD_PORT" --num-gpus="$GPUS_PER_NODE" --block 2>/dev/null; then
break
fi
echo "Attempt $i failed, retrying in 5s..."
sleep 5
done
fi
max_retries: 0
timeout_minutes: 90
env_variables:
NCCL_SOCKET_IFNAME: eth0
Il comando inline command avvia un head Ray con tutte le GPU sul nodo, esegue il driver con python train_ray.py, quindi arresta il cluster. Include anche un ramo worker che si unisce all'head, così lo stesso comando continua a funzionare se si scala il job su più nodi.
Passaggio 2: Definire il driver di Ray Train
train_ray.py definisce un train_func che viene eseguito su ogni worker e un main che configura il TorchTrainer per utilizzare tutte le GPU del cluster.
prepare_model incapsula il modello in DDP e lo sposta sulla GPU del worker.
prepare_data_loader aggiunge un campionatore distribuito:
def train_func(config: dict):
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, torch_dtype=torch.bfloat16)
model.config.use_cache = False
model = prepare_model(model) # DDP wrap + device placement
loader = DataLoader(dataset, batch_size=config["batch_size"], shuffle=True, drop_last=True)
loader = prepare_data_loader(loader) # distributed sampler + GPU transfer
optimizer = torch.optim.AdamW(model.parameters(), lr=config["lr"])
...
ray.train.report({"loss": out.loss.item(), "step": step})
def main():
ray.init(address="auto")
total_gpus = int(ray.cluster_resources().get("GPU", 0))
trainer = TorchTrainer(
train_func,
train_loop_config={"lr": 2e-5, "batch_size": 4, "max_steps": 100},
scaling_config=ScalingConfig(num_workers=total_gpus, use_gpu=True),
)
trainer.fit()
Lo script completo è elencato in Script di training completo alla fine di questa pagina.
Passaggio 3: Invia il run
air run -f train.yaml --dry-run
air run -f train.yaml --watch
Passaggio 4: Controllare l'esecuzione
air get run <run-id>
air logs <run-id>
Il nodo head di Ray e il driver vengono entrambi eseguiti sul nodo 0, quindi i log provengono da un unico nodo.
Dove atterrare i risultati
Le metriche riportate con ray.train.report e registrate con MLflow vengono visualizzate nell'esperimento MLflow denominato in experiment_name, visibile nell'interfaccia utente MLflow dell'area di lavoro.
Script di training completo
Il file completo train_ray.py per la copia-incolla:
#!/usr/bin/env python3
"""Distributed data-parallel fine-tuning with Ray Train on a single 8x H100 node.
The workload `command` starts a Ray head with 8 GPUs and runs this script. Ray Train's
TorchTrainer launches one worker per GPU (8 total), wraps the model in DDP, shards
the dataset across workers, and aggregates metrics. Each worker runs `train_func`.
Uses a public model (no Hugging Face token required) so the example runs as-is.
"""
import os
import mlflow
import ray
import ray.train
import torch
from datasets import load_dataset
from ray.train import RunConfig, ScalingConfig
from ray.train.torch import TorchTrainer, prepare_data_loader, prepare_model
from torch.utils.data import DataLoader
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_NAME = "Qwen/Qwen2.5-3B"
DATASET_NAME = "tatsu-lab/alpaca"
MAX_SEQ_LEN = 1024
def build_dataset(tokenizer):
raw = load_dataset(DATASET_NAME, split="train[:8000]")
def format_example(row):
prompt = f"### Instruction:\n{row['instruction']}\n\n"
if row.get("input"):
prompt += f"### Input:\n{row['input']}\n\n"
text = f"{prompt}### Response:\n{row['output']}{tokenizer.eos_token}"
out = tokenizer(text, truncation=True, max_length=MAX_SEQ_LEN, padding="max_length")
out["labels"] = out["input_ids"].copy()
return out
return raw.map(format_example, remove_columns=raw.column_names)
def train_func(config: dict):
"""Runs on every Ray Train worker (one per GPU)."""
rank = ray.train.get_context().get_world_rank()
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, torch_dtype=torch.bfloat16)
model.config.use_cache = False
# prepare_model moves the model to this worker's GPU and wraps it in DDP.
model = prepare_model(model)
dataset = build_dataset(tokenizer).with_format("torch")
loader = DataLoader(dataset, batch_size=config["batch_size"], shuffle=True, drop_last=True)
# prepare_data_loader injects a DistributedSampler and moves batches to the GPU.
loader = prepare_data_loader(loader)
optimizer = torch.optim.AdamW(model.parameters(), lr=config["lr"])
# AI Runtime injects MLFLOW_RUN_ID and configures the databricks tracking URI on
# the node, so logging works without DATABRICKS_HOST/TOKEN. Gate on MLFLOW_RUN_ID
# so the script also runs cleanly off-platform (e.g. locally) where it is unset.
use_mlflow = rank == 0 and bool(os.environ.get("MLFLOW_RUN_ID"))
if use_mlflow:
mlflow.start_run(run_id=os.environ.get("MLFLOW_RUN_ID"))
mlflow.log_params({"model": MODEL_NAME, "lr": config["lr"], "batch_size": config["batch_size"]})
model.train()
step = 0
for batch in loader:
out = model(
input_ids=batch["input_ids"],
attention_mask=batch["attention_mask"],
labels=batch["labels"],
)
out.loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
optimizer.zero_grad()
step += 1
ray.train.report({"loss": out.loss.item(), "step": step})
if use_mlflow:
mlflow.log_metric("train_loss", out.loss.item(), step=step)
if step >= config["max_steps"]:
break
if use_mlflow:
mlflow.end_run()
def main():
ray.init(address="auto")
total_gpus = int(ray.cluster_resources().get("GPU", 0))
print(f"Ray cluster ready: {total_gpus} GPU(s)", flush=True)
trainer = TorchTrainer(
train_func,
train_loop_config={"lr": 2e-5, "batch_size": 4, "max_steps": 100},
scaling_config=ScalingConfig(num_workers=total_gpus, use_gpu=True),
run_config=RunConfig(storage_path="/tmp/ray_results", name="qwen-sft"),
)
result = trainer.fit()
print(f"Training finished. Final metrics: {result.metrics}", flush=True)
ray.shutdown()
if __name__ == "__main__":
main()