Ray: Plataforma de Machine Learning

6 May 2024| Tags: Machine Learning, ML, Cluster ML

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

Dashboard

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 …

SO WHAT DO YOU THINK ?

Contact us and tell us your needs
+34 644 237 135

Contact hola@taniwa.es