Connect with us

Entwicklung & Code

GPT-5.3-Codex: OpenAI stellt neues Coding-Modell vor


OpenAIs GPT-5.3-Codex erscheint nur knapp zwei Monate nach Veröffentlichung von GPT-5.2-Codex, das Mitte Dezember veröffentlicht wurde. Laut Entwickler vereint die neue Version die Coding-Fähigkeiten von GPT-5.2-Codex mit den Denk- und Wissensfähigkeiten von GPT-5.2. Dabei sei es um 25 Prozent schneller als sein Vorgänger.

Weiterlesen nach der Anzeige

Weiter sagt OpenAI in der Ankündigung, dass GPT-5.3-Codex in vier Leistungstests für Programmierung und praktische Anwendungen auf zwei der vier Benchmarks neue Bestwerte erreiche (SWE-Bench Pro, Terminal-Bench) und bei den anderen beiden (OSWorld, GDPval) starke Leistung zeige.

Laut OpenAI ist GPT‑5.3‑Codex das erste Modell des Unternehmens, „das maßgeblich an seiner eigenen Entwicklung beteiligt war“. Dafür nutzte das Codex-Team frühe Versionen, „um sein eigenes Training zu debuggen, seine eigene Bereitstellung zu verwalten und Testergebnisse und Bewertungen zu diagnostizieren“ – es habe damit dazu beigetragen, seine eigene Entwicklung zu beschleunigen, heißt es.

Ferner erläutert OpenAI, dass GPT-5.3-Codex im agentischen Coding-Benchmark Terminal-Bench 2.0 seinen Vorgänger als auch das neu erschienene Claude Opus 4.6 (65,4 Prozent) um etwa zwölf Prozent übertreffe – OpenAIs neues Tool erreichte in dem Benchmark 77,3 Prozent. Dabei verbrauche das Modell weniger Token als die Codex-Vorgänger.



GPT-Codex-5.3 im Benchmark-Vergleich mit dem Vorgänger und GPT 5.2.

(Bild: OpenAI)

OpenAI will GPT-5.3-Codex für zahlende ChatGPT-Nutzer in allen Codex-Umgebungen bereitstellen: in der App, über die CLI, in der IDE und im Web. Zudem arbeite OpenAI daran, „bald einen sicheren API-Zugriff zu ermöglichen“. Zudem hatte Apple vor wenigen Tagen angekündigt, KI-Coding-Agenten wie Claude und Codex direkt in die Entwicklungsumgebung Xcode ab Version 26.3 einzubinden.

Weiterlesen nach der Anzeige

Die Entwicklungsumgebung nutzt dafür das quelloffene Model Context Protocol (MCP), das den KI-Agenten Zugriff auf Projektstruktur, Build-Logs und Apples Dokumentation ermöglicht. Die neuen Funktionen knüpfen an die Integration von externen Large Language Models (LLMs) in Xcode 26 an, die es schon ermöglichte, Quelltexte automatisch ohne Copy & Paste an GPT und Claude weiterzugeben. Auch GitHub kündigte die Integration der beiden Coding-Agenten an.


(afl)



Source link

Entwicklung & Code

Interview: So arbeiten die Entwickler bei OpenAI


Für viele Entwickler sind Programmierassistenten auf Basis großer Sprachmodelle (LLMs) nicht mehr wegzudenken. Da Kompetenz in diesem Feld für neue Modelle besonders relevant ist, nennen Entwickler Coding-Kapazitäten oft neben Mathe-Fähigkeiten, wenn sie die nächste Generation ihrer Produkte hypen wollen. Derzeit nutzen Entwickler oft nicht das Eine Modell, sondern greifen für verschiedene Anforderungen auf die Klassenbesten verschiedener Anbieter zu – wenn nicht sogar kleinere Modelle simplere Aufgaben abwickeln.

Weiterlesen nach der Anzeige

Unter dem Namen Codex bündelt OpenAI die Programmierfähigkeiten seines Angebots, auf die sich über eine CLI-Variante, als IDE-Extension oder auf dem Mac neuerdings per App zugreifen lässt. Im Gespräch mit iX erzählt Dominik Kundel, Developer Experience Lead bei OpenAI, über Softwareentwicklung mit Codex bei OpenAI und den Zielen, die das Unternehmen mit dem Tool verfolgt.




Dominik Kundel ist Developer Experience Lead für Codex bei OpenAI in San Francisco. Er sitzt bei OpenAI zwischen dem Produkt- und dem Go-to-Market-Team, programmiert am Tooling und an der Dokumentation und Lehrmaterialien, um dafür zu sorgen, dass Leute das Meiste aus Codex herausholen können.

iX: OpenAI hat zwischen 2021 und 2025 drei Werkzeuge vorgestellt, die Codex heißen. Was ist der aktuellste Ableger der Reihe denn jetzt genau?

Dominik: Grundsätzlich verstehen wir Codex als eine Einheit. Codex ist ein Software Engineer, der da sein soll, wo Entwickler arbeiten. Das ist einmal die Terminal-Oberfläche Codex CLI. Außerdem gibt es Codex für Code Reviews in GitHub und IDE Extensions, um Codex in Cursor oder in VS Code zu benutzen. Darunter liegen die Codex-Modelle, aktuell GPT-5.2 Codex. Das sind auf Programmieren trainierte Modelle und der Codex Harness, in dem die Agenten interagieren. Diese Teile geben wir auch in der API raus, worüber Cursor oder Open Code ebenfalls mit Codex interagieren können.

Wie helft ihr euren Nutzern dabei, den Überblick über ihren generierten Code zu behalten?

