Interaktive Coding-Notebooks mit Elixir, Nx und Livebook

27.09.2023Raffael Schneider
Tech Elixir Livebook Numerical Elixir TensorFlow GPU PyTorch ML-Ops

Seit seinen bescheidenen Anfängen hat sich Elixir zu einer beliebten Programmiersprache entwickelt, die sich auf die Anforderungen moderner, simultan laufender verteilter Systeme konzentriert. Wir haben hier bei b-nova schon mehrmals über Elixir und das Elixir-Ökosystem geschrieben und finden, dass es verschiedene Problemdomänen besonders gut löst. Beispielsweise lassen sich Webapplikationen effizient und einfach in Elixirs bekanntem Web-Framework Phoenix abbilden.

Es gibt innerhalb des Ökosystems und dessen Community seit geraumer Zeit die Absicht, die Welt der wissenschaftlichen Programmierung und Datenanalyse zu erobern. Der Deckmantel dieses Vorhabens ist das sogenannte Projekt Nx, was einfach für Numerical Elixir steht. Unter Nx fallen unterschiedliche Subprojekte, darunter auch Livebook, eine Art Alternative zu dem bekannten Jupyter Notebook. In diesem Blogartikel werden wir auf Nx wie auch Livebook näher eingehen und zeigen, was man damit alles machen kann.

Elixir versteht auch Numerical Computing dank Nx

Der Vorstoß von Elixir in das Gebiet der numerischen Berechnungen war weder plötzlich noch unerwartet. Die Einführung von Projekten wie Matrex, einer Elixir-Bibliothek für Matrixberechnungen, die eine C-basierte CBLAS-Implementierung aufwies, zeigte das Potenzial von Elixir in dieser Nische und inspirierte die Community, eine umfassendere und robustere Lösung anzustreben.

So entstand das Nx-Projekt. Nx steht, wie bereits oben erwähnt, für Numerical Elixir und ist einfach gesagt eine Bibliothek für numerisches Computing. Gerade in den letzten Jahren, als ML und AI besonders bekannt wurden, nicht zuletzt durch ChatGPT, zielt das Projekt natürlich darauf ab, Elixir in Bereichen wie maschinellem Lernen, künstlicher Intelligenz und Data Science zu etablieren.

Im Herzen von Nx stehen Tensoren, die in vielen modernen wissenschaftlichen und maschinellen Lernanwendungen eine zentrale Rolle spielen. Ohne hier allzu tief in die Materie einzusteigen, sei daran erinnert, dass ein Tensor einfach ein multidimensionales Array ist. Nx bietet eine Reihe von Operationen sowie Abstraktionen an, um auf diesen Tensoren CPU-effizient zu arbeiten. Nx kann auch automatische Differenzierung durchführen, was es für maschinelles Lernen und ähnliche Anwendungen nützlich macht, ähnlich wie NumPy, PyTorch oder TensorFlow es tun.

José Valim, der Schöpfer von Elixir, der auch maßgeblich am Nx-Projekt beteiligt ist, hat Nx auf Hacker News wie folgt zusammengefasst:

