Kurs
Structured Query Language, kurz SQL (oft „Sequel“ ausgesprochen), ist eine deklarative Programmiersprache, mit der in Datenbankmanagementsystemen Daten gespeichert, abgerufen, verwaltet und manipuliert werden.
Sie wurde Anfang der 1970er Jahre von den IBM-Forschern Raymond Boyce und Donald Chamberlain entwickelt, kam jedoch erst 1979 kommerziell auf den Markt, als Relational Software, Inc. (heute Oracle) seine Implementierung vorstellte.
Heute gilt SQL dank seiner Einfachheit und der Fähigkeit, große Datensätze effizient zu verwalten und zu analysieren, weithin als Standard-Relationales Datenbankmanagementsystem (RDBMS) und ist in unserer zunehmend datengetriebenen Welt unverzichtbar.
Doch die datengetriebene Welt entwickelt sich weiter.
Künstliche Intelligenz gewinnt rasant an Bedeutung, und große Sprachmodelle haben sich als äußerst leistungsfähige Werkzeuge für verschiedenste Aufgaben etabliert. Das einzige Problem ist, dass sich die Interaktion mit diesen Sprachmodellen bisweilen wie eine Unterhaltung in einer Fremdsprache anfühlt. Genau hier kommt LMQL ins Spiel.
LMQL wurde vom SRI Lab der ETH Zürich entwickelt und fungiert als persönlicher Übersetzer zwischen Entwicklerinnen und Entwicklern und ihren Sprachmodellen. Konkret bringt LMQL die Stärken von SQL in die Welt der Sprachmodelle und macht Interaktionen damit reibungsloser, effizienter und sogar unterhaltsamer.
Im weiteren Verlauf dieses Tutorials sprechen wir über:
- Was ist LMQL?
- Warum LMQL?
- LMQL einrichten
- Praktische Anwendungsfälle für LMQL
- Grenzen von LMQL
- Best Practices
Was ist LMQL?
LMQL, kurz für Language Models Query Language, ist eine innovative Programmiersprache für Large Language Models (LLMs). Sie verbindet deklarative, SQL-ähnliche Elemente mit einer imperativen Skriptsyntax und bietet so eine strukturierte und intuitive Möglichkeit, Informationen aus LLMs zu extrahieren oder Antworten zu generieren.
Außerdem ist LMQL ein Superset von Python, das heißt, es erweitert Python um neue Funktionen. So lassen sich Prompts in natürlicher Sprache erstellen, die Text und Code kombinieren, wodurch Abfragen flexibler und ausdrucksstärker werden.
Laut der Dokumentation „bietet LMQL eine neuartige Möglichkeit, traditionelle Programmierung mit der Fähigkeit zu verknüpfen, LLMs im Code aufzurufen. Es geht über klassische Templating-Sprachen hinaus, indem es die Interaktion mit LLMs nativ auf Ebene deines Programmcodes integriert.“
Die Programmiersprache wurde von ihren Schöpfern in einem Forschungspapier mit dem Titel Prompting is Programming: A Query Lnague for Large Language Models vorgestellt – als Lösung, um ein Phänomen zu ermöglichen, das sie „LMP“ (Language Model Prompting) nannten.
Zur Einordnung: Große Sprachmodelle zeigen in vielen Aufgaben, etwa bei Fragebeantwortung und Codegenerierung, herausragende Leistungen. Grundsätzlich sind LLMs darin geübt, auf Basis gegebener Eingaben automatisch logische Sequenzen nach statistischen Wahrscheinlichkeiten zu erzeugen.
Diese Fähigkeit lässt sich nutzen, indem man LLMs mit Anweisungen in natürlicher Sprache oder Beispielen „promptet“, um die Ausführung verschiedener nachgelagerter Aufgaben anzustoßen. Fortgeschrittene Prompting-Techniken ermöglichen sogar Interaktionen zwischen Nutzenden, dem Sprachmodell und externen Tools wie Taschenrechnern.
Die Herausforderung besteht darin, Spitzenleistungen zu erreichen oder LLMs für spezifische Aufgaben zu maßschneidern, was typischerweise komplexe, aufgabenspezifische Programme erfordert, die dennoch von spontanen Interaktionen abhängen können.
Language Model Prompting ist ein aufstrebendes Feld, das genau diese Probleme adressiert. LMQL folgt den Prinzipien von LMP: Es kombiniert Texteingaben und Scripting auf intuitive Weise und ermöglicht es, Einschränkungen für die Ausgaben des Sprachmodells zu definieren.
Warum LMQL?
Neuere Generationen von Sprachmodellen lassen sich konzeptionell leicht mit Beispielen oder Anweisungen prompten. Um ihr volles Potenzial auszuschöpfen und mit neuen Modellen Schritt zu halten, braucht es jedoch ein tiefes Verständnis ihrer inneren Funktionsweise sowie der anbieterspezifischen Bibliotheken und Implementierungen.
Beispielsweise ist es schwierig, den Decoding-Prozess auf eine Liste erlaubter Wörter oder Phrasen zu beschränken, da Sprachmodelle mit Tokens arbeiten. Ob lokal oder über eine API genutzt – LLMs sind wegen ihrer Größe in der Ausführung kostspielig.
LMQL kann die Anzahl der Aufrufe an das Sprachmodell (LM) reduzieren, indem es vordefiniertes Verhalten und die durch Constraints eingeführte Suchbeschränkung nutzt.
Ein weiterer Grund für LMQL ist, dass viele Prompting-Techniken einen Ping-Pong-Dialog zwischen Sprachmodell und Nutzenden erfordern (wie wir es von Chatbots wie ChatGPT kennen) oder hochspezialisierte Schnittstellen, etwa für Rechenaufgaben mit externer Steuerlogik.
Diese Prompts umzusetzen, verlangt viel Handarbeit und Eingriffe in die Decoding-Verfahren des Modells, was die Übertragbarkeit der Implementierungen einschränkt. Und da ein LM pro Schritt nur ein einzelnes (Subwort-)Token generiert, sind für das vollständige Ergebnis oft mehrere Aufrufe nötig.
Bestehende LMs bieten zudem keine Möglichkeit, Ausgaben zu beschränken – ein entscheidender Punkt, wenn LMs in der Produktion eingesetzt werden. Stell dir vor, du entwickelst eine Sentiment-Analyse, die negative Bewertungen markieren soll. Das Programm erwartet dann Antworten wie „positiv“, „negativ“ oder „neutral“.
Häufig liefert das LLM jedoch etwas wie „The sentiment for the provided customer review is positive“, was deine API nur schwer verarbeiten kann. Constraints sind daher äußerst hilfreich.
Mit LMQL kannst du die Ausgabe anhand für Menschen verständlicher Begriffe steuern – statt über die Tokens, mit denen LMs intern arbeiten.
LMQL einrichten
LMQL lässt sich lokal installieren oder online über die webbasierte Playground-IDE nutzen. Beachte: Wenn du selbst gehostete Modelle über Transformers oder llama.cpp verwenden möchtest, musst du LMQL lokal installieren. So geht’s:
Installation und Umgebung einrichten
Die lokale Installation von LMQL ist recht unkompliziert.
Führe einfach folgenden Befehl in einer Python->= 3.10-Umgebung aus:
pip install lmql
Wenn du Modelle auf einer lokalen GPU ausführen willst, installiere LMQL in einer Umgebung mit GPU-fähiger PyTorch-Installation >= 1.11.
Hier der Befehl, um LMQL mit GPU-Abhängigkeiten per pip zu installieren:
pip install lmql[hf]
Hinweis: Es ist gute Praxis, Abhängigkeiten in einer virtuellen Umgebung zu installieren.
Nach der Installation hast du drei Möglichkeiten, LMQL-Programme auszuführen:
1. Playground
Du kannst eine lokale Instanz der Playground-IDE starten.
lmql playground
Dieser Befehl öffnet eine browserbasierte Playground-IDE. Falls sie nicht automatisch startet, rufe http://localhost:3000 auf.
Beachte, dass diese Methode eine Installation von Node.js voraussetzt.
2. Kommandozeile
Als Alternative zum Playground gibt es das Kommandozeilen-Tool. Damit lassen sich lokale .lmql-Dateien ausführen. Verwende dazu einfach folgenden Befehl:
lmql run
3. Python-Integration
Da LMQL ein Superset ist, kannst du es direkt aus einem Python-Programm heraus ausführen. Importiere dazu einfach das lmql-Paket. Sämtlicher Query-Code muss über lmql.run oder mit dem @lmql.query-Decorator ausgeführt werden.
Wenn du lokale Transformer-Modelle in der Playground-IDE oder mit dem Kommandozeilen-Tool verwendest, musst du zuerst eine Instanz der LMQL Inference API für das jeweilige Modell mit dem Befehl lmql serve-model starten.
LMQL-Syntax verstehen
Ein LMQL-Programm besteht aus fünf grundlegenden Bausteinen, die das Verhalten einer Query maßgeblich bestimmen. Dazu zählen:
- Query
- Decoder
- Model
- Constraints
- Distribution
Schauen wir uns jeden Teil genauer an.
Query
Der zentrale Kommunikationskanal zwischen Sprachmodell und Nutzenden ist der Query-Block.
Hier ist eine einfache LMQL-Query:
# Source: https://lmql.ai/docs/
"Say 'this is a test':[RESPONSE]" where len(TOKENS(RESPONSE)) < 25
—-- Model Output—--
"""
Say 'this is a test': RESPONSE This is a test
"""
Der Query-Block behandelt jeden String auf oberster Ebene als direkte Abfrage an das Sprachmodell. Ähnlich wie bei Python-f-Strings unterstützen diese Query-Strings zwei spezielle, maskierte Teilfelder.
Beachte, dass die vom Sprachmodell zu generierende Passage mit [varname] dargestellt wird. Das wird auch „Hole“ genannt – zur where-Klausel gleich mehr.
Das Auslesen eines Variablenwerts aus dem aktuellen Scope ist auch mit {varname} möglich.
Zum Beispiel:
# Source: https://lmql.ai/docs/language/overview.html
# review to be analyzed
review = """We had a great stay. Hiking in the mountains was fabulous and the food is really good."""
# use prompt statements to pass information to the model
"Review: {review}"
Decoder
LMQL unterstützt verschiedene Decoding-Algorithmen, mit denen aus der Token-Verteilung eines Sprachmodells Text generiert wird. Dadurch kann der zu verwendende Decoding-Algorithmus zu Beginn einer Query festgelegt werden.
Es gibt zwei Möglichkeiten, den Decoding-Algorithmus festzulegen:
1. Als Teil der Query – hierbei definierst du Algorithmus und Parameter direkt in der Query. Laut LMQL-Dokumentation „kann das besonders nützlich sein, wenn die Wahl des Decoders für das konkrete Programm relevant ist“. So sieht das im Code aus:
# Source: https://lmql.ai/docs/language/decoding.html
# use beam search with beam width 2 for
# the entire program
beam(n=2)
# uses beam search to generate RESPONSE
"This is a query with a specified decoder: [RESPONSE]"
2. Extern – hierbei werden Algorithmus und Parameter außerhalb des eigentlichen Programms festgelegt, also getrennt davon. Das ist nur möglich, wenn LMQL in einem Python-Kontext verwendet wird.
# Source: https://lmql.ai/docs/language/decoding.htmlimport lmql
@lmql.query(model="openai/text-davinci-003", decoder="sample", temperature=1.8)
def tell_a_joke():
'''lmql
"""A list good dad joke. A indicates the punchline:
Q:[JOKE]
A:[PUNCHLINE]""" where STOPS_AT(JOKE, "?") and STOPS_AT(PUNCHLINE, "\n")
'''
tell_a_joke() # uses the decoder specified in @lmql.query(...)
tell_a_joke(decoder="beam", n=2) # uses a beam search decoder with n=2
Model
LMQL wird von den Entwicklern als „High-Level-Frontendsprache für Textgenerierung“ beschrieben. Das bedeutet, sie ist nicht an ein bestimmtes Textgenerierungsmodell gebunden. Stattdessen werden im Backend unterschiedliche Modelle unterstützt, etwa OpenAI model, llama.cpp und HuggingFace Transformers.
Das Laden von Modellen ist recht einfach. Du kannst die Funktion lmql.model(...) verwenden, die ein lmql.LMM-Objekt erzeugt.
So sieht das aus:
# Source: https://lmql.ai/docs/models/
lmql.model("openai/gpt-3.5-turbo-instruct") # OpenAI API model
lmql.model("random", seed=123) # randomly sampling model
lmql.model("llama.cpp:<YOUR_WEIGHTS>.gguf") # llama.cpp model
lmql.model("local:gpt2") # load a `transformers` model in-process
lmql.model("local:gpt2", cuda=True, load_in_4bit=True) # load a `transformers` model in process with additional arguments
lmql.model("gpt2") # access a `transformers` model hosted via `lmql serve-model`
Sobald das lmql.LLM-Objekt erstellt ist, kannst du das Modell auf zwei Arten an das Query-Programm übergeben:
1. Modell extern angeben
# Source: https://lmql.ai/docs/models/
import lmql
# uses 'chatgpt' by default
@lmql.query(model="chatgpt")
def tell_a_joke():
'''lmql
"""A great good dad joke. A indicates the punchline
Q:[JOKE]
A:[PUNCHLINE]""" where STOPS_AT(JOKE, "?") and \
STOPS_AT(PUNCHLINE, "\n")
'''
tell_a_joke() # uses chatgpt
tell_a_joke(model=lmql.model("openai/text-davinci-003")) # uses text-davinci-003
2. Eine Query mit der from-Klausel verwenden
# Source: https://lmql.ai/docs/models/
argmax
"This is a query with a specified 'from'-clause: [RESPONSE]"
from
"openai/text-ada-001"
Constraints
Einer der großen Pluspunkte von LMQL sind die Constraints. Sie ermöglichen es, Einschränkungen für die Ausgabe des Sprachmodells festzulegen. Das unterstützt skriptgestütztes Prompting, indem es sicherstellt, dass die Modellausgabe an der gewünschten Stelle endet, und gibt dir zugleich Kontrolle über das Modell während des Decodings.
Unterstützte Constraints sind unter anderem:
- Stopp-Phrasen
- Zahlentyp-Constraints
- Auswahl aus einer Menge
- Zeichenlänge
- Tokenlänge
- Regex-Constraints (Vorschau)
- Kombinierte Constraints
- Benutzerdefiniert
Weitere Details findest du auf der Constraints-Seite in der LMQL-Dokumentation.
Distribution
Die Distribution-Anweisung ist ein zentrales Element in LMQL. Sie steuert Format und Struktur der Ausgabe, indem sie festlegt, wie die generierten Ergebnisse verteilt und dargestellt werden.
So sieht das in der Praxis aus:
# Source: https://lmql.ai/docs/language/overview.html
argmax
# review to be analyzed
review = """We had a great stay. Hiking in the mountains was fabulous and the food is really good."""
# use prompt statements to pass information to the model
"Review: {review}"
"Q: What is the underlying sentiment of this review and why?"
# template variables like [ANALYSIS] are used to generate text
"A:[ANALYSIS]" where not "\n" in ANALYSIS
# use constrained variable to produce a classification
"Based on this, the overall sentiment of the message can be considered to be[CLS]"
distribution
CLS in [" positive", " neutral", " negative"]
----- Model Output ------
Review: We had a great stay. Hiking in the mountains was fabulous and the food is really good.
Q: What is the underlying sentiment of this review and why?
A: ANALYSIS The underlying sentiment of this review is positive because the reviewer had a great stay, enjoyed the hiking and found the food to be good.
Based on this, the overall sentiment of the message can be considered to be CLS
Grenzen von LMQL und Community-Support
Wie bei jeder Technologie gibt es auch bei LMQL einige Einschränkungen. Zum Beispiel:
- Die LMQL-Bibliothek ist noch nicht lange verfügbar und bislang nicht weit verbreitet. Entsprechend ist die Community klein, und es gibt nur wenige externe Ressourcen, die dir bei Problemen helfen.
- Die Dokumentation der Bibliothek ist nicht so detailliert, wie sie sein könnte.
- Durch Einschränkungen der OpenAI-API ist es nicht möglich, LMQL vollumfänglich mit ChatGPT zu nutzen, da die populärsten und leistungsfähigsten Modelle nicht zugänglich sind.
Auch wenn das für manche ein Hindernis sein mag, ist wichtig zu betonen, dass die Bibliothek noch recht neu ist und sich in aktiver Entwicklung befindet – viele dieser Einschränkungen könnten in künftigen Versionen behoben werden.
Fazit
LMQL ist eine SQL-ähnliche Programmiersprache und zugleich ein Superset von Python. Sie vereinfacht das Extrahieren von Informationen und das Generieren von Antworten aus LLMs, indem sie deklarative Elemente mit einer imperativen Skriptsyntax kombiniert. Entwicklerinnen und Entwickler können mit LMQL die Textgenerierung gezielt steuern – ein äußerst wertvolles Werkzeug für vielfältige Anwendungen in der Tech-Branche.
Wenn du weitermachen willst, schau dir an:
