Mojo 🔥: un nuevo lenguaje de programación para Inteligencia Artificial

Mojo 🔥: un nuevo lenguaje de programación para Inteligencia Artificial

Otra vez la misma historia. La informática vive en una especie de condena de Sísifo, pues cada cierto tiempo se observa la misma historia, una especie de eterno retorno: el surgimiento de un nuevo lenguaje de programación, un nuevo framework, una nueva biblioteca, etc. y, en la mayoría de los casos, poco hay de nuevo.

En cambio, hay mucho de viejo: Una especie de disfraz para contentar a los ansiosos por nuevas herramientas: «Sólo me interesa mantenerme actualizado» es el mantra común. Pero no todo es negativo. En ese tsunami de tecnologías que emergen, de vez en cuando, una se lleva las miradas, no tanto por lo que promete —todos se ensalzan por marketing— sino por quién lo promete.

A principio de 2023 apareció Mojo, un lenguaje de programación para Inteligencia Artificial (IA) creado por la empresa Modular, liderado por Chris Lattner, creador de Swift (Apple) y de LLVM (la principal herramienta para construir compiladores).

Mi primera impresión fue dudar, pues cada cierto tiempo aparece un nuevo lenguaje que dice ser gran alternativa a los ya establecidos para IA (a saber: Python, R, C++ e incluso Julia). Aunque en esta ocasión fue diferente, pues al ver el nombre de Lattner —quien es un gran ingeniero—, al menos podemos estar seguros de algo: detrás de Mojo hay calidad.

En este artículo revisaremos cinco características de Mojo que lo hacen merecedor de atención. Luego daré algunas consideraciones personales con respecto al lenguaje y concluiré con algunas sugerencias.

Características

Mojo 🔥 se presenta como un superset de Python (es decir, mantiene sus mismas características e incorpora nuevas), con un rendimiento similar a C.

A continuación, presentaré sus características más destacadas que, según sus autores, sostienen dicha afirmación.

Importante: al momento de que escribo este artículo (mayo de 2023), sólo es posible usar Mojo a través de una lista de espera, así que debemos aguardar un tiempo para que esté disponible para todos.

Mojo tiene el eslogan de «a new programming language for all AI developers» (nuevo lenguaje de programación para todos los desarrolladores de IA). Con eso prevemos entonces que todas sus nuevas características se centran en dar énfasis a temas como el rendimiento y la paralelización, fundamentales para crear sistemas de IA.

1) Compatibilidad total con Python

El roadmap de Mojo es usar CPython como base y que sea totalmente compatible con el código de Python. Algo similar a cuando apareció C++, donde se incorporaron nuevas características (clases) sin perder la compatibilidad con C.

Ahora bien, Mojo tiene las siguientes diferencias con Python.

  • Añade tipos. Mojo tiene struct estáticas, mientras que Python cuenta con class dinámicas. Además, las struct en Mojo le permiten ganar rendimiento. Dentro de este tipo es posible utilizar las palabras reservadas var y let, donde la primera define una variable mutable y la segunda inmutable.
  • Paralelismo nativo. A diferencia de Python, Mojo está diseñado para hacer más fácil trabajar con paralelismos, similar a lo que ocurre con otros lenguajes como Julia.

Las demás características las mostraré a continuación.

2) Tipos progresivos

Este sistema de tipo le da la responsabilidad al programador sobre qué expresiones dentro de su programa le añaden o no tipos, haciendo variar la garantía del sistema. En otras palabras, en los sistemas de tipos progresivos permiten a los programadores aprovechar las ventajas de los tipos dinámicos, así como lo mejor de los sistemas estáticos donde ellos requieran.

Veamos un ejemplo:

def ordenamiento(v: ArraySlice[Int]):
  for i in range(len(v)):
    for j in range(len(v) - i - 1):
      if v[j] > v[j + 1]:
        swap(v[j], v[j + 1])

Aquí se le asigna el tipo ArraySlice[Int] al argumento v de la función ordenamiento. En Python esto no es posible: lo más similar es asignar un hint (anotación), pero en ningún caso este permite funcionar como un sistema de tipado estático, como sí lo provee Mojo.

Sumado a esto, trae cuestiones más profundas en la implementación del compilador, pues estos tipos progresivos aumentan sus garantías (correctitud) en las porciones del código donde el programador le indicó que lo haga mediante el uso de algún tipo.

Esto se puede apreciar en cómo y dónde surgen los errores.

3) Abstracción de costo cero

Significa que se pueden añadir funcionalidades de alto nivel como metaprogramación, inicialización de estructuras, algoritmos paramétricos (siguiente característica) o cualquier otra sofisticada abstracción sin que sea penalizado en tiempo de compilación (de ahí el costo cero).

struct Par:
  var primero: Int
  var segundo: F32
  
  def __init__(self, primero: Int, segundo: F32):
    self.primero = primero
    self.segundo = segundo

En este ejemplo, la estructura Par tiene dos atributos primero y segundo, ambos con tipado estático, que luego son inicializados en el constructor. Por ejemplo, Python no tiene abstracciones de costo cero, por tanto, la estructura Par como cualquier abstracción tendería a aumentar el tiempo de compilación.

4) Algoritmos paramétricos portables

Permite aprovechar la metaprogramación —que trabaja en tiempo de compilación— para escribir algoritmos agnósticos al hardware y reducir, de esta forma, el código, a través de su tipo SIMD (Single Instruction, Multiple Data), que es un conjunto de instrucciones que representa un vector de bajo nivel en hardware.

Esto evita adaptar el código para operar con distintas arquitecturas de CPU como SSE, AVX-512, NEO, SVE, entre otras compatibles con SIMD.

def exp[dt: DType, elts: Int]
    (x: SIMD[dt, elts]) -> SIMD[dt, elts]:
  x = clamp(x, -88.3762626647, 88.37626266)
  k = floor(x * INV_LN2 + 0.5)
  r = k * NEG_LN2 + x
  return ldexp(_exp_taylor(r), k)

En la función exp se añade el tipo SIMD como argumento de entrada y para su devolución. Este tipo de característica es útil para quienes quieran aplicar optimizaciones de bajo nivel contra el hardware, aprovechándolo de mayor manera y ganando rendimiento en sus sistemas de IA (algo que no suele ser simple en Python).

5) Autojuste integrado al lenguaje

Cuando se trabaja en bajo nivel, hay veces que para acelerar un algoritmo se debe encontrar el correcto ajuste en las dimensiones de los vectores que dependen de cada hardware. Entonces, en vez de buscar y luego evaluar cada posible valor, usé autotune, que hace internamente estas comprobaciones y le devuelve el valor óptimo más adecuado al hardware.

def exp_buffer[dt: DType](data: ArraySlice[dt]):

  # Busca el mejor tamaño del vector desde varias posibilidades
  alias vector_len = autotune(1, 4, 8, 16, 32)
  
  # Lo usa para crear la vectorización
  vectorize[exp[dt, vector_len]](data)

El ejemplo superior se dan cinco alternativas para inicializar las dimensiones de un vector. Sin una función que busque el mejor valor posible, sería preciso realizar pruebas en cada caso, volviéndose una labor tediosa.

Breves consideraciones

En el sitio web de Mojo se muestra una tabla que lo compara con Python 3.10.9, PyPy y Scalar C++ en la implementación del algoritmo Mandelbrot, en cuanto a tiempo de ejecución.

Imagen extraída: https://www.modular.com/mojo

Evidentemente, es sorprendente —como todas las comparaciones presentes en el sitio web de un nuevo lenguaje de programación—. Sin embargo, hay que ser cautelosos con este tipo de gráficos, pues esconden la implementación de cada alternativa, algo crucial para hacer una comparación justa.

Otra consideración interesante de Mojo es con respecto a una decisión que está escrita en su repositorio:

Creemos que un grupo pequeño y unido de ingenieros con una visión compartida puede avanzar más rápido que un esfuerzo comunitario, así que seguiremos incubándolo dentro de Modular hasta que esté más completo.

Una de las ventajas de comenzar un proyecto en una comunidad es que se puede recibir retroalimentación de cualquier parte, pero también no siempre es recomendable cuando se está empezando porque es proclive a tomar malas decisiones de diseño que más tarde no pueden modificarse.

Así, pienso que es una buena decisión dejar el corazón de Mojo en manos de ingenieros especializados en diseño de lenguajes, al menos antes de abrirlo a todos. En este punto, es normal que surja la pregunta: ¿Algún día lo dejarán open source? Ya veremos.

Conclusión

Mojo es acaso el primer lenguaje de programación que se propone la —ardua— tarea de tener compatibilidad total con Python y, entonces, añadir nuevas características y funcionalidades que lo hagan tan eficiente como C.

Además, como se pudo ver en este artículo, Mojo está diseñado para personas que no solo trabajen en el nivel superior de las aplicaciones de IA, sino también para quienes necesitan mayor control a bajo nivel, donde optimizar el hardware es un deber. Así, añade una capa no presente en Python: características de lenguajes de bajo nivel como C.

Y aunque puede parecer demasiado sorprendente teniendo de aval a Chris Lattner —que ya ha demostrado su valía creando Swift y LLVM—, lo mejor que podemos hacer es esperar para ver si nos vuelve a asombrar.


Algunas sugerencias para mantenerte al día con el desarrollo del lenguaje:

  • Considera seguir el Twitter de la empresa Modular y el proyecto en GitHub.
  • Procura añadirte a la lista de espera en este enlace para ser de los primeros en usarlo.
  • Sigue la cuenta del autor principal detrás de Mojo: Chris Lattner.

Referencias

Notas

Los códigos fueron extraídos y adaptados desde la documentación oficial de Mojo.

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.