“When we started the Numerical Elixir effort, we were excited about the possibilities of mixing projects like Google XLA’s (from Tensorflow) and LibTorch (from PyTorch) with the Erlang VM abilities to run concurrent, distributed, and fault-tolerant software.” (Quelle: https://news.ycombinator.com/item?id=35525661)

Oder zu Deutsch: “Als wir das Numerical Elixir-Projekt starteten, waren wir begeistert von den Möglichkeiten, Projekte wie Google XLA (von Tensorflow) und LibTorch (von PyTorch) mit den Fähigkeiten der Erlang-VM zur Ausführung von paralleler, verteilter und fehlertoleranter Software zu kombinieren.”

Diese Anbindung erfolgt über das Erlang-System NIF (Native Implemented Functions), das es Elixir-Programmen ermöglicht, native Funktionen in anderen Sprachen wie C oder C++ aufzurufen. Konkret verwendet Nx die Bibliothek XLA (Accelerated Linear Algebra), eine spezialisierte Bibliothek für lineare Algebra, die von Google entwickelt und als Teil des TensorFlow-Projekts veröffentlicht wurde. XLA wurde entwickelt, um hochleistungsfähige lineare Algebraberechnungen auf einer Vielzahl von Hardware-Plattformen durchzuführen, einschließlich CPUs, GPUs und sogar TPUs (Tensor Processing Units).

Kurzer Vergleich mit PyTorch

Hier ist ein einfacher Code-Snippet in PyTorch, der die Verwendung einer Tensor-Operation veranschaulicht. Für dieses Beispiel nehmen wir an, dass wir zwei Tensoren haben und einige grundlegende Operationen darauf ausführen möchten.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import torch

# Erstellt zwei Tensoren
tensor1 = torch.tensor([[1, 2], [3, 4]])
tensor2 = torch.tensor([[5, 6], [7, 8]])

# Führt einige grundlegende Operationen aus
sum = tensor1 + tensor2
product = tensor1 * tensor2
difference = tensor1 - tensor2

# Druckt die Ergebnisse
print('Sum:\n', sum)
print('Product:\n', product)
print('Difference:\n', difference)

Wenn dieses Python-Skript ausgeführt wird, solltest du folgende Ausgabe erhalten:

1
2
3
4
5
6
7
8
9
Sum:
 tensor([[ 6,  8],
        [10, 12]])
Product:
 tensor([[ 5, 12],
        [21, 32]])
Difference:
 tensor([[-4, -4],
        [-4, -4]])

Hier ist der äquivalente Code, um dieselben Operationen in Elixir’s Nx durchzuführen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
defmodule MyNxModule do
  require Nx.Defn

  import Nx.Defn

  defn tensor_operations do
    # Erstellt zwei Tensoren
    tensor1 = Nx.tensor([[1, 2], [3, 4]])
    tensor2 = Nx.tensor([[5, 6], [7, 8]])

    # Führt einige grundlegende Operationen aus
    sum = Nx.add(tensor1, tensor2)
    product = Nx.multiply(tensor1, tensor2)
    difference = Nx.subtract(tensor1, tensor2)

    {sum, product, difference}
  end
end

In diesem Code erstellen wir zwei 2x2-Tensoren, tensor1 und tensor2, mit Hilfe der Nx.tensor()-Funktion. Wir führen dann eine Reihe von Operationen auf diesen Tensoren aus: Addition (Nx.add()), Multiplikation (Nx.multiply()) und Subtraktion (Nx.subtract()). Man beachte, dass diese Funktionen nicht die Tensoren selbst verändern (da Elixir eine immutability-basierte Sprache ist), sondern neue Tensoren zurückgeben, die das Ergebnis der Operationen darstellen.

Da die Elixir-Runtime, die sogenannte BEAM, eine interaktive Prompt bietet, werten wir hier die Ergebnisse des MyNxModule wie folgt aus:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
iex> MyNxModule.tensor_operations() 
{#Nx.Tensor<
  s64[2][2]
  [
    [6, 8],
    [10, 12]
  ]
>, #Nx.Tensor<
  s64[2][2]
  [
    [5, 12],
    [21, 32]
  ]
>, #Nx.Tensor<
  s64[2][2]
  [
    [-4, -4],
    [-4, -4]
  ]
>}

Hier sehen wir, dass die tensor_operations()-Funktion ein Tuple mit drei Tensoren zurückgibt, die die Summe, das Produkt und die Differenz der ursprünglichen Tensoren repräsentieren.

Wie Sie sehen können, sind die Grundoperationen in Nx sehr ähnlich zu denen in PyTorch. Beide verwenden Tensoren als grundlegende Datenstruktur und bieten ähnliche APIs für die Arbeit mit diesen Tensoren. Der Hauptunterschied besteht darin, dass PyTorch in Python geschrieben ist und daher die syntaktischen und semantischen Eigenschaften dieser Sprache nutzt, während Nx in Elixir geschrieben ist und die Eigenschaften dieser Sprache nutzt, wie z.B. Immutabilität und Pattern Matching.

Das Nx-Ökosystem

Nx ist in erster Linie die Kernbibliothek zur Berechnung von numerischen Problemen, insbesondere Tensoren. Aber Nx ist auch ein Dachprojekt für weitere Subprojekte.

  • EXLA: EXLA ist die Elixir Client-Bibliothek für Googles XLA (Accelerated Linear Algebra), eine spezialisierte Compilerbibliothek für lineare Algebra und maschinelles Lernen. EXLA ermöglicht es Elixir-Programmen, XLA zur effizienten Ausführung von Nx-Operationen zu nutzen, insbesondere auf Hardwarebeschleunigern wie GPUs und TPUs.
  • Axon: Axon ist eine Bibliothek für neuronale Netzwerke und Deep Learning, die auf Nx aufbaut. Es bietet eine High-Level-API zum Definieren, Trainieren und Ausführen von neuronalen Netzwerken. Axon unterstützt auch viele gängige Merkmale von Deep-Learning-Frameworks, wie verschiedene Arten von Layern, Aktivierungsfunktionen, Optimierungsverfahren und Verlustfunktionen.
  • Explorer: Im Kontext von Elixir und Nx könnte Explorer potenziell ein Werkzeug für Datenexploration und Visualisierung sein, das es ermöglicht, Datensätze und die Ergebnisse von Berechnungen und Modelltrainings zu “erkunden”.
  • Scholar: Scholar ist eine Bibliothek für traditionelle maschinelle Lernwerkzeuge, die auf Nx aufbaut. Sie implementiert verschiedene Algorithmen für Klassifizierung, Regression, Clustering, Dimensionsreduktion, Metriken und Vorverarbeitung.
  • Bumblebee: Bumblebee ist eine Bibliothek, die vortrainierte neuronale Netzwerkmodelle auf Basis von Axon bereitstellt. Sie beinhaltet eine Integration mit HuggingFace-Modellen, was es jedem ermöglicht, Modelle herunterzuladen und ML-Aufgaben mit nur wenigen Codezeilen durchzuführen. Mit Bumblebee können Benutzer einfach leistungsfähige Modelle wie BERT für ihre eigenen Anwendungen verwenden.
  • Livebook: Livebook ist eine webbasierte Oberfläche für interaktive und kollaborative Programmierung mit Elixir. Es ähnelt Jupyter-Notebooks und ermöglicht es Benutzern, Elixir-Code in einem interaktiven Format mit Text, Code, Live-Ausgabe und Visualisierungen zu schreiben und auszuführen. Livebook nutzt Nx und seine Subprojekte, um leistungsstarke Werkzeuge für wissenschaftliches und datengetriebenes Computing in Elixir bereitzustellen. Livebook wird in diesem TechUp unser Einstiegspunkt in die Welt von Elixir und Nx sein.

Wir hatten José Valim schon erwähnt gehabt, er selber hat das Nx-Ökosystem wie im folgenden Screenshot ersichtlich zusammengefasst:

Screenshot 2023-08-01 at 09.07.52.png

Von Nx zu Livebook

Mit Nx hat die Elixir-Community ihre Ambitionen im Bereich des numerischen Computings unter Beweis gestellt. Aber es fehlt noch was wichtiges dabei, nämlich das Tooling, welches sich im Python-Ökosystem schon lange etabliert hat. Sie wollten eine vollständige, interaktive und benutzerfreundliche Umgebung schaffen, die den gesamten Datenanalyse-Workflow unterstützt. Damit war die Idee von Livebook geboren.

Eigenschaften von Livebook

  • Interaktives Coding: Wie andere Notebook-Tools auch ermöglicht Livebook es, Code und Kommentare in einer einzigen Datei zu kombinieren, die in verschiedene Abschnitte, so genannte Zellen, unterteilt ist. Es lässt sich Code in einer Zelle schreiben und diesen dann ausführen, um das Ergebnis sofort in derselben Zelle anzuzeigen. Diese Interaktivität ermöglich exploratives Programmieren und erleichtert das Debuggen und Testen von Code.

  • Unterstützung für Elixir und Erlang/OTP: Livebook ist in Elixir geschrieben und nutzt vollständig die Vorteile dieser Sprache und des Erlang/OTP-Systems. Du kannst beliebigen Elixir-Code in Livebook ausführen und auf die gesamte Elixir-Standardbibliothek sowie auf Drittanbieter-Pakete zugreifen. Du kannst auch die Funktionen der Erlang/OTP-Plattform nutzen, wie z. B. die Fähigkeit, gleichzeitige Prozesse zu erstellen, und den Zugriff auf OTP-Behaviours wie GenServer und Supervisor.

  • Integration mit Nx: Livebook wurde entwickelt, um eng mit Nx zusammenzuarbeiten. Du kannst Nx-Code direkt in Livebook ausführen und die Ergebnisse in einer leicht verständlichen, formatierten Form anzeigen. Das macht Livebook zu einem ausgezeichneten Werkzeug für die Arbeit mit numerischen Daten und maschinellem Lernen.

  • Datenvisualisierung: Eine der herausragenden Eigenschaften von Livebook ist die eingebaute Unterstützung für Datenvisualisierung. Livebook kann verschiedene Arten von Diagrammen und Grafiken erstellen, um deine Daten zu visualisieren und dir dabei zu helfen, Muster und Trends in deinen Daten zu erkennen. Diese Visualisierungen sind interaktiv, so dass du zoomen, scrollen und andere Aktionen ausführen kannst, um die Daten aus verschiedenen Perspektiven zu betrachten.

  • Live-Updates und Multi-Session: Livebook unterstützt Live-Updates, so dass mehrere Benutzer gleichzeitig an einem Notebook arbeiten und die Änderungen der anderen in Echtzeit sehen können. Ursprünglich waren Livebook-Apps für lang laufende Anwendungen gedacht, wobei immer nur eine Instanz einer Livebook-App gleichzeitig lief. Da Livebook jedoch nativen Support für mehrere Benutzer hat, teilen alle Benutzer, die auf eine App zugreifen, dieselbe Instanz. Die neue Version führt Multi-Session Livebook-Apps ein, bei denen jeder Benutzer eine exklusive Version der App für sich erhält. Diese Apps können ähnlich wie Single-Session-Apps beliebig lange laufen, werden aber meist Benutzereingaben verarbeiten, mehrere Anweisungen ausführen und dann beendet werden.

  • Persistenz und Portabilität: Livebook-Notebooks sind einfach nur Elixir-Script-Dateien mit der Endung .livemd. Das lässt sich gut mit Git versionieren und liest sich einfacher als das JSON-basierte Dateiformat von Jupyter .ipynb.

  • Natives Secrets Management: Livebook verfügt seit geraumer Zeit über die Möglichkeit, Secrets in Livebook verschlüsselt abzulegen.

  • Livebook Desktop-App: Es gibt seit einem Jahr auch die Möglichkeit, Nicht-Techies die Möglichkeit zu geben, bestehende Livebooks anzuschauen und auszuführen, ohne dass sie weder die Elixir noch die Erlang/OTP aufsetzen müssten. Das bringt Livebook einer ganz neuen Nutzergruppe näher.

Lokales Setup von Livebook

Die Installation von Livebook ist ein recht einfacher Prozess und kann auf verschiedenen Betriebssystemen wie GNU/Linux, macOS oder auch Windows durchgeführt werden. Hier ist eine Schritt-für-Schritt-Anleitung zur Installation von Livebook auf einem System mit Elixir und Erlang/OTP:

Schritt 1: Elixir und Erlang/OTP installieren

Livebook ist in Elixir geschrieben und benötigt daher Elixir und Erlang/OTP, um zu laufen. Am besten überprüfen wir, ob Elixir bereits auf unserem System installiert ist. Falls man mal eines der Elixir-TechUps durchgemacht hat, ist die Wahrscheinlichkeit hoch. Falls nicht, lässt sich das mit folgendem Befehl kurz feststellen:

1
$ elixir --version

Wenn Elixir und Erlang/OTP nicht bereits installiert sind, kann man Elixir und die Erlang/OTP wie in den folgenden zwei Links beschrieben installieren:

ℹ️ Tipp: Ich persönlich nutze hierfür asdf als Versionsmanager, mit dem sich ganz einfach und elegant genau die richtige Runtime einer gegeben Technologie oder Tech-Stacks anziehen lässt. Wir haben natürlich auch ein TechUp dazu: Vereinfache deinen Workflow mit einem Versionsmanager wie asdf | b-nova.

Schritt 2: Das Livebook-Repository von GitHub klonen

Jetzt klonen wir das Livebook-Repository von GitHub. Das geht mit folgendem Befehl:

1
$ git clone https://github.com/livebook-dev/livebook.git

Dieser Befehl erstellt einen neuen Ordner namens livebook im aktuellen Verzeichnis und kopiert alle Dateien aus dem Livebook-Repository in diesen Ordner.

Schritt 3: In das Livebook-Verzeichnis wechseln und die Abhängigkeiten installieren

Nun wechseln wir in das livebook-Verzeichnis und installieren die Elixir-Abhängigkeiten, welche durch das Projekt (siehe mix.exs) vorgegeben sind, mit den folgenden Befehlen:

1
2
cd livebook
mix deps.get --only prod

Schritt 4: Livebook starten

Jetzt können wir Livebook mit dem folgenden Befehl starten:

1
MIX_ENV=prod mix phx.server

Dieser Befehl startet den Livebook-Server auf dem lokalen System. Falls die folgende Ausgabe ersichtlich ist, ist das Starten von Livebook erfolgreich geglückt. Die Ausgabe besagt, dass der Server läuft und auf welcher URL dieser erreichbar ist,

1
2
3
...
Generated livebook app
[Livebook] Application running at http://localhost:8080/?token=jaiusa5vojhdmejopubxcegdw7kttgyy

Die angegeben URL http://localhost:8080 gilt es in einem Webbrowser der Wahl zu öffnen. Am besten noch mit dem Token, ansosten kann man einfach den Token in der Web-Oberfläche übertragen. In diesem Falle jaiusa5vojhdmejopubxcegdw7kttgyy (wird bei jedem Start neu generiert und der Wert wird anders sein).

Screenshot 2023-07-31 at 15.18.06.png

ℹ️ Zu beachten: Livebook ist ein Open-Source-Projekt und es wird stets daran weiterentwickelt. Am besten auf dem GitHub-Repository schauen, falls etwas nicht funktionieren sollte.

Noch was zu CUDA und Grafikkarten

Bevor wir ins Befüllen von unseren Livebooks einsteigen, sollte überprüft werden, ob der verwendete Rechner eine unterstützte NVIDIA-Grafikkarte verbaut hat, da diese die Berechnungen erheblich beschleunigen kann. Wie ihr vielleicht bereits wisst, nutzen numerische Operationen nicht selten Nachkommastellen-Algorithmen, welche enorm von GPUs profitieren. Falls eine unterstützte Grafikkarte vorhanden ist, sollte man EXLAs Anleitung zur Auswahl von XLA_TARGET befolgen und die passende CUDA-Version für die Grafikkarte installieren. Nach der Installation von CUDA kann die korrekte XLA_TARGET-Umgebungsvariable wie folgt gesetzt werden:

Value Target environment
tpu libtpu
cuda120 CUDA 12.0+, cuDNN 8.8+ (recommended)
cuda118 CUDA 11.8+, cuDNN 8.7+ (recommended)
cuda114 CUDA 11.4+, cuDNN 8.2+
cuda111 CUDA 11.1+, cuDNN 8.0.5+
cuda CUDA x.y, cuDNN (building from source only)
rocm ROCm (building from source only)

Die XLA TARGET-Umgebungsvariable sollte dann im Livebook festgelegt werden. Beispielsweise mit dem Wert cuda120.

Wenn keine unterstützte NVIDIA-Grafikkarte vorhanden ist, ist das kein Problem. Auch neuronale Netze können auch auf der CPU genutzt werden, allerdings werden die Berechnungen dann langsamer sein. Zum Beispiel wird auf einem MacBook mit einer M1 Max-CPU die Generierung von zwei Bildern mit Stable Diffusion mehrere Minuten dauern. Alle anderen Aufgaben, wie z.B. die Textgenerierung, werden jedoch innerhalb weniger Sekunden abgeschlossen. Es wird also zwar langsamer sein, aber es wird funktionieren!

Was Livebook anders macht

Auf Hacker News kam mal die Frage auf, warum gerade die Elixir-Runtime, die sogenannte BEAM, sich insbesondere gut eignet für Datenprozessierung. José Valim hat darauf geantwortet, dass Erlang Konkurrenz innerhalb eines Betriebssystemprozesses und Verteilung über mehrere Nodes bietet. In Livebook wird “Concurrency” verwendet, um die Kommunikation mit Ergebnissen während der Ausführung zu ermöglichen. Durch “Branched Sections” können mehrere Experimente im selben Notebook parallel ausgeführt werden. Verteilung in Erlang ist ähnlich wie Konkurrenz und er zeigt ein Beispiel, wie man ein ML-Modell von concurrent zu verteilt ändern kann. Er betont, dass es sinnvoll ist, standardmäßig auf Concurrency zu setzen, da serielle Verarbeitung teurer sein kann, insbesondere bei der Datenanalyse. Die funktionale Programmierung in Erlang ist für die Datenanalyse vorteilhaft, und Livebook-Notebooks sind einfach reproduzierbar.

Screenshot 2023-08-01 at 09.15.42.png

Siehe auch dieses Video zu “Meta programmable functional notebooks with Livebook” auf Youtube.

Fazit

Das Nx-Projekt ist in meinen Augen ein richtiger Senkrechtstarter und hat allemal das Zeug und die Reife um etablierten Toolings Konkurrenz zu machen. Es ist mir klar, dass Elixir eine Nischenexistenz geniesst und definitiv kein weit verbreitetes Ökosystem ist. Und dennoch muss man Nx und insbesondere Livebook eine Qualität und Poliertheit der Features eingestehen. 🔥