Julia: Matrix-Multiplikations-API

Erstellt am 28. Sept. 2017  ·  208Kommentare  ·  Quelle: JuliaLang/julia

Derzeit enthält der spärliche Matmult-Code die folgende Zeile:

https://github.com/JuliaLang/julia/blob/056b374919e11977d5a8d57b446ad1c72f3e6b1d/base/sparse/linalg.jl#L94 -L95

Ich gehe davon aus, dass dies bedeutet, dass wir die allgemeineren A_mul_B*!(α,A,B,β,C) = αAB + βC Methoden haben möchten, die C (die BLAS gemm API) für dichte Arrays überschreiben. Ist das noch so? (Es scheint auch möglich zu sein, beide APIs beizubehalten, dh die A_mul_B*!(C,A,B) -Methoden beizubehalten, die einfach als A_mul_B*!(C,A,B) = A_mul_B*!(1,A,B,0,C) .)

Ich persönlich möchte, dass die gemm API für alle Array-Typen definiert wird (dies wurde an anderer Stelle ausgedrückt). Die Implementierung dieser Methoden für dichte Arrays scheint ziemlich einfach zu sein, da sie nur direkt gemm et al. Aufrufen würden. Der Sparse-Fall ist bereits implementiert. Die einzige wirkliche Modifikation wäre das reine Julia- Generikum Matmult , das keine Argumente von α und β akzeptiert.

Dies würde zu generischem Code führen, der mit jeder Art von Array / Nummer funktioniert. Ich habe derzeit eine einfache Implementierung von expm (nachdem ich die Änderung an _generic_matmatmult! ), die mit Bigfloats und spärlichen Arrays funktioniert.

linear algebra

Hilfreichster Kommentar

Ich schlage eine einzige Runde der Zustimmungsabstimmung mit einer Frist von 10 Tagen vor. Zustimmungsabstimmung bedeutet: Jeder stimmt für alle Optionen, die er für die Fortsetzung der Diskussion für vorzuziehen hält. Personen, die jetzt lieber ihren am wenigsten bevorzugten Namen als eine fortgesetzte Diskussion haben möchten, sollten für alle drei stimmen. Wenn keine Option eine breite Zustimmung erhält oder das Abstimmungsschema selbst keine breite Zustimmung findet, müssen wir die Diskussion fortsetzen. Bei beinahe Verbindungen zwischen genehmigten Optionen kann @tkf entscheiden (PR-Autorenprivileg).

+1: Ich stimme diesem Abstimmungsschema zu und habe meine Zustimmungsstimmen abgegeben.
-1: Ich bin mit diesem Abstimmungsschema nicht einverstanden. Wenn zu viele oder zu wichtige Personen diese Option auswählen, ist die Abstimmung umstritten.

Herz: mul! ist der weiteren Diskussion vorzuziehen.
Rakete: muladd! ist der weiteren Diskussion vorzuziehen.
Hurra: addmul! ist der weiteren Diskussion vorzuziehen.

