601 - Inteligencia Artificial en local con Docker

601 - Inteligencia Artificial en local con Docker

Tener una #IA local en #docker y #linux es sencillo y tiene muchas posibilidades, crear un #asistente virtual está mas cerca que nunca y a tu alcance

1:25
-3:15

En este episodio te traigo de nuevo la Inteligencia Artificial, pero en este caso en local y en Docker, para no tener que compartir nuestros datos con un tercero. La cuestión, es que hasta la fecha, siempre había utilizado APIs de terceros para ver que opciones y posibilidades tenía con esto de la IA. Sin embargo, quería ir un paso mas allá. Quería experimentar y conocer la posibilidad de tener una IA en local. Ver cuan lejos estamos de esto. Cuanto de lejos estamos de tener un asistente virtual que nos quite de aquellos trabajos y rutinas monótonas que no nos aportan nada. Y si, la impresión que me he llevado es muy positiva, y veo que no estamos nada lejos de esto. Ahora ya tenemos el software, que seguro que tiene margen de mejora, pero ya está. Ahora nos falta hardware especializado que se dedique únicamente a esto. Así, en este episodio te quiero contar sobre mi prueba de concepto con la Inteligencia Artificial en local con Docker.

Inteligencia Artifical en local con Docker

La cuestión es que podía haberme tirado a montarlo todo en mi propio equipo, directamente, pero teniendo Docker, porque hacerlo de otra forma. Así, salvo la parte correspondiente a la lectura de las respuestas, a la conversión de texto a voz, el resto de piezas están montadas con Docker, y lo cierto es que ha sido algo relativamente sencillo.

Ya le dedicaré un artículo a explicar como he configurado Docker para poder utilizar la tarjeta de vídeo. Pero salvo esto, todo lo que he estado haciendo lo puedes encontrar a continuación.

Las piezas del asistente

Para construir este asistente virtual, he utilizado diferentes piezas que me permiten recorrer el camino desde que le hablo, hasta que me responde mediante su propia voz. Los pasos a seguir son,

  1. Transcribir las instrucciones
  2. Procesar las instrucciones
  3. Convertir la respuesta de texto a voz

La transcripción

Para realizar la transcripción estoy utilizando faster-whisper, que es una reimplementación del modelo de OpenAI Whisper. Esta implementación es hasta cuatro veces mas rápida para la misma precisión y con un consumo de recursos mas reducidos. Esto tanto lo utilices con CPU como con GPU.

Para que te hagas una idea, aquí tienes unos números que te pueden dar una idea de la magnitud que estamos hablando,

ImplementationPrecisionBeam sizeTimeMax. GPU memoryMax. CPU memory
openai/whisperfp1654m30s11325MB9439MB
faster-whisperfp16554s4755MB3244MB
faster-whisperint8559s3091MB3117MB

Executed with CUDA 11.7.1 on a NVIDIA Tesla V100S.

En concreto yo he estado utilizando una imagen Docker llamada Whisper ASR Websesrvice que te proporciona una API con la que poder relacionarte de forma sencilla. Así el docker-compose.yml, en mi caso ha sido el siguiente,

services:
  whisper:
    image: onerahmet/openai-whisper-asr-webservice:latest-gpu
    container_name: whisper
    restart: always
    init: true
    environment:
      ASR_MODEL: large-v1
      ASR_ENGINE: faster_whisper
      ASR_MODEL_PATH: /data/whisper
    volumes:
      - data:/data/whisper
    ports:
      - 9000:9000
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

volumes:
  data: {}

Fíjate que estoy utilizando el modelo large-v1 y a pesar de eso, los resultados que se obtienen son excelentes. Por otro lado, también estoy utilizando la GPU para conseguir mas velocidad y que mi equipo esté dentro de los parámetros de temperatura recomendados.

Así para convertir un audio en formato mp3, tan solo tengo que utilizar una llamada a la API, como lo que muestro a continuación,

curl -s -X 'POST' \
        'http://localhost:9000/asr?encode=true&task=transcribe&language=es&vad_filter=false&word_timestamps=false&output=txt' \
        -H 'accept: application/json' \
        -H 'Content-Type: multipart/form-data' \
        -F "audio_file=@${AUDIO_FILE};type=audio/mpeg"

Esto mismo, también lo puedes hacer desde Python, utilizando requests de la siguietne forma,