Einerseits mit der Funktion Codex Code Review, die automatisch mit der ChatGPT-Subscription kommt. Codex ist gut darin, selbst komplexe Codebases zu navigieren und zu verstehen. Wir haben sehr große Codebases bei OpenAI und testen das Ganze damit selber. Andererseits ist Codex gut darin, Rückfragen zu stellen, um den Code zu verbessern. Wir benutzen Codex selbst viel, um sozusagen aufzuräumen. Wir schicken Codex die Aufgabe, Sachen zu refactorn oder Bugs zu finden. Ich hab letztes Jahr am Agents SDK gearbeitet und hatte dabei konstant mehrere Codex-Instanzen laufen, die noch nach Bugs gesucht haben oder Sachen verbessert haben.

Weiterlesen nach der Anzeige

Das heißt, ihr entwickelt bei OpenAI selbst mit Codex?

Grundsätzlich sieht es bei uns so aus, wie bei vielen Silicon Valley Softwarefirmen, wir benutzen also Git und PR-Reviews. Allerdings haben wir durch den ganzen Prozess Codex verteilt. Das heißt, Entwickler, aber auch Product Manager, Designer, Data Scientists, andere, eigentlich mittlerweile fast die komplette Firma benutzt Codex, um Code zu schreiben. Der Code geht dann aber noch durch die traditionellen Pull Requests Reviews und den ganzen Prozess. Wir nutzen Codex aber auch für einen zusätzliches Review, durch das aller Code läuft. Wir haben das Modell explizit auf Code Reviews trainiert.

Mich überrascht häufig, wenn Codex Sachen findet, die ich selbst nicht gefunden hätte. Vor allem, da ich zum Teil an Dokumentation arbeite und dann etwa einen Pull Request hochschicke und auf einmal dann ein Kommentar kommt, dass auf der aktuellen Seite die Dokumentation und der Source Code nicht übereinstimmen. Etwa, weil es eine Logikproblem gibt. Trotzdem wird jeder PR noch von Menschen durchschaut. Häufig ist es so, dass die Leute als Erstes Codex benutzen, um den PR zu reviewen und dann eventuell irgendwelche CI/CD Probleme von Codex reparieren lassen, bevor ein Kollege den Pull Request dann durchschaut.

Hast du das Gefühl, du hast dann noch die Kontrolle über die ganzen Agenten oder bist du eigentlich nur noch ein Mensch, der Sachen abnickt?

Nee, ich habe noch Kontrolle. Vor allem bei komplexeren Problemen bitte ich Codex, erstmal einen Plan zu schreiben. Wir haben einen Kollegen, Aaron Friel, der nennt seine Pläne „Exec Plans“. Er lässt das Modell ein komplettes Dokument schreiben, wo es dokumentiert, welche Entscheidungen es getroffen hat und was der Fortschritt ist. Da hat man ein Log, durch das man nochmal durchgehen kann und die Richtigkeit der Entscheidungen bestätigen kann. Das lässt sich auch noch weiter aufteilen, um weiterhin mehrere PR-Reviews zum Durchgehen zu haben.

Was wir generell vorschlagen ist, die gleichen Systeme aufzusetzen, wie wenn man mit einem großen Team an denselben Sachen arbeitet. Das heißt, CI ist eine der ersten Sachen, die ich normalerweise aufsetze, um sicherzustellen, dass ich dann auch Test Coverage habe. Das hilft dann auch Codex. Codex ist generell darauf trainiert, zu verifizieren, ob die Aufgaben fertig sind. Wenn man also nach einem neuen Feature fragt und bereits Tests hat, schreibt Codex automatisch neue Tests. Sowas hilft dann bei der Maintenance. Genauso wie weiterhin Code Reviews zu machen und Dokumentation zu behalten. Ich habe das Gefühl, dass die Codebases besser aussehen, weil Codex hilft Features zu dokumentieren und auch bei anderen Aufgaben hilft, die in der Realität oft hinten anstehen.

Benutzt ihr nur Codex oder benutzt ihr auch Modelle von anderen Anbietern?

Wir benutzen nur OpenAI-Modelle. Bei der Wahl des Editors sind wir nicht festgelegt, da kann jeder Kollege die IDE of Choice einsetzen, die eventuell noch weitere KI-Features hat. Wenn ich mal Code schreiben muss, dann benutze ich Cursor, wo ich dann das Cursor Tab Modell benutze. Cursor ist allerdings auch ein großer OpenAI-Kunde.

Viele Entwickler schwören aktuell auf Claude Code mit Opus 4.5. Wie wollt ihr da mit Codex aufholen?

Ich glaube, dass es da zwei Perspektiven gibt. Auf der einen Seite sind die Leute, die Claude Code sehr mögen, mit den Features, die es gibt und auch das Terminal Interface, was die Modelle haben. Die Leute mögen es, mit Opus zu schreiben. Wir hören häufig, dass Codex zu langsam ist. Da arbeiten wir auch dran. Auf der anderen Seite gibt es viele Leute, die mittlerweile auf Codex schwören. Die Anwender loben, dass sie Codex ein Problem geben und das Tool einfach daran arbeitet. Wenn sie dann später wiederkommen, ist Codex komplett fertig.

Anders als bei Claude Code, wo man sich dran gewöhnt hat, hin und her zu schreiben, ist Codex gut darin, ein Problem zu nehmen und wenn es das Ziel verstanden hat, einfach für mehrere Stunden an diesem Problem arbeiten. Peter Steinberger, der im Moment auf X und LinkedIn sehr viral geht, schreibt darüber, dass er Codex bevorzugt und wie er das meiste aus Codex rausholt.

Wie wollt ihr Codex denn schneller machen?

Ich kann da keine Details nennen, aber wir haben zum Beispiel vor Kurzem eine Cerebras-Partnerschaft angekündigt.

Du hast über große Codebasen gesprochen, wie ihr sie ja selber habt. Gibt es besondere Strategien für den Umgang damit?

Mono-Repos helfen sehr, um dem Modell Kontext zu geben. Also, in der Lage zu sein, Codex etwa auf das Backend zu verweisen, wenn man gerade zum Beispiel an einer Android-App arbeitet. Ein gutes Beispiel dafür ist unser Browser Atlas. Da gibt es das Agent Panel, über das ein Agent in einem logged-in oder logged-out State dann selbst mit dem Browser umgehen kann. Das Feature und den Wechsel zwischen den Zuständen hat größtenteils Codex geschrieben. Dafür musste es die Codebase mit vier verschiedenen Sprachen durchgehen. Diesen Kontext zu geben ist sehr hilfreich.

Außerdem schlagen wir vor, CI/CD zu haben und generell Validation Tools. Wenn man also Frontend-Produkte baut, auch die Tools zu haben, die sicherstellen, dass die Frontend-Komponenten richtig gerendert werden. Man kann dann die Screenshots wieder als Image-Input in Codex reingeben und Codex kann sich dann quasi selbst validieren. Ein weiterer Punkt ist Naming. Wir empfehlen, Namen zu benutzen, die sehr einfach zu finden sind. Codex benutzt nämlich Tools wie grep und ripgrep, um sich in der Codebase zurechtzufinden. Wenn es die Sachen schnell finden kann, ist Codex wesentlich schneller.

Einer der Gründe, warum Codex den Leuten langsam vorkommt, ist, dass es häufig erstmal auf eine Tour geht, um sich zurechtzufinden. Codex springt nicht direkt rein und schreibt irgendeinen Code, sondern es geht erstmal rum und versucht, zu verstehen. Genauso wie das ein Software-Entwickler machen würde: Wie sieht die Codebase hier aus, wo sind die Daten oder die Dateien, mit denen ich umgehen möchte. Das Modell versucht zu verstehen, wie das Ganze aufgebaut ist, bevor es dann anfängt. Naming Conventions, die dem Modell erlauben einfacher herumzuspringen, helfen.

Was hebt die neue Codex App vom CLI oder dem Plug-in-Einsatz ab?

Die App ist gezielt entwickelt, um Leuten beim Multitasking zu helfen. Viele Leute nutzen mehrere Codex-Instanzen parallel, die dann mehrere CLI-Tabs nebeneinander aufbauen. Die Codex-App ist als Command Center gedacht. Man kann den Überblick über alle Projekte behalten und schnell zwischen den Projekten wechseln. Dabei hat die App ein ähnliches User-Interface wie die IDE-Extension, man hat also Zugriff auf Features wie Worktrees. Wenn man an mehrere Features in der gleichen Codebase arbeiten will, kann man mehrere Worktrees aufbauen, um dann Aufgaben im Hintergrund laufen zu lassen und dann schnell dazwischen zu wechseln. Außerdem heben wir in der App Agent Skills hervor, also die Möglichkeit, dem Agenten neue Capabilities wie bestimmte APIs oder bestimmte Tools beizubringen.

Also generell Tool-Use-Funktionen?

Das ist ähnlich wie Tool Use, nur, dass der Agent das Ganze „progressively discovered“. Man kann jetzt bestimmte Prozesse einbauen. Ich habe letztens ein Screenshot-Skill gebaut, der auch in der App enthalten ist. Damit kann man Codex anweisen, Screenshots der App zu machen, die Codex dann benutzen kann, um selber zu verifizieren, ob es den Job richtig gemacht hat. Als ich diesen Skill gebaut habe, habe ich dann meinen PR zu GitHub geschickt, der Codex Code Review auf GitHub hat dann ein Problem gefunden. Ich habe dann den GitHub „address code review“-Skill benutzt, um Codex auf GitHub zu schicken und das Problem zu analysieren, zu fixen und ein Update zu dem PR zu schicken.

Man kann in diese Skills Prozesse einarbeiten und dann mit dem kombinieren, was wir Automations nennen. Automations sind dann Aufgaben, die entweder jede Stunde oder zu einer bestimmten Uhrzeit am Tag laufen. Die Automations laufen im Hintergrund auf einem Worktree. Wenn sie irgendein Problem finden, können sie das Ganze an dich weiterleiten. Ein Kollege hat beispielsweise jede Stunde eine Automation laufen, die alle seine Pull Requests durchgeht und guckt, ob CI bei denen grün ist oder ob es irgendwelche Probleme gibt und fixt die dann automatisch selber. Oder die Automation läuft einmal am Tag durch Sentry durch und guckt sich die Error-Logs an. Dann sucht sich das Programm ein besonders großes Problem aus und versucht es selber zu fixen und öffnet einen PR.

Mit Codex und den Automations in der App kann man als Entwickler dann auch die Aufgaben neben der Feature-Entwicklung im Blick behalten. Also die Aufgaben, die so an der Seite hängen oder nicht-technische Aufgaben sind, wie etwa bei der Codebase auf dem Laufenden zu bleiben. Da kann man sich zum Beispiel einmal am Tag ein automatisiertes Update mit den Änderungen an der Codebase schicken lassen und dazu, was in dem Fall an der Dokumentation aktualisiert werden muss.

Du hast berichtet, dass ihr am Ende immer die Ergebnisse von Codex kontrolliert. Wie vibe-coding-freundlich ist euer Tool?

Wir wollen, dass Codex eine Stütze für professionelle Entwickler ist. Codex kann an sehr komplexen Problemen arbeiten, weswegen es auch etwas langsamer ist. Das heißt, es kommt darauf an, was man aus dem Vibe Coding herausholen will. Ich habe zum Beispiel schonmal eine komplette Demo-App während eines Meetings gebaut, was viele so Vibe Coding nennen würden. Es funktioniert, aber es ist nicht die gleiche Erfahrung, als wenn man sich beispielsweise was mit Lovable bauen lassen würde.

Dominik, vielen Dank für das Interview.


(pst)



Source link

Weiterlesen

Entwicklung & Code

Large Language Models: Die Mathematik hinter Transformers


Im Jahr 2017 veröffentlichte ein Team bei Google ein Paper mit einem gewagten Titel: „Attention Is All You Need.“ Das war nicht nur akademische Prahlerei. Die vorgestellte Transformer-Architektur veränderte grundlegend, wie wir KI-Systeme bauen. Heute basiert jedes große Sprachmodell von GPT über Claude bis Gemini auf diesem Fundament. Wenn Sie ein modernes KI-Tool nutzen, haben Sie bereits mit einem Transformer interagiert.

Weiterlesen nach der Anzeige


Michael Stal

Michael Stal

Prof. Dr. Michael Stal arbeitet seit 1991 bei Siemens Technology. Seine Forschungsschwerpunkte umfassen Softwarearchitekturen für große komplexe Systeme (Verteilte Systeme, Cloud Computing, IIoT), Eingebettte Systeme und Künstliche Intelligenz.

Er berät Geschäftsbereiche in Softwarearchitekturfragen und ist für die Architekturausbildung der Senior-Software-Architekten bei Siemens verantwortlich.

Als Software Engineer haben Sie vielleicht gehört, dass Transformers „nur aus Matrixmultiplikationen“ bestehen oder dass sie „Aufmerksamkeitsmechanismen verwenden“. Diese Beschreibungen sind zwar technisch korrekt, verfehlen aber die elegante mathematische Argumentation, die Transformers funktionieren lässt. Dieser Artikel nimmt Sie mit auf eine Reise zu den Problemen, die Transformers motivierten, bis zu den hochmodernen Optimierungen in Produktionssystemen. Am Ende verstehen Sie nicht nur, was die Mathematik hinter Transformers bedeutet, sondern warum sie genau so sein muss. Damit erlangen Sie das Wissen, um einen Transformer von Grund auf zu implementieren.

Wir folgen einem roten Faden: Wie bauen wir ein System, das Beziehungen zwischen Elementen in einer Sequenz verstehen kann, unabhängig davon, wie weit diese Elemente auseinanderliegen, und dies effizient genug, um auf Milliarden von Beispielen zu trainieren? Jede Formel, jede architektonische Entscheidung resultiert aus der Beantwortung dieser Frage.

Vor Transformers dominierte der Ansatz der Recurrent Neural Networks (RNN) die Sequenzverarbeitung. Um zu verstehen, warum Transformers existieren, müssen wir verstehen, warum wir RNNs trotz ihrer Eleganz nicht auf die Probleme skalieren können, die wir lösen wollen.

Stellen Sie sich vor, Sie bauen ein Übersetzungssystem. Sie erhalten den englischen Satz „The cat sat on the mat“ und müssen die französische Übersetzung produzieren. Ein RNN verarbeitet dies sequenziell. Es liest „The“, aktualisiert seinen internen Zustand, liest dann „cat“, aktualisiert seinen Zustand erneut, und so weiter. Die Idee: Wenn das Übersetzungssystem den Satz zu Ende gelesen hat, enthält sein versteckter Zustand eine komprimierte Repräsentation von allem, was es gesehen hat.


Chatbot umringt von Laptops

Chatbot umringt von Laptops

(Bild: Golden Sikorka/Shutterstock)

Die Online-Konferenz LLMs im Unternehmen zeigt am 19. März, wie KI-Agenten Arbeitsprozesse übernehmen können, wie LLMs beim Extrahieren der Daten helfen und wie man Modelle effizient im eigenen Rechenzentrum betreibt.

So aktualisiert ein RNN seinen versteckten Zustand bei jedem Zeitschritt:

Weiterlesen nach der Anzeige

h_t = tanh(W_hh * h_{t-1} + W_xh * x_t + b_h)

In dieser Formel repräsentiert h_t den versteckten Zustand zum Zeitpunkt t, x_t ist die Eingabe zum Zeitpunkt t, W_hh ist eine Gewichtsmatrix, die den vorherigen versteckten Zustand transformiert, W_xh transformiert die aktuelle Eingabe, und b_h ist ein Bias-Term. Die tanh-Funktion beschränkt das Ergebnis, um die Werte begrenzt zu halten.

Das Problem zeigt sich, wenn Sie das Ganze aus der Perspektive vieler Zeitschritte betrachten. Angenommen, Sie übersetzen ein langes Dokument, und eine kritische Information erscheint im ersten Satz, aber Sie benötigen diese Information, um den hundertsten Satz korrekt zu übersetzen. Diese Information muss somit eine hundertfache Multiplikation mit W_HH überleben.

In der Praxis führt dies zu zwei katastrophalen Problemen:

Erstens das Problem verschwindender Gradienten. Beim Training neuronaler Netzwerke berechnen wir Gradienten, die uns sagen, wie wir Gewichte anpassen sollen. Diese Gradienten müssen rückwärts durch die Zeit fließen. Wenn der Gradient bei Schritt 100 die Gewichte beeinflussen muss, die wir im Schritt 1 verarbeitet haben, muss er hundertmal mit der Ableitung der tanh-Funktion multipliziert werden. Da die Ableitung von tanh immer kleiner als eins ist, schrumpft der Gradient exponentiell. Wenn er die frühen Schritte erreicht, bleibt er im Wesentlichen bei null und die Gewichte somit unverändert, ohne dazuzulernen.

Zweitens gibt es, selbst wenn wir verschwindende Gradienten mit Techniken wie LSTMs oder GRUs lösen, ein fundamentaleres Problem: Sequenzielle Verarbeitung ist langsam. Moderne GPUs zeichnen sich durch parallele Berechnung aus. Sie können massive Matrizen unglaublich schnell multiplizieren. Aber RNNs zwingen uns, Sequenzen Schritt für Schritt zu verarbeiten, weil jeder Schritt vom vorherigen abhängt. Sie können h_100 nicht berechnen, bis Sie h_99 berechnet haben, was h_98 erfordert, und so weiter, bis wir schlussendlich h_1 erreichen. Diese sequenzielle Abhängigkeit macht das Training auf den massiven Datensätzen, die wir für moderne KI benötigen, unerträglich langsam.

Die Transformer-Architektur löst beide Probleme mit einer radikalen Idee: Was, wenn wir alle Positionen in der Sequenz gleichzeitig betrachten und das Modell lernen lassen könnten, welche Positionen füreinander relevant sind? Hier kommt Attention ins Spiel.

Bevor wir in mathematische Formeln eintauchen, bauen wir Intuition darüber auf, was Attention tatsächlich macht. Stellen Sie sich vor, Sie lesen diesen Satz: „Die Trophäe passte nicht in die Truhe, weil sie zu groß war.“ Worauf bezieht sich „sie“? Ihr Gehirn verarbeitet dies nicht Wort für Wort unter Beibehaltung eines versteckten Zustands. Stattdessen schauen Sie, wenn Sie auf „sie“ stoßen, durch den Satz zurück, finden relevanten Kontext (Trophäe und Truhe) und bestimmen, was angesichts von „zu groß“ sinnvoll ist.

Aufmerksamkeitsmechanismen formalisieren diesen intuitiven Prozess. Im Kern ist Attention ein differenzierbarer Nachschlagemechanismus (Lookup-Mechanismus). In einer traditionellen Lookup-Tabelle oder Hash-Map geben Sie einen Schlüssel an und erhalten einen Wert zurück. Attention macht etwas Ähnliches, aber mit einem entscheidenden Unterschied: Statt exakter Übereinstimmungen berechnet es einen gewichteten Durchschnitt aller Werte, wobei die Gewichte davon abhängen, wie gut jeder Schlüssel zu Ihrer Abfrage passt.

Konkretisieren wir dies mit einem einfachen Beispiel. Angenommen, Sie haben drei Wörter in einem Satz, und jedes Wort repräsentiert sich als Vektor. Sie möchten eine neue Repräsentation für das zweite Wort berechnen, die Informationen von den anderen Wörtern basierend auf ihrer Relevanz einbezieht.

# Einfaches Beispiel mit drei Wortvektoren

wort1 = [1.0, 0.0] # "Die"

wort2 = [0.5, 0.5] # "Katze"

wort3 = [0.0, 1.0] # "saß"

Um nun eine attention-erweiterte Repräsentation für wort2 zu berechnen, müssen wir beantworten: Wie relevant ist jedes andere Wort im Textfragment für wort2? Wir könnten diese Relevanz als Skalarprodukt berechnen, das Ähnlichkeit misst:

relevanz_1_zu_2 = skalarprodukt(wort1, wort2) = 1.0 * 0.5 + 0.0 * 0.5 = 0.5

relevanz_2_zu_2 = skalarprodukt(wort2, wort2) = 0.5 * 0.5 + 0.5 * 0.5 = 0.5

relevanz_3_zu_2 = skalarprodukt(wort3, wort2) = 0.0 * 0.5 + 1.0 * 0.5 = 0.5

Diese rohen Relevanzwerte sind nicht sehr nützlich, weil sie sich nicht zu eins summieren lassen. Wir wollen Gewichte, die wir für einen gewichteten Durchschnitt nutzen können. Hier kommt die Softmax-Funktion ins Spiel. Softmax konvertiert beliebige Werte in eine Wahrscheinlichkeitsverteilung:

def softmax(werte):

exp_werte = [exp(w) for w in werte]

summe_exp = sum(exp_werte)

return [e / summe_exp for e in exp_werte]

Die Softmax-Funktion besitzt eine schöne Eigenschaft: Sie ist differenzierbar, sodass wir sie mit Backpropagation trainieren können, und sie produziert immer Ausgaben, die zu eins summieren und allesamt positive Werte besitzen. Die Exponentialfunktion stellt sicher, dass größere Werte exponentiell mehr Gewicht erhalten, was einen weichen Auswahlmechanismus schafft.

Wenden wir Softmax auf unsere Relevanzwerte an, …

gewichte = softmax([0.5, 0.5, 0.5]) = [0.33, 0.33, 0.33]

…, können wir die Attention-erweiterte Repräsentation als gewichtete Summe berechnen:

erweitertes_wort2 = 0.33 * wort1 + 0.33 * wort2 + 0.33 * wort3

= 0.33 * [1.0, 0.0] + 0.33 * [0.5, 0.5] + 0.33 * [0.0, 1.0]

= [0.495, 0.495]

Dies ist die Essenz von Attention: Wir berechnen, wie relevant jede Position für die aktuelle Position ist, normalisieren diese Relevanzen zu Gewichten und nehmen einen gewichteten Durchschnitt. Das Genie der Transformer liegt darin, diesen Prozess lernbar und effizient zu machen.

Jetzt sind wir bereit, die tatsächliche Attention-Formel abzuleiten, die Transformers nutzen. Wir bauen sie Schritt für Schritt auf und bekommen Einblick, warum jede Komponente existiert.

Unser Ziel: Einen Mechanismus schaffen, bei dem wir für jede Position in einer Sequenz eine Repräsentation berechnen können, die Informationen von allen anderen Positionen basierend auf gelernter Relevanz einbezieht. Wir benötigen drei Dinge:

  1. eine Möglichkeit auszudrücken, „wonach ich suche“ und das an jeder Position. Wir nennen dies die Query (Q). Denken Sie zum Beispiel an eine Suchanfrage in einer Datenbank.
  2. eine Möglichkeit auszudrücken, „was ich anzubieten habe“ an jeder Position. Wir nennen dies den Key (K). Denken Sie an den Index in einer Datenbank, gegen den wir abgleichen.
  3. die tatsächliche Information, die wir abrufen möchten. Wir nennen dies den Value (V). Denken Sie an die in der Datenbank gespeicherten Daten.

Warum Keys und Values trennen? Weil das, was wir zur Bestimmung der Relevanz verwenden (der Key), sich von dem unterscheiden kann, was wir tatsächlich abrufen möchten (der Value). Zum Beispiel könnte beim Übersetzen von „Die Katze saß“ das Wort „Katze“ relevant sein, weil es ein Substantiv ist (das erfasst der Key), aber was wir tatsächlich abrufen möchten, ist seine volle semantische Bedeutung (das erfasst der Value).

Wir erzeugen Q, K und V, indem wir unsere Eingabe mit gelernten Gewichtsmatrizen multiplizieren:

Q = X * W_Q

K = X * W_K

V = X * W_V

Hier ist X unsere Eingabematrix, wobei jede Zeile ein Wort-Embedding ist, und W_Q, W_K, W_V lernbare Gewichtsmatrizen. Diese Matrizen lernen während des Trainings, die richtige Art von Informationen für Queries, Keys und Values zu extrahieren.

Um nun Attention zu berechnen, müssen wir messen, wie gut jede Query zu jedem Key passt. Die natürliche Wahl ist das Skalarprodukt, weil es Ähnlichkeit misst: Wenn zwei Vektoren in die gleiche Richtung zeigen, ist ihr Skalarprodukt groß; wenn sie orthogonal sind, ist es Null; wenn sie in entgegengesetzte Richtungen zeigen, ist es negativ.

Wir berechnen alle Query-Key-Ähnlichkeiten auf einmal, indem wir Q und K transponiert multiplizieren:

scores = Q * K^T

Dies gibt uns eine Matrix, bei der Eintrag (i,j) beschreibt, wie intensiv Position i (die Query) auf Position j (den Key) achten sollte. Die Dimensionen ergeben sich daraus wie folgt: Wenn wir n Positionen betrachten und jede Query/jeder Key d-dimensional ist, dann erhalten wir eine Matrix Q mit der Größe n mal d, K^T mit der Größe d mal n, und ihr Produkt mit der Größe n mal n.

Hier stoßen wir auf unser erstes Problem. Erhöht sich die Dimension d wachsen die Skalarprodukte ebenfalls. Um zu sehen, warum, bedenken Sie, dass ein Skalarprodukt eine Summe von d Termen ist. Wenn jeder Term die Varianz sigma zum Quadrat hat, hat die Summe die Varianz d mal sigma zum Quadrat aufgrund der Eigenschaften der Varianz. Dies bedeutet, das Skalarprodukt wächst mit der Quadratwurzel von d.

Warum ist dies ein Problem? Weil wir Softmax auf diese Werte anwenden. Softmax mit sehr großen Eingaben führt allerdings zu extrem „spitzen Werten“. Um dies zu sehen, betrachten Sie:

softmax([10, 9, 8]) = [0.665, 0.245, 0.090]

softmax([100, 90, 80]) = [0.9999, 0.0001, 0.0000]

Wenn die Eingaben für Softmax riesengroß sind, ergibt sich im Wesentlichen eine harte Auswahl, die nur den größten Wert auswählt und alles andere ignoriert. Dies löscht Gradienten während des Trainings aus, weil die Ableitung von Softmax fast überall Null erreicht, außer am Maximum.

Die Lösung besteht darin, die Skalarprodukte durch die Quadratwurzel der Dimension zu skalieren:

skalierte_scores = (Q * K^T) / sqrt(d_k)

Diese Skalierung stellt sicher, dass unabhängig von der Dimension die Varianz der Werte ungefähr konstant bleibt. Die Quadratwurzel wirkt speziell dem Quadratwurzelwachstum entgegen, das wir früher identifiziert haben.

Jetzt wenden wir Softmax an, um Attention-Gewichte zu erhalten:

attention_gewichte = softmax(skalierte_scores)

Schließlich verwenden wir diese Gewichte, um einen gewichteten Durchschnitt der Values zu berechnen:

ausgabe = attention_gewichte * V

Alles zusammengesetzt erhalten wir die Scaled Dot-Product Attention-Formel:

Attention(Q, K, V) = softmax((Q * K^T) / sqrt(d_k)) * V

Implementieren wir das in Code, um es zu veranschaulichen:


import numpy as np

def scaled_dot_product_attention(Q, K, V):
    """
    Berechnet Scaled Dot-Product Attention.
    
    Args:
        Q: Query-Matrix der Form (n, d_k), wobei n die Sequenzlänge ist
        K: Key-Matrix der Form (n, d_k)
        V: Value-Matrix der Form (n, d_v)
        
    Returns:
        ausgabe: Attention-Ausgabe der Form (n, d_v)
        attention_gewichte: Attention-Gewichtsmatrix der Form (n, n)
    """
    # Holt die Dimension der Keys für die Skalierung
    d_k = Q.shape[-1]
    
    # Berechnet Attention-Scores durch Skalarprodukt von Queries und Keys
    # Form: (n, d_k) @ (d_k, n) = (n, n)
    scores = np.matmul(Q, K.transpose(-2, -1))
    
    # Skaliert Scores durch Quadratwurzel der Key-Dimension
    # Dies verhindert, dass Softmax zu spitz wird
    skalierte_scores = scores / np.sqrt(d_k)
    
    # Wendet Softmax an, um Attention-Gewichte zu erhalten
    # Jede Zeile summiert zu 1, repräsentiert eine Wahrscheinlichkeitsverteilung
    attention_gewichte = softmax(skalierte_scores)
    
    # Berechnet gewichtete Summe der Values
    # Form: (n, n) @ (n, d_v) = (n, d_v)
    ausgabe = np.matmul(attention_gewichte, V)
    
    return ausgabe, attention_gewichte

def softmax(x):
    """
    Berechnet Softmax-Werte für jede Zeile der Matrix x.
    Numerisch stabile Implementierung, die den Maximalwert subtrahiert.
    """
    # Subtrahiert Maximum für numerische Stabilität
    # Dies verhindert Overflow durch exp von großen Zahlen
    exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
    return exp_x / np.sum(exp_x, axis=-1, keepdims=True)


Dies ist der Kern des Transformers. Alles andere baut auf diesem Fundament auf. Beachten Sie, wie die Formel aus ersten Prinzipien entstand: Wir wollten einen differenzierbaren Lookup-Mechanismus, wir wählten Skalarprodukte für Ähnlichkeit, wir skalierten, um Sättigung zu verhindern, und wir verwendeten Softmax, um normalisierte Gewichte zu erhalten.



Source link

Weiterlesen

Entwicklung & Code

Cross-Plattform-Applikationen mit Rust 2: Crux im Einsatz


close notice

This article is also available in
English.

It was translated with technical assistance and editorially reviewed before publication.




Marcel Koch berät mit seinem siebenköpfigen Team kleine und mittelständische Unternehmen und entwickelt branchenübergreifend Cross-Platform-Apps für Desktop und Mobile sowie Webapplikationen – bevorzugt mit TypeScript, Rust, Flutter oder Java, gestützt auf CI/CD und IaC. Dabei setzt er auf pragmatische, passgenaue Lösungen, denn Software ist kein Selbstzweck. Neben soliden technischen Kenntnissen schult er in Gewaltfreier Kommunikation, Transaktionsanalyse sowie Agilität und fördert einen kritischen Blick auf Cloud Hypes. Marcel ist Speaker, Autor von Fachartikeln und Büchern und regelmäßig in Podcasts zu hören.

Die Programmiersprache Rust eignet sich gut für die Umsetzung von Cross-Plattformprojekten. Der erste Teil der Artikelserie hat die grundlegenden Konzepte einer langlebigen Cross-Plattform-Architektur vorgestellt: Ein herausgelöster Core in Rust bildet das Fundament für nachhaltige Apps. Die Implementierung eines einfachen MVVM-Patterns mit ViewModel, Actions und State zeigte eine einfache konkrete Umsetzung dieses Ansatzes. Wie sich die Architektur verfeinern lässt, zeigt sich, wenn man sie um Validierungen erweitert.

Weiterlesen nach der Anzeige

Das in Rust geschriebene Framework Crux für die plattformübergreifende Entwicklung implementiert die im ersten Artikelteil vorgestellten Konzepte. Was Aktion hieß, nennt Crux Event. Der Zustand(State) heißt Model. Durch den ähnlichen Namen ist die Grenze zum ViewModel leider nicht mehr ganz so deutlich. Das ViewModel heißt nämlich auch bei Crux ViewModel. Umso wichtiger ist es, die Abgrenzung bei der Umsetzung im Hinterkopf zu behalten.

Zudem bringt Crux mit Effect und Command noch weitere wichtige Konzepte mit.

Ein Effect bildet einen Seiteneffekt der hexagonalen Architektur ab. In diesem Kontext sind Seiteneffekte gleichbedeutend mit Plattformspezifika und dem Rendern des User Interface (UI). Dabei ist ein Effect keine Einbahnstraße. Durch ein Command lässt es sich mit einem Event verknüpfen, sodass der verarbeitete Effekt beantwortet und die Antwort in der App auf ein weiteres Event angewendet werden kann. Auf diese Art lässt sich der Zugriff auf das jeweilige Dateisystem und auf native APIs abstrahieren und umsetzen.

Crux definiert außerdem die Begriffe App, Core und Shell.

Weiterlesen nach der Anzeige

  • Die App ist das zentrale Trait und ein Pendant zum Core aus Teil 1
  • Core umhüllt die App und sorgt dafür, dass ein Event in die App hinein- und nur eine Liste von Effect oder das ViewModel aus der App hinausgeht
  • Shell bezeichnetet den Konsumenten des Cores (bepackt mit der App), wie eine native App auf Basis von Swift, Kotlin oder C#



Die Architektur von Crux unterscheidet App, Core und Shell und Effekte (Abb. 1)

(Bild: Marcel Koch)

Das zuvor in Teil 1 implementierte Beispiel wird erneut aufgegriffen und auf Crux übertragen. Als Erstes die einfachen Typen (Listing 1):

Listing 1: Crux: Event/Model/ViewModel-Definitionen


#[derive(Deserialize, Serialize)]
pub enum Event {
    ChangeName(String),
    ChangeEmail(String),
    ApplyChanges,
}

#[derive(Default)]
pub struct Model {
    name: String,
    email: String,
}

#[derive(Deserialize, Serialize)]
pub struct ViewModel {
    pub name: String,
    pub email: String,
}


Hierbei gibt es keine Überraschungen. Actions werden zu Event (Einzahl), State wird zu Model und das ViewModel bleibt bestehen.

Als Nächstes die neuen Typen (Listing 2):

Listing 2: Crux: Effect-Enum und App-Struct


#[effect]
pub enum Effect {
    Render(RenderOperation),
}

#[derive(Default)]
pub struct EmailApp;


Das Enum Effect definiert alle möglichen Kommunikationen aus dem Core hinaus. Das Struct EmailApp bleibt leer. Es implementiert im nächsten Schritt das Trait App von Crux.

Die Implementierung von App ist in drei Blöcke (siehe Kommentare in Listing 3) unterteilt.

Listing 3: Crux: App-Trait-Implementierung


impl App for EmailApp {
    // 1
    type Event = Event;
    type Model = Model;
    type ViewModel = ViewModel;
    type Capabilities = (); // deprecated
    type Effect = Effect;

    // 2
    fn update(
        &self,
        event: Self::Event,
        model: &mut Self::Model,
        _caps: &Self::Capabilities,
    ) -> Command<:effect self::event=""> {
        match event {
            Event::ChangeEmail(email) => {
                model.email = email.clone();
            }
            Event::ChangeName(name) => {
                model.name = name.clone();
            }
            Event::ApplyChanges => {}
        }
        render()
    }

    // 3
    fn view(&self, model: &Self::Model) -> Self::ViewModel {
        ViewModel {
            name: model.name.clone(),
            email: model.email.clone(),
        }
    }
}


Der erste Block legt die grundlegenden assoziierten Typen fest, die App vorsieht. Diese Typen sind aus Listing 1 und 2 bekannt. Der Typ Capabilities ist ein Relikt und gilt als veraltet (deprecated). Dieses Konzept wurde vor der Einführung von Command genutzt. Daher ist es lediglich aus Gründen der Rückwärtskompatibilität vorhanden und lässt sich ignorieren.

Die update-Methode nimmt eingehende Events entgegen und passt daraufhin den Zustand (Model) an. Änderungen am Namen oder der E-Mail-Adresse werden auch hier direkt im Model gespeichert. Nach der Verarbeitung eines Events wird ein RenderEffect ausgelöst. Dieser kann in der Shell aufgegriffen, das ViewModel angefragt und das Re-Rendering angestoßen werden.

Die view-Methode in Abschnitt 3 bietet die Schnittstelle, um das ViewModel zu erstellen. Wie zuvor erzeugt das ViewModel das aktuelle Model (Zustand) und bereitet die relevanten Informationen für die UI so auf, dass die Benutzeroberfläche sie direkt anzeigen kann.

Um die App zu verwenden, gilt es diese im Core zu umhüllen (zu wrappen):

let core: Arc> = Arc::new(Core::new());

Dieser Core-Instanz lässt sich ein Event übergeben und die zurückkommenden Effekte können verarbeitet werden (Listing 4).

Listing 4: Crux: Effect-Verarbeitung


let effects: Vec =
    core.process_event(ChangeEmail("marcel.koch@example.org".into()));

for effect in effects {
  match effect {
    Effect::Render(_) => {
      let view_model = core.view();
      
      assert_eq!(view_model.email, "marcel.koch@example.org")
    }
  }
}


process_event nimmt das Event entgegen und gibt eine Liste von Effekten zurück. Das Beispiel behandelt nur eine Art von Effect: Render. Es wird geprüft, ob das ViewModel die eben übergebene E-Mail-Adresse enthält.

Ist das UI verbunden, tritt es bei jeder Änderung der E-Mail-Adresse auf.

Geht es nicht um einen reinen Aufruf innerhalb von Rust, ist die Integration in andere Technologien abhängig von Serialisierung. Diese Aufgabe übernimmt auf der Rust-Seite die Bridge. Ein einfacher Einsatz sähe in Rust wie folgt aus:

Listing 5: Crux: Bridge-Integration


let serialized = 
    bincode::serialize(&ChangeEmail("marcel.koch@example.org".into())).unwrap();

let effects: Vec = bridge.process_event(&serialized).unwrap();

let effects: Vec> =
bincode::deserialize(effects.as_slice()).unwrap();

for request in effects {
  let effect = request.effect;
  
  match effect {
    EffectFfi::Render(_) => {
    let view_model = bridge.view().unwrap();
    let view_model: ViewModel =
    bincode::deserialize(&view_model).unwrap();
    
    assert_eq!(view_model.email, "marcel.koch@example.org")
    }
  }
}


Es ist der gleiche Ablauf wie zuvor mit dem reinen Core. Die einzigen Unterschiede sind die Serialisierung des Events und die Deserialisierung der Effekte und des ViewModel. Diese Serialisierungen werden in einem realistischen Einsatz in den jeweiligen Fremdtechnologien (.NET, Swift etc.) durchgeführt. Diese Umsetzung zeigt der nächste Teil dieser Artikelserie.



Source link

Weiterlesen

Beliebt