Ich schlage vorläufig vor, dass 75% Zustimmung und 5% definitiv beschlussfähig sein sollten (dh 75% der Personen, die überhaupt abgestimmt haben, einschließlich der Nichtübereinstimmung mit dem gesamten Abstimmungsverfahren, und mindestens 5 Personen haben der Gewinnoption zugestimmt, wenn die Teilnahme gering ist , dann sind 5/6 oder 6/8 beschlussfähig, aber einstimmig 4/4 könnte als Fehlschlag angesehen werden.

Alle 208 Kommentare

Ref. # 9930, # 20053, # 23552. Beste!

Danke für die Referenzen. Ich nehme an, dieses Problem hat mehr mit dem Hinzufügen der Methoden im Stil von gemm zu tun als mit einer API-Überarbeitung, aber es kann geschlossen werden, wenn wir der Meinung sind, dass es # 9930 immer noch zu ähnlich ist.

Gibt es als Ausgangspunkt Unterstützung dafür, dass _generic_matmatmul! die API gemm ? Es ist eine ziemlich einfache Änderung und rein additiv / nicht brechend, da die aktuelle Methode einfach mit α=1 und β=0 implementiert werden würde. Ich kann die PR machen. Ich würde wahrscheinlich ähnlich wie diese Version vorgehen (in dieser Version habe ich alle Transponierungsmaterialien geschnitten, weil ich sie nicht brauchte, das würde ich hier nicht tun).

Ja. Das wäre ein guter Anfang. Wir müssen jedoch die Reihenfolge der Argumente berücksichtigen. Ursprünglich dachte ich, es sei natürlicher, der BLAS-Reihenfolge zu folgen, aber wir sind ziemlich konsistent, wenn es darum geht, zuerst Ausgabeargumente zu haben, was auch für das aktuelle Drei-Argument A_mul_B! der Fall ist. Darüber hinaus würde, wie Sie bereits betont haben, die Version mit drei Argumenten der Version mit fünf Argumenten mit α=1 und β=0 und Standardwertargumente sind die letzten. Natürlich müssen wir dafür nicht unbedingt die Standardwertsyntax verwenden, aber es wäre sinnvoll, sie hier zu verwenden.

Warum nicht einfach eine generische gemm -Funktion einführen?

Ja. Das wäre ein guter Anfang. Wir müssen jedoch die Reihenfolge der Argumente berücksichtigen. Ursprünglich dachte ich, es sei natürlicher, der BLAS-Reihenfolge zu folgen, aber wir sind ziemlich konsistent darüber, zuerst Ausgabeargumente zu haben, was auch für das aktuelle A_mul_B! Mit drei Argumenten der Fall ist. Darüber hinaus würde, wie Sie bereits ausgeführt haben, die Version mit drei Argumenten der Version mit fünf Argumenten mit α = 1 und β = 0 entsprechen, und Standardwertargumente sind die letzten. Natürlich müssen wir dafür nicht unbedingt die Standardwertsyntax verwenden, aber es wäre sinnvoll, sie hier zu verwenden.

Klingt gut. Wir können die Diskussion über die tatsächliche Reihenfolge der Argumente und das Umbenennen der Methode in # 9930 fortsetzen. Hier geht es mehr darum, nur die Version mit fünf Argumenten verfügbar zu haben, also werde ich die aktuelle Schnittstelle Ax_mul_Bx!(α,A,B,β,C) beibehalten.

Warum nicht einfach eine generische Gemm-Funktion einführen?

Schlagen Sie vor, zusätzlich zu den oben genannten Änderungen _generic_matmatmul! in gemm! umzubenennen?

Um es klarer zu machen, ich denke, wir sollten am Ende eine einzige Methode mul(C,A,B,α=1,β=0) , zusammen mit faulen Transponierungs- / Adjoint-Typen, auf die wir versenden können, aber das wird eine andere PR sein.

Warum nicht einfach eine generische Gemm-Funktion einführen?

Ich denke, gemm ist eine Fehlbezeichnung in Julia. In BLAS gibt der Teil ge an, dass die Matrizen allgemein sind , dh keine spezielle Struktur haben, die ersten m multiplizieren und die Liste m eine Matrix ist . In Julia ist der ge (allgemein) Teil in der Signatur codiert, ebenso wie der letzte m (Matrix), also denke ich, wir sollten ihn einfach mul! .

Ist die Notation mul!(α, A, B, β, C) von SparseArrays.jl

https://github.com/JuliaLang/julia/blob/160a46704fd1b349b5425f104a4ac8b323ea85af/stdlib/SparseArrays/src/linalg.jl#L32

"finalisiert" als offizielle Syntax? Und dies wäre zusätzlich zu mul!(C, A, B) , lmul!(A, B) und rmul!(A,B) ?

Ich bin kein großer Fan von A , B und C in verschiedenen Reihenfolgen.

Ist die Notation mul!(α, A, B, β, C) von SparseArrays.jl als offizielle Syntax "finalisiert"?

Ich würde nein sagen. Ursprünglich gefiel mir die Idee, BLAS zu folgen (und die Reihenfolge stimmte auch damit überein, wie dies normalerweise mathematisch geschrieben wird), aber jetzt halte ich es für sinnvoll, nur die Skalierungsargumente als optionales viertes und fünftes Argument hinzuzufügen.

Zur Verdeutlichung möchten Sie optionale Argumente in diesem Sinne

function mul!(C, A, B, α=1, β=0)
 ...
end

Die andere Option wären optionale Schlüsselwortargumente

function mul!(C, A, B; α=1, β=0)
...
end

aber ich bin nicht sicher, ob die Leute mit Unicode zu glücklich sind.

Ich bin sehr zufrieden mit Unicode, aber es ist wahr, dass wir versuchen, immer eine ASCII-Option zu haben, und dies wäre hier möglich. Die Namen α und β sind auch nicht sehr intuitiv, es sei denn, Sie kennen BLAS, daher denke ich, dass die Verwendung von Positionsargumenten hier die bessere Lösung ist.

Meiner Meinung nach wäre es eine logischere Nomenklatur, muladd!(A, B, C, α=1, β=1) den verschiedenen BLAS-Routinen zuordnen zu lassen, die Multiplikation und Addition durchführen . ( gemm wie oben, aber auch zB axpy wenn A ein Skalar ist.)

Die mul! -Funktion könnte dann eine Schnittstelle wie mul!(Y, A, B, ...) die eine beliebige Anzahl von Argumenten akzeptiert (genau wie * ), solange alle Zwischenergebnisse in Y gespeichert werden können. ( Ein optionaler kwarg könnte die Reihenfolge der Multiplikation mit einem vernünftigen Standard angeben.

mul!(Y, A::AbstractVecOrMat, B:AbstractVecOrMat, α::Number) hätte die Standardimplementierung muladd!(A, B, Y, α=α, β=0) , ebenso wie die anderen Permutationen von zwei Matrix / Vektoren und einem Skalar.

Eine weitere Abstimmung, bei der mul!(C, A, B, α, β) für dichte Matrizen definiert wird. Dies würde es ermöglichen, generischen Code für dichte und spärliche Matrizen zu schreiben. Ich wollte eine solche Funktion in meinem nichtlinearen Paket der kleinsten Quadrate definieren, aber ich denke, dies ist Typpiraterie.

Ich war auch versucht, mul!(C, A, B, α, β) Methoden für das MixedModels -Paket zu schreiben und mich auf ein bisschen Typpiraterie einzulassen, aber es wäre viel besser, wenn solche Methoden in den LinearAlgebra wären Paket. Methoden für ein muladd! Generikum für diese Operation zu haben, wäre auch für mich in Ordnung.

Ich bin dafür, obwohl ich denke, dass es wahrscheinlich einen anderen Namen haben sollte als nur mul! . muladd! scheint vernünftig, bin aber offen für Vorschläge.

Vielleicht mulinc! für Multiplikationsinkrement?

Vielleicht können wir so etwas wie addmul!(C, A, B, α=1, β=1) ?

Ist das nicht eine Form von muladd! ? Oder ist die Idee dahinter, es addmul! dass es das Argument add und nicht das Argument multiplizieren mutiert? Würde man jemals das Multiplikationsargument mutieren?

Beachten Sie, dass wir in einigen Fällen nicht-erste Elemente mutieren, z. B. lmul! und ldiv! , damit wir sie in der üblichen "Muladd" -Reihenfolge ausführen können (dh muladd!(A,B,C) ). Die Frage ist, in welche Reihenfolge α und β gehen sollen. Eine Möglichkeit wäre, die Schlüsselwortargumente zu machen?

Wäre es nicht schön, wenn Sie den Implementierern die Möglichkeit überlassen würden, die Typen der Skalare α und β zu versenden? Es ist einfach, Zucker für Endbenutzer hinzuzufügen.

Ich dachte, wir haben uns bereits für mul!(C, A, B, α, β) mit Standardwerten für α , β . Wir verwenden diese Version in https://github.com/JuliaLang/julia/blob/b8ca1a499ff4044b9cb1ba3881d8c6fbb1f3c03b/stdlib/SparseArrays/src/linalg.jl#L32 -L50. Ich denke, einige Pakete verwenden auch dieses Formular, aber ich kann mich nicht erinnern, welches auf meinem Kopf liegt.

Vielen Dank! Es wäre schön, wenn das dokumentiert wäre.

Ich dachte, wir haben uns bereits für mul!(C, A, B, α, β) mit Standardwerten für α , β .

SparseArrays verwendet es, aber ich kann mich nicht erinnern, dass es irgendwo diskutiert wurde.

In mancher Hinsicht ist der Name muladd! natürlicher, da es sich um eine Multiplikation gefolgt von einer Addition handelt. Die Standardwerte von α und β, muladd!(C, A, B, α=1, β=0) (beachten Sie, dass der Standardwert für β Null und nicht Eins ist), verwandeln ihn wieder in mul!(C, A, B) .

Es scheint ein Fall von Pedanterie gegen Konsistenz zu sein, ob man dies mul! oder muladd! nennt, und ich denke, der Fall der vorhandenen Methode in SparseArrays würde für mul! sprechen. Ich befinde mich in dem merkwürdigen Fall, trotz meines Lieblingszitats von Oscar Wilde für Beständigkeit zu argumentieren: "Beständigkeit ist die letzte Zuflucht der Einfallslosen."

In mancher Hinsicht ist der Name muladd! natürlicher, da es sich um eine Multiplikation gefolgt von einer Addition handelt. Die Standardwerte von α und β, muladd!(C, A, B, α=1, β=0) (beachten Sie, dass der Standardwert für β Null und nicht Eins ist), verwandeln ihn wieder in mul!(C, A, B) .

Es gibt eine interessante Ausnahme, wenn C Infs oder NaNs enthält: Theoretisch sollte das Ergebnis immer noch NaNs sein, wenn β==0 . Dies ist in der Praxis nicht der Fall, da BLAS und unser spärlicher Matrixcode explizit nach β==0 suchen und ihn dann durch Nullen ersetzen.

Sie könnten davon ausgehen, dass Standardeinstellungen von α=true, β=false da true und false "stark" 1 bzw. 0 sind, in dem Sinne, dass true*x immer x und false*x sind immer zero(x) .

lmul! sollte auch dieses außergewöhnliche Verhalten aufweisen: https://github.com/JuliaLang/julia/issues/28972

true und false sind "stark" 1 bzw. 0 in dem Sinne, dass true*x immer x und false*x immer zero(x) .

Das wusste ich nicht!:

julia> false*NaN
0.0

FWIW, ich bin ziemlich zufrieden mit der Lesbarkeit der LazyArrays.jl-Syntax für diesen Vorgang:

y .= α .* Mul(A,x) .+ β .* y

Hinter den Kulissen wird es für BLAS-kompatible Arrays (gebändert und schrittweise) auf mul!(y, A, x, α, β) gesenkt.

Das wusste ich nicht!

Es ist Teil dessen, was im = Complex(false, true) bringt.

SparseArrays verwendet es, aber ich kann mich nicht erinnern, dass es irgendwo diskutiert wurde.

Es wurde oben in https://github.com/JuliaLang/julia/issues/23919#issuecomment -365463941 erörtert und in https://github.com/JuliaLang/julia/pull/26117 ohne Einwände implementiert. Wir haben nicht die α,β -Versionen im dichten Fall, daher wäre der einzige Ort in diesem Repo, an dem eine Entscheidung unmittelbare Auswirkungen hätte, SparseArrays .

Was ist mit LinearAlgebra.BLAS.gemm! ? Sollte es nicht auch als 5-ary mul! verpackt werden?

Es sollte aber noch niemand hat es getan. Es gibt viele Methoden in matmul.jl .

Es wurde oben in # 23919 (Kommentar) diskutiert und in # 26117 ohne Einwände implementiert.

Betrachten Sie dies als meinen Einwand. Ich würde einen anderen Namen bevorzugen.

Warum sollte es ein anderer Name sein? Sowohl im dichten als auch im spärlichen Fall führt der Basisalgorithmus sowohl die Multiplikation als auch die Addition durch.

Wenn wir diesen Funktionen unterschiedliche Namen geben, haben wir mul!(C,A,B) = dgemm(C,A,B,1,0) und muladd!(C,A,B,α, β) = dgemm(C,A,B,α, β) .

Der einzige Vorteil, den ich sehe, ist, wenn wir die Methoden tatsächlich aufteilen und einen if β==0 -Aufruf im Fall C = A*B speichern.

Zu Ihrer Information, ich habe in # 29634 angefangen, daran zu arbeiten, um die Schnittstelle zu matmul.jl hinzuzufügen. Ich hoffe, es zu beenden, bis der Name und die Unterschrift entschieden sind :)

Ein Vorteil von muladd! wäre, dass wir ternäre muladd!(A, B, C) (oder muladd!(C, A, B) ?) Mit dem Standard α = β = true (wie im ursprünglichen Vorschlag https erwähnt: //github.com/JuliaLang/julia/issues/23919#issuecomment-402953987). Die Methode muladd!(A, B, C) ähnelt muladd für Number s, daher denke ich, dass es ein natürlicherer Name ist, insbesondere wenn Sie muladd bereits kennen.

@andreasnoack Anscheinend geht es in Ihrer früheren Diskussion um die Methodensignatur und den Vorzug von Positionsargumenten gegenüber Schlüsselwortargumenten, nicht um den Methodennamen. Haben Sie Einwände gegen den Namen muladd! ? (Die Existenz von 5-ary mul! in SparseArrays mag eine sein, aber die Definition des abwärtskompatiblen Wrappers ist nicht schwer.)

Sowohl mul! als auch muladd! scheint überflüssig zu sein, wenn das erstere nur das letztere mit Standardwerten für α und β . Darüber hinaus wurde der Teil add von BLAS kanonisiert. Wenn wir eine glaubwürdige generische lineare Algebra-Anwendung für muladd! entwickeln könnten, würde ich gerne davon hören, aber ansonsten würde ich es vorziehen, die Redundanz zu vermeiden.

Außerdem würde ich es sehr bevorzugen, die Strong-Zero-Eigenschaft von false von der Diskussion über mul! trennen. IMO sollte jeder Nullwert von β so stark sein wie in BLAS und wie in den aktuellen Methoden mit fünf Argumenten mul! . Dh dieses Verhalten sollte eine Folge von mul! und nicht die Art von β . Die Alternative wäre schwierig zu bearbeiten. ZB mul!(Matrix{Float64}, Matrix{Float64}, Matrix{Float64}, 1.0, 0.0) ~ könnte ~ BLAS nicht verwenden.

Wir können nicht ändern, was BLAS tut, aber das Erfordernis eines starken Nullverhaltens für Floats bedeutet, dass jede Implementierung einen Zweig benötigt, um auf Null zu prüfen.

Wenn wir eine glaubwürdige generische lineare Algebra-Anwendung für muladd! könnten

@andreasnoack Damit meinen Sie vermutlich "Bewerbung für _three-argument_ muladd! ", da Sie sonst nicht zustimmen würden, mul! mit fünf Argumenten aufzunehmen?

Aber ich kann immer noch ein Beispiel finden, bei dem muladd!(A, B, C) nützlich ist. Wenn Sie beispielsweise ein "Small-World" -Netzwerk aufbauen möchten, ist es nützlich, eine "faule" Summierung einer gebänderten Matrix und einer Matrix mit geringer Dichte zu haben. Sie können dann etwas schreiben wie:

A :: SparseMatrixCSC
B :: BandedMatrix
x :: Vector  # input
y :: Vector  # output

# Compute `y .= (A .+ B) * x` efficiently:
fill!(y, 0)
muladd!(x, A, y)  # y .+= A * x
muladd!(x, B, y)  # y .+= B * x

Aber es macht mir nichts aus, dort true s manuell zu schreiben, da ich es einfach für meine Verwendung einpacken kann. Die Funktion mit fünf Argumenten als stabile dokumentierte API ist hier das wichtigste Ziel.

Zurück zum Punkt:

Sowohl mul! als auch muladd! scheint überflüssig, wenn erstere nur die letztere mit Standardwerten für α und β .

Wir haben jedoch einige * in Bezug auf mul! * implementiert, wobei der "Standardwert" des Ausgabearrays entsprechend initialisiert wurde. Ich denke, es gibt solche "Verknüpfungs" -Beispiele in Base und Standardbibliotheken? Ich denke, es ist sinnvoll, sowohl mul! als auch muladd! , obwohl mul! nur eine Abkürzung von muladd! .

Ich würde es sehr bevorzugen, dass wir die Strong-Zero-Eigenschaft von false von der Diskussion über mul!

Ich bin damit einverstanden, dass es konstruktiv wäre, sich darauf zu konzentrieren, zuerst den Namen der Version mit fünf Argumenten für das Multiplizieren zu addieren ( mul! vs muladd! ).

Ich habe keine gute Arbeit geleistet, als ich nach einem generischen Anwendungsfall gefragt habe, bei dem Sie muladd benötigen, um generisch über Matrizen und Zahlen hinweg zu arbeiten. Die Zahlenversion wäre muladd ohne das Ausrufezeichen, also ergab das, was ich fragte, keinen Sinn.

Ihr Beispiel könnte einfach so geschrieben werden

mul!(y, A, x, 1, 1)
mul!(y, B, x, 1, 1)

Daher sehe ich immer noch keine Notwendigkeit für muladd! . Denken Sie nur, dass dieser Fall so häufig ist, dass das Schreiben von 1, 1 zu ausführlich ist?

Wir haben jedoch einige * in Bezug auf mul! * implementiert, wobei der "Standardwert" des Ausgabearrays entsprechend initialisiert wurde. Ich denke, es gibt solche "Verknüpfungs" -Beispiele in Base und Standardbibliotheken?

Ich verstehe das nicht. Könnten Sie versuchen, näher darauf einzugehen? Über welche Verknüpfungen sprechen Sie hier?

Daher sehe ich immer noch keine Notwendigkeit für muladd! . Denken Sie nur, dass dieser Fall so häufig ist, dass das Schreiben von 1, 1 zu ausführlich ist?

Ich denke, muladd! ist auch aussagekräftiger, was es tatsächlich tut (obwohl es vielleicht addmul! ).

Ich habe kein Problem mit dem Namen muladd! . In erster Linie denke ich einfach nicht, dass wir dafür funktionieren müssen, und in zweiter Linie denke ich nicht, dass es sich lohnt, mul! zugunsten von muladd! / addmul! zu verwerfen.

Denken Sie nur, dass dieser Fall so häufig ist, dass das Schreiben von 1, 1 zu ausführlich ist?

Nein. Ich bin völlig in Ordnung mit dem Aufrufen der Funktion mit fünf Argumenten, solange es sich um eine öffentliche API handelt. Ich habe nur versucht, ein Beispiel zu nennen, in dem ich nur eine Version mit drei Argumenten benötige (da ich dachte, das wäre Ihre Anfrage).

Über welche Verknüpfungen sprechen Sie hier?

https://github.com/JuliaLang/julia/blob/f068f21d6099632bd5543ad065d5de96943c9181/stdlib/LinearAlgebra/src/matmul.jl#L140 -L143

Ich denke, dass * das hier definiert ist, als Abkürzung von mul! . Es ist "nur" mul! mit einem Standardwert. Warum sollte mul! nicht ein muladd! / addmul! mit Standardwerten sein?

Es gibt rmul! und lmul! die ebenfalls als ähnliche "Verknüpfungen" definiert sind:

https://github.com/JuliaLang/julia/blob/f068f21d6099632bd5543ad065d5de96943c9181/stdlib/LinearAlgebra/src/triangular.jl#L478 -L479

abwertend mul!

Ich dachte, in der Diskussion ging es darum, eine neue Schnittstelle hinzuzufügen oder nicht. Wenn wir mul! ablehnen müssen, um eine neue API hinzuzufügen, denke ich nicht, dass es sich lohnt.

Die Hauptargumente, an die ich denken kann, sind:

  • Konzeptionell ist die 5-Argument-Form mehr als nur "multiplizieren" und vermittelt dies deutlicher.
  • Sie können dann addmul!(C, A, B) anstelle von mul!(C,A,B,1,1) oder mul!(C,A,B,true,true) schreiben.

Ich denke, dass * das hier definiert ist, als Abkürzung von mul! . Es ist "nur" mul! mit einem Standardwert. Also, warum nicht mul lassen! ein muladd! / addmul! mit Standardwerten sein?

Weil * die Standardmethode zum Multiplizieren von Matrizen ist und wie die meisten Benutzer dies tun würden. Im Vergleich dazu würde muladd! bei Verwendung nicht annähernd * . Darüber hinaus ist es sogar ein vorhandener Operator, während muladd! / addmul! eine neue Funktion wäre.

Denken Sie nicht, dass rmul! und lmul! zu diesem Muster passen, da sie im Allgemeinen keine Standardwertversionen von fehl am Platz befindlichen mul! -Methoden sind.

Simon fasst die Vorteile im obigen Beitrag gut zusammen. Die Frage ist, ob die Vorteile groß genug sind, um eine zusätzliche Funktion einer Umbenennung zu rechtfertigen (was eine Abwertung von mul! ). Hier sind wir uns nicht einig. Ich denke nicht, dass es das wert ist.

Wenn Sie sagen, dass sich das Umbenennen nicht lohnt, haben Sie berücksichtigt, dass die API nicht vollständig öffentlich ist? Damit meine ich, dass es nicht in Julias Dokumentation steht.

Ich weiß, dass LazyArrays.jl (und andere Pakete?) Es bereits so blind verwendet, dass es nicht gut wäre, dem Semver zu folgen. Trotzdem ist es nicht so öffentlich wie andere Funktionen.

mul! wird aus LinearAlgebra exportiert und weit verbreitet verwendet, sodass wir es an dieser Stelle definitiv ablehnen müssten. Es ist eine Schande, dass wir diese Diskussion nicht hatten, als aus A_mul_B! mul! oder zumindest vor 0.7, weil es ein viel besserer Zeitpunkt gewesen wäre, die Funktion umzubenennen.

Wie wäre es, wenn Sie jetzt mul! und den Namen für LinearAlgebra v2.0 aktualisieren, wenn wir stdlibs separat aktualisieren können?

LazyArrays.jl verwendet mul! da es für viele Arten von Matrizen nicht flexibel ist (und einen Compiler-Langsamkeitsfehler auslöst, wenn Sie mit StridedArray s überschreiben). Es gibt eine alternative Konstruktion der Form

y .= Mul(A, x)

was ich finde, ist beschreibender. Das Analogon mit 5 Argumenten ist

y .= a .* Mul(A, x) .+ b .* y

Ich würde mich dafür aussprechen, mul! zu verwerfen und zum LazyArrays.jl-Ansatz in LinearAlgebra.jl überzugehen, aber das wird ein schwieriger Fall.

LowRankApprox.jl verwendet zwar mul! , aber ich könnte es ändern, um den LazyArrays.jl-Ansatz zu verwenden und dadurch den Compiler-Fehler zu vermeiden.

IN ORDNUNG. Ich dachte, es gibt nur zwei Vorschläge. Aber anscheinend gibt es ungefähr drei Vorschläge?:

  1. Drei- und Fünf-Argumente mul!
  2. Drei- und Fünf-Argumente muladd!
  3. Drei-Argument mul! und fünf-Argument muladd!

( muladd! kann addmul! )

Ich dachte, wir vergleichen 1 und 3. Mein Verständnis ist jetzt, dass @andreasnoack 1 und 2

Ich würde sagen, 2 ist überhaupt keine Option, da mul! mit drei Argumenten eine öffentliche API ist und weit verbreitet ist. Was ich mit "Die API ist nicht vollständig öffentlich" meinte, war, dass mul! fünf Argumenten nicht dokumentiert ist .

Ja, mein Plan war es, mul! zu behalten (als 3-arg- und möglicherweise 4-arg-Form). Ich denke, das lohnt sich, da 3 Argumente mul! und addmul! ein anderes Verhalten haben würden, dh wenn wir addmul!(C, A, B, α, β) , hätten wir:

mul!(C, A, B) = addmul!(C, A, B, 1, 0)
mul!(C, A, B, α) = addmul!(C, A, B, α, 0)
addmul!(C, A, B) = addmul!(C, A, B, 1, 1)
addmul!(C, A, B, α) = addmul!(C, A, B, α, 1)

Möglicherweise möchten Sie sie jedoch in der Praxis nicht auf diese Weise implementieren, z. B. ist es möglicherweise einfacher, nur die 4-Argumente mul! und addmul! separat zu definieren und die 5-Argumente addmul! as:

addmul!(C, A, B, α, β) = addmul!(C .= β .* C, A, B, α)

Stoßen!

Möglicherweise möchten Sie sie jedoch nicht auf diese Weise in die Praxis umsetzen, z. B. ist es möglicherweise einfacher, nur das 4-Argument-Mul zu verwenden! und addmul! separat und definieren Sie das 5-Argument-Addmul! wie:
addmul!(C, A, B, α, β) = addmul!(C .= β .* C, A, B, α)

Warum nicht gleich optimal? Der Punkt, den Sie nicht so machen, ist, dass Sie die Elemente von C einmal besuchen müssen, was für große Matrizen definitiv effizienter ist. Außerdem kann ich kaum glauben, dass der Code länger wäre, wenn nur die 5-Argumente addmul! Vergleich zu den 4-Argumenten mul! und addmul! separat definiert würden.

Zu Ihrer Information: Ich habe die Implementierung von _generic_matmatmul! in LinearAlgebra so geändert, dass 5 Argumente in LazyArrays verwendet werden: https://github.com/JuliaArrays/LazyArrays.jl/blob/8a50250fc6cf3f2402758088227769cf2de2e053/src/linalj

Hier heißt es via:

materialize!(MulAdd(α, A, b, β, c)) 

Der eigentliche Code (in tiled_blasmul! ) lässt sich jedoch leicht wieder in LinearAlgebra übersetzen.

Was kann getan werden, um diesen Prozess zu beschleunigen? Dinge, an denen ich arbeite, würden wirklich von einer einheitlichen Matrix-Multiplikations-API mit direktem Mul + Add profitieren

Die neueste Version von Strided.jl unterstützt jetzt auch 5 Argumente mul!(C,A,B,α,β) , die nach Möglichkeit an BLAS gesendet werden und ansonsten eine eigene (Multithread-) Implementierung verwenden.

@ Jutho tolles Paket! Gibt es eine Roadmap für die nächsten Schritte? könnte der Plan sein, irgendwann mit LinearAlgebra zu fusionieren?

Dies war nie meine Absicht, aber ich bin nicht dagegen, wenn dies irgendwann verlangt wird. Ich denke jedoch, dass meine liberale Verwendung von @generated -Funktionen (obwohl es nur eine gibt) in der allgemeinen mapreduce -Funktionalität möglicherweise nicht gut für Base geeignet ist.

Meine persönliche Roadmap: Dies ist hauptsächlich ein Low-Level-Paket, das von übergeordneten Paketen verwendet wird, dh der neuen Version von TensorOperations und einem anderen Paket, an dem ich arbeite. Eine weitere Unterstützung für die grundlegende lineare Algebra wäre jedoch gut (z. B. fällt das Anwenden von norm auf ein StridedView derzeit auf eine eher langsame Implementierung von norm in Julia Base zurück). Und wenn ich Zeit habe und lerne, mit GPUs zu arbeiten, versuche ich, ein ebenso allgemeines mapreducekernel für GPUArray s zu implementieren.

Ich denke, der bisherige Konsens ist:

  1. Wir sollten mul!(C, A, B) behalten
  2. Wir benötigen eine Funktion mit einigen 5 Argumenten, um C = αAB + βC multiplizieren und zu addieren

Ich schlage vor, sich zunächst auf den Namen der 5-Argument-Funktion zu konzentrieren und später eine zusätzliche API zu diskutieren (wie 3- und 4-Argument addmul! ). Aber dies ist die "Funktion", die wir von _not_ mit mul! daher ist es schwierig, nicht zu mischen.

@andreasnoack Ist Ihre Besorgnis über die @simonbyrne über https://github.com/JuliaLang/julia/issues/23919#issuecomment -431046516 behoben? Ich denke, es besteht kein Grund zur Abwertung.

Zu Ihrer Information, ich habe gerade die Implementierung # 29634 abgeschlossen. Ich freue mich, wenn jemand, der mit LinearAlgebra vertraut ist, dies überprüfen kann.

Ich denke, es ist einfacher und besser, alles mul! . Es vermeidet auch die Abwertung. Wenn wir wirklich wirklich einen anderen Namen wollen, ist muladd besser.

Noch etwas, das Sie vielleicht berücksichtigen sollten, wenn Sie die mul! API diskutieren:

Als scale! verschwand und in den Übergang von 0,6 -> 0,7 aufgenommen wurde, war ich ein bisschen traurig, weil für mich die Skalarmultiplikation (eine Eigenschaft von Vektorräumen) sehr unterschiedlich war als das Multiplizieren von Objekten selbst (eine Eigenschaft von Algebren) ). Trotzdem habe ich den mul! -Ansatz voll und ganz angenommen und schätze die Fähigkeit zu rmul!(vector,scalar) und lmul!(scalar,vector) wenn die Skalarmultiplikation nicht kommutativ ist. Aber jetzt stört mich der un-julianische Name von zwei anderen vorhandenen Vektorraumoperationen jeden Tag mehr: axpy! und seine Verallgemeinerung axpby! . Könnten diese auch in mul! / muladd! / addmul! ? Obwohl es etwas seltsam ist, wenn einer der beiden Faktoren in A*B bereits ein Skalar ist, ist kein zusätzlicher Skalarfaktor α erforderlich.
Aber vielleicht dann in Analogie zu

mul!(C, A, B, α, β)

es könnte auch eine geben

add!(Y, X, α, β)

axpby! ersetzen.

@andreasnoack Ist Ihre Besorgnis über die @simonbyrne über # 23919 (Kommentar) behoben? Ich denke, es besteht kein Grund zur Abwertung.

Siehe den letzten Absatz von https://github.com/JuliaLang/julia/issues/23919#issuecomment -430952179. Ich denke immer noch, dass die Einführung einer neuen Funktion es nicht wert ist. Wenn wir es trotzdem tun, sollten wir das aktuelle 5-Argument mul! .

@Jutho Ich denke, das Umbenennen von acp(b)y! in add! wäre eine gute Idee.

Siehe den letzten Absatz von # 23919 (Kommentar) . Ich denke immer noch, dass die Einführung einer neuen Funktion es nicht wert ist.

Ja, ich habe es gelesen und geantwortet, dass mul! fünf Argumenten nicht dokumentiert wurde und nicht Teil der öffentlichen API war. Technisch gesehen besteht also keine Notwendigkeit für eine Abschreibung. Siehe den letzten Absatz von https://github.com/JuliaLang/julia/issues/23919#issuecomment -430975159 (Natürlich wäre es sowieso nett, eine Ablehnung zu haben, also habe ich sie bereits in # 29634 implementiert.)

Hier gehe ich davon aus, dass die Deklaration der öffentlichen API aufgrund der Dokumentation einer Signatur (z. B. mul!(C, A, B) ) nicht für andere Signaturen gilt (z. B. mul!(C, A, B, α, β) ). Wenn es nicht der Fall ist, denke ich, dass Julia und ihre stdlib zu viele Interna freigeben. Hier ist zum Beispiel die dokumentierte Signatur von Pkg.add

https://github.com/JuliaLang/julia/blob/0d713926f85dfa3e4e0962215b909b8e47e94f48/stdlib/Pkg/src/Pkg.jl#L76 -L79

während die eigentliche Definition ist

https://github.com/JuliaLang/julia/blob/0d713926f85dfa3e4e0962215b909b8e47e94f48/stdlib/Pkg/src/API.jl#L69 -L70

https://github.com/JuliaLang/julia/blob/0d713926f85dfa3e4e0962215b909b8e47e94f48/stdlib/Pkg/src/API.jl#L27 -L33

Wenn das Vorhandensein der Dokumentation von mindestens einer Signatur von Pkg.add impliziert, dass andere Signaturen eine öffentliche API sind, kann Pkg.jl das Verhalten aufgrund von Implementierungsdetails nicht entfernen, ohne die Hauptversion zu beschädigen, z. B.: Pkg.add(...; mode = :develop) läuft Pkg.develop(...) ; Alle Schlüsselwortargumente für Context! werden unterstützt (was möglicherweise tatsächlich beabsichtigt ist).

Aber das ist sowieso nur mein Eindruck. Denken Sie, dass mul!(C, A, B, α, β) so öffentlich war wie mul!(C, A, B) ?

Ich denke, wir reden aneinander vorbei. Was ich sage ist, dass ich (immer noch) nicht denke, dass es sich lohnt, eine andere Funktion einzuführen. Daher mein Verweis auf meinen vorherigen Kommentar. Dies ist unabhängig von der Diskussion über die Ablehnung von mul! mit fünf Argumenten.

Wenn wir uns jedoch dazu entschließen, eine weitere Funktion hinzuzufügen, ist es meiner Meinung nach am besten, fünf Argumente mul! zu verwerfen, anstatt sie nur zu brechen. Natürlich wird es nicht so häufig verwendet wie mul! mit drei Argumenten, aber warum nicht ablehnen, anstatt es nur zu brechen?

Dies ist unabhängig von der Diskussion über die Ablehnung von mul! mit fünf Argumenten.

Meine Interpretation des letzten Absatzes Ihres Kommentars https://github.com/JuliaLang/julia/issues/23919#issuecomment -430952179 war, dass Sie die Vorteile von @simonbyrne anerkannt haben, die https://github.com/JuliaLang/julia/issues aufgeführt sind mul! mit fünf Argumenten öffentlich ist oder nicht.

Sie haben aber auch die Rechtfertigung einer "zusätzlichen Funktion" erwähnt, auf die Sie sich jetzt wohl beziehen. Argumentieren Sie, dass die Berechnungen _C = AB_ und _C = αAB + βC_ ähnlich genug sind, so dass der gleiche Name beide beschreiben kann? Ich bin eigentlich anderer Meinung, da es andere Möglichkeiten gibt, mul! drei Argumenten zu verallgemeinern: zB warum nicht mul!(y, A₁, A₂, ..., Aₙ, x) für _y = A₁ A₂ ⋯ Aₙ x_ https://github.com/JuliaLang/julia / issue / 23919 # issuecomment -402953987?

Warum nicht ablehnen, anstatt es nur zu zerbrechen?

Wie ich in den vorherigen Kommentaren sagte, stimme ich zu, dass es das Richtige ist, fünf Argumente mul! zu verwerfen, wenn wir eine andere Funktion einführen würden. Dieser Code existiert bereits in meiner PR # 29634.

Argumentieren Sie, dass die Berechnungen C = AB und C = αAB + βC ähnlich genug sind, so dass der gleiche Name beide beschreiben kann?

Ja, da Ersteres nur Letzteres mit β=0 . Es ist fair zu argumentieren, dass muladd! / addmul! ein genauerer Name für C = αAB + βC aber um dorthin zu gelangen, müsste entweder eine andere Matrixmultiplikationsfunktion eingeführt werden ( muladd! / addmul! ) oder mul! umbenennen und ich denke nicht, dass es sich jetzt lohnt. Wenn dies im Frühjahr geschehen wäre, wäre es einfacher gewesen, eine Änderung in Betracht zu ziehen.

Ich bin eigentlich anderer Meinung, da es andere Möglichkeiten gibt, Mul mit drei Argumenten zu verallgemeinern!:

Julia hat zufällig die In-Place-Matrix-Multiplikationsmethoden ohne die Argumente α und β , aber die Matrix-Multiplikations-Tradition basiert wirklich auf BLAS-3 und dort ist die allgemeine Matrix-Multiplikationsfunktion C = αAB + βC .

Umbenennen von mul!

Meinen Sie es in stdlib oder in Downstream-Benutzermodul / Code umzubenennen? Wenn Sie das erstere meinen, ist es bereits (für LinearAlgebra und SparseArrays) in # 29634 erledigt, sodass Sie sich darüber keine Sorgen machen müssen. Wenn Sie letzteres meinen, denke ich, dass es wieder auf öffentliche oder nicht öffentliche Diskussionen hinausläuft.

Die Tradition der Matrixmultiplikation basiert wirklich auf BLAS-3

Aber Julia ist bereits von der Namenskonvention von BLAS abgewichen. Wäre es nicht schön, einen aussagekräftigeren Namen zu haben?

Meinen Sie es in stdlib oder in Downstream-Benutzermodul / Code umzubenennen?

29634 benennt die Funktion mul! . Es fügt die neue Funktion addmul! .

Aber Julia ist bereits von der Namenskonvention von BLAS abgewichen.

Ich spreche nicht über die Benennung. Zumindest nicht genau, da Fortran 77 einige Einschränkungen aufweist, die wir in Bezug auf Funktionsnamen und Versand nicht haben. Ich spreche über das, was berechnet wird. Die allgemeine Matrixmultiplikationsfunktion in BLAS-3 berechnet C = αAB + βC und in Julia war es mul! (fka A_mul_B! ).

Wäre es nicht schön, einen aussagekräftigeren Namen zu haben?

Es würde, und das habe ich schon mehrmals gesagt. Das Problem ist, dass es nicht so viel schöner ist, dass wir zwei Matrixmultiplikationsfunktionen haben sollten, die im Grunde dasselbe tun.

29634 benennt die Funktion mul! . Es fügt die neue Funktion addmul! .

Was ich damit sagen wollte war, dass mul! fünf Argumenten in addmul! .

Das Problem ist, dass es nicht so viel schöner ist, dass wir zwei Matrixmultiplikationsfunktionen haben sollten, die im Grunde dasselbe tun.

Ich denke, ob sie im Grunde gleich sind oder nicht, ist etwas subjektiv. Ich denke, dass sowohl _C = αAB + βC_ als auch _Y = A₁ A₂ ⋯ Aₙ X_ eine mathematisch gültige Verallgemeinerung von _C = AB_ sind. Wenn _C = αAB + βC_ nicht die eindeutige Verallgemeinerung ist, denke ich nicht, dass das Argument stark genug ist. Es hängt auch davon ab, ob Sie die BLAS-API kennen und ich bin mir nicht sicher, ob dies das Grundwissen für typische Julia-Benutzer ist.

Außerdem unterscheiden sich _C = AB_ und _C = αAB + βC_ rechnerisch sehr darin, dass der Inhalt von C verwendet wird oder nicht. Es ist ein Nur-Ausgabe-Parameter für den ersteren und ein Eingabe-Ausgabe-Parameter für den letzteren. Ich denke, dieser Unterschied verdient einen visuellen Hinweis. Wenn ich sehe, dass mul!(some_func(...), ...) und mul! die Form mit fünf Argumenten haben, muss ich die Anzahl der Argumente zählen (was schwierig ist, wenn sie das Ergebnis eines Funktionsaufrufs sind, da Sie mit Klammern übereinstimmen müssen). um zu sehen, ob some_func eine Berechnung oder nur eine Zuordnung vornimmt. Wenn wir addmul! kann ich sofort erwarten, dass some_func in mul!(some_func(...), ...) nur die Zuordnung übernimmt.

Ich denke, ob sie im Grunde gleich sind oder nicht, ist etwas subjektiv. Ich denke, dass sowohl C = αAB + βC als auch Y = A₁ A₂ ⋯ Aₙ X eine mathematisch gültige Verallgemeinerung von C = AB sind. Wenn C = αAB + βC nicht die eindeutige Verallgemeinerung ist, denke ich nicht, dass das Argument stark genug ist.

Es ist möglicherweise nicht die eindeutige Verallgemeinerung, es handelt sich jedoch um eine Verallgemeinerung, die zu ungefähr identischen Kosten berechnet werden kann und ein nützliches Grundelement für die Erstellung anderer linearer Algebra-Algorithmen bildet. Bei vielen Gelegenheiten wollte ich bei der Implementierung verschiedener Algorithmen im Zusammenhang mit linearer Algebra eine Beta ungleich Null haben und musste immer auf BLAS.gemm! zurückgreifen. Andere Verallgemeinerungen wie die von Ihnen erwähnte können ohnehin nicht auf einmal ohne Zwischenzeiträume berechnet werden, daher ist eine In-Place-Version viel weniger nützlich. Darüber hinaus sind sie nicht so allgemein nützlich wie eine primitive Operation.

Es hängt auch davon ab, ob Sie die BLAS-API kennen und ich bin mir nicht sicher, ob dies das Grundwissen für typische Julia-Benutzer ist.

Solange die Standardargumente α=1 und β=0 sind, werden die drei Argumente mul! das tun, was jeder Julia-Benutzer ohne BLAS-Hintergrund vernünftigerweise erwarten würde. Für die erweiterten Optionen muss man das Handbuch konsultieren, wie es mit jeder Sprache und jeder Funktion zu tun hätte. Darüber hinaus ersetzt dieser einzelne mul! -Anruf nicht nur gemm sondern auch gemv und trmv (was seltsamerweise nicht α und hat β Parameter in der BLAS-API) und wahrscheinlich viele andere.

Ich bin damit einverstanden, dass BLAS-3 die richtige Verallgemeinerung in Bezug auf die Berechnung ist und sehr gut komponiert. Ich spreche eine andere mögliche Verallgemeinerung an, nur weil ich denke, dass sie nicht "eindeutig genug" ist, um die Verwendung des gleichen Namens zu rechtfertigen. Siehe auch Argument "Nur Ausgabe vs. Eingabe-Ausgabe" im letzten Absatz von https://github.com/JuliaLang/julia/issues/23919#issuecomment -441267056. Ich denke, ein anderer Name erleichtert das Lesen / Überprüfen von Code.

Darüber hinaus ersetzt dieser einzelne mul! -Anruf nicht nur gemm sondern auch gemv und trmv (was seltsamerweise nicht α und hat β Parameter in der BLAS-API) und wahrscheinlich viele andere.

Ja, bereits in # 29634 implementiert und es kann losgehen, sobald der Name festgelegt ist (und überprüft wird)!

Es fällt mir schwer, diesem Gespräch zu folgen (es ist ein bisschen lang und weitläufig ... Entschuldigung!). Ist der Leitvorschlag so etwas wie mul!(C, A, B; α=true, β=false) ?

Ich glaube nicht, dass das Schlüsselwortargument für α und β auf dem Tisch liegt. Beispielsweise hat @andreasnoack Keyword-Argumente in https://github.com/JuliaLang/julia/issues/23919#issuecomment -365762889 verworfen. @simonbyrne erwähnte Keyword-Argumente in https://github.com/JuliaLang/julia/issues/23919#issuecomment -426881998, aber sein jüngster Vorschlag https://github.com/JuliaLang/julia/issues/23919#issuecomment -431046516 ist positionell Argumente.

Wir haben den Namen noch nicht festgelegt (dh mul! vs addmul! vs muladd! ) und ich denke, das ist das zentrale Thema (oder zumindest ist das mein Wunsch).

Wie lösen Sie diese Art von Kontroverse normalerweise? Wählen? Triage?

ist der führende Vorschlag so etwas wie Mul! (C, A, B; α = wahr, β = falsch)?

Ich mag das, aber ohne die Kwargs.

Ich habe das Schlüsselwort nicht gesehen. Ich zögere auch, Unicode-Schlüsselwörter hinzuzufügen. Ich denke, Positionsargumente mit diesen Standardwerten sind in Ordnung. Was die bevorstehende Abstimmung betrifft, ist meine auf einfach mul! . Ich denke, dies ist eine Verallgemeinerung von mul! , die ausreichend spezifisch und nützlich ist, um keinen neuen Namen zu benötigen.

Um Daten zu sammeln (zumindest im Moment), lassen Sie uns abstimmen:

Was ist Ihr Lieblingsfunktionsname für _C = αAB + βC_?

  • : +1: mul!
  • : -1: addmul!
  • : smile: muladd!
  • : tada: etwas anderes

Für mich scheint addmul! (A+B)C anstelle von AB + C .

Zuerst habe ich für mul! gestimmt, dann habe ich mir die Operation angesehen und dachte: "Es wird multipliziert und dann addiert. Natürlich sollten wir es muladd! . Jetzt kann ich nicht daran denken, es zu nennen." Alles andere. Die Tatsache, dass es vorhanden ist, wird durch ! deutlich angezeigt, und der Skalierungsteil scheint für Keyword-Argumente geeignet zu sein.

Es wird multipliziert und dann addiert. Natürlich sollten wir es muladd!

Nur wenn Sie den Standardwert β=true , aber für jeden anderen Wert ist es wieder etwas allgemeineres. Was bringt es also, es nicht mul! , wo jeder andere Wert als der Standardwert β=false Ihnen auch nur etwas allgemeineres gibt? Und wie würden Sie die Argumente im Vergleich zu muladd(x,y,z) = x*y + z ordnen? Wird etwas verwirrend sein, nein?

Ich denke, muladd! hat den Nachteil, beschreibend zu klingen, wenn es nicht so ist: Ein beschreibender Name wäre so etwas wie scalemuladd! , um den Skalierungsteil zu erwähnen.

Ich bevorzuge also mul! da es unscheinbar genug ist, um nicht zu Erwartungen zu führen.

Trotzdem nenne ich die Lazy-Version in LazyArrays.jl MulAdd .

Ich bevorzuge muladd! gegenüber mul! weil es schön ist, eine Funktion, die niemals den Wert von C ( mul! ) verwendet, von einer Funktion zu unterscheiden, die sie verwendet ( muladd! ).

  • Technisch ist es eine Matrixmultiplikation: [AC] * [Bα; Iβ] oder, siehe Kommentar unten, [αAβC] * [B; ICH]
  • ~ Wir haben bereits ein 5-arg mul! für spärliche Matrizen, dasselbe für dichtes Linalg wäre konsistent ~ (kein neues Argument)

Ich würde es also lieber mul! .

  • Technisch gesehen ist es eine Matrixmultiplikation: [AC] * [Bα; Iβ]

... wenn eltype eine kommutative Multiplikation hat

... wenn eltype kommutativ ist.

IIRC aus einer Diskussion mit @andreasnoack Julia definiert nur gemm / gemv als y <- A * x * α + y * β weil das am sinnvollsten ist.

@haampie Das ist gut zu wissen! Wie ich es umgekehrt in # 29634 implementiert habe.

Dies ist jedoch nur begrenzt hilfreich

       C = α*A*B + β*C

ist der beste Weg, den ich finden kann, um die Operation auszudrücken, und daher wäre vielleicht ein Makro <strong i="8">@call</strong> C = α*A*B + β*C oder <strong i="10">@call_specialized</strong> ... oder etwas in dieser Richtung eine natürliche Schnittstelle - auch für ähnliche Situationen. Dann kann die zugrunde liegende Funktion wie auch immer aufgerufen werden.

@mschauer LazyArrays.jl von @dlfivefifty hat eine großartige Syntax zum Aufrufen von mul! 5 Argumenten wie Ihre Syntax (und mehr!).

Ich denke, wir müssen zuerst eine funktionsbasierte API einrichten, damit Paketautoren damit beginnen können, sie für ihre speziellen Matrizen zu überladen. Dann kann die Julia-Community anfangen, Zucker zu experimentieren, um ihn zu nennen.

Nur wenn Sie den Standardwert β=true , aber für jeden anderen Wert ist es wieder etwas allgemeineres. Was bringt es also, es nicht mul! , wo jeder andere Wert als der Standardwert β=false Ihnen auch nur etwas allgemeineres gibt? Und wie würden Sie die Argumente im Vergleich zu muladd(x,y,z) = x*y + z ordnen? Wird etwas verwirrend sein, nein?

Sicher, es gibt einige Skalierungen, aber die "Knochen" der Operation sind eindeutig multipliziert und addiert. Ich würde auch gut mit muladd!(A, B, C, α=true, β=false) umgehen, um die Signatur von muladd . Müsste natürlich dokumentiert werden, aber das versteht sich von selbst. Es lässt mich irgendwie wünschen, dass muladd den additiven Teil übernommen hat, aber das Schiff ist auf diesem gesegelt.

Und wie würden Sie die Argumente im Vergleich zu muladd(x,y,z) = x*y + z ordnen? Wird etwas verwirrend sein, nein?

Dies ist der Grund, warum ich addmul! gegenüber muladd! bevorzuge. Wir können sicherstellen, dass die Reihenfolge der Argumente nichts mit dem Skalar muladd zu tun hat. (Obwohl ich muladd! gegenüber mul! bevorzuge)

FWIW hier ist eine Zusammenfassung der bisherigen Argumente. (Ich habe versucht, neutral zu sein, aber ich bin pro muladd! / addmul! , also denke daran ...)

Die Hauptstreitigkeit besteht darin, dass, ob _C = AB_ und _C = αAB + βC_ unterschiedlich genug sind, um letzterem einen neuen Namen zu geben.

Sie sind ähnlich genug, weil ...

  1. Es ist BLAS-3 und gut zusammensetzbar. _C = αAB + βC_ ist also eine offensichtliche Verallgemeinerung von _C = AB_ (https://github.com/JuliaLang/julia/issues/23919#issuecomment-441246606, https://github.com/JuliaLang/julia/issues/). 23919 # issuecomment-441312375 usw.)

  2. _ " muladd! hat den Nachteil, dass es beschreibend klingt, wenn es nicht so ist: Ein beschreibender Name wäre so etwas wie scalemuladd! , um den Skalierungsteil zu erwähnen." _ --- https://github.com/ JuliaLang / julia / issue / 23919 # issuecomment -441819470

  3. _ "Technisch ist es eine Matrixmultiplikation: [AC] * [Bα; Iβ]" _ --- https://github.com/JuliaLang/julia/issues/23919#issuecomment -441825009

Sie sind unterschiedlich genug, weil ...

  1. _C = αAB + βC_ ist mehr als multipliziert (https://github.com/JuliaLang/julia/issues/23919#issuecomment-430809383, https://github.com/JuliaLang/julia/issues/23919#issuecomment-427075792, https://github.com/JuliaLang/julia/issues/23919#issuecomment-441813176 usw.).

  2. Es könnte andere Verallgemeinerungen von mul! wie Y = A₁ A₂ ⋯ Aₙ X (https://github.com/JuliaLang/julia/issues/23919#issuecomment-402953987 usw.)

  3. Nur-Eingabe- oder Eingabe-Ausgabe-Parameter: Es ist verwirrend, eine Funktion zu haben, die die Daten in C basierend auf der Anzahl der Argumente verwendet (https://github.com/JuliaLang/julia/issues/23919#issuecomment) -441267056, https://github.com/JuliaLang/julia/issues/23919#issuecomment-441824982)

Ein weiterer Grund, warum mul! besser ist, weil ...:

  1. Sparse Matrix hat es bereits. Es ist also gut für die Abwärtskompatibilität. Gegenargument: mul! fünf Argumenten ist nicht dokumentiert, daher müssen wir es nicht als öffentliche API betrachten.

und warum muladd! / addmul! besser ist, weil ...:

  1. Wir können verschiedene "praktische Funktionen" mit drei oder vier Argumenten für mul! und muladd! / addmul! separat verwenden (https://github.com/JuliaLang/julia/issues) / 23919 # issuecomment-402953987, https://github.com/JuliaLang/julia/issues/23919#issuecomment-431046516 usw.). Gegenargument: Das Schreiben von mul!(y, A, x, 1, 1) ist im Vergleich zu mul!(y, A, x) nicht sehr ausführlich (https://github.com/JuliaLang/julia/issues/23919#issuecomment-430674934 usw.)

Danke für die objektive Zusammenfassung @tkf

Ich würde auch gut mit Muladd umgehen können (A, B, C, α = wahr, β = falsch), um der Signatur von Muladd zu entsprechen.

Ich hoffe, dass für eine Funktion namens mulladd! der Standardwert β=true . Dennoch denke ich, dass diese Reihenfolge von Argumenten, die von muladd diktiert wird, in Bezug auf mul!(C,A,B) sehr verwirrend sein wird

Vielleicht irre ich mich, aber ich würde denken, dass die meisten Leute / Anwendungen / High-Level-Code (die nicht nur mit dem Multiplikationsoperator * zufrieden sind) mul! benötigen. Die Möglichkeit, auch βC mit β=1 ( true ) oder auf andere Weise zu mischen, wird in Code niedrigerer Ebene von Personen verwendet, die wissen, dass die BLAS-API für die Matrixmultiplikation dies ermöglicht diese. Ich würde vermuten, dass diese Leute unter mul! nach dieser Funktionalität suchen würden. Dies ist die etablierte Julia-Schnittstelle zu gemm , gemv , ... Hinzufügen eines neuen Namens (was verwirrend ist entgegengesetzte Argumentationsreihenfolge) scheint es nicht wert zu sein; Ich sehe den Gewinn nicht?

Ich würde vermuten, dass diese Leute unter mul! nach dieser Funktionalität suchen würden. Dies ist die etablierte Julia-Schnittstelle zu gemm , gemv , ... Hinzufügen eines neuen Namens (was verwirrend ist entgegengesetzte Argumentationsreihenfolge) scheint es nicht wert zu sein; Ich sehe den Gewinn nicht?

Ich denke, die Auffindbarkeit ist kein großes Problem, da wir einfach muladd! in mul! docstring erwähnen können. Diejenigen, die sich mit BLAS auskennen, wissen, wo sie nach einer API suchen müssen, oder?

In Bezug auf Positions- und Schlüsselwortargumente: Es wird hier noch nicht diskutiert, aber ich denke, dass C = αAB + βC mit α als diagonale Matrix genauso effizient und einfach implementiert werden kann wie skalare α . Eine solche Erweiterung erfordert, dass wir den Typ α versenden können, was mit dem Schlüsselwortargument unmöglich ist.

Für nicht kommutative eltype möchten Sie möglicherweise C = ABα + Cβ effizient berechnen, indem Sie muladd!(α', B', A', β', C') aufrufen (hypothetische Argumentreihenfolge). Möglicherweise müssen Sie in der Lage sein, auf faulen Wrappern Adjoint(α) und Adjoint(β) zu versenden. (Ich persönlich verwende in Julia keine nicht kommutativen Zahlen, daher ist dies wahrscheinlich sehr hypothetisch.)

Ich stimme dem Punkt von @Jutho zu , dass diese Multiplikationsfunktion eine

Ein weiteres Argument zur Vermeidung von Keyword-Argumenten ist das, was @andreasnoack vor https://github.com/JuliaLang/julia/issues/23919#issuecomment -365762889 gesagt hat:

Die Namen α und β sind auch nicht sehr intuitiv, es sei denn, Sie kennen BLAS

@tkf , β != 0 wird geringer sein als die von β == 0 , und diejenigen, die es brauchen, werden nicht überrascht sein, dies etwas allgemeiner zu finden Verhalten unter mul! . Daher sehe ich keinen Vorteil darin, dies unter einem neuen Namen zu trennen, zumal die Argumentreihenfolge durcheinander ist (mindestens mit muladd! ). Wenn es eine neue Methode sein muss, sympathisiere ich auch mit Ihrem Argument für addmul! .

Diejenigen, die es brauchen, werden nicht überrascht sein, dieses etwas allgemeinere Verhalten unter mul! .

Ich stimme diesem Punkt zu.

Daher sehe ich keinen Vorteil darin, dies unter einem neuen Namen zu trennen.

Wenn Sie keine Schäden sehen, denke ich, dass dies ein globaler Gewinn ist, da andere Menschen Vorteile sehen.

zumal die Argumentreihenfolge durcheinander ist (mindestens mit muladd! )

Ich denke, du würdest das als Schaden ansehen und ich verstehe den Punkt. Ich denke nur, dass andere Vorteile für muladd! / addmul! wichtiger sind.

Ich denke, du würdest das als Schaden ansehen und ich verstehe den Punkt. Ich denke nur, dass Muladd andere Vorteile bringt! / Addmul! sind wichtiger.

Das ist in der Tat der Schaden, zusammen mit der Tatsache, dass mul! immer der einzige Einstiegspunkt in mehrere BLAS-Operationen im Zusammenhang mit der Multiplikation war, sei es dadurch eingeschränkt, dass kein vollständiger Zugriff auf α und β gewährt wurde. Und jetzt mit muladd! gibt es zwei verschiedene Einstiegspunkte, abhängig von nur einem geringfügigen Unterschied in der angeforderten Operation, die leicht von einem Argument erfasst werden können (und tatsächlich von einem Argument in der BLAS-API erfasst werden). . Ich denke, es war in erster Linie ein Fehler bei Julia, keinen vollständigen Zugriff auf die BLAS-API anzubieten (also danke, dass Sie das @tkf behoben haben). Trotz der alten schrecklichen Namenskonvention von fortran wussten diese Leute wohl, warum sie die Dinge so machten. Aber ich denke auch, dass diese Operationsfamilie (dh die durch α und β parametrisierte 2-Parameter-Operationsfamilie) wie in BLAS unter einem einzigen Einstiegspunkt zusammengefasst ist.

Das meiner Ansicht nach gültigste Gegenargument ist der Unterschied, ob auf die Originaldaten in C zugegriffen wird oder nicht. Aber angesichts der Tatsache, dass Julia das Multiplizieren mit false , um ein Ergebnis von Null zu garantieren, selbst wenn der andere Faktor NaN , denke ich, dass dies ebenfalls erledigt ist. Aber vielleicht muss diese Tatsache besser kommuniziert / dokumentiert werden (es ist schon eine Weile her, seit ich die Dokumentation gelesen habe), und ich habe auch erst kürzlich davon erfahren. (Deshalb benötige ich in KrylovKit.jl die Existenz einer fill! -Methode, um einen beliebigen vektorähnlichen Benutzertyp mit Nullen zu initialisieren. Aber jetzt weiß ich, dass ich stattdessen nur rmul!(x,false) kann. Ich muss also nicht festlegen, dass fill! implementiert ist.

Ich denke nur, dass Muladd andere Vorteile bringt! / Addmul! sind wichtiger.

Lassen Sie mich also die Frage umkehren, was sind diese anderen Vorteile einer neuen Methode? Ich habe Ihre Zusammenfassung noch einmal gelesen, sehe aber nur den Punkt des Zugriffs auf C , den ich gerade kommentiert habe.

Ich habe meiner Frau heute Morgen gegenüber erwähnt, dass es in der Julia-Community ein zweimonatiges Gespräch über die Benennung einer Operation gegeben hat. Sie schlug vor, es "Fred!" Zu nennen. - kein Akronym, keine tiefe Bedeutung, nur ein guter Name. Stell das einfach für sie da raus.

Gut, dass sie ein Ausrufezeichen hat! 😄

Lassen Sie mich zunächst für alle Fälle klarstellen, dass mein Anliegen fast nur auf der Lesbarkeit des Codes beruht, nicht auf der Beschreibbarkeit oder Auffindbarkeit. Sie schreiben Code einmal, lesen ihn aber oft.

Was ist dieser andere Vorteil einer neuen Methode?

Wie Sie kommentiert haben, denke ich, dass das Argument Ausgabe gegen Eingabe-Ausgabe-Parameter am wichtigsten ist. Aber dies ist nur ein Grund, warum ich denke, dass _C = αAB + βC_ sich von _C = AB_ unterscheidet. Ich denke auch, dass die einfache Tatsache, dass sie sich in dem Sinne unterscheiden, dass der erstere Ausdruck die strikte "Obermenge" des letzteren ist, eine klare visuelle Angabe im Code erfordert. Ein anderer Name hilft einem fortgeschrittenen Programmierer (oder einem kleinen, nicht fokussierten, fortgeschrittenen Programmierer), Code zu überfliegen und zu bemerken, dass er etwas Seltsameres als mul! .

Ich habe gerade die Umfrage überprüft (Sie müssen oben auf "Mehr laden" klicken) und es sieht so aus, als ob einige Stimmen von mul! auf muladd! verschoben wurden. Das letzte Mal, als ich es sah, gewann mul! . Lassen Sie es uns hier aufnehmen, bevor sie sich bewegten: Lachen:

  • mul! : 6
  • addmul! : 2
  • muladd! : 8
  • etwas anderes: 1

Etwas ernster denke ich immer noch, dass diese Daten nicht zeigen, dass mul! oder muladd! klarer sind als die anderen. (Obwohl es zeigt, dass addmul! eine Minderheit ist: schluchz :)

Es fühlt sich an, als wären wir festgefahren. Wie gehen wir weiter?

Nennen Sie es stattdessen einfach gemm! ?

Nennen Sie es einfach gemm! stattdessen?

Ich hoffe, das ist ein Witz ... es sei denn, Sie schlagen gemm!(α, A::Matrix, x::Vector, β, y::Vector) = gemv!(α, A, x, β, y) für den Matrix * -Vektor vor.

Könnten wir die mul! -Schnittstelle, die bereits vorhanden ist (mit spärlichen Matrizen), verlassen, damit wir die PR zusammenführen und die Verbesserungen vornehmen können, und uns Sorgen machen, ob wir muladd! in eine andere PR einfügen möchten ?

Vielleicht ist es hier schon allen klar, aber ich wollte nur betonen, dass die Abstimmung nicht ist

  • mul! vs muladd!

aber

  • mul! vs ( mul! und muladd! )

dh mit zwei mutierenden Multiplikationsfunktionen anstelle einer einzigen.

Ich habe mich entschieden, nicht mehr zu posten, da jedes Mal, wenn ich zugunsten von mul! gepostet habe, die Stimmen von mul! auf ( mul! und muladd! ) zu wechseln schienen.

Ich habe jedoch eine Frage? Wenn wir mit der aktuellen Mehrheitsabstimmung gehen und gleichzeitig mul!(C,A,B) und muladd!(A,B,C,α=true,β=true) , möchte ich eine PR vorbereiten, die axpy! und axpby! durch ersetzt ein eher julianischer Name add! , sollte das add!(y, x, α=true, β=true) oder add!(x, y, α=true, β=true) (wobei aus Gründen der Klarheit y mutiert ist). Oder etwas anderes?

Falls es nicht offensichtlich ist, würde muladd!(A,B,C) gegen die Konvention verstoßen, dass mutierte Argumente an erster Stelle stehen .

Könnten wir die bereits vorhandene Schnittstelle mul! ?

@jebej Ich denke, dieses Argument der "Abwärtskompatibilität" wird ausführlich diskutiert. Es überzeugt jedoch niemanden (wenn man sich die Umfrage ansieht, bin es nicht nur ich).

Sorgen Sie sich, ob wir muladd! in eine andere PR aufnehmen möchten?

Es ist schlecht, die öffentliche API zu brechen. Wenn wir also mul! sagen, dann ist es mul! für immer (obwohl LinearAlgebra theoretisch seine Hauptversion stoßen kann, um die API zu brechen).

Ich möchte eine PR vorbereiten, die axpy! und axpby! durch einen julianischeren Namen add! , sollte das add!(y, x, α=true, β=true) oder add!(x, y, α=true, β=true)

@ Jutho Danke, das wäre toll! Ich denke, die Auswahl der Argumentationsreihenfolge wäre einfach, wenn wir uns für die Aufrufsignatur der Multiplikations-Additions-API entschieden hätten.

muladd!(A,B,C) würde gegen die Konvention verstoßen, dass mutierte Argumente an erster Stelle stehen .

@simonbyrne Aber (wie Sie bereits in https://github.com/JuliaLang/julia/issues/23919#issuecomment-426881998 erwähnt haben) mutieren lmul! und ldiv! nicht das erste Argument. Ich denke also, wir müssen muladd!(A,B,C,α,β) von der Auswahl ausschließen, sondern es als negativen Punkt für diese Signatur zählen.

(Aber ich würde sagen, dass wir mit muladd!(α, A, B, β, C) wenn wir eine API für "Textreihenfolge" haben wollen.)

Eine Sache, die ich unter dem Ergebnis der Abstimmung nicht verstehe, ist übrigens die Asymmetrie von muladd! und addmul! . Wenn Sie C = βC + αAB schreiben, denke ich, dass addmul! natürlicher ist.

@tkf Es geht darum, welche Operation Sie zuerst ausführen. Für mich bedeutet addmul! , dass Sie zuerst eine Addition durchführen und dann wie in (A+B)C multiplizieren. Natürlich ist es subjektiv. Aber gute Namen sollten die Intuition ansprechen.

Ah, ich verstehe diesen Punkt.

Da dies immer noch nicht funktioniert, würde mein Vorschlag das Verwendungsmuster haben, das aus Funktionsdefinitionen mit (mit @callexpr für die Sekunde) besteht.

@callexpr(C .= β*C + α*A*B) = implementation(C, β, α, A, B)
@callexpr(C .= β*C + A*B) = implementation(C, β, true, A, B)

und vielleicht eine versandfreundlichere Form (mit @callname für die Sekunde)

function @callname(β*C + A*B)(C::Number, β::Number, A::Number, B::Number)
     β*C + A*B
end

und ruft an

@callexpr(A .= 2*C + A*B)
@callexpr(2*3 + 3*2)

und niemand muss sich Sorgen machen (oder wissen), wie callexpr algebraische Operationen in einen eindeutigen Funktionsnamen zerlegt (der nicht von den Argumentsymbolen abhängt, sondern nur von Operationen und der Reihenfolge der Operationen).
Ich habe ein bisschen über die Implementierung nachgedacht und es sollte gut machbar sein.

@mschauer Ich denke das ist eine interessante Richtung. Können Sie eine neue Ausgabe eröffnen? Die von Ihnen vorgeschlagene API kann viele andere Probleme lösen. Ich denke, es muss einen sorgfältigen Entwurfsprozess durchlaufen, als eine einzelne Instanz des Problems zu lösen, das es lösen kann.

Also habe ich das Gerücht gehört, dass das Feature Freeze von 1.1 nächste Woche ist. Obwohl die nächste kleinere Veröffentlichung "nur" vier Monate entfernt ist, wäre es wirklich schön, wenn wir sie in 1.1 haben könnten ...

Auf jeden Fall müssen wir vor dem Zusammenführen der PR auch die Anrufsignatur (Reihenfolge der Argumente und des Schlüsselworts oder nicht) festlegen.

Also lasst uns noch einmal abstimmen (da ich festgestellt habe, dass es ein schönes Stimulans ist).

_Wenn wir muladd! für _C = ABα + Cβ_ verwenden, was ist Ihre bevorzugte Anrufsignatur?

  • : +1: muladd!(C, A, B, α, β)
  • : -1: muladd!(A, B, C, α, β)
  • : smile: muladd!(C, A, B; α, β) (wie: +1:, aber mit Schlüsselwortargumenten)
  • : tada: muladd!(A, B, C; α, β) (wie: -1:, aber mit Schlüsselwortargumenten)
  • : verwirrt: muladd!(A, B, α, C, β)
  • : heart: etwas anderes

Wenn Sie andere Keyword-Argumentnamen im Sinn haben, stimmen Sie mit α und β für diejenigen ab und kommentieren Sie dann, welche Namen besser sind.

Da wir uns noch nicht entschieden haben, wie der Name lauten soll, müssen wir dies auch für mul! tun:

_Wenn wir mul! für _C = ABα + Cβ_ verwenden, was ist Ihre bevorzugte Anrufsignatur?

  • : +1: mul!(C, A, B, α, β)
  • : -1: mul!(A, B, C, α, β)
  • : smile: mul!(C, A, B; α, β) (wie: +1:, aber mit Schlüsselwortargumenten)
  • : tada: mul!(A, B, C; α, β) (das ist unmöglich)
  • : verwirrt: mul!(A, B, α, C, β)
  • : heart: etwas anderes

HINWEIS: Wir ändern die vorhandene API mul!(C, A, B)

HINWEIS: Wir ändern die vorhandene API mul!(C, A, B)

Ich hatte dieser Tatsache nicht genug Aufmerksamkeit geschenkt - wir haben bereits mul! und das bedeutet es:

mul!(Y, A, B) -> Y

Berechnet das Matrix-Matrix- oder Matrix-Vektor-Produkt A*B und speichert das Ergebnis in Y , wobei der vorhandene Wert von Y überschrieben wird. Beachten Sie, dass Y weder mit A noch mit B werden darf.

Angesichts dessen erscheint es sehr natürlich, dies einfach so zu erweitern:

mul!(Y, A, B) -> Y
mul!(Y, A, B, α) -> Y
mul!(Y, A, B, α, β) -> Y

Berechnet das Matrix-Matrix- oder Matrix-Vektor-Produkt A*B und speichert das Ergebnis in Y , wobei der vorhandene Wert von Y überschrieben wird. Beachten Sie, dass Y weder mit A noch mit B werden darf. Wenn ein skalarer Wert, α , angegeben wird, wird der α*A*B anstelle von A*B berechnet. Wenn ein Skalarwert β wird, wird stattdessen α*A*B + β*Y berechnet. Die gleiche Aliasing-Einschränkung gilt für diese Varianten.

Ich denke jedoch, dass dies ein großes Problem darstellt: Es scheint mindestens genauso selbstverständlich, dass mul!(Y, A, B, C, D) A*B*C*D in Y berechnet - und dieser generische Begriff kollidiert sehr schlecht mit mul!(Y, A, B, α, β) Computing α*A*B + β*C . Darüber hinaus scheint es mir, dass es nützlich und möglich ist, A*B*C*D in Y berechnen, um Zwischenzuweisungen zu vermeiden, sodass ich diese Bedeutung wirklich nicht blockieren möchte .

In Anbetracht dieser anderen natürlichen Verallgemeinerung von mul! ist hier ein anderer Gedanke:

mul!(Y, α, A, B) # Y .= α*A*B

Dies passt in das allgemeine Modell von mul!(out, args...) bei dem Sie berechnen und in out schreiben, indem Sie args miteinander multiplizieren. Es hängt vom Versand ab, dass α skalar ist, anstatt es zu einem Sonderfall zu machen - es ist nur eine andere Sache, die Sie multiplizieren. Wenn α ein Skalar ist und A , B und Y Matrizen sind, können wir an BLAS senden, um dies super effizient zu tun. Andernfalls können wir eine generische Implementierung haben.

Wenn Sie sich in einem nicht kommutativen Feld befinden (z. B. Quaternionen), können Sie außerdem steuern, auf welcher Seite die Skalierung um α erfolgt: mul!(Y, A, B, α) skaliert um α rechts statt links:

mul!(Y, A, B, α) # Y .= A*B*α

Ja, wir können BLAS nicht für Quaternionen aufrufen, aber es ist generisch und wir können es wahrscheinlich immer noch einigermaßen effizient ausführen (vielleicht sogar irgendwie in einige BLAS-Aufrufe umwandeln).

Unter der Annahme dieses Ansatzes für Y .= α*A*B die nächste Frage: Was ist mit dem Skalieren und Inkrementieren von Y ? Ich fing an, über Schlüsselwörter dafür nachzudenken, aber dann kam mir das nicht kommutative Feld in den Sinn, das sich zu umständlich und begrenzt anfühlte. Also habe ich stattdessen über diese API nachgedacht - was auf den ersten Blick etwas seltsam erscheint, aber ertrage es mit mir:

mul!((β, Y), α, A, B) # Y .= β*Y .+ α*A*B

Ein bisschen seltsam, aber es funktioniert. Und in einem nicht kommutativen Feld können Sie darum bitten, Y mit β rechts wie folgt zu multiplizieren:

mul!((Y, β), α, A, B) # Y .= Y*β .+ α*A*B

In einem nicht kommutativen Feld können Sie sowohl links als auch rechts wie folgt skalieren:

mul!((β₁, Y, β₂), α₁, A, B, α₂) # Y .= β₁*Y*β₂ + α₁*A*B*α₂

Das ist natürlich ein bisschen komisch und es gibt keine BLAS-Operation dafür, aber es ist eine Verallgemeinerung von GEMM, die uns eine Menge Dinge ausdrücken lässt und die wir trivial an BLAS-Operationen senden können, ohne überhaupt böse zu sein, wenn / sonst Geäst.

Ich mag den Vorschlag von verfügbar machen möchten. IMO, am Ende sollte es einfach aussehen, wie ein zugehöriges Makro:

@affine! Y = β₁*Y*β₂ + α₁*A*B*α₂

Die zugrunde liegende Funktion wäre dann so etwas wie @StefanKarpinski vorschlägt.

Aber wir sollten hier weiter gehen. Ich denke wirklich, wenn Sie eine API dafür und eine generische Funktion erstellen, wird jemand eine Julia-Bibliothek erstellen, die dies effizient erledigt. Daher stimme ich zu, dass wir uns hier nicht einfach an BLAS halten sollten. Dinge wie MatrixChainMultiply.jl erstellen bereits DSLs für mehrere Matrixberechnungen, und DiffEq macht sein eigenes Ding mit affinen Operatorausdrücken. Wenn wir nur eine Darstellung für einen affinen Ausdruck in Base haben, können wir alle unsere Arbeiten so definieren, dass sie sich auf dasselbe beziehen.

@dlfivefifty hat sich vorher mit fauler linearer Algebra befasst, ich denke, das sollte hier wirklich wiederbelebt werden. Das Erstellen von verzögerten Darstellungen von Broadcasts war entscheidend, damit elementweise Operationen auf abstrakten Arrays und auf alternativer Computerhardware funktionieren. Wir brauchen dasselbe für die lineare Algebra. Eine Darstellung linearer algebraischer Ausdrücke würde es uns ermöglichen, neue BLAS-Kernel im laufenden Betrieb von einem Julia BLAS zu definieren oder die Gleichungen auf eine GPU / TPU zu übertragen.

Im Wesentlichen beschränken sich alle Berechnungen im wissenschaftlichen Rechnen auf elementweise und lineare algebraische Operationen. Daher scheint eine umfassende Beschreibung von beiden für die Erstellung von Werkzeugen zum Metaprogrammieren und Erforschen neuer Designs von entscheidender Bedeutung zu sein.

Ich müsste mehr über diesen Vorschlag nachdenken, aber im Moment möchte ich nur kommentieren, dass ich nicht glaube, dass Sie A*B*C ohne eine temporäre Berechnung berechnen möchten. Es scheint mir, dass Sie mit vielen arithmetischen Operationen bezahlen müssten, um das Temporäre zu vermeiden.

Ich glaube nicht, dass Sie A*B*C ohne temporäres berechnen möchten.

Für mul! Sie jedoch bereits ein Ausgabearray. Ich bin mir nicht sicher, ob das hilft oder nicht. In jedem Fall scheint es sich um ein Implementierungsdetail zu handeln. Die API mul!(Y, A, B, C...) drückt aus, was Sie berechnen möchten, und lässt die Implementierung den besten Weg auswählen, was das allgemeine Ziel hier war.

Ich mag den Vorschlag von verfügbar machen möchten.

@ChrisRackauckas : Ich denke, die Dinge, auf die Sie sich mul! wie dieser scheint genau die Art von allgemeiner, aber leicht verständlicher Operation zu sein, die wir auf dieser Ebene wollen.

Beachten Sie, dass es keine wirkliche Debatte über mul!(Y, α, A, B) - es muss so ziemlich Y .= α*A*B bedeuten, denn was würde es sonst bedeuten? Für mich ist hier die einzige offene Frage, ob die Verwendung eines Tupels mit einer Matrix und linken und / oder rechten Skalaren eine vernünftige Möglichkeit ist, um auszudrücken, dass wir das Ausgabearray inkrementieren und skalieren möchten. Die allgemeinen Fälle wären:

  1. mul!(Y::Matrx, args...) : Y .= *(args...)
  2. mul!((β, Y)::{Number, Matrix}, args...) : Y .= β*Y + *(args...)
  3. mul!((Y, β)::{Matrix, Number}, args...) : Y .= Y*β + *(args...)
  4. mul!((β₁, Y, β₂)::{Number, Matrix, Number}, args...) : Y .= β₁*Y*β₂ + *(args...)

Für das erste Argument wäre nichts anderes erlaubt. Dies könnte als allgemeinere Konvention für andere Operationen übernommen werden, bei denen es sinnvoll ist, das Ausgabearray entweder zu überschreiben oder zu akkumulieren, optional kombiniert mit einer Skalierung.

Es ist mir nicht in den Sinn gekommen, mul!(out, args...) und eine GEMM-ähnliche Oberfläche "zusammenzuführen"! Ich mag die Erweiterbarkeit davon (aber dann schreibe die Antwort unten und jetzt bin ich mir nicht sicher ...)

Aber ich mache mir Sorgen, wenn es einfach als Überlastungsschnittstelle zu verwenden ist. Wir müssen uns auf das Typsystem verlassen, um für verschachtelte Tupel gut zu funktionieren. Funktionieren verschachtelte Tupel in Julias Typsystem genauso gut wie flache Tupel? Ich frage mich, ob etwas wie " Tuple{Tuple{A1,B1},C1,D1} spezifischer ist als Tuple{Tuple{A2,B2},C2,D2} iff Tuple{A1,B1,C1,D1} spezifischer ist als Tuple{A2,B2,C2,D2} ". Andernfalls wäre es schwierig, sie als Überladungs-API zu verwenden.

Beachten Sie, dass wir Skalartypen versenden müssen, um den Neuinterpretations-Hack für die komplexen Matrizen zu verwenden (dies ist aus PR # 29634, achten Sie also nicht auf den Funktionsnamen):

https://github.com/JuliaLang/julia/blob/fae1a7a3ae646c7ea1c08982976b57096fb0ae8d/stdlib/LinearAlgebra/src/matmul.jl#L157 -L169

Eine weitere Sorge ist, dass dies eine etwas eingeschränkte Schnittstelle für einen Berechnungsgraphen-Executor ist. Ich denke, der Hauptzweck der Multiply-Add-Schnittstelle besteht darin, eine Überladungs-API bereitzustellen, mit der Bibliotheksimplementierer einen kleinen wiederverwendbaren Rechenkern definieren können, der effizient implementiert werden kann. Dies bedeutet, dass wir nur _C = ABα_ implementieren können und nicht z. B. _αAB_ (siehe https://github.com/JuliaLang/julia/pull/29634#issuecomment-443103667). Die Unterstützung von _α₁ABα₂_ für nicht kommutative eltype erfordert entweder ein temporäres Array oder die Erhöhung der Anzahl der arithmetischen Operationen. Es ist nicht klar, welchen Benutzer er möchte, und im Idealfall sollte dies konfigurierbar sein. Zu diesem Zeitpunkt benötigen wir eine vom Ausführungsmechanismus getrennte Berechnungsgraphendarstellung. Ich denke, dies sollte besser in externen Paketen untersucht werden (z. B. LazyArrays.jl, MappedArrays.jl). Wenn wir jedoch irgendwann eine Implementierungsstrategie finden, die den größten Teil des Anwendungsfalls abdeckt, wäre es sinnvoll, mul! als Haupteinstiegspunkt zu verwenden. Ich denke, dies ist tatsächlich ein weiterer Grund, muladd! zu bevorzugen; Zuweisen eines Speicherplatzes für die zukünftige aufrufende API.

Ich hätte mehr über diesen Vorschlag denken , aber für jetzt, ich werde nur anmerken , dass ich glaube nicht , Sie mögen , dass ohne einen temporären A B C berechnen. Es scheint mir, dass Sie mit vielen arithmetischen Operationen bezahlen müssten, um das Temporäre zu vermeiden.

Sie können in der Tat beweisen, dass jede Kontraktion einer beliebigen Anzahl von Tensoren die effizienteste Methode zur Bewertung des Ganzen immer die Verwendung paarweiser Kontraktionen ist. Das Multiplizieren mehrerer Matrizen ist also nur ein Sonderfall. Sie sollten sie paarweise multiplizieren (die beste Reihenfolge ist natürlich ein nicht triviales Problem). Deshalb denke ich, dass mul!(Y,X1,X2,X3...) kein so nützliches Grundelement ist. Und am Ende denke ich, dass mul! eine primitive Operation ist, die Entwickler für ihre spezifischen Typen überladen können. Jede kompliziertere Operation kann dann unter Verwendung einer übergeordneten Konstruktion geschrieben werden, z. B. unter Verwendung von Makros, und würde beispielsweise einen Berechnungsgraphen erstellen, der am Ende durch Aufrufen primitiver Operationen wie mul! ausgewertet wird. Natürlich könnte dieses Grundelement allgemein genug sein, um Fälle wie den nicht kommutativen @StefanKarpinski erwähnt.

Solange keine Matrixmultiplikation / Tensorkontraktion beteiligt ist, ist es wahr, dass das Denken in primitiven Operationen nicht so nützlich ist und es vorteilhaft sein kann, alles wie beim Rundfunk miteinander zu verschmelzen.

Im Allgemeinen stimme ich zu, dass es gut wäre, einen Standardtyp für die verzögerte Darstellung / Berechnung in Base zu haben, aber ich denke nicht, dass mul! der Weg ist, ihn zu konstruieren.

@tkf :

Aber ich mache mir Sorgen, wenn es einfach als Überlastungsschnittstelle zu verwenden ist. Wir müssen uns auf das Typsystem verlassen, um für verschachtelte Tupel gut zu funktionieren. Funktionieren verschachtelte Tupel in Julias Typsystem genauso gut wie flache Tupel?

Ja, an dieser Front sind wir alle gut. Ich bin mir nicht sicher, wo die Verschachtelung ins Spiel kommt, aber das Übergeben einiger Dinge in einem Tupel ist genauso effizient wie das Übergeben aller Dinge als unmittelbare Argumente - es wird genauso implementiert.

Dies bedeutet, dass wir nur _C = ABα_ implementieren können und nicht z. B. _αAB_

Ich bin verwirrt ... Sie können mul!(C, A, B, α) und mul!(C, α, A, B) schreiben. Sie könnten sogar mul!(C, α₁, A, α₂, B, α₃) schreiben. Dies scheint die mit Abstand flexibelste generische Matrix-Multiplikations-API zu sein, die bisher vorgeschlagen wurde.

Eine weitere Sorge ist, dass dies eine etwas eingeschränkte Schnittstelle für einen Berechnungsgraphen-Executor ist. Ich denke, der Hauptzweck der Multiply-Add-Schnittstelle besteht darin, eine Überladungs-API bereitzustellen, mit der Bibliotheksimplementierer einen kleinen wiederverwendbaren Rechenkern definieren können, der effizient implementiert werden kann.

Zu diesem Zeitpunkt benötigen wir eine vom Ausführungsmechanismus getrennte Berechnungsgraphendarstellung.

Das mag der Fall sein, aber dies ist nicht der richtige Ort dafür - das kann und sollte in externen Paketen entwickelt werden. Alles, was wir brauchen, um dieses spezielle Problem zu lösen, ist eine Matrix-Multiplikations-API, die verallgemeinert, was an BLAS-Operationen gesendet werden kann - und das ist ziemlich genau das, was dies bewirkt.

@ Jutho

Das Multiplizieren mehrerer Matrizen ist also nur ein Sonderfall. Sie sollten sie paarweise multiplizieren (die beste Reihenfolge ist natürlich ein nicht triviales Problem). Deshalb denke ich, dass mul!(Y,X1,X2,X3...) kein so nützliches Grundelement ist.

Die Operation mul! würde es der Implementierung ermöglichen, die Reihenfolge der Multiplikation zu wählen, was eine nützliche Eigenschaft ist. In der Tat war die Möglichkeit, dies möglicherweise zu tun, der Grund, warum wir die * -Operation in erster Linie als n-ary analysiert haben, und die gleiche Argumentation gilt noch mehr für mul! da, wenn Sie sie verwenden Sie kümmern sich vermutlich genug um die Leistung.

Im Allgemeinen kann ich nicht sagen, ob Sie für oder gegen meinen Vorschlag für mul! argumentieren.

Ich bin mir nicht sicher, wo die Verschachtelung ins Spiel kommt, aber einige Dinge in einem Tupel zu übergeben ist genauso effizient wie sie alle als unmittelbare Argumente zu übergeben

Ich habe mir keine Sorgen um die Effizienz gemacht, sondern um die Unklarheiten beim Versenden und bei den Methoden, da selbst die aktuelle LinearAlgebra etwas fragil ist (was möglicherweise auf mein mangelndes Verständnis des Typsystems zurückzuführen ist; manchmal überrascht es mich). Ich erwähnte das verschachtelte Tupel, da ich dachte, dass die Auflösung der Methode durch Überschneiden des Tupeltyps aller Positionsargumente erfolgt. Das gibt Ihnen ein flaches Tupel. Wenn Sie im ersten Argument ein Tupel verwenden, haben Sie ein verschachteltes Tupel.

Dies bedeutet, dass wir nur _C = ABα_ implementieren können und nicht z. B. _αAB_

Ich bin verwirrt ... Sie können mul!(C, A, B, α) und mul!(C, α, A, B) schreiben.

Ich wollte sagen "Wir können _C = ABα_ nur so effizient implementieren

Zu diesem Zeitpunkt benötigen wir eine vom Ausführungsmechanismus getrennte Berechnungsgraphendarstellung.

Das mag der Fall sein, aber dies ist nicht der richtige Ort dafür - das kann und sollte in externen Paketen entwickelt werden.

Genau das ist mein Punkt. Ich schlage vor, diese API als minimalen Baustein für eine solche Verwendung zu betrachten (das ist natürlich nicht der ganze Zweck). Die Implementierung und das Design von vararg mul! können durchgeführt werden, nachdem Benutzer den Designbereich in externen Paketen erkundet haben.

Das Versenden von Typen selbst für die aktuellen mul! ist bereits "kaputt": Es gibt eine kombinatorische Zunahme von Mehrdeutigkeitsüberschreibungen, die für die Arbeit mit zusammensetzbaren Array-Typen wie SubArray und Adjoint erforderlich sind.

Die Lösung besteht darin, Merkmale zu verwenden, und LazyArrays.jl verfügt über eine Proof-of-Concept-Version von mul! mit Merkmalen.

Dies ist jedoch eher eine Diskussion über die Implementierung als über die API. Die Verwendung von Tupeln zum Gruppieren von Begriffen fühlt sich jedoch falsch an: Ist das nicht das Typensystem? In diesem Fall gelangen Sie zur Lösung LazyArrays.jl.

Der Mul! Eine Operation würde es der Implementierung ermöglichen, die Reihenfolge der Multiplikation zu wählen, was eine nützliche Eigenschaft ist. In der Tat war die Fähigkeit, dies möglicherweise zu tun, der Grund, warum wir die * Operation in erster Linie als n-ary analysiert haben, und die gleiche Argumentation gilt noch mehr für mul! denn wenn Sie es verwenden, ist Ihnen die Leistung vermutlich wichtig genug.

Dass * als n -ary analysiert wird, ist äußerst nützlich. Ich verwende es in TensorOperations.jl, um das Makro @tensoropt zu implementieren, das tatsächlich die Kontraktionsreihenfolge optimiert. Dass ich eine n -ary-Version von mul! weniger nützlich finde, liegt daran, dass es aus Effizienzgründen wenig Sinn macht, einen vorab zugewiesenen Platz für das Ergebnis bereitzustellen, wenn alle Zwischenprodukte vorhanden sind Arrays müssen noch innerhalb der Funktion zugewiesen und dann gc'ed werden. Tatsächlich haben in TensorOperations.jl mehrere Personen festgestellt, dass die Zuweisung großer Provisorien einer der Orte ist, an denen Julias GC wirklich schlecht abschneidet (was häufig zu GC-Zeiten von 50% führt).

Daher würde ich mul! auf eine wirklich primitive Operation beschränken, wie sie auch von @tkf befürwortet wird, wenn ich das richtig verstehe: Multiplizieren von zwei Matrizen mit einer dritten mit möglicherweise skalaren Koeffizienten. Ja, wir können uns den allgemeinsten Weg vorstellen, dies für nicht kommutative Algebren zu tun, aber ich denke, die unmittelbare Notwendigkeit besteht darin, bequem auf die von BLAS (gemm, gemv, ...) bereitgestellten Funktionen zuzugreifen, die Julias mul! bietet

Ich mag Ihren Vorschlag mit Tupeln nicht, könnte aber mögliche Verwirrung vorhersehen
Das Beschränken von Fall 4 auf Fall 2 von 3 scheint Standardwerte β₁ = 1 und β₂ = 1 (oder tatsächlich true ) zu implizieren. Wenn jedoch keine angegeben sind, bedeutet dies plötzlich β₁ = β₂ = 0 ( false ). Sicher, die Syntax ist etwas anders, da Sie mul!(Y, args...) schreiben, nicht mul!((Y,), args...) . Am Ende ist es eine Frage der Dokumentation, deshalb wollte ich nur darauf hinweisen.

Zusammenfassend bin ich also nicht wirklich gegen diese Syntax, obwohl es sich um eine neue Art von Paradigma handelt, das eingeführt wird und dann wahrscheinlich auch an anderen Stellen befolgt werden sollte. Ich lehne es ab, dies sofort auf die Multiplikation einer beliebigen Anzahl von Matrizen zu verallgemeinern, deren Nutzen ich, wie oben dargelegt, nicht sehe.

@dlfivefifty : Dies ist jedoch eher eine Diskussion über die Implementierung als über die API. Die Verwendung von Tupeln zum Gruppieren von Begriffen fühlt sich jedoch falsch an: Ist das nicht das Typensystem? In diesem Fall gelangen Sie zur Lösung LazyArrays.jl.

Aber wir werden hier keine faulen Arrays verwenden - dafür gibt es bereits LazyArrays. In der Zwischenzeit brauchen wir eine Möglichkeit, die Skalierung von Y auszudrücken. Die Verwendung von Tupeln scheint ein einfacher, leichter Ansatz zu sein, um diese geringe Menge an Struktur auszudrücken. Hat jemand andere Vorschläge? Wir könnten lscale und / oder rscale Schlüsselwörter für β₁ und β₂ , aber das fühlt sich nicht eleganter an und wir würden die Fähigkeit dazu verlieren Versand darauf, was nicht entscheidend ist, aber schön zu haben.

@Jutho : Daher würde ich mul! auf eine wirklich primitive Operation beschränken, wie sie auch von @tkf befürwortet wird, wenn ich das richtig verstehe: Multiplizieren von zwei Matrizen mit einer dritten mit möglicherweise skalaren Koeffizienten. Ja, wir können uns den allgemeinsten Weg vorstellen, dies für nicht kommutative Algebren zu tun, aber ich denke, die unmittelbare Notwendigkeit besteht darin, bequem auf die von BLAS (gemm, gemv, ...) bereitgestellten Funktionen zuzugreifen, die Julias mul! bietet

Ich kann nur eine kleine Teilmenge von Operationen für mul! , vielleicht sogar nur diejenigen, die strukturell gültigen BLAS-Aufrufen entsprechen. Das wäre:

# gemm: alpha = 1.0, beta = 0.0
mul!(Y::Matrix, A::Matrix, B::Matrix) # gemm! Y, A

# gemm: alpha = α, beta = 0.0 (these all do the same thing for BLAS types)
mul!(Y::Matrix, α::Number, A::Matrix, B::Matrix)
mul!(Y::Matrix, A::Matrix, α::Number, B::Matrix)
mul!(Y::Matrix, A::Matrix, B::Matrix, α::Number)

# gemm: alpha = α, beta = β (these all do the same thing for BLAS types)
mul!((β::Number, Y::Matrix), α::Number, A::Matrix, B::Matrix)
mul!((β::Number, Y::Matrix), A::Matrix, α::Number, B::Matrix)
mul!((β::Number, Y::Matrix), A::Matrix, B::Matrix, α::Number)
mul!((Y::Matrix, β::Number), α::Number, A::Matrix, B::Matrix)
mul!((Y::Matrix, β::Number), A::Matrix, α::Number, B::Matrix)
mul!((Y::Matrix, β::Number), A::Matrix, B::Matrix, α::Number)

# gemm: alpha = α, beta = β₁*β₂ (these all do the same thing for BLAS types)
mul!((β₁::Number, Y::Matrix, β₂::Number), α::Number, A::Matrix, B::Matrix)
mul!((β₁::Number, Y::Matrix, β₂::Number), A::Matrix, α::Number, B::Matrix)
mul!((β₁::Number, Y::Matrix, β₂::Number), A::Matrix, B::Matrix, α::Number)

Zu welchem ​​Ende? Warum so viele Variationen beim Ausdrücken von BLAS-Operationen zulassen?

  1. Weil es den Menschen ermöglicht, ihre Absicht auszudrücken - wenn die Absicht darin besteht, links oder rechts oder auf beiden zu multiplizieren, warum nicht den Menschen erlauben, dies auszudrücken und die richtige Implementierung zu wählen?

  2. Wir können generische Fallbacks haben, die auch für nicht kommutative Elementtypen das Richtige tun.

Der springende Punkt dieser Ausgabe ist eine Verallgemeinerung der In-Place-Matrixmultiplikation, die gemm subsumiert! während generischer als gemm!. Warum nicht einfach weiter gemm! schreiben?

Aber wir werden hier keine faulen Arrays machen

Ich sage nicht "volle faule Arrays", ich schlage faul im gleichen Sinne vor wie Broadcasted , das letztendlich beim Kompilieren entfernt wird. Im Wesentlichen würde ich ein Applied hinzufügen, um die verzögerte Anwendung einer Funktion darzustellen, und anstatt Tupel (die keinen Kontext enthalten) zu verwenden, hätten Sie so etwas wie

materialize!(applied(+, applied(*, α, A, B), applied(*, β, C)))

Dies könnte wie die .* -Notation der Sendung mit Zucker überzogen sein, um die Lesbarkeit zu verbessern, aber ich denke, dies ist im Gegensatz zu dem auf Tupeln basierenden Vorschlag sofort klar, was gewünscht wird.

@StefanKarpinski Wie bereits erwähnt , stimme ich sicherlich zu, dass wir über eine Schnittstelle nachdenken sollten, die zukunftssicher ist und korrekt auf andere

mul!((β₁::Number, Y::Matrix, β₂::Number), α₁::Number, A::Matrix, α₂::Number, B::Matrix, α₃::Number)

und alle reduzierten Versionen davon, dh wenn alle 5 skalaren Argumente fehlen können, sind das 2 ^ 5 = 32 verschiedene Möglichkeiten. Und dies kombiniert mit allen Möglichkeiten verschiedener Matrizen oder Vektoren.

Ich stimme @dlfivefifty zu, dass ein

Ja, mir wurde klar, dass ich einige der Optionen ausgelassen habe, aber 32 Methoden scheinen mir nicht so verrückt zu sein, schließlich müssen wir sie nicht von Hand schreiben. Hinzufügen eines „Broadcast-ähnliches System“ oder ein fauler Bewertungssystem , das uns schreiben können materialize!(applied(+, applied(*, α, A, B), applied(*, β, C))) scheint für dieses Problem wie ein weit größer hinaus und Weise außerhalb des Gültigkeitsbereichs. Alles, was wir wollen, ist eine Art der Rechtschreibung der allgemeinen Matrixmultiplikation, die sowohl generisch ist als auch den Versand an BLAS ermöglicht. Wenn wir uns nicht alle einig sind, bin ich geneigt, die Leute weiterhin direkt gemm! anrufen zu lassen.

Ja, das stimmt wahrscheinlich; Ich ging davon aus, dass es mit skalaren Argumenten im Hintergrund einfacher wäre, Standardwerte bereitzustellen. Aber wenn wir mit etwas @eval Metaprogrammierung leicht alle 32 Definitionen generieren können, ist das gleich gut. (Beachten Sie, wie Sie sicherlich wissen, dass mul nicht nur gemm! sondern auch gemv und trmm und ...).

Lassen Sie mich hinzufügen, dass es nicht nur ein BLAS-Wrapper ist. Es gibt andere reine Julia-spezialisierte Methoden in stdlib. Es ist auch wichtig, dies als überladende API zu haben: Paketautoren können mul! für ihre speziellen Matrixtypen definieren.

Ich denke, das ist meine Haltung:

  1. Wir könnten jetzt genauso gut mul!(C, A, B, a, b) da es bereits in SparseArrays.jl vorhanden ist
  2. Wir sollten nichts anderes tun, da der Versand auf Matrixtypen nicht gut skaliert. (Als Betreuer von BandedMatrices.jl, BlockArrays.jl, LowRankApprox.jl usw. kann ich das aus Erfahrung sagen.)
  3. Trait-basiertes Design lässt sich gut skalieren, aber es ist am besten, all-in zu gehen und eine Sendung wie Applied , da das Designmuster bereits festgelegt ist. Dies muss bis Julia 2.0 warten, wobei in LazyArrays.jl ein Prototyp weiterentwickelt wird, der meinen Anforderungen entspricht.

@dlfivefifty Glaubst du, dass die Schwierigkeit bei der Disambiguierung von mul!((Y, β), α, A, B) API gleich ist wie in mul!(Y, A, B, α, β) ? Wenn man Matrix-Wrapper wie Transpose berücksichtigt, führt dies zu einer Schwierigkeit, einschließlich 2- und 3-Tupeln. Dies klingt nach einer stärkeren Erhöhung der Schwierigkeit (obwohl ich weiß, dass Tupel ein Spezialfall in Julias Typsystem ist).

  1. Wir könnten jetzt genauso gut mul!(C, A, B, a, b) da es bereits in SparseArrays.jl vorhanden ist

Die Tatsache, dass jemand entschieden hat, dass mul!(C, A, B, a, b) C .= b*C + a*A*B bedeuten soll, ohne es vollständig durchzudenken, ist nachdrücklich kein guter Grund, dies zu verdoppeln. Wenn mul! die In-Place-Version von * ist, sehe ich nicht, wie mul!(out, args...) etwas anderes als out .= *(args...) bedeuten kann. Ehrlich gesagt, erhalten Sie auf diese Weise ein System, das aus schlecht durchdachten, inkonsistenten APIs besteht, die nur durch einen historischen Zufall existieren. Die mul! -Funktion wird nicht aus SparseArrays _ exportiert, und diese bestimmte Methode ist nicht dokumentiert. Dies ist also der schwächste Grund, eine schlecht konzipierte Methode zu verankern, die wahrscheinlich nur hinzugefügt wurde, weil die Funktion nicht vorhanden war nicht öffentlich! Ich schlage vor, dass wir diesen Fehler rückgängig machen und stattdessen diese Methode von mul! löschen / umbenennen.

Aus dem Rest dieser Diskussion geht hervor, dass wir nichts anderes tun sollten, da alle Stakeholder etwas ausgefalleneres mit Merkmalen und / oder Faulheit außerhalb der Standardbibliothek tun möchten. Ich bin damit einverstanden, da das Löschen von Dingen immer schön ist.

Aus dem Rest dieser Diskussion geht hervor, dass wir nichts anderes tun sollten, da alle Stakeholder etwas ausgefalleneres mit Merkmalen und / oder Faulheit außerhalb der Standardbibliothek tun möchten. Ich bin damit einverstanden, da das Löschen von Dingen immer schön ist.

Es scheint, dass Sie ein bisschen satt werden, was verständlich ist. Ich denke jedoch nicht, dass diese Schlussfolgerung wahr ist. Wenn Sie sicher sind, dass der aktuelle Vorschlag skalierbar implementiert werden kann und es für Paketentwickler dennoch bequem ist, diese Definition für ihre eigenen Matrix- und Vektortypen (wie von @tkf erwähnt) zu überladen, wäre dies eine großartige Möglichkeit nach vorne.

Insbesondere würde ich denken, dass Paketentwickler nur Folgendes implementieren müssen:

mul!((β₁, Y::MyVecOrMat, β₂), α₁, A::MyMat, α₂, B:: MyVecOrMat, α₃)

und vielleicht zB

mul!((β₁, Y::MyVecOrMat, β₂), α₁, A::Adjoint{<:MyMat}, α₂, B:: MyVecOrMat, α₃)
...

während Julia Base (oder besser gesagt die LinearAlgebra-Standardbibliothek) sich um die Behandlung aller Standardwerte usw. kümmert.

Ich würde denken, dass Paketentwickler nur Folgendes implementieren müssen:

mul!((β₁, Y::MyVecOrMat, β₂), α₁, A::MyMat, α₂, B:: MyVecOrMat, α₃)

Ich würde vorschlagen zu dokumentieren

mul!((Y, β), A, B, α)

als zu überladende Signatur. Dies liegt daran, dass andere Standorte für α die große O-Zeit-Komplexität ändern. Siehe: https://github.com/JuliaLang/julia/pull/29634#issuecomment -443103667. Dies gibt nicht kommutativen Zahlen eine nicht erstklassige Behandlung. Aber AFAICT niemand hier verwendet tatsächlich nicht kommutative Zahlen und ich denke, wir sollten warten, bis ein tatsächlicher Bedarf besteht.

Eine Sache, die ich an @StefanKarpinskis Ansatz mul!((Y, β), α::Diagonal, A, B) für einen Matrixtyp vom Typ A (z. B. Adjoint{_,<:SparseMatrixCSC} ) implementieren können, ohne die zeitliche Komplexität zu ändern . (Dies ist wichtig für meine Anwendung.) Natürlich würde ein weiterer Weg in der API eine weitere Diskussion erfordern, insbesondere, wie die Existenz einer speziellen Methode abgefragt werden kann. Die Möglichkeit, die API zu erweitern, ist dennoch großartig.

Wenn jemand meine Besorgnis über Unklarheiten in Bezug auf Methoden klarstellt, bin ich für den gruppenweisen Ansatz.

Dies liegt daran, dass andere Orte für α die große O-Zeit-Komplexität ändern.

Ist das etwas spärliche Matrix speziell? Ich bin nicht ganz der Meinung, besonders nicht für dichte Matrizen. In der Implementierung, auf die Sie verlinken, wird ein Fall angezeigt, in dem α zwischen A und B .

Ich würde denken, dass Paketentwickler nur implementieren müssen ...

Dies ist sehr stark vereinfacht. Nehmen wir an, wir haben eine Matrix, die sich wie eine schrittweise Matrix verhält, z. B. PseudoBlockMatrix aus BlockArrays.jl. Um gemm! vollständig zu unterstützen, müssen wir jede Permutation von PseudoBlockMatrix mit (1) selbst, (2) StridedMatrix , (3) Adjoint von sich selbst überschreiben , (4) Transpose s von sich selbst, (5) Adjoint s von StridedMatrix , (6) Transpose s von StridedMatrix , und möglicherweise andere. Dies sind bereits 6 ^ 3 = 216 verschiedene Kombinationen. Dann möchten Sie trmm! und müssen dasselbe mit UpperTriangular , UnitUpperTriangular , ihren Adjoints, ihren Transponierungen usw. tun. Dann gsmm! mit Symmetric und Hermitian .

In vielen Anwendungen möchten wir jedoch nicht nur mit den Matrizen arbeiten, sondern auch mit ihren Unteransichten, insbesondere für Blockmatrizen, bei denen wir mit Blöcken arbeiten möchten. Jetzt müssen wir jede Permutation von Ansichten unserer Matrix zusammen mit den 6 obigen Kombinationen hinzufügen.

Jetzt haben wir Tausende von Überschreibungen, die StridedMatrix betreffen, was ein sehr komplizierter Unionstyp ist. Dies ist zu viel für den Compiler, sodass die Zeit für using Minuten statt Sekunden dauert.

An diesem Punkt wird klar, dass die aktuellen mul! und damit die vorgeschlagenen Erweiterungen von mul! vom Design her fehlerhaft sind und Paketentwickler sich daher einfach nicht darum kümmern sollten. Glücklicherweise bietet LazyArrays.jl eine vorübergehende Problemumgehung mithilfe von Merkmalen.

Daher stimme ich @StefanKarpinski darin überein, die Dinge so zu

@dlfivefifty , ich bezog mich nur darauf, wie die skalaren Argumente behandelt werden. Alle Komplikationen mit verschiedenen Matrixtypen, die derzeit bereits für mul!(C,A,B) , bleiben natürlich bestehen.

Dies liegt daran, dass andere Orte für α die große O-Zeit-Komplexität ändern.

Ist das etwas spärliche Matrix speziell? Ich bin nicht ganz der Meinung, besonders nicht für dichte Matrizen. In der Implementierung, auf die Sie verlinken, wird ein Fall angezeigt, in dem α zwischen A und B .

@Jutho Ich denke im Allgemeinen kann man α in die innerste Schleifenposition bringen. In diesem Fall können Sie beispielsweise α₁*A*B*α₃ aber nicht A*α₂*B

https://github.com/JuliaLang/julia/blob/11c5680d5620b0b64420055e8474a2b8cf757010/stdlib/LinearAlgebra/src/matmul.jl#L661 -L670

Ich denke, dass mindestens α₁ oder α₂ in α₁*A*α₂*B*α₃ 1 , um eine zunehmende Komplexität der asymptotischen Zeit zu vermeiden.

@dlfivefifty Aber selbst LazyArrays.jl benötigt einige primitive Funktionen, an die gesendet werden kann, oder? Mein Verständnis ist, dass es die "Versandhölle" löst, aber nicht die Anzahl der Berechnungen verringert, die "Kernel" implementieren müssen.

Nein, es gibt kein "Primitiv" auf die gleiche Weise, wie Broadcasted kein "Primitiv" hat. Aber ja, im Moment löst es nicht die "Kernel" -Frage. Ich denke, der nächste Schritt besteht darin, es neu zu gestalten, um einen faulen Applied -Typ mit einem ApplyStyle . Dann könnte es MulAddStyle , um BLAS-ähnliche Operationen so zu erkennen, dass die Reihenfolge keine Rolle spielt.

Ich würde materialize! oder copyto! als primitiv bezeichnen. Zumindest ist es der Baustein für den Rundfunkmechanismus. Ebenso muss LazyArrays.jl seine verzögerte Darstellung auf Funktionen mit Schleifen oder ccall s auf externe Bibliotheken reduzieren, oder? Wäre es schlecht, wenn der Name einer solchen Funktion mul! ?

Dies ist sehr stark vereinfacht. Nehmen wir an, wir haben eine Matrix, die sich wie eine Schrittmatrix verhält, wie z. B. PseudoBlockMatrix aus BlockArrays.jl. Gemm voll unterstützen! Wir müssen jede Permutation von PseudoBlockMatrix mit (1) sich selbst, (2) StridedMatrix, (3) Adjoints von sich selbst, (4) Transponierten von sich selbst, (5) Adjunkten von StridedMatrix, (6) Transponierten von StridedMatrix und möglicherweise anderen überschreiben . Dies sind bereits 6 ^ 3 = 216 verschiedene Kombinationen. Dann möchten Sie trmm unterstützen! und Sie müssen dasselbe mit UpperTriangular, UnitUpperTriangular, ihren Adjoints, ihren Transponierten usw. tun. Dann gsmm! mit Symmetric und Hermitian.
In vielen Anwendungen möchten wir jedoch nicht nur mit den Matrizen arbeiten, sondern auch mit ihren Unteransichten, insbesondere für Blockmatrizen, bei denen wir mit Blöcken arbeiten möchten. Jetzt müssen wir jede Permutation von Ansichten unserer Matrix zusammen mit den 6 obigen Kombinationen hinzufügen.
Jetzt haben wir Tausende von Überschreibungen, an denen StridedMatrix beteiligt ist, ein sehr komplizierter Unionstyp. Dies ist zu viel für den Compiler, sodass die Verwendungszeit über Minuten statt über Sekunden dauert.

Ich bin mir sicher einig, dass die aktuelle Union vom Typ StridedArray ein großer Konstruktionsfehler ist. Ich habe Ihren Versuch unterstützt, dies irgendwann zu beheben.

Drüben bei Strided.jl implementiere ich mul! wenn alle beteiligten Matrizen von meinem eigenen benutzerdefinierten Typ (Abstract)StridedView sind. Immer wenn die Typen A, B und C gemischt sind, lasse ich Julia Base / LinearAlgebra kümmern sich darum. Dies ist natürlich in einer @strided zu verwenden, in der versucht wird, alle möglichen Basistypen in den Typ StridedView zu konvertieren. Hier kann StridedView Unteransichten, Transponierungen und Adjoints sowie bestimmte Umformen darstellen, die alle denselben (parametrischen) Typ haben. Insgesamt beträgt der vollständige Multiplikationscode etwa 100 Zeilen:
https://github.com/Jutho/Strided.jl/blob/master/src/abstractstridedview.jl#L46 -L147
Der native Julia-Fallback für den Fall, dass BLAS nicht angewendet wird, wird mithilfe der allgemeineren mapreducedim! -Funktionalität implementiert, die von diesem Paket bereitgestellt wird, und ist nicht weniger effizient als die in LinearAlgebra . es ist aber auch multithreaded.

Ich denke, dass mindestens α₁ oder α₂ in α₁*A*α₂*B*α₃ 1 sein muss, um eine zunehmende Komplexität der asymptotischen Zeit zu vermeiden.

@tkf , ich würde annehmen, dass, wenn diese one(T) oder noch besser true annehmen, konstante Propagierung und Compiler-Optimierungen diese Multiplikation automatisch eliminieren. in der innersten Schleife, wenn es ein No-Op ist. Es wäre also immer noch bequem, nur die allgemeinste Form definieren zu müssen.

Ich bin mir nicht sicher, ob wir uns auf eine konstante Ausbreitung verlassen können, um alle Multiplikationen mit 1 ( true ) zu eliminieren. Zum Beispiel kann eltype Matrix . In diesem Fall denke ich, dass true * x (wobei x::Matrix ) eine Heap-zugewiesene Kopie von x erstellen muss. Kann Julia etwas zaubern, um das zu beseitigen?

@Jutho Ich denke, dieser Benchmark zeigt, dass Julia die Zwischenmultiplikationen in einigen Fällen nicht eliminieren kann:

function simplemul!((β₁, Y, β₂), α₁, A, α₂, B, α₃)
    <strong i="7">@assert</strong> size(Y, 1) == size(A, 1)
    <strong i="8">@assert</strong> size(Y, 2) == size(B, 2)
    <strong i="9">@assert</strong> size(A, 2) == size(B, 1)
    <strong i="10">@inbounds</strong> for i in 1:size(A, 1), j = 1:size(B, 2)
        acc = zero(α₁ * A[i, 1] * α₂ * B[1, j] * α₃ +
                   α₁ * A[i, 1] * α₂ * B[1, j] * α₃)
        for k = 1:size(A, 2)
            acc += A[i, k] * α₂ * B[k, j]
        end
        Y[i, j] = α₁ * acc * α₃ + β₁ * Y[i, j] * β₂
    end
    return Y
end

function simplemul!((Y, β), A, B, α)
    <strong i="11">@assert</strong> size(Y, 1) == size(A, 1)
    <strong i="12">@assert</strong> size(Y, 2) == size(B, 2)
    <strong i="13">@assert</strong> size(A, 2) == size(B, 1)
    <strong i="14">@inbounds</strong> for i in 1:size(A, 1), j = 1:size(B, 2)
        acc = zero(A[i, 1] * B[1, j] * α +
                   A[i, 1] * B[1, j] * α)
        for k = 1:size(A, 2)
            acc += A[i, k] * B[k, j]
        end
        Y[i, j] = acc * α + Y[i, j] * β
    end
    return Y
end

fullmul!(Y, A, B) = simplemul!((false, Y, false), true, A, true, B, true)
minmul!(Y, A, B) = simplemul!((Y, false), A, B, true)

using LinearAlgebra
k = 50
n = 50
A = [randn(k, k) for _ in 1:n, _ in 1:n]
B = [randn(k, k) for _ in 1:n]
Y = [zeros(k, k) for _ in 1:n]
<strong i="15">@assert</strong> mul!(copy(Y), A, B) == fullmul!(copy(Y), A, B) == minmul!(copy(Y), A, B)

using BenchmarkTools
<strong i="16">@btime</strong> mul!($Y, $A, $B)     # 63.845 ms (10400 allocations: 99.74 MiB)
<strong i="17">@btime</strong> fullmul!($Y, $A, $B) # 80.963 ms (16501 allocations: 158.24 MiB)
<strong i="18">@btime</strong> minmul!($Y, $A, $B)  # 64.017 ms (10901 allocations: 104.53 MiB)

Schöner Benchmark. Ich hatte auch bereits bemerkt, dass es diese Zuordnungen durch einige ähnliche Experimente tatsächlich nicht beseitigen wird. In solchen Fällen kann es nützlich sein, einen speziellen Singleton-Typ für One definieren, der nur *(::One, x::Any) = x und *(x::Any, ::One) = x und den kein Benutzertyp jemals benötigen muss. Dann könnte der Standardwert, zumindest für α₂ , One() .

Ah ja, das ist klug! Ich dachte zuerst, ich wäre jetzt damit einverstanden, α₁ * A * α₂ * B * α₃ aber dann denke ich, dass ich ein anderes Problem gefunden habe: Es ist mathematisch nicht eindeutig, was wir tun sollen, wenn (sagen wir) A eine Matrix-of-Matrix ist und α₁ ist eine Matrix. Es wird kein Problem sein, wenn wir niemals nicht skalare Argumente in α -Positionen unterstützen. Es macht es jedoch unmöglich, Y .= β₁*Y*β₂ + *(args...) als mentales Modell von mul!((β₁, Y, β₂), args...) . Darüber hinaus wäre es sehr schön, wenn diagonale Matrizen an α₁ oder α₂ werden könnten, da sie manchmal fast "kostenlos" berechnet werden können (und in Anwendungen wichtig sind). Ich denke, es gibt zwei Routen:

(1) Gehen Sie mit mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) aber wenn Sie die Methode überladen, müssen die Argumente α und β Diagonal akzeptieren. Es ist einfach, einen Aufrufpfad zu definieren, damit der Endbenutzercode ihn weiterhin über skalare Werte aufrufen kann. Damit dies jedoch effizient funktioniert, muss die "O (1) -Version" von Diagonal(fill(λ, n)) https://github.com/JuliaLang/julia/pull/30298#discussion_r239845163 in LinearAlgebra implementiert werden. Beachten Sie, dass die Implementierungen für skalare und diagonale α nicht sehr unterschiedlich sind. Oft werden nur α und α.diag[i] getauscht. Daher denke ich nicht, dass es für die Paketautoren eine große Belastung ist.

Dies löst die oben erwähnte Mehrdeutigkeit, da Sie jetzt mul!(Y, α * I, A, B) aufrufen können, wenn A eine Matrix der Matrix ist und α eine Matrix ist, die als eltype behandelt werden sollte. A .

(2) Vielleicht ist die obige Route (1) noch zu komplex? Wenn ja, gehen Sie stattdessen erst einmal zu muladd! . Wenn wir jemals mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) unterstützen möchten, ist die Migration unter Beibehaltung der Abwärtskompatibilität nicht schwierig.

An dieser Stelle muss ich mich fragen, ob wir nicht nur ein sehr eingeschränktes und spezialisiertes matmul!(C, A, B, α, β) das nur für diese allgemeine Signatur definiert ist:

matmul!(
    C :: VecOrMatT,
    A :: Matrix{T},
    B :: VecOrMatT,
    α :: Union{Bool,T} = true,
    β :: Union{Bool,T} = false,
) where {
    T <: Number,
    VecOrMatT <: VecOrMat{T},
}

Es ist auch wirklich erstaunlich, dass diese Unterschrift geschrieben und versandt werden kann.

Das ist im Wesentlichen mein Vorschlag (2), richtig? (Ich vermute, A :: Matrix{T} bedeutet nicht wörtlich Core.Array{T,2} ; sonst ist es mehr oder weniger nur gemm! )

Ich würde mich über diese vorübergehende Lösung freuen und kann sie teilweise in den von mir verwalteten Paketen unterstützen („teilweise“ aufgrund des Problems mit herausragenden Merkmalen), obwohl sie der Mischung einen anderen Namen hinzufügt: mul! , muladd! und jetzt matmul! .

... Ist es nicht an der Zeit, dass jemand "Triage sagt ..." schreibt und es anruft?

Es ist auch wirklich erstaunlich, dass diese Unterschrift geschrieben und versandt werden kann.

Ist die Tatsache, dass Sie genau diese Signatur sauber versenden können, nicht ein Argument dafür, dass dies nur eine Methode von mul! . Es kann dann auch sauber veraltet sein, falls eine allgemeinere Lösung zustande kommt.

Dies ist eine Methode von mul!

Wenn wir mit mul!(C, A, B, α, β) gibt es keine Möglichkeit, es auf mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) und ähnliches zu verallgemeinern, ohne die Kompatibilität zu beeinträchtigen. (Vielleicht ist es ein "Feature", da wir für immer von dieser Diskussion frei sind: Lächeln :).

Lassen Sie mich auch beachten, dass die Begrenzung des Elementtyps auf Number _und_ und die Beibehaltung der Kompatibilität mit dem aktuellen 3-Argument mul! (der bereits Nicht- Number Elementtypen unterstützt) viel mit sich bringt der Vervielfältigung.

Ich habe keine Ahnung, was Sie von mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) erwarten würden ... also denke ich, dass es eine Funktion ist.

Stoßen. Es macht mich traurig, überall BLAS-Funktionen verwenden zu müssen, um eine bessere Leistung vor Ort zu erzielen.

@StefanKarpinski Könnten Sie es in Triage

Wir können diskutieren, obwohl ich nicht sicher bin, welches Ergebnis ohne einige Gesprächspartner erzielt werden soll, was heutzutage normalerweise nicht der Fall ist. Um ehrlich zu sein, ich habe ziemlich viel Zeit und Mühe darauf verwendet, diese Diskussion zu einer Lösung zu bringen, und jede Idee scheint auf die eine oder andere Weise standhaft abgelehnt worden zu sein, also habe ich mich so ziemlich gerade mit diesem Thema befasst dieser Punkt. Wenn jemand eine Zusammenfassung des Problems erstellen könnte und warum die verschiedenen Vorschläge unzureichend sind, wäre dies hilfreich für eine produktive Triage-Diskussion. Sonst denke ich nicht, dass wir viel tun können.

Ich denke, der Mangel an Konsens bedeutet, dass es nicht der richtige Zeitpunkt ist, dies in StdLib aufzunehmen.

Warum nicht einfach ein Paket MatMul.jl, das einen der Vorschläge implementiert, die Down-Benutzer verwenden können? Ich verstehe nicht, warum es in der Praxis so wichtig ist, in StdLib zu sein. Ich werde dies gerne in den Paketen unterstützen, die ich pflege.

Ich denke nur an eine schöne julianische Version von gemm! und gemv! passend zu dem, was wir bereits in SparseArrays haben. Per @andreasnoack oben:

Ich dachte, wir haben uns bereits für mul!(C, A, B, α, β) mit Standardwerten für α , β . Wir verwenden diese Version in

julia / stdlib / SparseArrays / src / linalg.jl

Zeilen 32 bis 50 in b8ca1a4

...
Ich denke, einige Pakete verwenden auch dieses Formular, aber ich kann mich nicht erinnern, welches auf meinem Kopf liegt.

Dieser Vorschlag hatte 7 Daumen hoch und keine Daumen runter. Warum implementieren wir diese Funktion nicht einfach für dichte Vektoren / Matrizen? Das wäre eine einfache Lösung, die den häufigsten Anwendungsfall abdeckt, oder?

IN ORDNUNG. Ich denke, es gibt nicht einmal einen Konsens darüber, ob es einen Konsens gibt: heat_smile:

Ich dachte, dass so ziemlich jeder [*] diese API wollte und es ist nur eine Frage des Funktionsnamens und der Signatur. Im Vergleich zu _nicht_ mit dieser API dachte ich, dass jeder mit einer der Optionen zufrieden ist (sagen wir mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) , muladd!(C, A, B, α, β) und mul!(C, A, B, α, β) ). Wenn nicht jemand ein überzeugendes Argument dafür vorbringen kann, dass eine bestimmte API viel schlimmer ist, als sie nicht zu haben, wäre ich mit jeder Triage zufrieden.

@StefanKarpinski Sie können jedoch das triage -Tag entfernen, wenn Sie der Meinung sind, dass die Diskussion noch nicht konsolidiert genug ist.

[*] OK, @dlfivefifty , ich denke, Sie zweifeln sogar an den aktuellen 3-Argumenten mul! . Dafür müsste jedoch die 3-arg mul! -Schnittstelle von Grund auf neu geändert werden. Daher dachte ich, dass dies weit über den Rahmen dieser Diskussion hinausgeht (die ich als Hinzufügen einer Form einer 5-arg-Variante interpretiert habe). Ich denke, wir brauchen etwas, das "genug" funktioniert, bis LazyArrays.jl reift.

Warum nicht einfach ein Paket MatMul.jl, das einen der Vorschläge implementiert, die Down-Benutzer verwenden können?

@dlfivefifty Ich denke, es ist wichtig, es in LinearAlgebra.jl zu haben, da es eine Schnittstellenfunktion ist (überladbare API). Da mul!(C::AbstractMatrix, A::AbstractVecOrMat, B::AbstractVecOrMat) in LinearAlgebra.jl implementiert ist, können wir mul! nicht als MatMul.muladd! . Es gibt natürlich einige Problemumgehungen, aber es ist viel schöner, eine einfache Implementierung zu haben, insbesondere wenn man bedenkt, dass "nur" die Entscheidung über den Namen und die Signatur erforderlich ist.

Warum implementieren wir diese Funktion nicht einfach für dichte Vektoren / Matrizen?

@chriscoey Leider ist dies nicht der einzige Favorit für alle: https://github.com/JuliaLang/julia/issues/23919#issuecomment -441717678. Hier ist meine Zusammenfassung der Vor- und Nachteile für diese und andere Optionen: https://github.com/JuliaLang/julia/issues/23919#issuecomment -441865841. (Siehe auch die Kommentare anderer Leute)

Von der Triage: Es gibt langfristige Pläne für generische Tensorkontraktions-APIs, einschließlich Compiler-Unterstützung und Auswahl für BLAS. Mittelfristig sollten Sie jedoch nur die API auswählen, die Ihren unmittelbaren Anforderungen entspricht. Wenn es mit BLAS übereinstimmt, erscheint die Auswahl von BLAS-Namen sinnvoll.

@ Keno , gibt es Informationen, die Sie über die generische Tensorkontraktions-API und die Compiler-Unterstützung teilen können? Ich könnte auch einige interessante Informationen teilen, wenn auch (noch) nicht öffentlich.

Es wurde kein API-Design für irgendetwas davon durchgeführt, nur ein allgemeines Gefühl, dass wir es haben sollten. Ich bin mir bewusst, dass Sie an einigen dieser Dinge gearbeitet haben. Es wäre also gut, eine Design-Sitzung zum richtigen Zeitpunkt abzuhalten, aber ich glaube, wir sind noch nicht da.

Wenn es mit BLAS übereinstimmt, erscheint die Auswahl von BLAS-Namen sinnvoll.

Dies steht völlig im Widerspruch zu dem, was wir bisher für die generischen Funktionsnamen der linearen Algebra getan haben.

Was ist der Plan für starke / schwache β == 0 in der vorgeschlagenen allgemeinen Version von BLAS.gemm!(α, A, B, β, C) ?

Wenn wir auf BLAS Anrufe senken, verhält es sich wie eine starke Null, obwohl dies jetzt nicht mit lmul! . Ich kann mir keine Lösung dafür vorstellen, außer auf generic_muladd! wenn β == 0 .

Was ist der Plan für starkes / schwaches β == 0

Es wurde nur kurz um meinen Kommentar in https://github.com/JuliaLang/julia/issues/23919#issuecomment -430139849 besprochen, sodass Triage diese Frage wahrscheinlich nicht ansprach.

@ Keno Obwohl es noch kein API-Design gibt, stellen Sie sich vor, dass "APIs einschließlich Compiler-Unterstützung und Auswahl für BLAS" als mutierend definiert oder unveränderlich sind, wie die lineare XLA-Algebra, um das zu unterstützen Compiler? Dh denken Sie, dass mul! und / oder muladd! Teil solcher APIs wären?

ping @Keno für die @andreasnoack ‚s Frage in https://github.com/JuliaLang/julia/issues/23919#issuecomment -475.534.454

Entschuldigung, ich sollte auf dieses Problem zurückkommen (insbesondere habe ich um Triage gebeten), aber ich wusste nicht, wie die nächste Aktion die Entscheidung von Triage treffen würde.

Wenn es mit BLAS übereinstimmt, erscheint die Auswahl von BLAS-Namen sinnvoll.

Wie @andreasnoack betonte, können wir gemm! nicht verwenden (sagen wir), weil wir die Matrix-Vektor-Multiplikation usw. unterstützen wollen. Aber ich denke, wir können diese Triage-Entscheidung einfach ignorieren (die nur sagt "Wenn sie mit BLAS übereinstimmt"). ; tut es nicht).

Wählen Sie einfach die API aus, die Ihren unmittelbaren Anforderungen entspricht.

Ich denke, wir können dieser Richtung folgen. Ich denke, es bedeutet, die von @StefanKarpinski vorgeschlagene mul! / muladd! / addmul! auszuwählen.

Wir kehren irgendwie zur ursprünglichen Diskussion zurück. Aber ich finde es schön, dass wir die Einschränkung haben, nicht mehr über API zu diskutieren.

Irgendeine Idee, wie man einen Namen aus mul! / muladd! / addmul! auswählt?


@chriscoey Ich denke, es ist besser, zukünftige API anderswo zu diskutieren. Dieses Problem ist bereits sehr lang und wir können keine Fortschritte erzielen, wenn wir uns nicht auf eine mittelfristige Lösung konzentrieren. Wie wäre es mit dem Öffnen einer neuen Ausgabe (oder eines Diskurs-Threads)?

Ich schlage eine einzige Runde der Zustimmungsabstimmung mit einer Frist von 10 Tagen vor. Zustimmungsabstimmung bedeutet: Jeder stimmt für alle Optionen, die er für die Fortsetzung der Diskussion für vorzuziehen hält. Personen, die jetzt lieber ihren am wenigsten bevorzugten Namen als eine fortgesetzte Diskussion haben möchten, sollten für alle drei stimmen. Wenn keine Option eine breite Zustimmung erhält oder das Abstimmungsschema selbst keine breite Zustimmung findet, müssen wir die Diskussion fortsetzen. Bei beinahe Verbindungen zwischen genehmigten Optionen kann @tkf entscheiden (PR-Autorenprivileg).

+1: Ich stimme diesem Abstimmungsschema zu und habe meine Zustimmungsstimmen abgegeben.
-1: Ich bin mit diesem Abstimmungsschema nicht einverstanden. Wenn zu viele oder zu wichtige Personen diese Option auswählen, ist die Abstimmung umstritten.

Herz: mul! ist der weiteren Diskussion vorzuziehen.
Rakete: muladd! ist der weiteren Diskussion vorzuziehen.
Hurra: addmul! ist der weiteren Diskussion vorzuziehen.

Ich schlage vorläufig vor, dass 75% Zustimmung und 5% definitiv beschlussfähig sein sollten (dh 75% der Personen, die überhaupt abgestimmt haben, einschließlich der Nichtübereinstimmung mit dem gesamten Abstimmungsverfahren, und mindestens 5 Personen haben der Gewinnoption zugestimmt, wenn die Teilnahme gering ist , dann sind 5/6 oder 6/8 beschlussfähig, aber einstimmig 4/4 könnte als Fehlschlag angesehen werden.

Das Einfrieren der Funktionen für 1.3 ist um den 15. August: https://discourse.julialang.org/t/release-1-3-branch-date-approaching-aug-15/27233?u=chriscoey. Ich hoffe, wir können es bis dahin zusammenführen lassen 😃. Vielen Dank an alle, die bereits abgestimmt haben!

Wir müssen auch noch das Verhalten von β == 0 https://github.com/JuliaLang/julia/issues/23919#issuecomment -475420149 entscheiden, das orthogonal zur Entscheidung des Namens ist. Außerdem muss der Zusammenführungskonflikt in meiner PR gelöst werden, und die PR muss die Implementierungsdetails überprüfen (z. B. den Ansatz, den ich dort verwendet habe, um undef s im Zielarray zu behandeln). Möglicherweise stellen wir während der Überprüfung andere Probleme fest. Also, ich bin mir nicht sicher, ob es in 1.3 kommen kann ....

Betreff : β == 0 , ich denke, @andreasnoacks Kommentar https://github.com/JuliaLang/julia/issues/23919#issuecomment -430139849 (meine Zusammenfassung: β == 0 -Handling sollte BLAS sein -kompatibel, um BLAS so weit wie möglich zu nutzen) macht Sinn. Es ist schwierig, andere gegensätzliche Meinungen als das Argument von @simonbyrne zu finden ("Jede Implementierung benötigt einen Zweig, um auf Null zu prüfen"). Gibt es noch andere Argumente gegen die BLAS-ähnliche Behandlung mit β == 0 ?

@simonbyrne In Bezug auf Ihren Kommentar https://github.com/JuliaLang/julia/issues/23919#issuecomment -430375349 denke ich nicht, dass explizite Verzweigung ein großes Problem ist, da es sich meistens nur um ein onelineares β != 0 ? rmul!(C, β) : fill!(C, zero(eltype(C))) . Für eine sehr generische Implementierung, bei der Sie z. B. C = Matrix{Any}(undef, 2, 2) , erfordert die Implementierung ohnehin eine explizite Behandlung mit "starken Nullen" (siehe die Hilfsfunktion _modify! in meinem PR https: // github .com / JuliaLang / julia / pull / 29634 / files # diff-e5541a621163d78812e05b4ec9c33ef4R37). Daher denke ich, dass BLAS-ähnliches Handling hier die beste Wahl ist. Was denken Sie?

Ist es möglich, schwache Nullen mit hoher Leistung zu haben? In dem Sinne, dass wir wollen würden:

julia> A = [NaN 0;
                     1 0]

julia> b = [0.0,0];

julia> 0.0*A*b
2-element Array{Float64,1}:
 NaN  
   0.0

julia> false*A*b
2-element Array{Float64,1}:
 0.0
 0.0

Das heißt, wir müssten manuell bestimmen, welche Zeilen NaN wenn wir auf BLAS (das eine starke 0 verwendet) senken.

@dlfivefifty Ich denke, BLAS kann mit NaN in A und B umgehen, aber nicht in C ?

Ich denke, es gibt keine Möglichkeit, NaN-fähiges _C = α * A * B + 0 * C_ effizient mit BLAS.gemm! usw. [1] auszuführen , und @andreasnoack .

[1] Sie müssen irgendwo isnan.(C) sparen und anschließend C "verschmutzen"

@chethega seit der Abstimmung sind mehr als 10 Tage

@chriscoey Du hast recht, die Abstimmung ist geschlossen.

Ich bin zu schlecht in Github, um eine vollständige Liste der Personen zu erhalten, die abgestimmt haben (dies wäre erforderlich, um zu berechnen, wie viele Personen überhaupt Stimmen abgegeben haben). Wenn ich jedoch die Zahlen betrachte, scheint es ziemlich klar zu sein, dass mul! eine überwältigende Unterstützung hat (wahrscheinlich ein 75% iges Genehmigungsquorum verwaltet) und der zweite Anwärter muladd! weit unter 50% liegt.

Es gab keinen einzigen Einwand gegen das Abstimmungsschema. Ich rufe die Abstimmung an, mul! hat gewonnen und der Name wird entschieden. @tkf kann es weiter fliegen lassen :)

@chethega Danke, es war ein schönes Schema, um dies zu entscheiden!

Übrigens kann ich den Rebase nicht sofort durchführen (aber vielleicht innerhalb weniger Wochen). Wenn also jemand den Rebase oder die Neuimplementierung durchführen möchte, warten Sie bitte nicht auf mich.

Leider hatten wir keine Abstimmung über die NaN-Semantik. Feature Freeze ist nächste Woche und wir haben nicht genug Zeit für eine aussagekräftige Abstimmung.

Ich schlage vor, wir haben ein unverbindliches Referendum, um eine Momentaufnahme der Stimmung im Thread zu sammeln.

Ich sehe folgende Optionen:

  1. Diskutieren Sie weiter, hoffen Sie, dass der Konsens irgendwie vor Ablauf der Frist erreicht wird oder dass die Kernleute uns eine Verlängerung gewähren oder so. : tada: (zur Verdeutlichung bearbeiten: Dies ist die Standardoption, dh was passiert, wenn wir keinen Konsens über eine andere Option erzielen können. Das wahrscheinlichste Ergebnis ist, dass 5-arg mul! bis 1.4 verzögert wird.)
  2. Führen Sie die neue Funktion mit undefiniertem NaN-Verhalten als Rücklaufsperre zusammen. Sobald wir einen Konsens erreicht haben, aktualisieren wir Code und / oder Dokumente, um das entschiedene NaN-Verhalten zu erhalten (starke gegen schwache Nullen). Der Rückschlag eines undefinierten implementierungsdefinierten NaN-Verhaltens könnte unbestimmt sein, aber das steht heute nicht zur Abstimmung. (Implementieren Sie, was am schnellsten ist. Benutzer, die sich darum kümmern, müssen verschiedene Methoden aufrufen.) :Daumen hoch:
  3. Gemm bedeutet Gemm! Zusammenführen für 1.3 mit dokumentiertem Engagement für starke Nullen. :Herz:
  4. NaN bedeutet NaN ! Zusammenführen für 1.3 mit dokumentierter Verpflichtung zu schwachen Nullen. :Augen:
  5. Mach etwas, einfach alles, bevor du die 1.3 Klippe triffst. :Rakete:
  6. Umfrage ablehnen. :Daumen runter:
  7. Schreiben
  8. Vorgeschlagenes Abwärtsgewinde : Alternative Rücklaufsperre. Versuchen Sie, schnell zu verschmelzen und machen Sie eine schwache / starke Null-Divergenz zu einem Fehler für 1,3-Alpha, bis wir später eine überlegte Entscheidung treffen können. Das heißt, wir verschmelzen mit !(alpha === false) && iszero(alpha) && !all(isfinite, C) && throw(ArgumentError()) und dokumentieren, dass diese Fehlerprüfung wahrscheinlich zugunsten von etwas anderem eingestellt wird. :verwirrt:

Sie können auch mehrere, möglicherweise widersprüchliche Optionen auswählen, und @tkf /

Edit: Derzeit nur: tada: (Geduld) und: Rakete: (Ungeduld) sind widersprüchlich, aber beide sind mit allen anderen kompatibel. Als geklärter Abwärtspfad wird Triage das Umfrageergebnis hoffentlich zu einem nicht festgelegten Zeitpunkt zwischen dem 14. Mittwoch und dem 15. Do zählen und es auf eine nicht festgelegte Weise berücksichtigen. Dies ist wiederum als "Zustimmungsabstimmung" gemeint, dh wählen Sie alle Optionen aus, die Sie mögen, nicht nur Ihre Lieblingsoption. Es versteht sich, dass: Rakete: nicht missbilligt: ​​Daumen hoch:,: Herz:,: Augen: und: verwirrt:. Ich entschuldige mich dafür, dass diese Umfrage schneller ist als die letzte.

Ich würde für eine starke Null (: Herz :) stimmen, wenn es "Merge for 1.x" anstelle von "Merge for 1.3" wäre. Wären die Optionen nicht sinnvoll, wenn alle "Merge for 1.3" "Merge 1.x" sind?

Danke @chethega. @tkf Ich bin in der Lage, den neuen Mul wirklich zu brauchen! So schnell wie möglich, ohne sich zu sehr um die NaN-Entscheidung zu kümmern (solange die Leistung nicht beeinträchtigt wird).

Haben Sie LazyArrays.jl überprüft? Zu Ihrer Information, es hat wirklich gute Unterstützung für die Fused-Matrix-Multiplikation. Sie können BLAS.gemm! usw. auch sicher verwenden, da es sich um öffentliche Methoden handelt. Https://docs.julialang.org/en/latest/stdlib/LinearAlgebra/#LinearAlgebra.BLAS.gemm!

Ich brauche wirklich das generische Mul! da wir eine Vielzahl von strukturierten und spärlichen und einfachen alten dichten Matrizen verwenden, um verschiedene Optimierungsprobleme am effizientesten darzustellen. Ich bin wegen der Großzügigkeit und Geschwindigkeit hier.

Aha. Und ich erinnerte mich nur daran, dass wir Dinge in LazyArrays.jl besprochen haben, also wussten Sie es natürlich schon ...

In Bezug auf "ASAP" dient Julias viermonatiger Veröffentlichungszyklus zumindest als Design, um "Merge Rush" kurz vor dem Einfrieren der Features zu vermeiden. Ich weiß, dass es nicht fair für mich ist, das zu sagen, weil ich das Gleiche schon einmal versucht habe ... Aber ich denke, jemand muss dies als Erinnerung erwähnen. Die gute Seite ist, Julia ist super einfach zu bauen. Sie können es direkt nach dem Zusammenführen bis zur nächsten Version verwenden.

Bearbeiten: loslassen -> zusammenführen

Vielen Dank. Ich finde Fristen als hilfreiche Motivatoren, und ich möchte vermeiden, dass dies wieder abgestanden wird. Daher würde ich vorschlagen, dass wir versuchen, die Frist als Ziel zu verwenden.

Es ist großartig, dass Sie diesem Thread aktiv Energie zuführen!

Ich brauche wirklich das generische Mul! da wir eine Vielzahl von strukturierten und spärlichen und einfachen alten dichten Matrizen verwenden, um verschiedene Optimierungsprobleme am effizientesten darzustellen. Ich bin wegen der Großzügigkeit und Geschwindigkeit hier.

Ein mul! mit 5 Argumenten funktioniert nicht gut, wenn Sie viele verschiedene Typen haben: Sie benötigen kombinatorisch viele Überschreibungen, um Mehrdeutigkeiten zu vermeiden. Dies ist eine der Beweggründe für das LazyArrays.jl MemoryLayout -System. Genau aus diesem Grund wird es in BandedMatrices.jl und BlockBandedMatrices.jl für "strukturierte und spärliche" Matrizen verwendet. (Hier werden sogar Unteransichten von gebänderten Matrizen an gebänderte BLAS-Routinen gesendet.)

Danke, ich werde LazyArrays erneut versuchen.

Da der 5-Argumente-Mul im Allgemeinen als vorübergehende Notlösung angesehen wird (bis eine Lösung wie LazyArrays in 2.0 verwendet werden kann), können wir meiner Meinung nach darauf abzielen, ihn zusammenzuführen, ohne auf lange Sicht die ideale oder perfekte Lösung zu sein.

@chethega Wann sollten wir die Zahlen für die neue unverbindliche Abstimmung zählen?

@tkf Sicher, Sie haben Recht, dass starke / schwache / undefinierte Nullen auch für 1.x Sinn machen.

Ich denke jedoch, dass es einige Leute gibt, die jetzt lieber 1,3 mul! haben möchten, als bis 1.4 zu warten, um 5-arg mul! . Wenn es keine Frist gäbe, würde ich noch etwas warten und mir etwas mehr Zeit nehmen, um zu überlegen, wie eine ordnungsgemäße Umfrage durchgeführt werden soll (mindestens 10 Tage für die Abstimmung). Am wichtigsten ist, dass wir keine aussagekräftige Abstimmung haben können, ohne zuerst konkurrierende Implementierungen hinsichtlich der Geschwindigkeit und Eleganz schwacher / starker Nullen vorzustellen und zu bewerten. Ich persönlich vermute, dass schwache Nullen fast so schnell wie starke Nullen gemacht werden können, indem zuerst iszero(alpha) überprüft, dann die Matrix nach !isfinite Werten durchsucht und erst dann ein langsamer Pfad mit zusätzlicher Zuordnung verwendet wird. aber ich bevorzuge trotzdem eine starke Nullsemantik.

@chethega Wann sollten wir die Zahlen für die neue unverbindliche Abstimmung zählen?

Triage muss diese Woche eine Entscheidung (Verzögerung / stark / schwach / Backstop) für das 1.3 Alpha treffen. Ich denke, Donnerstag, der 15. oder Mittwoch, der 14. sind sinnvolle Optionen für die Triage, um eine Zählung vorzunehmen und dies zu berücksichtigen. Ich werde wahrscheinlich am Donnerstag nicht teilnehmen können, also muss jemand anderes zählen.

Realistisch gesehen ist es in Ordnung, hier konservativ zu sein und die Frist zu verpassen, die Diskussion fortzusetzen und auf 1.4 zu warten.

Andererseits haben wir möglicherweise bereits einen Konsens erzielt, ohne es zu bemerken: @andreasnoack machte einige starke Argumente dafür, dass ein

Warum nicht erstmal einen Fehler werfen:

β == 0.0 && any(isnan,C) && throw(ArgumentError("use β = false"))

Warum nicht erstmal einen Fehler werfen?

Ich habe diese Option zur Umfrage hinzugefügt. Tolle Kompromissidee!

Nur um die Erwartungen zu setzen: Das Einfrieren der Funktionen für 1.3 erfolgt in drei Tagen. Es gibt also im Grunde keine Möglichkeit, dies rechtzeitig zu erreichen. Wir sind ziemlich streng in Bezug auf das Einfrieren und Verzweigen von Features, da dies der einzige Teil des Veröffentlichungszyklus ist, dessen Timing wir wirklich steuern können.

Die Arbeit ist jedoch bereits in https://github.com/JuliaLang/julia/pull/29634 erledigt. Muss nur angepasst und neu basiert werden.

@tkf für # 29634 Könnten Sie die noch zu erledigende Arbeit auflisten (einschließlich Umbenennen und Behandeln von Nullen gemäß der Abstimmung)? Ich weiß, dass Sie beschäftigt sind, also können wir vielleicht einen Weg finden, alle verbleibenden Aufgaben aufzuteilen, damit die Last nicht wieder auf Sie fällt.

Die TODOs, an die ich denken kann, sind:

Meine PR implementiert die BLAS-Semantik der β = 0 -Handhabung. Daher muss auch eine andere Behandlung implementiert werden, z. B. das Auslösen eines Fehlers.

Meine PR implementiert die BLAS-Semantik der β = 0 -Handhabung.

Entschuldigung, mein Gedächtnis war abgestanden; Meine Implementierung war nicht konsistent und verbreitet NaN manchmal. Zusätzliches TODO besteht also darin, das Verhalten von β = 0.0 konsistent zu machen.

Der Typ MulAddMul nur für den internen Gebrauch bestimmt, oder?

Ja, es ist ein internes Detail. Meine Sorgen waren (1) es gibt möglicherweise zu viele Spezialisierungen (Beta = 0 usw. ist im Typparameter codiert) und (2) es verringert die Lesbarkeit des Quellcodes.

Das sind berechtigte Bedenken. Wir produzieren bereits eine Menge Spezialisierungen im linearen Algebra-Code, daher ist es gut zu überlegen, ob wir uns hier wirklich spezialisieren müssen. Ich war allgemein der Meinung, dass wir für kleine Matrizen optimieren sollten, da diese nicht kostenlos sind (wie Sie sagten, es kompliziert den Quellcode und könnte die Kompilierungszeiten verlängern) und die Leute besser dran sind, StaticArrays für kleine Matrixmultiplikationen zu verwenden. Daher neige ich dazu, nur die Werte zur Laufzeit zu überprüfen, aber wir können dies später jederzeit anpassen, wenn wir unsere Meinung ändern, damit dies keine Verzögerungen verursacht.

Zu Ihrer Information: Weiche Nullen haben einfache Implementierungen:

if iszero(β) && β !== false && !iszero(α)
   lmul!(zero(T),y) # this handles soft zeros correctly
   BLAS.gemv!(α, A, x, one(T), y) # preserves soft zeros
elseif iszero(α) && iszero(β)
   BLAS.gemv!(one(T), A, x, one(T), y) # puts NaNs in the correct place
   lmul!(zero(T), y) # everything not NaN should be zero
elseif iszero(α) && !iszero(β)
   BLAS.gemv!(one(T), A, x, β, y) # puts NaNs in the correct place
   BLAS.gemv!(-one(T), A, x, one(T), y) # subtracts out non-NaN changes
end

@andreasnoack Entschuldigung, ich habe vergessen, dass wir die Spezialisierung tatsächlich brauchten, um die innerste Schleife für einige strukturierte Matrizen wie mul!(C, A::BiTriSym, B, α, β) https://github.com/JuliaLang/julia/pull/29634#issuecomment -440510551 zu optimieren. Einige Spezialisierungen können entfernt werden, aber das ist tatsächlich mehr Arbeit (also Verzögerungen).

Daher neige ich dazu, nur die Werte zur Laufzeit zu überprüfen, aber wir können dies später jederzeit anpassen, wenn wir unsere Meinung ändern, damit dies keine Verzögerungen verursacht.

Großartig!

@andreasnoack Vielen Dank, dass Sie dies rechtzeitig überprüft und zusammengeführt haben!

Jetzt, da es für 1.3 zusammengeführt wurde, macht es mich sehr nervös wegen der Implementierung: smile:. Ich weiß es zu schätzen, wenn die Leute hier ihren linearen Algebra-Code gründlicher testen können, wenn 1.3-rc herauskommt!

Kein Grund zur Sorge, 1,3 RCs + PkgEvals haben genügend Zeit, um Fehler zu beseitigen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

TotalVerb picture TotalVerb  ·  3Kommentare

felixrehren picture felixrehren  ·  3Kommentare

tkoolen picture tkoolen  ·  3Kommentare

omus picture omus  ·  3Kommentare

StefanKarpinski picture StefanKarpinski  ·  3Kommentare