def transcribe():
    params = {
        "encode": True,
        "task": "transcribe",
        "language": "es",
        "vad_filter": False,
        "word_timestamps": False,
        "output": "txt"
    }
    headers = {
        "accept": "application/json"
    }
    files = {"audio_file": open(FILENAME, "rb"), "type": "audio/mpeg"}
    response = requests.post("http://localhost:9000/asr", params=params,
                             headers=headers, files=files)
    if not response.ok:
        raise Exception(response.text)
    return response.text

Procesar

El siguiente paso es procesar las instrucciones que le damos a nuestro modelo de lenguaje. Para este segundo paso, he utilizado Open WebUI.

Open WebUI es una interfaz web auto administrada y extensible, con multitud de características, y con una interfaz de usuario que permite tener una experiencia lo mas similar a ChatGPT, que actualmente es el estándar de facto. Con esta solución, puedes trabajar directamente con tu propia Inteligencia Artificial sin necesidad de conexión al mundo exterior. Soporta varios ejecutores LLM, incluyendo Ollama y…

En este segundo proceso, he levantado dos servicios, uno correspondiente al propio modelo, con el que puedo interactuar vía API. El segundo de los servicios es una interfaz web, con un aspecto muy similar al del ChatGPT. Realmente, para el proceso que estoy haciendo, no es necesario este segundo servicio, sin embargo, quiero ver el comportamiento, y conocer de primera mano cual es la experiencia de usuario.

El docker-compose.yml que he utilizado es el siguiente,

services:
  ollama:
    image: ollama/ollama
    container_name: ollama
    restart: unless-stopped
    init: true
    tty: true
    ports:
      - 11434:11434
    volumes:
      - ollama:/root/.ollama
    # GPU support
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: open-webui
    restart: unless-stopped
    init: true
    volumes:
      - open-webui:/app/backend/data
    depends_on:
      - ollama
    ports:
      - 8080:8080
    environment:
      - 'OLLAMA_BASE_URL=http://ollama:11434'
      - 'WEBUI_SECRET_KEY='
    extra_hosts:
      - host.docker.internal:host-gateway


volumes:
  ollama: {}
  open-webui: {}

En este segundo caso, también he utilizado una pequeña función en Python que me permite enviar lo que necesito procesar al modelo de lenguaje. En este caso es la siguiente,

def process(messages):
    payload = {
        "model": "llama3",
        "stream": False,
        "messages": messages
        }
    headers = {
        "Content-Type": "application/json",
        "accept": "application/json"
    }
    response = requests.post("http://localhost:11434/api/chat",
                             headers=headers, json=payload)
    if not response.ok:
        raise Exception(response.text)
    return response.json()["message"]

Para alimentar el proceso, messages es un diccionario que se va completando con cada una de las respuestas que va dando tanto el modelo de lenguaje como yo. De esta manera en cada momento se envía la conversación completa.

De texto a voz

Esta última parte, no la tengo en Docker, al menos de momento. Ya veremos si en un siguiente paso, la Dockerizo o no. Para este caso estoy utilizando una herramienta que es llama piper.

Esta herramienta en el caso de ArchLinux, la puedes instalar directamente desde los repositorios AUR, y tienes distintas voces para ver y descargar en Hugging Face.

Para este último proceso, también he utilizado Python con la siguiente función,

def read(text):
    echo = local["echo"]
    piper_tts = local["piper-tts"]
    pw_play = local["pw-play"]
    model = "/data/voices/es_ES-davefx-medium.onnx"
    chain = echo[text] | \
        piper_tts["--model", model, "-s", "1", "--output-raw"] | \
        pw_play["--rate", "22050", "--channel-map", "LE", "-"]
    print(chain)
    result = chain()
    print(result)

Conclusión

Con independencia de la experiencia, lo cierto es que me he llevado unas muy buenas impresiones. Esto está muy cerca de convertirse en un verdadero asistente virtual, con la posibilidad de hacer muchas de las tareas monótonas y que menos valor nos aportan.

Sin lugar a dudas, lo que mas me ha impresionado es la conversión de voz a texto y de texto a voz. Algo que hasta el momento siempre lo he visto como robótica. Ahora ya no tiene nada que ver, con lo que hemos estado viendo, o mejor dicho escuchando, hasta el momento.

Sin embargo, la conclusión clara a la que he llegado, es que es imprescindible tener una tarjeta gráfica para poder exprimir esto con cierta solvencia.


Más información,

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *