Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Importante
Esta característica se encuentra en su versión beta. Los administradores del área de trabajo pueden controlar el acceso a esta característica desde la página Vistas previas . Consulte Administrar versiones preliminares de Azure Databricks.
En este ejemplo se ejecuta el ajuste fino distribuido de datos paralelos con Ray TrainTorchTrainer en 8 GPU H100 en un solo nodo. Un script de Bootstrap inicia un clúster de Ray en el nodo y, a continuación, el controlador de Ray Train inicia un trabajo por GPU, ajusta el modelo en DDP y particiona el conjunto de datos entre los nodos de trabajo automáticamente.
Realiza un ajuste fino de un modelo público (Qwen2.5-3B), de modo que se ejecuta tal cual sin necesidad de un token de Hugging Face.
La carga de trabajo hace lo siguiente:
- Carga el proyecto local con
code_source: snapshot. - Inicia una cabeza de Ray con las 8 GPU y, a continuación, ejecuta el controlador ray Train.
- Usa
ray.train.torch.prepare_modelyprepare_data_loaderpara controlar el ajuste de DDP, la ubicación del dispositivo y el muestreo distribuido. - Registra las métricas en MLflow.
Prerequisites
- La CLI
airestá instalada y autenticada. Consulte Instalación de la CLI de AI Runtime.
Diseño del proyecto
Cree un directorio con los siguientes archivos.
ray_train_distributed/
├── train.yaml # air workload config (inline dependencies + Ray bootstrap)
└── train_ray.py # Ray Train TorchTrainer driver + per-worker training
Paso 1: Escribir la carga de trabajo YAML
train.yaml solicita un único GPU_8xH100 nodo. Las dependencias se declaran directamente bajo environment (con el entorno de ejecución version), y el command inicia un clúster de Ray en el nodo y luego ejecuta el driver, por lo que la carga de trabajo no necesita un archivo de dependencias independiente ni un script de lanzamiento:
experiment_name: air-ray-train-distributed
environment:
version: 'AI5'
dependencies:
# AI5 (the databricks-ai runtime) already ships ray, transformers, datasets, and
# huggingface_hub, so they no longer need to be listed here. It does ship fsspec
# 2023.5.0, which is too old for modern huggingface_hub and breaks dataset/model
# downloads, so 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
La instrucción inline command inicia un nodo principal de Ray con todas las GPU del nodo, ejecuta el proceso controlador con python train_ray.py y, a continuación, detiene el clúster. También incluye una rama de nodo de trabajo que se conecta al nodo principal, por lo que el mismo comando sigue funcionando si se escala el trabajo a varios nodos.
Paso 2: Definir el controlador de Ray Train
train_ray.py define un train_func que se ejecuta en cada worker y un main que configura el TorchTrainer para usar todas las GPU del clúster.
prepare_model encapsula el modelo en DDP y lo mueve a la GPU del nodo de trabajo.
prepare_data_loader añade un muestreador distribuido:
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()
El script completo aparece en Script de entrenamiento completo al final de esta página.
Paso 3: Enviar la ejecución
air run -f train.yaml --dry-run
air run -f train.yaml --watch
Paso 4: Inspección de la ejecución
air get run <run-id>
air logs <run-id>
El nodo principal de Ray y el controlador se ejecutan en el nodo 0, por lo que los registros se transmiten desde un único nodo.
Dónde llegan los resultados
Las métricas reportadas con ray.train.report y registradas con MLflow aparecen en el experimento de MLflow cuyo nombre se indica en experiment_name, que puede consultarse en la interfaz de usuario de MLflow del área de trabajo.
Script de entrenamiento completo
El train_ray.py completo para copiar y pegar:
#!/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()