Introducción
Ray es un framework de Python que permite la ejecución de aplicaciones Python en paralelo y de forma distribuida. Está diseñado principalmente para ser usado en aplicaciones de Machine Learning y AI, pero también se puede usar en cualquier aplicación Python que requiera computación distribuida.
Se puede montar un cluster de Ray para ejecutar las aplicaciones desplegándolo en cualquier infraestructura: en las nubes de AWS, GCP, Azure, en un Kubernetes, On Premise, etc.
Os vamos a contar como hemos desplegado un cluster de Ray con contenedores en una sola máquina y como se ha usado para servir un modelo de Hugging Face que traduce textos del inglés al francés.
Machine Learning en Ray
Las librerías de Machine Learning de Ray simplifican el ecosistema de frameworks, plataformas y herramientas de aprendizaje automático al proporcionar una experiencia de código abierto y unificada para un aprendizaje automático escalable.
Reducen la fricción del paso de los entornosde desarrollo a los de producción ya que el mismo código Python se escala sin problemas desde un portátil hasta un clúster grande.
Estas librerías permiten usar los frameworks más populares, como XGBoost, PyTorch, Hugging Face, etc y con cambios mínimos en el código poder realizar desde el entrenamiento del modelo hasta hasta el despliegue del mismo.
Las librerias de Ray para Machine Learning son:
- Ray Data: Ray Data abstrae la fragmentación de los datos de entrenamiento, la paralelización de la inferencia sobre estos fragmentos y la transferencia de datos del almacenamiento a la CPU y la GPU utilizando un paradigma de streaming que se adapta mejor a las cargas de trabajo de GPU.
- Ray Train: Ray Train es una biblioteca de alto nivel para el entrenamiento distribuido de los modelos utilizando PyTorch, Hugging Face, TensorFlow, Keras, XGBoost, etc
- Ray Tune: Para la ejecución de experimentos y el ajuste de hiperparámetros a cualquier escala. usando frameworks como PyTorch, XGBoost, TensorFlow, Keras, etc y ejecutando algoritmos como Population Based Training (PBT) y HyperBand/ASHA..
- Ray Serve: Para el despliegue de modelos de Machine Learning. Permite servir modelos de PyTorch, TensorFlow, Scikit-learn, XGBoost, etc en un cluster de Ray.
Despliegue de un cluster de Ray
Para desplegar un cluster de Ray en una sola máquina hemos usado Docker con Docker Compose.
Creamos una imagen de Docker rayapps:1.0.0 con todas las librerías de Python necesarias para desplegar el modelo de Hugging Face y el cluster de Ray.
1
|
docker build -t rayapps:1.0.0 .
|
Con el siguiente Dockerfile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
FROM rayproject/ray:2.7.0
USER root
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -y vim
RUN apt-get install -y curl zsh
RUN mkdir -p /apps
RUN pip install --upgrade pip
COPY requirements.txt /apps
RUN python -m pip install --no-cache-dir -r /apps/requirements.txt
# It is not necessary locally (we have this directory mounted on the nodes)
# but we need it if we want to deploy it to kubernetes
# https://docs.ray.io/en/latest/serve/production-guide/docker.html
COPY ./apps /apps
|
Y el docker-compose.yml es:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
services:
redis:
image: redis:alpine
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
environment:
- REDIS_REPLICATION_MODE=master
profiles: ["ray_cluster"]
ray-head:
image: ${APPS_IMAGE}
restart: unless-stopped
depends_on:
- redis
ports:
- "${DASHBOARDPORT}:${DASHBOARDPORT}"
- "${APPS_PORT}:${APPS_PORT}"
env_file:
- .env
working_dir: "/apps"
volumes:
- "./apps:/apps"
command: bash -c "ray start --head --dashboard-port=${DASHBOARDPORT} --dashboard-host=0.0.0.0 --block"
shm_size: 2g
privileged: true
deploy:
resources:
limits:
cpus: ${NUM_CPU_HEAD}
memory: ${NUM_MEM_HEAD}
profiles: ["ray_cluster"]
ray-worker:
image: ${APPS_IMAGE}
restart: unless-stopped
depends_on:
- ray-head
- redis
env_file:
- .env
working_dir: "/apps"
volumes:
- "./apps:/apps"
command: bash -c "ray start --address=ray-head:${HEADPORT} --num-cpus=${NUM_CPU_WORKER} --block"
shm_size: 2g
privileged: true
deploy:
mode: replicated
replicas: ${NUM_WORKERS}
resources:
limits:
cpus: ${NUM_CPU_WORKER}
memory: ${NUM_MEM_WORKER}
profiles: ["ray_cluster"]
apps:
container_name: apps
image: ${APPS_IMAGE}
build:
context: .
dockerfile: Dockerfile
shm_size: 11gb
privileged: true
user: root
entrypoint: "bash"
working_dir: "/apps"
volumes:
- "./apps:/apps"
#command: zsh -c "while sleep 1000; do :; done"
deploy:
resources:
limits:
cpus: ${NUM_CPU_WORKER}
memory: ${NUM_MEM_WORKER}
profiles: ["apps"]
volumes:
redis_data:
networks:
default:
name: ray_net
|
Las variables de entorno que se usan en el fichero docker-compose.yml son:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
HOST=localhost
APPS_IMAGE=rayapps:1.0.0
HEADPORT=6379
DASHBOARDPORT=8265
APPS_PORT=8000
NUM_WORKERS=1
NUM_CPU_HEAD=2
NUM_MEM_HEAD=2g
NUM_CPU_WORKER=2
NUM_MEM_WORKER=2g
RAY_REDIS_ADDRESS=redis:6379
SANDBOX_IMAGE=sandbox:1.0.0
SANDBOX_PORT=8501
# Your private IP
BACK_HOST_IP=192.168.1.142
|
El cluster de Ray se compone de tres contenedores:
- Redis: Base de datos en memoria que se usa para almacenar los metadatos del cluster de Ray.
- Ray Head: Nodo maestro del cluster de Ray. Se encarga de coordinar el cluster y de servir la interfaz web de monitorización.
- Ray Worker: Nodos de trabajo del cluster de Ray. Se encargan de ejecutar las tareas de Python que se envían al cluster.
Al que hemos añadido apps que nos sirve para que desde dentro de ese conenedor realizar los despliegues con los herramientas cliente de Ray, como luego explicaremos.
Para desplegar el cluster de Ray usando el docker-compose haremos:
1
|
docker-compose --profile ray_cluster up
|
Y veremos tres contenedores levantados:
1
2
3
4
5
6
7
|
docker ps
CONTAINER_ID IMAGE COMMAND PORTS NAMES
68b8ba6ed2fd rayapps:1.0.0 "bash -c 'ray start …" ray-worker_1
93c73102f501 rayapps:1.0.0 "bash -c 'ray start …" 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp,
0.0.0.0:8265->8265/tcp, :::8265->8265/tcp ray-head_1
e036dbe667be redis:alpine "docker-entrypoint.s…" 6379/tcp redis_1
|
Desplegar un modelo de Hugging Face
La aplicación del modelo de Hugging Face realizar la traducción de un texto del inglés al francés utilizando el modelo de traducción de Hugging Face y desplegandolo en el cluster de Ray con un Ray Serve.
Su código es el siguiente (model.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import ray
from ray import serve
from fastapi import FastAPI
from transformers import pipeline
app = FastAPI()
@serve.deployment
@serve.ingress(app)
class Translator:
def __init__(self):
# Load model
self.model = pipeline("translation_en_to_fr", model="t5-small")
@app.post("/")
def translate(self, text: str) -> str:
# Run inference
model_output = self.model(text)
# Post-process output to return only the translation text
translation = model_output[0]["translation_text"]
return translation
translator_app = Translator.bind()
|
Creamos un archivo app.yaml para definir como vamos a desplegar la aplicación en el cluster de Ray:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
proxy_location: EveryNode
http_options:
host: 0.0.0.0
port: 8000
grpc_options:
port: 9000
grpc_servicer_functions: []
applications:
- name: app1
route_prefix: /
import_path: model:translator_app
runtime_env: {}
deployments:
- name: Translator
num_replicas: 1
ray_actor_options:
num_cpus: 0.2
num_gpus: 0.0
|
Nos metemos en el contenedor apps
1
|
docker-compose run apps
|
Y desplegamos la aplicación con el comando:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
serve deploy app.yaml -a http://ray-head:52365
serve status -a http://ray-head:52365
proxies:
d4b4c5cfe0bc7e7b0b4da780f26624717a1843301ce20f60914d0794: HEALTHY
a7fea79ca8e174da8c6c6fbd74343f6e93674bfc03156d92c23363c4: HEALTHY
applications:
app1:
status: DEPLOYING
message: ''
last_deployed_time_s: 1717098678.7034333
deployments:
Translator:
status: UPDATING
replica_states:
STARTING: 1
message: ''
|
Y fuera del contenedor ya podemos probar la aplicación en el puerto 8000:
1
2
3
|
curl http://localhost:8000?text=Hello -X 'POST' -H 'Content-Type: application/json'
"Bonjour"
|
También podemos ver la interfaz web de monitorización del cluster de Ray en el puerto http://localhost:8265

Siguientes Pasos
En este post hemos visto que es muy fácil crear un cluster de Ray con Dockers y desplegar un modelo de Machine Learning para consumirlo con una API REST.
Los próximos pasos van a ser intentar desplegar el cluster en máquinas con GPU y usar las librerías de Ray Train para hacer fine tunning de un modelo de Hugging Face para clasificar textos.
Os vamos contando …