Julia: API de multiplication matricielle

Créé le 28 sept. 2017  ·  208Commentaires  ·  Source: JuliaLang/julia

Actuellement, il existe la ligne suivante dans le code matmult épars:

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

Je suppose que cela signifie que nous voulons avoir les méthodes plus générales A_mul_B*!(α,A,B,β,C) = αAB + βC qui écrasent C (l'API BLAS gemm ) pour les tableaux denses. Est-ce toujours le cas? (Il semble également possible de conserver les deux API, c'est-à-dire de conserver les méthodes A_mul_B*!(C,A,B) , qui seraient simplement définies comme A_mul_B*!(C,A,B) = A_mul_B*!(1,A,B,0,C) .)

J'aimerais personnellement que l'API gemm définie pour tous les types de tableaux (cela a été exprimé ailleurs ). L'implémentation de ces méthodes pour des tableaux denses semble assez simple, puisqu'elles appelleraient directement gemm et al. Le cas clairsemé est déjà implémenté. La seule vraie modification serait le matmult générique pur julia, qui n'accepte pas les arguments α et β .

Cela conduirait à un code générique qui fonctionne avec n'importe quel type de tableau / nombre. J'ai actuellement une implémentation simple de expm (après avoir effectué la modification de _generic_matmatmult! ) qui fonctionne avec les bigfloats et les tableaux clairsemés.

linear algebra

Commentaire le plus utile

Je suggère un seul tour de vote d'approbation avec un délai de 10 jours à partir de maintenant. Le vote d'approbation signifie: tout le monde vote pour toutes les options qu'il juge préférables à la poursuite de la discussion. Les gens qui préfèrent avoir leur nom le moins préféré maintenant plutôt qu'une discussion continue devraient voter pour les trois. Si aucune option ne reçoit une large approbation ou si le système de vote lui-même ne parvient pas à obtenir une large approbation, nous devons poursuivre la discussion. En cas de quasi-liens entre les options approuvées, @tkf décide (privilège de l'auteur PR).

+1: Je suis d'accord avec ce schéma de vote et j'ai émis mes votes d'approbation.
-1: Je ne suis pas d'accord avec ce schéma de vote. Si trop de personnes ou trop de personnes importantes choisissent cette option, le vote est sans objet.

Coeur: mul! est préférable à la poursuite de la discussion.
Rocket: muladd! est préférable à la poursuite de la discussion.
Hourra: addmul! est préférable à la poursuite de la discussion.

Je suggère provisoirement que 75% d'approbation et 5 devraient définitivement atteindre le quorum (c'est-à-dire 75% des personnes qui ont voté du tout, y compris en désaccord avec l'ensemble de la procédure de vote, et au moins 5 personnes ont approuvé l'option gagnante; si la participation est faible , alors 5/6 ou 6/8 font quorum mais l'unanimité 4/4 pourrait être considérée comme un échec).

Tous les 208 commentaires

Réf. # 9930, # 20053, # 23552. Meilleur!

Merci pour les références. Je suppose que ce problème a plus à voir avec l'ajout des méthodes de style gemm qu'une refonte de l'API, mais il peut être fermé si nous pensons qu'il est encore trop similaire à # 9930.

Comme point de départ, y aurait-il un support pour que _generic_matmatmul! ait l'API gemm ? C'est un changement assez simple, et purement additif / insécable, puisque la méthode actuelle serait simplement implémentée en ayant α=1 et β=0 . Je peux faire le PR. J'irais probablement de la même manière avec cette version (dans cette version, j'ai coupé tous les trucs de transposition parce que je n'en avais pas besoin, je ne le ferais pas ici).

Oui. Ce serait un bon début. Nous devons cependant considérer l'ordre des arguments. À l'origine, je pensais qu'il était plus naturel de suivre l'ordre BLAS, mais nous sommes assez cohérents sur le fait d'avoir d'abord les arguments de sortie, ce qui est également le cas pour l'actuel à trois arguments A_mul_B! . De plus et comme vous l'avez déjà souligné, la version à trois arguments correspondrait à la version à cinq arguments avec α=1 et β=0 et les arguments de valeur par défaut sont les derniers. Bien sûr, nous ne devons pas nécessairement utiliser la syntaxe de valeur par défaut pour cela, mais il serait logique de l'utiliser ici.

Pourquoi ne pas simplement introduire une fonction générique gemm ?

Oui. Ce serait un bon début. Nous devons cependant considérer l'ordre des arguments. À l'origine, je pensais qu'il était plus naturel de suivre l'ordre BLAS, mais nous sommes assez cohérents sur le fait d'avoir d'abord les arguments de sortie, ce qui est également le cas pour l'actuel A_mul_B! À trois arguments. De plus et comme vous l'avez déjà souligné, la version à trois arguments correspondrait à la version à cinq arguments avec α = 1 et β = 0 et les arguments de valeur par défaut sont les derniers. Bien sûr, nous ne devons pas nécessairement utiliser la syntaxe de valeur par défaut pour cela, mais il serait logique de l'utiliser ici.

Ça m'a l'air bien. Nous pouvons continuer la discussion sur l'ordre réel des arguments et le changement de nom des méthodes dans # 9930. Il s'agit plus simplement d' avoir la version à cinq arguments disponible, je vais donc garder l'interface actuelle Ax_mul_Bx!(α,A,B,β,C) .

Pourquoi ne pas simplement introduire une fonction gemm générique?

Proposez-vous de renommer _generic_matmatmul! en gemm! en plus des changements ci-dessus?

Pour être plus clair, je pense que nous devrions finir par avoir une seule méthode mul(C,A,B,α=1,β=0) , ainsi que des types transposés / adjoints paresseux à distribuer, mais ce sera un autre PR.

Pourquoi ne pas simplement introduire une fonction gemm générique?

Je pense que gemm est un abus de langage chez Julia. Dans BLAS, la partie ge indique que les matrices sont générales , c'est-à-dire qu'elles n'ont pas de structure spéciale, le premier m est multiplié et la liste m est une matrice . Dans Julia, la partie ge (générale) est encodée dans la signature comme la dernière m (matrice) donc je pense que nous devrions simplement l'appeler mul! .

Est la notation mul!(α, A, B, β, C) de SparseArrays.jl

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

"finalisé" comme syntaxe officielle? Et ce serait en plus de mul!(C, A, B) , lmul!(A, B) et rmul!(A,B) ?

Je ne suis pas trop fan d'avoir A , B et C dans des commandes différentes.

La notation mul!(α, A, B, β, C) de SparseArrays.jl est-elle "finalisée" comme syntaxe officielle?

Je dirais non. À l'origine, j'aimais l'idée de suivre BLAS (et l'ordre correspondait également à la façon dont cela est généralement écrit mathématiquement) mais maintenant je pense qu'il est logique d'ajouter simplement les arguments de mise à l'échelle en tant que quatrième et cinquième arguments facultatifs.

Donc, juste pour clarifier, vous aimeriez des arguments optionnels dans le sens

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

L'autre option serait des arguments de mot-clé facultatifs

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

mais je ne suis pas sûr que les gens soient trop satisfaits de l'Unicode.

Je suis très content de l'unicode mais il est vrai que nous essayons toujours d'avoir une option ascii et ce serait possible ici. Les noms α et β sont pas non plus très intuitifs à moins que vous ne connaissiez BLAS, donc je pense que l'utilisation d'arguments de position est la meilleure solution ici.

À mon avis, une nomenclature plus logique serait de laisser muladd!(A, B, C, α=1, β=1) mapper aux différentes routines BLAS qui font la multiplication et l'addition . ( gemm comme ci-dessus, mais aussi par exemple axpy quand A est un scalaire.)

La fonction mul! pourrait alors avoir une interface comme mul!(Y, A, B, ...) prenant un nombre arbitraire d'arguments (tout comme * fait) tant que tous les résultats intermédiaires peuvent être stockés dans Y. ( Un kwarg facultatif pourrait spécifier l'ordre de multiplication, avec une valeur par défaut raisonnable)

mul!(Y, A::AbstractVecOrMat, B:AbstractVecOrMat, α::Number) aurait l'implémentation par défaut muladd!(A, B, Y, α=α, β=0) , tout comme les autres permutations de deux matrices / vecteurs et d'un scalaire.

Un autre vote pour définir mul!(C, A, B, α, β) pour les matrices denses. Cela permettrait d'écrire du code générique pour les matrices denses et éparses. Je voulais définir une telle fonction dans mon package des moindres carrés non linéaires mais je suppose que c'est du piratage de type.

J'ai également été tenté d'écrire des méthodes mul!(C, A, B, α, β) pour le paquet MixedModels et de me lancer dans un peu de piratage de type, mais ce serait bien mieux si ces méthodes étaient dans le LinearAlgebra paquet. Avoir des méthodes pour un générique muladd! pour cette opération me conviendrait aussi.

Je suis pour, même si je pense qu'il devrait probablement avoir un nom différent de celui de mul! . muladd! semble raisonnable, mais je suis certainement ouvert aux suggestions.

Peut-être mulinc! pour multiplier-incrémenter?

Peut-être pouvons-nous avoir quelque chose comme addmul!(C, A, B, α=1, β=1) ?

N'est-ce pas une forme de muladd! ? Ou est-ce l'idée derrière l'appeler addmul! qu'il mute l'argument add plutôt que l'argument multiplier? Est-ce qu'on muterait jamais l'argument de multiplication?

Notez que nous faisons muter les éléments non premiers dans certains cas, par exemple lmul! et ldiv! , donc nous pourrions les faire dans l'ordre habituel "muladd" (c'est- muladd!(A,B,C) dire α et β doivent passer? Une option serait de faire les arguments de mot-clé?

Ne serait-il pas bien si vous laissiez une option aux implémenteurs pour distribuer sur les types des scalaires α et β? Il est facile d'ajouter des sucres pour les utilisateurs finaux.

Je pensais que nous avions déjà choisi mul!(C, A, B, α, β) avec des valeurs par défaut pour α , β . Nous utilisons cette version dans https://github.com/JuliaLang/julia/blob/b8ca1a499ff4044b9cb1ba3881d8c6fbb1f3c03b/stdlib/SparseArrays/src/linalg.jl#L32 -L50. Je pense que certains packages utilisent également ce formulaire, mais je ne me souviens pas lequel sur ma tête.

Merci! Ce serait bien si cela était documenté.

Je pensais que nous avions déjà choisi mul!(C, A, B, α, β) avec des valeurs par défaut pour α , β .

SparseArrays l'utilise, mais je ne me souviens pas qu'il ait été discuté nulle part.

D'une certaine manière, le nom muladd! est plus naturel car il s'agit d'une multiplication suivie d'une addition. Cependant, les valeurs par défaut de α et β, muladd!(C, A, B, α=1, β=0) (notez que la valeur par défaut pour β est zéro, pas un), remettez-le en mul!(C, A, B) .

Il semble que ce soit un cas de pédantisme ou de cohérence d'appeler cela mul! ou muladd! et je pense que le cas de la méthode existante dans SparseArrays plaiderait pour mul! . Je me retrouve dans le cas curieux d'argumenter pour la cohérence malgré ma citation préférée d'Oscar Wilde, «La cohérence est le dernier refuge de l'inimagination».

D'une certaine manière, le nom muladd! est plus naturel car il s'agit d'une multiplication suivie d'une addition. Cependant, les valeurs par défaut de α et β, muladd!(C, A, B, α=1, β=0) (notez que la valeur par défaut pour β est zéro, pas un), remettez-le en mul!(C, A, B) .

Il y a une exception intéressante à cela lorsque C contient Infs ou NaNs: théoriquement, si β==0 , le résultat devrait toujours être NaNs. Cela ne se produit pas en pratique car BLAS et notre code de matrice éparse vérifient explicitement β==0 puis le remplacent par des zéros.

Vous pourriez considérer que les valeurs par défaut de α=true, β=false puisque true et false sont respectivement "fortes" 1 et 0, en ce sens que true*x est toujours x et false*x est toujours zero(x) .

lmul! devrait également avoir ce comportement exceptionnel: https://github.com/JuliaLang/julia/issues/28972

true et false sont respectivement 1 et 0 "forts", dans le sens où true*x est toujours x et false*x est toujours zero(x) .

Je ne savais pas ça!:

julia> false*NaN
0.0

FWIW, je suis plutôt satisfait de la lisibilité de la syntaxe LazyArrays.jl pour cette opération:

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

Dans les coulisses, il diminue à mul!(y, A, x, α, β) , pour les tableaux compatibles BLAS (bandés et strided).

Je ne savais pas ça!

Cela fait partie de ce qui fait que im = Complex(false, true) fonctionne.

SparseArrays l'utilise, mais je ne me souviens pas qu'il ait été discuté nulle part.

Il a été discuté ci-dessus dans https://github.com/JuliaLang/julia/issues/23919#issuecomment -365463941 et mis en œuvre dans https://github.com/JuliaLang/julia/pull/26117 sans aucune objection. Nous n'avons pas les versions α,β dans le cas dense, donc le seul endroit dans ce dépôt où une décision aurait un effet immédiat serait SparseArrays .

Et pour LinearAlgebra.BLAS.gemm! ? Ne devrait-il pas aussi être enveloppé comme 5-ary mul! ?

Cela devrait mais personne ne l'a encore fait. Il existe de nombreuses méthodes dans matmul.jl .

Il a été discuté ci-dessus dans # 23919 (commentaire) et implémenté dans # 26117 sans aucune objection.

Eh bien, considérez ceci mon objection. Je préférerais un nom différent.

Pourquoi serait-ce un nom différent? Tant dans le cas dense que clairsemé, l'algorithme de base effectue à la fois la multiplication et l'addition.

Si nous donnons à ces fonctions des noms différents, nous aurons mul!(C,A,B) = dgemm(C,A,B,1,0) et muladd!(C,A,B,α, β) = dgemm(C,A,B,α, β) .

Le seul avantage que je vois est de diviser les méthodes et de sauvegarder un appel if β==0 dans le cas C = A*B .

Pour info, j'ai commencé à travailler dessus dans # 29634 pour ajouter l'interface à matmul.jl . J'espère le terminer au moment où le nom et la signature seront décidés :)

Un avantage de muladd! serait que nous pouvons avoir ternaire muladd!(A, B, C) (ou muladd!(C, A, B) ?) Avec la valeur par défaut α = β = true (comme mentionné dans la suggestion originale https: //github.com/JuliaLang/julia/issues/23919#issuecomment-402953987). La méthode muladd!(A, B, C) est similaire à muladd pour Number s donc je suppose que c'est un nom plus naturel, surtout si vous connaissez déjà muladd .

@andreasnoack Il semble que votre discussion précédente porte sur la signature de méthode et préfère les arguments de position aux arguments de mot-clé, pas au nom de la méthode. Avez-vous une objection pour le nom muladd! ? (L'existence de 5-ary mul! dans SparseArrays pourrait en être un, mais définir le wrapper rétrocompatible n'est pas difficile.)

Avoir à la fois mul! et muladd! semble redondant lorsque le premier est juste le dernier avec des valeurs par défaut pour α et β . De plus, la partie add a été canonisée par BLAS. Si nous pouvions trouver une application d'algèbre linéaire générique crédible pour muladd! , j'aimerais en entendre parler mais sinon je préférerais éviter la redondance.

De plus, je préférerais fortement que nous gardions la propriété zéro fort de false séparée de la discussion de mul! . IMO toute valeur nulle de β devrait être forte comme dans BLAS et comme dans les méthodes actuelles à cinq arguments mul! . C'est-à-dire que ce comportement devrait être une conséquence de mul! et non du type de β . L'alternative serait difficile à utiliser. Par exemple, mul!(Matrix{Float64}, Matrix{Float64}, Matrix{Float64}, 1.0, 0.0) ~ pourrait ~ ne pouvait pas utiliser BLAS.

Nous ne pouvons pas changer ce que fait BLAS, mais _requiring_ un comportement zéro fort pour les flottants signifie que chaque implémentation aura besoin d'une branche pour vérifier zéro.

Si nous pouvions trouver une application d'algèbre linéaire générique crédible pour muladd!

@andreasnoack Par ceci, je suppose que vous voulez dire "demande pour _ trois arguments_ muladd! " car sinon vous n'accepteriez pas d'inclure cinq arguments mul! ?

Mais je peux toujours trouver un exemple où muladd!(A, B, C) est utile. Par exemple, si vous voulez construire un réseau "petit monde", il est utile d'avoir une sommation "paresseuse" d'une matrice en bandes et d'une matrice éparse. Vous pouvez ensuite écrire quelque chose comme:

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

Mais cela ne me dérange pas d'écrire manuellement true car je peux simplement l'envelopper pour mon utilisation. Avoir la fonction à cinq arguments en tant qu'API documentée stable est l'objectif le plus important ici.

Revenons au point:

Avoir à la fois mul! et muladd! semble redondant lorsque le premier n'est que le dernier avec des valeurs par défaut pour α et β .

Mais nous avons quelques * implémentés en termes de mul! avec la "valeur par défaut" du tableau de sortie initialisée de manière appropriée. Je pense qu'il peut y avoir de tels exemples de "raccourcis" dans Base et les bibliothèques standard? Je pense qu'il est logique d'avoir à la fois mul! et muladd! même si mul! n'est qu'un raccourci de muladd! .

Je préférerais fortement que nous gardions la propriété zéro fort de false séparée de la discussion de mul!

Je conviens qu'il serait constructif de se concentrer d'abord sur le nom de la version à cinq arguments de la multiplication-add ( mul! vs muladd! ).

Je n'ai pas fait du bon travail quand j'ai demandé un cas d'utilisation générique où vous aviez besoin de muladd pour travailler de manière générique sur des matrices et des nombres. La version numérique serait muladd sans le point d'exclamation, donc ce que j'ai demandé n'avait pas vraiment de sens.

Votre exemple pourrait simplement être écrit comme

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

donc je ne vois toujours pas le besoin de muladd! . Est-ce juste que vous pensez que ce cas est si courant que l'écriture de 1, 1 est trop verbeuse?

Mais nous avons quelques * implémentés en termes de mul! avec la "valeur par défaut" du tableau de sortie initialisée de manière appropriée. Je pense qu'il peut y avoir de tels exemples de "raccourcis" dans Base et les bibliothèques standard?

Je ne comprends pas celui-là. Pourriez-vous essayer d'élaborer? Quels sont les raccourcis dont vous parlez ici?

donc je ne vois toujours pas le besoin de muladd! . Est-ce juste que vous pensez que ce cas est si courant que l'écriture de 1, 1 est trop verbeuse?

Je pense que muladd! est également plus descriptif quant à ce qu'il fait réellement (même si cela devrait peut-être être addmul! ).

Je n'ai pas de problème avec le nom muladd! . Principalement, je ne pense pas que nous devrions avoir à fonctionner pour cela et d'autre part je ne pense pas que déprécier mul! en faveur de muladd! / addmul! vaut la peine.

Est-ce juste que vous pensez que ce cas est si courant que l'écriture de 1, 1 est trop verbeuse?

Non. Je suis tout à fait d'accord pour appeler une fonction à cinq arguments tant que c'est une API publique. J'ai juste essayé de donner un exemple où je n'ai besoin que de trois versions d'argument (car je pensais que c'était votre demande).

Quels sont les raccourcis dont vous parlez ici?

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

Je pense que * défini ici peut être considéré comme un raccourci de mul! . C'est "juste" mul! avec une valeur par défaut. Alors, pourquoi ne pas laisser mul! être un muladd! / addmul! avec des valeurs par défaut?

Il existe également rmul! et lmul! définis comme des "raccourcis" similaires:

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

obsolète mul!

Je pensais que la discussion portait sur l'ajout d'une nouvelle interface ou non. Si nous devons désapprouver mul! pour ajouter une nouvelle API, je ne pense pas que cela en vaille la peine.

Les principaux arguments auxquels je peux penser sont:

  • conceptuellement, la forme à 5 arguments fait plus que simplement "multiplier", et le transmet plus clairement.
  • vous pouvez alors écrire addmul!(C, A, B) au lieu de mul!(C,A,B,1,1) ou mul!(C,A,B,true,true) .

Je pense que * défini ici peut être considéré comme un raccourci de mul! . C'est "juste" mul! avec une valeur par défaut. Alors, pourquoi ne pas laisser mul! être un muladd! / addmul! avec des valeurs par défaut?

Parce que * est le moyen par défaut de multiplier les matrices et comment la plupart des utilisateurs le feraient. En comparaison, muladd! ne serait nulle part proche de * d'utilisation. De plus, c'est même un opérateur existant alors que muladd! / addmul! serait une nouvelle fonction.

Ne pensez pas que rmul! et lmul! correspondent à ce modèle car ils ne sont généralement pas des versions à valeur par défaut des méthodes mul! .

Simon résume bien les avantages dans le post ci-dessus. La question est de savoir si les avantages sont suffisamment importants pour justifier soit une fonction supplémentaire d'un changement de nom (ce qui signifie une dépréciation de mul! ). C'est là que nous ne sommes pas d'accord. Je ne pense pas que ça en vaille la peine.

Lorsque vous dites que renommer n'en vaut pas la peine, avez-vous tenu compte du fait que l'API n'est pas complètement publique? Je veux dire par là que ce n'est pas dans la documentation de Julia.

Je sais que LazyArrays.jl (et d'autres paquets?) L'utilise déjà si aveuglément suivre le semver ne serait pas bon. Mais encore, ce n'est pas aussi public que d'autres fonctions.

mul! est exporté à partir de LinearAlgebra et largement utilisé, nous devrons donc définitivement le désapprouver à ce stade. C'est dommage que nous n'ayons pas eu cette discussion quand A_mul_B! est devenu mul! ou au moins avant 0.7 car cela aurait été un bien meilleur moment pour renommer la fonction.

Pourquoi ne pas utiliser mul! pour l'instant et mettre à jour le nom de LinearAlgebra v2.0 quand nous pouvons mettre à jour les stdlibs séparément?

LazyArrays.jl n'utilise pas mul! car il n'est pas flexible pour de nombreux types de matrices (et déclenche un bogue de lenteur du compilateur lorsque vous remplacez par StridedArray s). Il donne une construction alternative de la forme

y .= Mul(A, x)

ce que je trouve est plus descriptif. L'analogue à 5 arguments est

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

Je plaiderais en faveur de la dépréciation de mul! et du passage à l'approche LazyArrays.jl dans LinearAlgebra.jl, mais ce sera un cas difficile à faire.

LowRankApprox.jl utilise mul! , mais je pourrais le changer pour utiliser l'approche LazyArrays.jl et ainsi éviter le bogue du compilateur.

D'ACCORD. Je pensais qu'il n'y avait que deux propositions. Mais apparemment, il y a environ trois propositions?:

  1. trois et cinq arguments mul!
  2. trois et cinq arguments muladd!
  3. trois arguments mul! et cinq arguments muladd!

( muladd! peut être appelé addmul! )

Je pensais que nous comparions 1 et 3. Je crois comprendre maintenant que

Je dirais que 2 n'est pas du tout une option car mul! à trois arguments est une API publique et largement utilisée. Ce que je voulais dire par "l'API n'est pas complètement publique", c'est que les cinq arguments mul! ne sont pas documentés .

Oui, mon plan était de conserver mul! (sous forme de 3 arg et éventuellement de 4 arg). Je pense que cela en vaut la peine puisque 3 arg mul! et addmul! auraient un comportement différent, c'est-à-dire étant donné addmul!(C, A, B, α, β) , nous aurions:

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)

Cependant, vous ne voudrez peut-être pas les implémenter de cette manière dans la pratique, par exemple, il peut être plus simple d'utiliser uniquement les 4-arg mul! et addmul! séparément, et définir le 5-arg addmul! comme:

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

Bosse!

Cependant, vous ne voudrez peut-être pas les implémenter de cette façon dans la pratique, par exemple, il peut être plus simple d'utiliser uniquement le mul 4-arg! et addmul! séparément, et définissez l'addmul! 5-arg! comme:
addmul!(C, A, B, α, β) = addmul!(C .= β .* C, A, B, α)

Pourquoi ne pas le faire de manière optimale tout de suite? Le but de ne pas le faire comme ça est que vous n'avez besoin de visiter qu'une seule fois les éléments de C , ce qui est nettement plus efficace pour les grandes matrices. De plus, j'ai du mal à croire que le code serait plus long en définissant uniquement le 5-arg addmul! par rapport à 4-arg mul! et addmul! séparément.

Pour votre information, j'ai modifié l'implémentation de LinearAlgebra de _generic_matmatmul! pour prendre 5-arguments dans LazyArrays: https://github.com/JuliaArrays/LazyArrays.jl/blob/8a50250fc6cf3f2402758088/197769cf2blasm053/slasm/sul

Ici, il est appelé via:

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

mais le code réel (en tiled_blasmul! ) serait facile à traduire en LinearAlgebra.

Que peut-on faire pour essayer d'accélérer ce processus? Les choses sur lesquelles je travaille bénéficieraient vraiment d'une API de multiplication matricielle unifiée avec mul + add en place

La dernière version de Strided.jl prend désormais également en charge 5 arguments mul!(C,A,B,α,β) , distribués à BLAS lorsque cela est possible, et en utilisant sa propre implémentation (multithread) sinon.

@Jutho super package! y a-t-il une feuille de route pour la suite? le plan pourrait-il être de fusionner éventuellement avec LinearAlgebra?

Cela n'a jamais été mon intention, mais je ne m'y oppose pas si cela est demandé à un moment donné. Cependant, je pense que mon utilisation libérale des fonctions @generated (bien qu'il n'y en ait qu'une) dans la fonctionnalité générale mapreduce pourrait ne pas convenir à Base.

Ma feuille de route personnelle: il s'agit principalement d'un package de bas niveau à utiliser par les packages de niveau supérieur, c'est-à-dire la nouvelle version de TensorOperations, et un autre package sur lequel je travaille. Cependant, un support supplémentaire pour l'algèbre linéaire de base serait bon (par exemple, l'application de norm à un StridedView revient actuellement à une implémentation plutôt lente de norm dans Julia Base). Et si j'ai le temps et que j'apprends à travailler avec les GPU, essayez d'implémenter un mapreducekernel tout aussi général pour GPUArray s.

Je pense que le consensus jusqu'à présent est:

  1. Nous devrions garder mul!(C, A, B)
  2. Nous avons besoin d'une fonction à 5 arguments _some_ pour ajouter plusieurs fois en place C = αAB + βC

Je suggère de se concentrer d'abord sur le nom de la fonction à 5 arguments et de discuter de l'API supplémentaire plus tard (comme 3 et 4 arguments addmul! ). Mais c'est la "fonctionnalité" que nous obtenons de _pas_ en utilisant mul! donc il est difficile de ne pas mélanger.

@andreasnoack est votre préoccupation au sujet deprecation / renommage résolu par @simonbyrne de commentaire ci - dessus https://github.com/JuliaLang/julia/issues/23919#issuecomment -431046516? Je pense qu'il n'y a pas besoin de dépréciation.

Pour info, je viens de terminer l'implémentation # 29634. J'apprécie si quelqu'un familier avec LinearAlgebra peut l'examiner.

Je pense qu'il est plus simple et préférable de tout nommer mul! . Cela évite également la dépréciation. Si nous voulons vraiment un nom différent, muladd est mieux.

Quelque chose d'autre à peut-être prendre en compte lors de la discussion de l'API mul! :

Quand scale! est parti et a été absorbé dans la transition 0,6 -> 0,7, j'étais un peu triste parce que pour moi, la multiplication scalaire (une propriété des espaces vectoriels) était très différente de la multiplication des objets eux-mêmes (une propriété des algèbres ). Néanmoins, j'ai pleinement adopté l'approche mul! , et j'apprécie beaucoup la possibilité de rmul!(vector,scalar) et lmul!(scalar,vector) lorsque la multiplication scalaire n'est pas commutative. Mais maintenant, je suis chaque jour plus gêné par le nom non julien de deux autres opérations d'espace vectoriel en place: axpy! et sa généralisation axpby! . Ceux-ci pourraient-ils également être absorbés dans mul! / muladd! / addmul! . Bien que ce soit un peu étrange, si l'un des deux facteurs de A*B est déjà un scalaire, il n'est pas nécessaire d'ajouter un facteur scalaire supplémentaire α .
Mais peut-être alors, par analogie avec

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

il pourrait aussi y avoir un

add!(Y, X, α, β)

pour remplacer axpby! .

@andreasnoack est votre préoccupation au sujet deprecation / renommage résolu par @simonbyrne de commentaire ci - dessus # 23919 (commentaire)? Je pense qu'il n'y a pas besoin de dépréciation.

Voir le dernier paragraphe de https://github.com/JuliaLang/julia/issues/23919#issuecomment -430952179. Je pense toujours que l'introduction d'une nouvelle fonction n'en vaut pas la peine. Si nous le faisons quand même, je pense que nous devrions désapprouver l'actuel à 5 ​​arguments mul! .

@Jutho Je pense que renommer acp(b)y! en add! serait une bonne idée.

Voir le dernier paragraphe du # 23919 (commentaire) . Je pense toujours que l'introduction d'une nouvelle fonction n'en vaut pas la peine.

Oui, je l'ai lu et j'ai répondu que mul! cinq arguments n'était pas documenté et ne faisait pas partie de l'API publique. Donc, techniquement, il n'y a pas besoin de dépréciation. Voir le dernier paragraphe de https://github.com/JuliaLang/julia/issues/23919#issuecomment -430975159 (Bien sûr, ce serait bien d'avoir une dépréciation de toute façon donc je l'ai déjà implémentée dans # 29634.)

Ici, je suppose que la déclaration de l'API publique due à la documentation d'une signature (par exemple, mul!(C, A, B) ) ne s'applique pas aux autres signatures (par exemple, mul!(C, A, B, α, β) ). Si ce n'est pas le cas, je pense que Julia et sa stdlib exposent trop de composants internes. Par exemple, voici la signature documentée de Pkg.add

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

alors que la définition réelle est

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

Si l'existence de la documentation d'au moins une signature de Pkg.add implique que d'autres signatures sont des API publiques, Pkg.jl ne peut pas supprimer le comportement en raison des détails d'implémentation sans modifier la version majeure, par exemple: Pkg.add(...; mode = :develop) exécute Pkg.develop(...) ; tous les arguments de mot-clé de Context! sont pris en charge (ce qui peut en fait être voulu).

Mais ce n'est que mon impression de toute façon. Considérez-vous que mul!(C, A, B, α, β) a été aussi public que mul!(C, A, B) ?

Je pense que nous nous parlons. Ce que je dis, c'est que je ne pense pas (toujours) que l'introduction d'une autre fonction en vaille la peine. D'où ma référence à mon commentaire précédent. Ceci est distinct de la discussion sur la dépréciation de cinq arguments mul! .

Cependant, si nous décidons d'ajouter une autre fonction, je pense qu'il serait préférable de déprécier cinq arguments mul! au lieu de simplement le casser. Bien sûr, ce n'est pas aussi couramment utilisé que mul! à trois arguments, mais pourquoi ne pas le désapprouver au lieu de simplement le casser?

Ceci est distinct de la discussion sur la dépréciation de cinq arguments mul! .

Mon interprétation du dernier paragraphe de votre commentaire https://github.com/JuliaLang/julia/issues/23919#issuecomment -430952179 était que vous aviez reconnu les avantages @simonbyrne listés https://github.com/JuliaLang/julia/issues / 23919 # issuecomment -430809383 pour une nouvelle fonction à cinq arguments, mais a considéré qu'elles étaient moins utiles que de conserver l '_ API publique_ (comme vous l'avez mentionné "renommer" et "déprécier"). C'est pourquoi j'ai pensé qu'il était important de considérer si mul! cinq arguments était public ou non.

Mais vous avez également mentionné la justification d'avoir «une fonction supplémentaire», ce à quoi vous faites référence, je suppose. Êtes-vous en train de dire que les calculs _C = AB_ et _C = αAB + βC_ sont suffisamment similaires pour que le même nom puisse décrire les deux? En fait, je ne suis pas d'accord car il peut y avoir d'autres façons de généraliser à trois arguments mul! : par exemple, pourquoi pas mul!(y, A₁, A₂, ..., Aₙ, x) pour _y = A₁ A₂ ⋯ Aₙ x_ https://github.com/JuliaLang/julia / issues / 23919 # issuecomment -402953987?

pourquoi ne pas le désapprouver au lieu de simplement le casser?

Comme je l'ai dit dans les commentaires précédents, je suis d'accord que la dépréciation de cinq arguments mul! est la bonne chose à faire _si_ nous devions introduire une autre fonction. Ce code existe déjà dans mon PR # 29634.

Êtes-vous en train de dire que les calculs C = AB et C = αAB + βC sont suffisamment similaires pour que le même nom puisse décrire les deux?

Oui, puisque le premier est juste le dernier avec β=0 . Il est juste de soutenir que muladd! / addmul! est un nom plus précis pour C = αAB + βC mais pour y parvenir, il faudrait soit introduire une autre fonction de multiplication matricielle ( muladd! / addmul! ) ou renommer mul! et je ne pense pas que cela en vaille la peine maintenant. Si cela s'était produit au printemps, il aurait été plus facile d'envisager un changement.

Je ne suis pas d'accord car il peut y avoir d'autres façons de généraliser mul à trois arguments!:

Julia a défini les méthodes de multiplication matricielle en place sans les arguments α et β mais la tradition de multiplication par matrice est vraiment basée sur BLAS-3 et là, la fonction de multiplication matricielle générale est C = αAB + βC .

renommer mul!

Voulez-vous dire le renommer dans stdlib ou dans le module / code utilisateur en aval? Si vous parlez du premier, c'est déjà fait (pour LinearAlgebra et SparseArrays) dans # 29634 donc je ne pense pas que vous ayez à vous en soucier. Si vous parlez de ce dernier, je pense que cela se résume encore une fois à une discussion publique ou non.

la tradition de multiplication par matrice est vraiment basée sur BLAS-3

Mais Julia s'est déjà écartée de la convention de dénomination de BLAS. Alors ne serait-il pas bien d'avoir un nom plus descriptif?

Voulez-vous dire le renommer dans stdlib ou dans le module / code utilisateur en aval?

29634 ne renomme pas la fonction mul! . Il ajoute la nouvelle fonction addmul! .

Mais Julia s'est déjà écartée de la convention de dénomination de BLAS.

Je ne parle pas de la dénomination. Du moins pas exactement puisque Fortran 77 a des limitations que nous n'avons pas à la fois en termes de noms de fonctions et de répartition. Je parle de ce qui est calculé. La fonction de multiplication matricielle générale dans BLAS-3 calcule C = αAB + βC et dans Julia, c'est mul! (fka A_mul_B! ).

Alors ne serait-il pas bien d'avoir un nom plus descriptif?

Ce serait le cas, et je l'ai dit à plusieurs reprises. Le problème est que ce n'est pas tellement plus agréable que nous devrions avoir deux fonctions de multiplication matricielle qui font essentiellement la même chose.

29634 ne renomme pas la fonction mul! . Il ajoute la nouvelle fonction addmul! .

Ce que je voulais dire, c'est que mul! cinq arguments a été renommé en addmul! .

Le problème est que ce n'est pas tellement plus agréable que nous devrions avoir deux fonctions de multiplication matricielle qui font essentiellement la même chose.

Je pense que si elles sont fondamentalement identiques ou non, c'est quelque peu subjectif. Je pense que _C = αAB + βC_ et _Y = A₁ A₂ ⋯ Aₙ X_ sont une généralisation mathématiquement valide de _C = AB_. À moins que _C = αAB + βC_ soit l'unique généralisation, je ne pense pas que l'argument soit assez fort. Cela dépend également si vous connaissez l'API BLAS et je ne suis pas sûr que ce soit la connaissance de base des utilisateurs typiques de Julia.

De plus, _C = AB_ et _C = αAB + βC_ sont très différents sur le plan informatique en ce que le contenu de C est utilisé ou non. Il s'agit d'un paramètre de sortie uniquement pour le premier et d'un paramètre d'entrée-sortie pour le second. Je pense que cette différence mérite un repère visuel. Si je vois mul!(some_func(...), ...) et mul! a la forme à cinq arguments, je dois compter le nombre d'arguments (ce qui est difficile quand ils sont les résultats d'un appel de fonction car vous devez faire correspondre les parenthèses) pour voir si some_func fait un calcul ou juste une allocation. Si nous avons addmul! alors je peux m'attendre immédiatement à ce que some_func in mul!(some_func(...), ...) ne fasse que l'allocation.

Je pense que si elles sont fondamentalement identiques ou non, c'est quelque peu subjectif. Je pense que C = αAB + βC et Y = A₁ A₂ ⋯ Aₙ X sont une généralisation mathématiquement valide de C = AB. À moins que C = αAB + βC ne soit l'unique généralisation, je ne pense pas que l'argument soit suffisamment fort.

Ce n'est peut-être pas la généralisation unique, mais c'est une généralisation qui peut être calculée à un coût à peu près identique, et qui forme une primitive utile pour construire d'autres algorithmes d'algèbre linéaire. À de nombreuses reprises, j'ai voulu avoir une version bêta non nulle lors de l'implémentation de divers algorithmes liés à l'algèbre linéaire, et j'ai toujours dû revenir à BLAS.gemm! . D'autres généralisations comme celle que vous évoquez ne peuvent de toute façon pas être calculées d'un seul coup sans temporaires intermédiaires, donc une version en place est beaucoup moins utile. De plus, ils ne sont pas aussi généralement utiles qu'une opération primitive.

Cela dépend également si vous connaissez l'API BLAS et je ne suis pas sûr que ce soit la connaissance de base des utilisateurs typiques de Julia.

Tant que les arguments par défaut α=1 et β=0 sont toujours en place, les trois arguments mul! feront ce que tout utilisateur Julia s'attendrait raisonnablement sans fond BLAS. Pour les options les plus avancées, il faut consulter le manuel, comme on aurait à faire avec n'importe quelle langue et n'importe quelle fonction. De plus, cet appel unique mul! ne remplace pas seulement gemm mais aussi gemv et trmv (qui curieusement n'a pas α et β dans l'API BLAS) et probablement beaucoup d'autres.

Je suis d'accord que BLAS-3 est la bonne généralisation en termes de calcul et il se compose très bien. J'évoque une autre généralisation possible simplement parce que je pense que ce n'est pas "assez unique" pour justifier l'utilisation du même nom. Voir aussi l'argument sortie seule vs entrée-sortie dans le dernier paragraphe de https://github.com/JuliaLang/julia/issues/23919#issuecomment -441267056. Je pense qu'un nom différent facilite la lecture / révision du code.

De plus, cet appel unique mul! ne remplace pas seulement gemm mais aussi gemv et trmv (qui curieusement n'a pas α et β dans l'API BLAS) et probablement beaucoup d'autres.

Oui, déjà implémenté dans # 29634 et il est prêt à être utilisé une fois que le nom est décidé (et révisé)!

J'ai du mal à suivre cette conversation (c'est un peu long et tentaculaire ... désolé!), La proposition principale est-elle quelque chose comme mul!(C, A, B; α=true, β=false) ?

Je ne pense pas que l'argument mot-clé pour α et β soit sur la table. Par exemple, @andreasnoack a ignoré les arguments de mot clé dans https://github.com/JuliaLang/julia/issues/23919#issuecomment -365762889. @simonbyrne a mentionné les arguments de mots clés dans https://github.com/JuliaLang/julia/issues/23919#issuecomment -426881998 mais sa dernière suggestion https://github.com/JuliaLang/julia/issues/23919#issuecomment -431046516 est positionnelle arguments.

Nous n'avons pas encore décidé du nom (c'est-à-dire mul! vs addmul! vs muladd! ) et je pense que c'est le sujet central (ou du moins c'est mon souhait).

Comment résolvez-vous généralement ce genre de controverse? Vote? Triage?

la proposition principale est-elle quelque chose comme mul! (C, A, B; α = vrai, β = faux)?

J'aime ça, mais sans les kwargs.

Je n'ai pas vu le mot-clé. J'hésite également à ajouter des mots-clés unicode. Je pense que les arguments de position avec ces valeurs par défaut sont bien. Quant au vote à venir, le mien est en clair mul! . Je pense que c'est une généralisation de mul! qui est suffisamment spécifique et utile pour ne pas avoir besoin d'un nouveau nom.

Juste pour collecter des données (du moins pour le moment), faisons le vote:

Quel est votre nom de fonction préféré pour _C = αAB + βC_?

  • : +1: mul!
  • : -1: addmul!
  • : sourire: muladd!
  • : tada: autre chose

Pour moi, addmul! semble décrire (A+B)C au lieu de AB + C .

Au début, j'ai voté pour mul! puis j'ai regardé l'opération et j'ai pensé "il fait une multiplication puis un ajout, évidemment nous devrions l'appeler muladd! . Maintenant, je ne peux pas penser à l'appeler Le fait qu'il soit en place est clairement indiqué par ! et la partie mise à l'échelle semble appropriée pour les arguments de mot-clé.

il fait une multiplication puis une addition, évidemment nous devrions l'appeler muladd!

Seulement si vous utilisez la valeur par défaut β=true , mais alors pour toute autre valeur, c'est à nouveau quelque chose de plus général. Alors, quel est l'intérêt de ne pas l'appeler mul! , où toute autre valeur que la valeur par défaut β=false vous donne également quelque chose de plus général? Et comment classeriez-vous les arguments, par rapport à muladd(x,y,z) = x*y + z ? Sera un peu déroutant, non?

Je pense que muladd! a l'inconvénient de paraître descriptif alors que ce n'est pas le cas: un descriptif nommé serait quelque chose comme scalemuladd! pour mentionner la partie mise à l'échelle.

Je préfère donc mul! car c'est assez indéfinissable pour ne pas conduire à des attentes.

Cela dit, j'appelle la version paresseuse dans LazyArrays.jl MulAdd .

Je préfère muladd! à mul! car il est agréable de garder distincte une fonction qui n'utilise jamais la valeur de C ( mul! ) d'une fonction qui l'utilise ( muladd! ).

  • Techniquement, c'est une multiplication matricielle: [AC] * [Bα; Iβ] ou, voir le commentaire ci-dessous, [αA βC] * [B; JE]
  • ~ Nous avons déjà un 5-arg mul! pour les matrices creuses, la même chose pour un linalg dense serait cohérent ~ (pas un nouvel argument)

Je serais donc en faveur de l'appeler mul! .

  • Techniquement, c'est une multiplication matricielle: [AC] * [Bα; Iβ]

... si eltype a une multiplication commutative

... si eltype est commutatif.

IIRC à partir d'une discussion avec @andreasnoack Julia définit simplement gemm / gemv comme y <- A * x * α + y * β parce que cela a plus de sens.

@haampie C'est bon savoir! Comme je l'ai implémenté dans l'autre sens dans # 29634.

C'est d'une aide limitée, mais

       C = α*A*B + β*C

est le meilleur moyen que je puisse trouver pour exprimer l'opération et donc peut-être qu'une macro <strong i="8">@call</strong> C = α*A*B + β*C ou <strong i="10">@call_specialized</strong> ... ou quelque chose du genre serait une interface naturelle - également pour des situations similaires. Ensuite, la fonction sous-jacente peut être appelée n'importe quoi.

@mschauer LazyArrays.jl par @dlfivefifty a une excellente syntaxe pour appeler 5-arguments mul! comme votre syntaxe (et plus encore!).

Je pense que nous devons d'abord établir une API basée sur les fonctions afin que les auteurs de packages puissent commencer à la surcharger pour leurs matrices spécialisées. Ensuite, la communauté Julia peut commencer à expérimenter des sucres pour l'appeler.

Seulement si vous utilisez la valeur par défaut β=true , mais pour toute autre valeur, c'est à nouveau quelque chose de plus général. Alors, à quoi ça sert de ne pas l'appeler mul! , où toute autre valeur que la valeur par défaut β=false vous donne également quelque chose de plus général? Et comment classeriez-vous les arguments par rapport à muladd(x,y,z) = x*y + z ? Sera un peu déroutant, non?

Bien sûr, il y a une certaine mise à l'échelle, mais les «os» de l'opération sont clairement multipliés et ajoutés. Je serais également très bien avec muladd!(A, B, C, α=true, β=false) pour correspondre à la signature de muladd . Devrait être documenté, bien sûr, mais cela va sans dire. Cela me fait en quelque sorte souhaiter que muladd prenne la partie additive en premier, mais le navire a navigué sur celle-là.

Et comment classeriez-vous les arguments, par rapport à muladd(x,y,z) = x*y + z ? Sera un peu déroutant, non?

C'est la raison pour laquelle je préfère addmul! à muladd! . Nous pouvons nous assurer que l'ordre des arguments n'a rien à voir avec le scalaire muladd . (Bien que je préfère muladd! à mul! )

FWIW voici un résumé des arguments jusqu'à présent. (J'ai essayé d'être neutre mais je suis pro muladd! / addmul! alors gardez cela à l'esprit ...)

Le principal désaccord est que _C = AB_ et _C = αAB + βC_ sont suffisamment différents pour donner un nouveau nom à ce dernier.

Ils sont assez similaires parce que ...

  1. C'est BLAS-3 et joliment composable. Ainsi, _C = αAB + βC_ est une généralisation évidente de _C = AB_ (https://github.com/JuliaLang/julia/issues/23919#issuecomment-441246606, https://github.com/JuliaLang/julia/issues/ 23919 # issueecomment-441312375, etc.)

  2. _ " muladd! a l'inconvénient de paraître descriptif quand ce n'est pas le cas: un descriptif nommé serait quelque chose comme scalemuladd! pour mentionner la partie mise à l'échelle." _ --- https://github.com/ JuliaLang / julia / issues / 23919 # issuecomment -441819470

  3. _ "Techniquement, c'est une multiplication matricielle: [AC] * [Bα; Iβ]" _ --- https://github.com/JuliaLang/julia/issues/23919#issuecomment -441825009

Ils sont assez différents parce que ...

  1. _C = αAB + βC_ est plus que multiplié (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, etc.).

  2. Il pourrait y avoir d'autres généralisations de mul! comme Y = A₁ A₂ ⋯ Aₙ X (https://github.com/JuliaLang/julia/issues/23919#issuecomment-402953987 etc.)

  3. Paramètre d'entrée uniquement vs paramètre d'entrée-sortie: il est déroutant d'avoir une fonction qui utilise les données de C fonction du nombre d'arguments (https://github.com/JuliaLang/julia/issues/23919#issuecomment -441267056, https://github.com/JuliaLang/julia/issues/23919#issuecomment-441824982)

Une autre raison pour laquelle mul! est meilleur parce que ...:

  1. La matrice clairsemée l'a déjà. C'est donc bon pour la rétrocompatibilité. Argument de compteur: cinq arguments mul! n'est pas documenté , nous n'avons donc pas besoin de le considérer comme une API publique.

et pourquoi muladd! / addmul! est mieux parce que ...:

  1. Nous pouvons avoir différentes "fonctions pratiques" à trois ou quatre arguments pour mul! et muladd! / addmul! séparément (https://github.com/JuliaLang/julia/issues / 23919 # issuecomment-402953987, https://github.com/JuliaLang/julia/issues/23919#issuecomment-431046516, etc.). Contre-argument: écrire mul!(y, A, x, 1, 1) n'est pas très verbeux comparé à mul!(y, A, x) (https://github.com/JuliaLang/julia/issues/23919#issuecomment-430674934, etc.)

Merci pour le résumé objectif @tkf

Je serais également très bien avec muladd! (A, B, C, α = vrai, β = faux) pour correspondre à la signature de muladd.

J'espère que pour une fonction appelée mulladd! la valeur par défaut serait β=true . Pourtant, je pense que cet ordre d'arguments, qui est dicté à partir de muladd , sera très déroutant par rapport à mul!(C,A,B)

Peut-être que je me trompe, mais je pense que la plupart des gens / applications / code de haut niveau (qui ne sont pas déjà satisfaits uniquement de l'opérateur de multiplication * ) ont besoin de mul! . La possibilité de mélanger également βC , avec β=1 ( true ) ou autre, sera utilisée dans le code de niveau inférieur, par des personnes qui savent que l'API BLAS pour la multiplication matricielle permet ce. Je suppose que ces personnes rechercheraient cette fonctionnalité sous mul! , qui est l'interface de Julia établie à gemm , gemv , ... Ajout d'un nouveau nom (ce qui déroutant l'ordre des arguments opposé) ne semble pas en valoir la peine; Je ne vois pas le gain?

Je suppose que ces personnes rechercheraient cette fonctionnalité sous mul! , qui est l'interface de Julia établie à gemm , gemv , ... Ajout d'un nouveau nom (ce qui déroutant l'ordre des arguments opposé) ne semble pas en valoir la peine; Je ne vois pas le gain?

Je pense que la découvrabilité n'est pas un gros problème puisque nous pouvons simplement mentionner muladd! dans mul! docstring. Ceux qui sont suffisamment qualifiés pour connaître BLAS sauraient où chercher une API, non?

En ce qui concerne les arguments positionnels par rapport aux mots clés: ce n'est pas encore discuté ici, mais je pense que C = αAB + βC avec α étant une matrice diagonale peut être implémenté aussi efficacement et facilement que le scalaire α . Une telle extension nécessite que nous puissions distribuer sur le type de α ce qui est impossible avec l'argument mot-clé.

De plus, pour un eltype non commutatif, vous pouvez calculer efficacement C = ABα + Cβ en appelant muladd!(α', B', A', β', C') (ordre hypothétique des arguments). Cela peut nécessiter que vous soyez en mesure d'envoyer sur des wrappers paresseux Adjoint(α) et Adjoint(β) . (Je n'utilise pas personnellement de nombres non commutatifs dans Julia, donc c'est probablement très hypothétique.)

Je suis d'accord avec le point de @Jutho selon lequel cette fonction de multiplication-ajout est une API de bas niveau pour les programmeurs qualifiés comme les implémenteurs de bibliothèques. Je pense que l'extensibilité a une priorité élevée pour cette API et que l'argument positionnel est la voie à suivre.

Un autre argument pour éviter l'argument de mot-clé est ce que @andreasnoack a dit avant https://github.com/JuliaLang/julia/issues/23919#issuecomment -365762889:

Les noms α et β sont pas non plus très intuitifs sauf si vous connaissez BLAS

@tkf , bien sûr, mon argument est plutôt: le nombre d'utilisations réelles de β != 0 sera plus petit que celui de β == 0 , et ceux qui en ont besoin ne seront pas surpris de trouver cela un peu plus général comportement sous mul! . Par conséquent, je ne vois pas l'intérêt de séparer cela sous un nouveau nom, d'autant plus que l'ordre des arguments est foiré (avec au moins muladd! ). S'il doit s'agir d'une nouvelle méthode, je sympathise également avec votre argument pour addmul! .

ceux qui en ont besoin ne seront pas surpris de trouver ce comportement un peu plus général sous mul! .

Je suis d'accord avec ce point.

Par conséquent, je ne vois pas l'intérêt de séparer cela sous un nouveau nom,

À moins que vous ne voyiez des dommages, je pense que c'est un gain mondial, car d'autres personnes en voient des avantages.

d'autant plus que l'ordre des arguments est foiré (avec au moins muladd! )

Je suppose que vous comptez cela comme un mal et je comprends le point. C'est juste que je pense que d'autres avantages pour muladd! / addmul! sont plus importants.

Je suppose que vous comptez cela comme un mal et je comprends le point. C'est juste que je pense que d'autres avantages pour muladd! / Addmul! sont plus importants.

C'est en effet le mal, ainsi que le fait que mul! a toujours été le point d'entrée unique dans plusieurs opérations BLAS liées à la multiplication, même si cela est limité en ne donnant pas un accès complet à α et β. Et maintenant avec muladd! , il y aurait deux points d'entrée différents, en fonction d'une légère différence dans l'opération demandée qui peut facilement être capturée par un argument (et en effet, qui est capturé par un argument dans l'API BLAS) . Je pense que c'était une erreur de Julia en premier lieu de ne pas offrir un accès complet à l'API BLAS (donc merci d'avoir corrigé @tkf). Malgré l'horrible convention de dénomination fortran, je suppose que ces gars savaient pourquoi ils faisaient les choses de cette façon. Mais de même, je pense que cette famille d'opérations (c'est-à-dire la famille d'opérations à 2 paramètres paramétrées par α et β) appartient ensemble sous un même point d'entrée, comme dans BLAS.

L'argument de compteur le plus valide à mon avis est la différence entre l'accès ou non aux données d'origine de C . Mais étant donné que Julia a adopté la multiplication par false comme un moyen de garantir un résultat nul, même lorsque l'autre facteur est NaN , je pense que cela est également pris en compte. Mais peut-être que ce fait doit être mieux communiqué / documenté (cela fait un moment que je n'ai pas lu la documentation), et je ne l'ai appris que récemment. (C'est pourquoi dans KrylovKit.jl, j'ai besoin de l'existence d'une méthode fill! , pour initialiser un type d'utilisateur de type vectoriel arbitraire avec des zéros. Mais maintenant je sais que je peux simplement utiliser rmul!(x,false) place, donc je n'ai pas besoin d'imposer que fill! soit implémenté).

C'est juste que je pense que d'autres avantages pour muladd! / Addmul! sont plus importants.

Alors permettez-moi d'inverser la question, quels sont ces autres avantages d'avoir une nouvelle méthode? J'ai relu votre résumé, mais je ne vois que l'intérêt d'accéder à C , que je viens de commenter.

J'ai mentionné ce matin à ma femme qu'il y avait eu une conversation de deux mois dans la communauté de Julia concernant la désignation d'une opération. Elle a suggéré que cela s'appelle "fred!" - pas d'acronyme, pas de signification profonde, juste un bon nom. Juste mettre ça là-bas en son nom.

C'est bien qu'elle ait inclus un point d'exclamation! 😄

Tout d'abord, au cas où, permettez-moi de préciser que ma préoccupation vient presque uniquement de la lisibilité du code, pas de l'écriture ou de la découvrabilité. Vous écrivez du code une fois mais lisez plusieurs fois.

quel est cet autre avantage d'avoir une nouvelle méthode?

Comme vous l'avez commenté, je pense que l'argument des paramètres de sortie et d'entrée-sortie est le plus important. Mais ce n'est qu'une des raisons pour lesquelles je pense que _C = αAB + βC_ est différent de _C = AB_. Je pense aussi que le simple fait qu'elles soient différentes en ce sens que la première expression est le strict "sur-ensemble" de la seconde nécessite une indication visuelle claire dans le code. Un nom différent aide un programmeur intermédiaire (ou un petit programmeur avancé peu focalisé) à parcourir le code et à remarquer qu'il utilise quelque chose de plus étrange que mul! .

Je viens de vérifier le sondage (vous devez cliquer à nouveau sur "Charger plus" ci-dessus) et il semble que certains votes soient passés de mul! à muladd! ? La dernière fois que je l'ai vu, mul! gagnait. Enregistrons-le ici avant qu'ils ne bougent: rire:

  • mul! : 6
  • addmul! : 2
  • muladd! : 8
  • autre chose: 1

Un peu plus sérieusement, je pense toujours que ces données ne montrent pas que mul! ou muladd! est plus clair que l'autre. (Bien que cela montre que addmul! est une minorité: sob :)

On a l'impression que nous sommes coincés. Comment allons-nous avancer?

Appelez-le simplement gemm! place?

Appelez ça gemm! au lieu?

J'espère que c'est une blague ... sauf si vous proposez gemm!(α, A::Matrix, x::Vector, β, y::Vector) = gemv!(α, A, x, β, y) pour matrice * vecteur.

Pourrions-nous laisser l'interface mul! qui existe déjà (avec des matrices éparses) pour le moment afin que nous puissions fusionner le PR et avoir les améliorations, et nous demander si nous voulons ajouter muladd! dans un autre PR ?

C'est peut-être déjà clair pour tout le monde ici, mais je voulais juste souligner que le vote n'est pas

  • mul! vs muladd!

mais

  • mul! vs ( mul! et muladd! )

c'est-à-dire avoir deux fonctions de multiplication mutantes au lieu d'une seule.

J'ai décidé de ne plus poster car à chaque fois que j'ai posté en faveur de mul! , les votes semblaient passer de mul! à ( mul! et muladd! ).

Cependant, j'ai une question? Si nous adoptons le vote majoritaire actuel, et que nous avons simultanément mul!(C,A,B) et muladd!(A,B,C,α=true,β=true) , et je veux préparer un PR qui remplace axpy! et axpby! par un nom plus julien add! , devrait-il être add!(y, x, α=true, β=true) ou add!(x, y, α=true, β=true) (où, pour plus de clarté, y est muté). Ou autre chose?

Au cas où cela ne serait pas évident, muladd!(A,B,C) violerait la convention selon laquelle les arguments mutés passent en premier .

Pouvons-nous quitter l'interface mul! qui existe déjà

@jebej Je pense que cet argument de "compatibilité descendante" est largement discuté. Pourtant, cela ne convainc personne (en regardant le sondage, ce n'est pas que moi).

Vous vous demandez si nous voulons ajouter muladd! dans un autre PR?

Il est mauvais de casser l'API publique. Donc, si nous disons mul! alors c'est mul! pour toujours (bien qu'en théorie LinearAlgebra puisse bousculer sa version principale pour casser l'API).

Je veux préparer un PR qui remplace axpy! et axpby! par un nom plus julien add! , devrait-il être add!(y, x, α=true, β=true) ou add!(x, y, α=true, β=true)

@Jutho Merci, ce serait génial! Je pense que le choix de l'ordre des arguments serait simple une fois que nous avons décidé de la signature d'appel de l'API multiply-add.

muladd!(A,B,C) violerait la convention selon laquelle les arguments mutés sont placés en premier .

@simonbyrne Mais (comme vous l'avez déjà mentionné dans https://github.com/JuliaLang/julia/issues/23919#issuecomment-426881998), lmul! et ldiv! mutent le non-premier argument. Je pense donc que nous n'avons pas besoin d'exclure muladd!(A,B,C,α,β) du choix, mais plutôt de le compter comme un point négatif pour cette signature.

(Mais je dirais d'aller avec muladd!(α, A, B, β, C) si nous allons avoir l'API "ordre textuel".)

Au fait, une chose que je ne comprends pas par le résultat du vote est l'asymétrie de muladd! et addmul! . Si vous écrivez C = βC + αAB , je pense que addmul! est plus naturel.

@tkf Il s'agit de l'opération que vous effectuez en premier. Pour moi addmul! suggère de faire d'abord une addition, puis de multiplier, comme dans (A+B)C . Bien sûr, c'est subjectif. Mais les bons noms doivent faire appel à l'intuition.

Ah, je comprends ce point.

Comme cela est toujours bloqué, ma proposition aurait le modèle d'utilisation composé de définitions de fonction avec (aller avec @callexpr pour la seconde)

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

et peut-être un formulaire plus convivial (aller avec @callname pour le second)

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

et appelle

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

et personne n'a à s'inquiéter (ou à savoir) comment callexpr transforme les opérations algébriques en un nom de fonction unique (qui ne dépend pas des symboles d'argument, uniquement des opérations et de l'ordre des opérations.)
J'ai réfléchi un peu à la mise en œuvre et cela devrait être bien faisable.

@mschauer Je pense que c'est une direction intéressante. Pouvez-vous ouvrir un nouveau numéro? L'API que vous proposez peut résoudre de nombreux autres problèmes. Je pense qu'il doit passer par un processus de conception minutieux plutôt que de résoudre une seule instance du problème qu'il peut résoudre.

J'ai donc entendu la rumeur selon laquelle le gel des fonctionnalités de 1.1 est la semaine prochaine. Bien que la prochaine version mineure ne soit "que" dans quatre mois, ce serait vraiment bien si nous pouvions l'avoir en 1.1 ...

Quoi qu'il en soit, nous devons également décider de la signature d'appel (ordre des arguments et mot-clé ou non) avant de fusionner le PR.

Alors refaisons le vote (car j'ai trouvé que c'est un bon stimulant).

_Si_ nous utilisons muladd! pour _C = ABα + Cβ_, quelle est votre signature d'appel préférée?

  • : +1: muladd!(C, A, B, α, β)
  • : -1: muladd!(A, B, C, α, β)
  • : smile: muladd!(C, A, B; α, β) (comme: +1 :, mais avec le mot clé argumentbs)
  • : tada: muladd!(A, B, C; α, β) (comme: -1 :, mais avec des arguments de mot-clé)
  • : confus: muladd!(A, B, α, C, β)
  • : coeur: autre chose

Si vous avez d'autres noms d'arguments de mots-clés en tête, votez pour ceux qui utilisent α et β , puis commentez quels noms sont les meilleurs.

Comme nous n'avons pas encore décidé du nom, nous devons également le faire pour mul! :

_Si_ nous utilisons mul! pour _C = ABα + Cβ_, quelle est votre signature d'appel préférée?

  • : +1: mul!(C, A, B, α, β)
  • : -1: mul!(A, B, C, α, β)
  • : smile: mul!(C, A, B; α, β) (comme: +1 :, mais avec le mot clé argumentbs)
  • : tada: mul!(A, B, C; α, β) (c'est impossible)
  • : confus: mul!(A, B, α, C, β)
  • : coeur: autre chose

REMARQUE: nous ne modifions pas l'API existante mul!(C, A, B)

REMARQUE: nous ne modifions pas l'API existante mul!(C, A, B)

Je n'avais pas prêté suffisamment d'attention à ce fait - nous avons déjà mul! et

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

Calcule le produit matrice-matrice ou matrice-vecteur A*B et stocke le résultat dans Y , en écrasant la valeur existante de Y . Notez que Y ne doit pas être aliasé avec A ou B .

Compte tenu de cela, il semble très naturel de simplement développer cela comme ceci:

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

Calcule le produit matrice-matrice ou matrice-vecteur A*B et stocke le résultat dans Y , en écrasant la valeur existante de Y . Notez que Y ne doit pas être aliasé avec A ou B . Si une valeur scalaire, α , est fournie, alors le α*A*B est calculé au lieu de A*B . Si une valeur scalaire, β est fournie, alors α*A*B + β*Y est calculé à la place. La même restriction d'alias s'applique à ces variantes.

Cependant, je pense qu'il y a un problème majeur avec ceci: il semble au moins aussi naturel pour mul!(Y, A, B, C, D) de calculer A*B*C*D en place en Y - et cette notion générique se heurte très mal à mul!(Y, A, B, α, β) calcul α*A*B + β*C . De plus, il me semble que calculer A*B*C*D en Y est une chose qu'il serait utile et possible de faire efficacement, en évitant les allocations intermédiaires, donc je ne voudrais vraiment pas bloquer ce sens .

Avec cette autre généralisation naturelle de mul! à l'esprit, voici une autre pensée:

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

Cela s'inscrit dans le modèle général de mul!(out, args...) où vous calculez et écrivez dans out en multipliant args ensemble. Il repose sur la répartition pour traiter α étant scalaire au lieu d'en faire un cas spécial - c'est juste une autre chose que vous multipliez. Lorsque α est un scalaire et que A , B et Y sont des matrices, nous pouvons envoyer à BLAS pour le faire très efficacement. Sinon, nous pouvons avoir une implémentation générique.

De plus, si vous êtes dans un champ non commutatif (par exemple quaternions), vous pouvez contrôler de quel côté la mise à l'échelle de α se produit sur: mul!(Y, A, B, α) échelles de α on la droite au lieu de la gauche:

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

Oui, nous ne pouvons pas appeler BLAS pour les quaternions, mais c'est générique et nous pouvons probablement encore le faire de manière raisonnablement efficace (peut-être même en le transformant en certains appels BLAS).

En supposant cette approche pour Y .= α*A*B la question suivante devient: qu'en est-il de la mise à l'échelle et de l'incrémentation de Y ? J'ai commencé à penser à des mots-clés pour cela, mais ensuite le champ non commutatif m'est venu à l'esprit qui me semblait trop maladroit et limité. Alors, j'ai commencé à penser à cette API à la place - ce qui semble un peu étrange au début, mais soyez patient:

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

Un peu bizarre, mais ça marche. Et dans un champ non commutatif, vous pouvez demander de multiplier Y par β à droite comme ceci:

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

En général, dans un champ non commutatif, vous pouvez mettre à l'échelle à gauche et à droite comme ceci:

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

Maintenant, bien sûr, c'est un peu bizarre et il n'y a pas d'opération BLAS pour cela, mais c'est une généralisation de GEMM qui nous permet d'exprimer un _lot_ de choses et que nous pouvons envoyer aux opérations BLAS de manière triviale sans même faire de méchant si / sinon branches.

J'aime beaucoup la suggestion de @StefanKarpinski en tant

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

La fonction sous-jacente serait alors quelque chose comme le propose @StefanKarpinski .

Mais il faut aller plus loin ici. Je pense vraiment que si vous créez une API pour cela et une fonction générique, quelqu'un créera une bibliothèque Julia qui le fera efficacement, donc je suis d'accord que nous ne devons pas nous en tenir à BLAS ici. Des choses comme MatrixChainMultiply.jl construisent déjà des DSL pour les calculs de matrices multiples et DiffEq fait sa propre chose avec les expressions d'opérateurs affines. Si nous n'avons qu'une seule représentation pour une expression affine dans Base, nous pouvons définir tout notre travail comme étant sur la même chose.

@dlfivefifty a déjà examiné l'algèbre linéaire paresseuse, je pense que cela devrait vraiment être relancé ici. Construire des représentations paresseuses de la diffusion était crucial pour que les opérations élémentaires fonctionnent sur des tableaux abstraits et sur du matériel de calcul alternatif. Nous avons besoin de la même chose pour l'algèbre linéaire. Une représentation d'expressions algébriques linéaires nous permettrait de définir de nouveaux noyaux BLAS à la volée à partir d'un Julia BLAS ou de transférer les équations vers un GPU / TPU.

Essentiellement, tous les calculs de l'informatique scientifique se résument à des opérations algébriques élémentaires et linéaires, donc avoir une description de haut niveau des deux semble essentiel pour créer des outils pour métaprogrammer et explorer de nouvelles conceptions.

Je devrais réfléchir davantage à cette proposition mais, pour l'instant, je vais juste commenter que je ne pense pas que vous aimeriez calculer A*B*C sans temporaire. Il me semble que vous devrez payer avec beaucoup d'opérations arithmétiques pour éviter le temporaire.

Je ne pense pas que vous aimeriez calculer A*B*C sans temporaire.

Pour mul! vous avez cependant déjà un tableau de sortie. Je ne sais pas si cela aide ou non. Dans tous les cas, cela ressemble à un détail d'implémentation. L'API mul!(Y, A, B, C...) exprime ce que vous voulez calculer et laisse l'implémentation choisir la meilleure façon de le faire, ce qui était l'objectif général ici.

J'aime beaucoup la suggestion de @StefanKarpinski en tant

@ChrisRackauckas : Je pense que les choses dans mul! comme ça semble être le genre d'opération générique mais facile à comprendre que nous voulons à ce niveau.

Notez qu'il n'y a pas de véritable débat à avoir à propos de mul!(Y, α, A, B) - cela signifie à peu près Y .= α*A*B puisque qu'est-ce que cela signifierait d'autre? Donc, pour moi, la seule question ouverte ici est de savoir si l'utilisation d'un tuple avec une matrice et des scalaires gauche et / ou droit est un moyen raisonnable d'exprimer que nous voulons incrémenter et mettre à l'échelle le tableau de sortie. Les cas généraux seraient:

  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...)

Rien d'autre ne serait autorisé pour le premier argument. Cela pourrait être adopté comme une convention plus générale pour d'autres opérations où il est judicieux d'écraser ou de s'accumuler dans le tableau de sortie, éventuellement combiné avec une mise à l'échelle.

Il ne m'est pas venu à l'esprit de "fusionner" mul!(out, args...) et une interface de type GEMM! J'aime l'extensibilité de celui-ci (mais alors commencez à écrire la réponse ci-dessous et maintenant je ne suis pas sûr ...)

Mais mon souci est que s'il est facile à utiliser comme interface de surcharge. Nous devons nous fier au système de types pour fonctionner correctement pour les tuples imbriqués. Les tuples imbriqués fonctionnent-ils aussi bien que les tuples plats dans le système de types de Julia? Je me demande si quelque chose comme " Tuple{Tuple{A1,B1},C1,D1} est plus spécifique que Tuple{Tuple{A2,B2},C2,D2} iff Tuple{A1,B1,C1,D1} est plus spécifique que Tuple{A2,B2,C2,D2} ". Sinon, il serait difficile de l'utiliser comme API de surcharge.

Notez que nous devons distribuer sur les types scalaires pour utiliser le hack de réinterprétation pour les matrices complexes (cela provient du PR # 29634, ne faites donc pas attention au nom de la fonction):

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

Un autre souci est qu'il s'agit d'une interface quelque peu limitée pour un exécuteur de graphe de calcul. Je pense que le but principal de l'interface multiply-add est de fournir une API de surcharge pour permettre aux implémenteurs de bibliothèques de définir un petit noyau de calcul réutilisable qui peut être implémenté efficacement. Cela signifie que nous ne pouvons implémenter que _C = ABα_ et non, par exemple, _αAB_ (voir https://github.com/JuliaLang/julia/pull/29634#issuecomment-443103667). La prise en charge de _α₁ABα₂_ pour eltype non commutatif nécessite soit un tableau temporaire, soit l'augmentation du nombre d'opérations arithmétiques. Il n'est pas clair quel utilisateur veut et idéalement, cela devrait être configurable. À ce stade, nous avons besoin d'une représentation graphique de calcul séparée du mécanisme d'exécution. Je pense que cela vaut mieux être exploré dans des packages externes (par exemple, LazyArrays.jl, MappedArrays.jl). Cependant, si nous pouvons trouver une stratégie d'implémentation qui couvre la plupart des cas d'utilisation à un moment donné, utiliser mul! comme point d'entrée principal aurait du sens. Je pense que c'est en fait une autre raison de favoriser muladd! ; allouer un espace pour la future API appelante.

Je devrais réfléchir davantage à cette proposition mais, pour l'instant, je vais juste commenter que je ne pense pas que vous aimeriez calculer A B C sans temporaire. Il me semble que vous devrez payer avec beaucoup d'opérations arithmétiques pour éviter le temporaire.

Vous pouvez en effet prouver que toute contraction d'un nombre arbitraire de tenseurs, le moyen le plus efficace d'évaluer le tout est toujours d'utiliser des contractions par paires. Donc, multiplier plusieurs matrices est juste un cas particulier de cela, vous devez les multiplier par paires (le meilleur ordre est bien sûr un problème non trivial). C'est pourquoi je pense que mul!(Y,X1,X2,X3...) n'est pas une primitive si utile. Et en fin de compte, c'est ce que je pense que mul! est, c'est une opération primitive que les développeurs peuvent surcharger pour leurs types spécifiques. Toute opération plus compliquée peut alors être écrite en utilisant une construction de niveau supérieur, par exemple en utilisant des macros, et par exemple construire un graphe de calcul, qui à la fin est évalué en appelant des opérations primitives comme mul! . Bien sûr, cette primitive pourrait être suffisamment générale pour inclure des cas comme celui non commutatif mentionné par @StefanKarpinski .

Tant qu'aucune multiplication matricielle / contraction tenseur n'est impliquée, il est vrai que penser en termes d'opérations primitives n'est pas si utile et il peut être bénéfique de tout fusionner comme le fait la diffusion.

En général, je suis d'accord qu'il serait bon d'avoir un type de graphique de représentation / calcul paresseux par défaut dans Base, mais je ne pense pas que mul! soit le moyen de le construire.

@tkf :

Mais mon souci est que s'il est facile à utiliser comme interface de surcharge. Nous devons nous fier au système de types pour fonctionner correctement pour les tuples imbriqués. Les tuples imbriqués fonctionnent-ils aussi bien que les tuples plats dans le système de types de Julia?

Oui, nous sommes tous bons sur ce front. Je ne sais pas d'où vient l'imbrication, mais passer certaines choses dans un tuple est tout aussi efficace que de les transmettre tous en arguments immédiats - c'est implémenté exactement de la même manière.

Cela signifie que nous ne pouvons implémenter que _C = ABα_ et non, par exemple, _αAB_

Je suis confus ... vous pouvez écrire mul!(C, A, B, α) et mul!(C, α, A, B) . Vous pouvez même écrire mul!(C, α₁, A, α₂, B, α₃) . Cela semble être de loin l'API de multiplication de matrice générique la plus flexible qui ait été proposée jusqu'à présent.

Un autre souci est qu'il s'agit d'une interface quelque peu limitée pour un exécuteur de graphe de calcul. Je pense que le but principal de l'interface multiply-add est de fournir une API de surcharge pour permettre aux implémenteurs de bibliothèques de définir un petit noyau de calcul réutilisable qui peut être implémenté efficacement.

À ce stade, nous avons besoin d'une représentation graphique de calcul séparée du mécanisme d'exécution.

C'est peut-être le cas, mais ce n'est pas le bon endroit - cela peut et doit être développé dans des packages externes. Tout ce dont nous avons besoin pour résoudre ce problème spécifique est une API de multiplication matricielle qui généralise ce qui peut être envoyé aux opérations BLAS - ce qui est à peu près exactement ce que cela fait.

@Jutho

Donc, multiplier plusieurs matrices est juste un cas particulier de cela, vous devez les multiplier par paires (le meilleur ordre est bien sûr un problème non trivial). C'est pourquoi je pense que mul!(Y,X1,X2,X3...) n'est pas une primitive si utile.

L'opération mul! permettrait à l'implémentation de choisir l'ordre de multiplication, ce qui est une propriété utile. En effet, la capacité de le faire potentiellement était la raison pour laquelle nous avons fait l'analyse de l'opération * comme n-aire en premier lieu et le même raisonnement s'applique encore plus à mul! puisque si vous l'utilisez , vous vous souciez probablement suffisamment des performances.

En général, je ne peux pas dire si vous êtes en faveur ou contre ma proposition de mul! .

Je ne sais pas où l'imbrication entre en jeu, mais passer certaines choses dans un tuple est tout aussi efficace que de les passer toutes en arguments immédiats

Je ne m'inquiétais pas de l'efficacité mais plutôt des ambiguïtés de répartition et de méthode puisque même la LinearAlgebra actuelle est quelque peu fragile (ce qui peut être dû à un manque de compréhension du système de type; parfois cela me surprend). Je mentionnais le tuple imbriqué car je pensais que la résolution de la méthode se faisait en croisant le type de tuple de tous les arguments positionnels. Cela vous donne un tuple plat. Si vous utilisez un tuple dans le premier argument, vous avez un tuple imbriqué.

Cela signifie que nous ne pouvons implémenter que _C = ABα_ et non, par exemple, _αAB_

Je suis confus ... vous pouvez écrire mul!(C, A, B, α) et mul!(C, α, A, B) .

Je voulais dire "nous ne pouvons implémenter _C = ABα_ que de manière aussi efficace que _C = AB_ et d'autres candidats comme _αAB_ ne peuvent pas être mis en œuvre efficacement dans toutes les combinaisons de types de matrices." (Par efficacité, j'entends la grande complexité du temps O.) Je ne suis pas sûr que ce soit vraiment le cas, mais au moins pour la matrice clairsemée, deux autres options sont disponibles.

À ce stade, nous avons besoin d'une représentation graphique de calcul séparée du mécanisme d'exécution.

C'est peut-être le cas, mais ce n'est pas le bon endroit - cela peut et doit être développé dans des packages externes.

C'est exactement ce que je veux dire. Je suggère de voir cette API comme un élément de base minimal pour une telle utilisation (bien sûr, ce n'est pas tout l'objectif). La mise en œuvre et la conception de vararg mul! peuvent être effectuées après que les gens ont exploré l'espace de conception dans des packages externes.

La répartition sur les types, même pour le mul! actuel, est déjà «cassée»: il y a une croissance combinatoire des remplacements d'ambiguïté nécessaires pour travailler avec des types de tableaux composables comme SubArray et Adjoint .

La solution est d'utiliser des traits, et LazyArrays.jl a une version de preuve de concept de mul! avec des traits.

Mais il s'agit plus d'une discussion sur la mise en œuvre que sur l'API. Mais utiliser des tuples pour grouper des termes semble faux: n'est-ce pas pour cela que le système de types est là? Dans ce cas, vous arrivez à la solution LazyArrays.jl.

Le mul! l'opération permettrait à l'implémentation de choisir l'ordre de multiplication, qui est une propriété utile. En effet, la capacité de le faire potentiellement était la raison pour laquelle nous avons fait l'analyse * de l'opération comme n-aire en premier lieu et le même raisonnement s'applique encore plus à mul! car si vous l'utilisez, vous vous souciez probablement suffisamment des performances.

Ce * analysé comme n -ary est extrêmement utile. Je l'utilise dans TensorOperations.jl pour implémenter la macro @tensoropt , qui optimise en effet l'ordre de contraction. Que je trouve une version n -ary de mul! moins utile, c'est parce qu'il y a peu d'intérêt, du point de vue de l'efficacité, à fournir une place pré-allouée pour mettre le résultat, si tous les intermédiaires les tableaux doivent encore être alloués à l'intérieur de la fonction, puis gc'ed. En fait, dans TensorOperations.jl, plusieurs personnes ont remarqué que l'attribution de grands temporaires est l'un des endroits où le gc de Julia se comporte vraiment mal (conduisant assez souvent à des temps de gc de 50%).

Par conséquent, je limiterais mul! à ce qui est vraiment une opération primitive, comme le préconise également @tkf si je comprends bien: multiplier deux matrices en une troisième, avec éventuellement des coefficients scalaires. Oui, nous pouvons penser à la façon la plus générale de faire cela pour les algèbres non commutatives, mais pour l'instant je pense que le besoin immédiat est un accès pratique à la fonctionnalité fournie par BLAS (gemm, gemv, ...) que Julia's mul! wrapper manque. Cela ne me dérange pas de réfléchir à la meilleure API pour m'assurer qu'elle est à l'épreuve du temps pour prendre en charge cette opération primitive pour des types de nombres plus généraux.

Je n'aime pas votre proposition avec des tuples, mais je pourrais prévoir une confusion potentielle
Restreindre du cas 4 au cas 2 sur 3 semble impliquer des valeurs par défaut β₁ = 1 et β₂ = 1 (ou en fait true ). Mais alors, si aucun n'est spécifié, cela signifie soudainement β₁ = β₂ = 0 ( false ). Bien sûr, la syntaxe est légèrement différente, puisque vous écrivez mul!(Y, args...) , pas mul!((Y,), args...) . En fin de compte, c'est une question de documentation, donc je voulais juste le souligner.

Donc, en résumé, non, je ne suis pas vraiment opposé à cette syntaxe, bien que ce soit un nouveau type de paradigme qui est en train d'être introduit, et qui devrait alors probablement être suivi dans d'autres endroits. Ce à quoi je m'oppose, c'est de vouloir immédiatement généraliser cela à la multiplication d'un nombre arbitraire de matrices, dont, comme je l'ai dit plus haut, je ne vois pas l'intérêt.

@dlfivefifty : Mais il s'agit plus d'une discussion sur l'implémentation que sur l'API. Mais utiliser des tuples pour grouper des termes semble faux: n'est-ce pas pour cela que le système de types est là? Dans ce cas, vous arrivez à la solution LazyArrays.jl.

Mais nous n'allons pas utiliser des tableaux paresseux complets ici - il y a déjà des LazyArrays pour cela. En attendant, nous avons besoin d'un moyen d'exprimer la mise Y échelle lscale et / ou rscale pour β₁ et β₂ , mais cela ne semble plus élégant et nous perdrions la possibilité de envoyer sur cela, ce qui n'est pas crucial mais c'est agréable d'avoir.

@Jutho : Par conséquent, je limiterais mul! à ce qui est vraiment une opération primitive, comme le préconise également @tkf si je comprends bien: multiplier deux matrices en une troisième, avec éventuellement des coefficients scalaires. Oui, nous pouvons penser à la façon la plus générale de faire cela pour les algèbres non commutatives, mais pour l'instant je pense que le besoin immédiat est un accès pratique à la fonctionnalité fournie par BLAS (gemm, gemv, ...) que Julia's mul! wrapper manque.

Je suis d' mul! pour ne définir qu'un petit sous-ensemble d'opérations pour

# 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)

À quelle fin? Pourquoi permettre autant de variations dans la façon d'exprimer les opérations BLAS?

  1. Parce que cela permet aux gens d'exprimer leur intention - si l'intention est de se multiplier à gauche ou à droite ou les deux, pourquoi ne pas permettre aux gens de l'exprimer et de choisir la bonne implémentation?

  2. Nous pouvons avoir des solutions de secours génériques qui font la bonne chose même pour les types d'élément non commutatifs.

Le but de ce problème est d'avoir une généralisation de la multiplication matricielle en place qui subsume gemm! tout en étant plus générique que gemm !. Sinon, pourquoi ne pas continuer à écrire gemm! ?

Mais nous n'allons pas utiliser des tableaux paresseux complets ici

Je ne dis pas "tableaux paresseux complets", je suggère paresseux dans le même sens que Broadcasted , qui est finalement supprimé au moment de la compilation. Essentiellement, j'ajouterais un Applied pour représenter l'application paresseuse d'une fonction et au lieu d'utiliser des tuples (qui ne contiennent aucun contexte), vous auriez quelque chose comme

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

Cela pourrait être recouvert de sucre comme la notation .* diffusion pour le rendre plus lisible, mais je pense que ce qui est recherché est immédiatement clair, contrairement à la proposition basée sur les tuple.

@StefanKarpinski , comme dit précédemment, je suis certainement d'accord pour dire que nous devrions envisager une interface qui soit à l'épreuve du temps et qui se généralise correctement à d'autres types de nombres. Le seul problème, je pense, est que votre liste n'est pas complète. En principe, vous pourriez avoir:

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

et toutes les versions réduites de celui-ci, c'est-à-dire si tous les 5 arguments scalaires peuvent être absents, c'est 2 ^ 5 = 32 possibilités différentes. Et cela combiné avec toutes les possibilités de différentes matrices ou vecteurs.

Je suis d'accord avec @dlfivefifty qu'une approche de type diffusion est plus faisable.

Oui, j'ai réalisé que j'avais omis certaines options, mais 32 méthodes ne me semblent pas si folles, après tout, nous n'avons pas à les écrire à la main. Ajout d' un « système semblable émission » ou un système d'évaluation paresseuse qui nous permet d' écrire materialize!(applied(+, applied(*, α, A, B), applied(*, β, C))) semble être un ajout beaucoup plus grande et sortir de la portée de cette question. Tout ce que nous voulons, c'est une manière d'orthographier la multiplication matricielle générale qui est à la fois générique et nous permet de nous envoyer vers BLAS. Si nous ne pouvons pas tous être d'accord là-dessus, je suis enclin à laisser les gens continuer d'appeler directement gemm! .

Oui, c'est probablement vrai; J'ai supposé qu'il serait plus facile avec des arguments scalaires à l'arrière, de fournir facilement des valeurs par défaut. Mais si avec une métaprogrammation @eval nous pouvons facilement générer les 32 définitions, c'est tout aussi bien. (Notez, comme vous le savez sûrement, mul n'est pas seulement gemm! mais aussi gemv et trmm et ...).

Permettez-moi d'ajouter que ce n'est pas seulement un wrapper BLAS. Il existe d'autres méthodes spécialisées de pure Julia dans stdlib. De plus, il est important de l'avoir comme API de surcharge: les auteurs de packages peuvent définir mul! pour leurs types de matrices spéciales.

Je suppose que c'est ma position:

  1. Nous pourrions aussi bien prendre en charge mul!(C, A, B, a, b) maintenant car il existe déjà dans SparseArrays.jl
  2. Nous ne devrions rien faire d'autre car la répartition sur des types de matrice ne s'adapte pas bien. (En tant que mainteneur de BandedMatrices.jl, BlockArrays.jl, LowRankApprox.jl, etc., je peux affirmer cela par expérience.)
  3. La conception basée sur les traits évolue bien, mais il serait préférable de tout faire et de faire une diffusion comme Applied , car le modèle de conception est déjà établi. Cela devra attendre Julia 2.0, avec un prototype en cours de développement dans LazyArrays.jl qui répond à mes besoins.

@dlfivefifty Pensez-vous que la difficulté de mul!((Y, β), α, A, B) API mul!(Y, A, B, α, β) ? Considérer des wrappers matriciels tels que Transpose introduit la difficulté, y compris 2 et 3 tuples semble augmenter davantage la difficulté (bien que je sache que tuple est un cas particulier dans le système de types de Julia).

  1. Nous pourrions aussi bien prendre en charge mul!(C, A, B, a, b) maintenant car il existe déjà dans SparseArrays.jl

Le fait que quelqu'un ait décidé que mul!(C, A, B, a, b) devrait signifier C .= b*C + a*A*B sans y réfléchir complètement n'est absolument pas une bonne raison de doubler cela. Si mul! est la version sur place de * alors je ne vois pas comment mul!(out, args...) peut signifier autre chose que out .= *(args...) . Franchement, c'est ainsi que vous vous retrouvez avec un système qui est un désordre d'API mal pensées et incohérentes qui n'existent que par accident historique. La fonction mul! n'est pas exportée à partir de SparseArrays _et_ cette méthode particulière n'est pas documentée, c'est donc vraiment la raison la plus faible possible pour enraciner une méthode mal conçue qui n'a probablement été ajoutée que parce que la fonction n'était pas pas public! Je propose d'annuler cette erreur et de supprimer / renommer cette méthode de mul! place.

D'après le reste de cette discussion, il semble que nous ne devrions rien faire d'autre puisque toutes les parties prenantes veulent faire quelque chose de plus sophistiqué avec des traits et / ou de la paresse en dehors de la bibliothèque standard. Je suis d'accord avec cela car supprimer des choses est toujours agréable.

D'après le reste de cette discussion, il semble que nous ne devrions rien faire d'autre puisque toutes les parties prenantes veulent faire quelque chose de plus sophistiqué avec des traits et / ou de la paresse en dehors de la bibliothèque standard. Je suis d'accord avec cela car supprimer des choses est toujours agréable.

Il semble que vous en avez un peu marre, ce qui est compréhensible. Cependant, je ne pense pas que cette conclusion soit vraie. Si vous êtes convaincu que la suggestion actuelle peut être mise en œuvre de manière évolutive tout en permettant aux développeurs de packages de surcharger cette définition pour leurs propres types de matrice et de vecteur (comme mentionné par @tkf), ce serait un excellent moyen vers l'avant.

En particulier, je pense que les développeurs de packages doivent uniquement implémenter:

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

et peut-être, par exemple,

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

tandis que Julia Base (ou plutôt la bibliothèque standard LinearAlgebra) s'occupe de gérer toutes les valeurs par défaut, etc.

Je pense que les développeurs de packages doivent uniquement implémenter:

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

Je suggérerais de documenter

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

comme signature à surcharger. C'est parce que d'autres emplacements pour α modifient la grande complexité du temps O. Voir: https://github.com/JuliaLang/julia/pull/29634#issuecomment -443103667. Cela donne aux nombres non commutatifs un traitement non de première classe. Mais AFAICT personne ici n'utilise réellement des nombres non commutatifs et je pense que nous devrions attendre qu'il y ait un réel besoin.

Une chose que j'aime dans l' approche de mul!((Y, β), α::Diagonal, A, B) pour le type de matrice _some_ de A (par exemple, Adjoint{_,<:SparseMatrixCSC} ) sans changer la complexité du temps . (Ceci est important pour mon application.) Bien sûr, aller de cette manière nécessiterait une discussion plus approfondie dans l'API, en particulier pour savoir comment interroger l'existence d'une méthode spécialisée. Pourtant, avoir la possibilité d'étendre l'API est formidable.

Si quelqu'un clarifie mon inquiétude concernant les ambiguïtés de méthode, je serai tout pour l'approche groupe par tuple.

Ceci est dû au fait que d'autres emplacements pour α modifient la grande complexité temporelle O.

Est-ce quelque chose de matrice clairsemée spécifiquement? Je n'ai pas tout à fait l'argumentation, surtout pas pour les matrices denses. Dans l'implémentation vers laquelle vous créez un lien, vous montrez un cas où α est compris entre A et B ?

Je pense que les développeurs de packages n'ont qu'à implémenter ...

C'est très simplifié. Supposons que nous ayons une matrice qui se comporte comme une matrice strided, telle que PseudoBlockMatrix de BlockArrays.jl. Pour prendre entièrement en charge gemm! nous devons remplacer chaque permutation de PseudoBlockMatrix avec (1) lui-même, (2) StridedMatrix , (3) Adjoint s de lui-même , (4) Transpose s de lui-même, (5) Adjoint s de StridedMatrix , (6) Transpose s de StridedMatrix , et éventuellement d'autres. C'est déjà 6 ^ 3 = 216 combinaisons différentes. Ensuite, vous voulez prendre en charge trmm! et vous devez faire de même avec UpperTriangular , UnitUpperTriangular , leurs adjoints, leurs transpositions, etc. Puis gsmm! avec Symmetric et Hermitian .

Mais dans de nombreuses applications, nous ne voulons pas seulement travailler avec les matrices, mais aussi leurs sous-vues, en particulier pour les matrices de blocs où nous voulons travailler avec des blocs. Nous devons maintenant ajouter chaque permutation de vues de notre matrice avec les 6 combinaisons ci-dessus.

Nous avons maintenant des milliers de remplacements, impliquant StridedMatrix , qui est un type d'union très compliqué. C'est trop pour le compilateur, ce qui fait que le temps using prend plus de minutes, au lieu de secondes.

À ce stade, on se rend compte que le mul! actuel, et par extension les extensions proposées de mul! , est défectueux par la conception et que les développeurs de packages ne devraient tout simplement pas s'en soucier. Heureusement, LazyArrays.jl fournit une solution de contournement temporaire en utilisant des traits.

Je suis donc en quelque sorte d'accord avec @StefanKarpinski pour laisser les choses telles qu'elles sont jusqu'à ce qu'une refonte plus substantielle soit poursuivie, car dépenser des efforts sur quelque chose qui est défectueux par la conception n'est pas une bonne utilisation du temps de personne.

@dlfivefifty , je faisais référence uniquement à la manière dont les arguments scalaires sont traités. Toutes les complications avec différents types de matrices qui existent déjà pour mul!(C,A,B) resteront bien sûr.

Ceci est dû au fait que d'autres emplacements pour α modifient la grande complexité temporelle O.

Est-ce quelque chose de matrice clairsemée spécifiquement? Je n'ai pas tout à fait l'argumentation, surtout pas pour les matrices denses. Dans l'implémentation vers laquelle vous créez un lien, vous montrez un cas où α est compris entre A et B ?

@Jutho Je pense qu'en général, vous ne pouvez pas mettre α dans la position la plus intérieure de la boucle. Par exemple, dans ce cas, vous pouvez prendre en charge α₁*A*B*α₃ mais pas A*α₂*B

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

Je pense qu'au moins α₁ ou α₂ en α₁*A*α₂*B*α₃ doit être 1 pour éviter d'augmenter la complexité asymptotique du temps.

@dlfivefifty Mais même LazyArrays.jl a besoin de certaines fonctions primitives à distribuer, non? Je crois comprendre que cela résout «l'enfer des expéditions» mais ne diminue pas le nombre de «noyaux» de calcul que les gens doivent implémenter.

Non, il n'y a pas de «primitive» de la même manière que Broadcasted n'a pas de «primitive». Mais oui pour le moment cela ne résout pas la question du «noyau». Je pense que la prochaine étape consiste à le repenser pour utiliser un type Applied paresseux avec un ApplyStyle . Ensuite, il pourrait y avoir MulAddStyle pour reconnaître les opérations de type BLAS, d'une manière que l'ordre n'a pas d'importance.

J'appellerais materialize! ou copyto! une primitive. Au moins, c'est la pierre angulaire du mécanisme de diffusion. De même, je suppose que LazyArrays.jl doit réduire sa représentation paresseuse à des fonctions avec des boucles ou ccall s à des bibliothèques externes à un moment donné, non? Serait-ce mauvais si le nom d'une telle fonction est mul! ?

C'est très simplifié. Supposons que nous ayons une matrice qui se comporte comme une matrice strided, telle que PseudoBlockMatrix de BlockArrays.jl. Pour soutenir pleinement gemm! nous devons remplacer chaque permutation de PseudoBlockMatrix avec (1) lui-même, (2) StridedMatrix, (3) S'adjoint de lui-même, (4) Se transpose de lui-même, (5) Adjoints de StridedMatrix, (6) Transpose de StridedMatrix, et éventuellement d'autres . C'est déjà 6 ^ 3 = 216 combinaisons différentes. Ensuite, vous voulez prendre en charge trmm! et vous devez faire la même chose avec UpperTriangular, UnitUpperTriangular, leurs adjoints, leurs transposés, etc. Alors gsmm! avec Symmetric et Hermitian.
Mais dans de nombreuses applications, nous ne voulons pas seulement travailler avec les matrices, mais aussi leurs sous-vues, en particulier pour les matrices de blocs où nous voulons travailler avec des blocs. Nous devons maintenant ajouter chaque permutation de vues de notre matrice avec les 6 combinaisons ci-dessus.
Nous avons maintenant des milliers de remplacements, impliquant StridedMatrix, qui est un type d'union très compliqué. C'est trop pour le compilateur, ce qui fait que le temps d'utilisation prend plus de minutes, au lieu de secondes.

Je conviens certainement que l'union de type StridedArray est un défaut de conception majeur. J'ai soutenu votre tentative de résoudre ce problème à un moment donné.

Sur Strided.jl, je n'implémente mul! lorsque toutes les matrices impliquées sont de mon propre type personnalisé (Abstract)StridedView , chaque fois qu'il y a une certaine mixité dans les types de A, B et C, je laisse Julia Base / LinearAlgebra gère cela. Bien sûr, cela doit être utilisé dans un environnement macro @strided , qui essaie de convertir tous les types de base possibles dans le type StridedView . Ici, StridedView peut représenter des sous-vues, des transpositions et des adjoints, ainsi que certaines remodelages, tous avec le même type (paramétrique). Dans l'ensemble, le code de multiplication complet est d'environ 100 lignes:
https://github.com/Jutho/Strided.jl/blob/master/src/abstractstridedview.jl#L46 -L147
Le remplacement natif de Julia au cas où BLAS ne s'appliquerait pas est implémenté en utilisant la fonctionnalité plus générale mapreducedim! fournie par ce package, et n'est pas moins efficace que celle de LinearAlgebra ; mais il est également multithread.

Je pense qu'au moins α₁ ou α₂ en α₁*A*α₂*B*α₃ doit être égal à 1 pour éviter d'augmenter la complexité asymptotique du temps.

@tkf , je suppose que si ces coefficients scalaires prennent une valeur par défaut one(T) , ou mieux encore, true , la propagation constante et les optimisations du compilateur élimineront automatiquement cette multiplication. dans la boucle la plus interne quand il s'agit d'un no-op. Il serait donc toujours pratique de ne définir que la forme la plus générale.

Je ne sais pas si nous pouvons compter sur une propagation constante pour éliminer toutes les multiplications par 1 ( true ). Par exemple, eltype peut être Matrix . Dans ce cas, je pense que true * x (où x::Matrix ) doit créer une copie allouée en tas de x . Julia peut-elle faire de la magie pour éliminer cela?

@Jutho Je pense que ce benchmark montre que Julia ne peut pas éliminer les multiplications intermédiaires dans certains cas:

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)

Belle référence. J'avais également déjà remarqué qu'il n'éliminera en effet pas ces allocations par des expériences similaires. Dans de tels cas, il peut être utile de définir un type singleton One usage spécial qui définit simplement *(::One, x::Any) = x et *(x::Any, ::One) = x , et qu'aucun type d'utilisateur n'a jamais besoin de traiter maintenant. Alors la valeur par défaut, au moins pour α₂ , pourrait être One() .

Ah, oui, c'est malin! J'ai d'abord pensé que j'étais maintenant d'accord pour supporter α₁ * A * α₂ * B * α₃ mais ensuite je pense avoir trouvé un autre problème: ce que nous devrions faire est mathématiquement ambigu lorsque (disons) A est une matrice de matrice et α₁ est une matrice. Ce ne sera pas un problème si nous ne supportons jamais les arguments non scalaires dans les positions α . Cependant, il est impossible de présenter Y .= β₁*Y*β₂ + *(args...) comme modèle mental de mul!((β₁, Y, β₂), args...) . De plus, ce serait vraiment bien si les matrices diagonales pouvaient être passées à α₁ ou α₂ car elles peuvent être calculées presque "gratuitement" parfois (et c'est important dans les applications). Je pense qu'il y a deux voies:

(1) Allez avec mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) mais lors de la surcharge de la méthode, les arguments α et β doivent accepter Diagonal . Il est facile de définir un chemin d'appel afin que le code de l'utilisateur final puisse toujours l'appeler via des valeurs scalaires. Mais pour que cela fonctionne efficacement, la "version O (1)" de Diagonal(fill(λ, n)) https://github.com/JuliaLang/julia/pull/30298#discussion_r239845163 doit être implémentée dans LinearAlgebra. Notez que les implémentations pour scalaire et diagonal α ne sont pas très différentes; souvent, il suffit d'échanger α et α.diag[i] . Je ne pense donc pas que ce soit un lourd fardeau pour les auteurs de paquets.

Cela résout l'ambiguïté que j'ai mentionnée ci-dessus car vous pouvez maintenant appeler mul!(Y, α * I, A, B) lorsque A est une matrice de matrice et α est une matrice qui devrait être traitée comme eltype sur A .

(2) Peut-être que l'itinéraire ci-dessus (1) est encore trop complexe? Si c'est le cas, optez plutôt pour muladd! pour le moment. Si jamais nous voulons prendre en charge mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) , migrer vers celui-ci tout en conservant la compatibilité descendante n'est pas difficile.

À ce stade, je dois me demander si nous ne devrions pas simplement avoir un matmul!(C, A, B, α, β) très restreint et spécialisé qui n'est défini que pour fonctionner pour cette signature générale:

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

En outre, il est vraiment étonnant que cette signature puisse être écrite et envoyée.

C'est essentiellement ma suggestion (2), non? (Je suppose que A :: Matrix{T} ne signifie pas littéralement Core.Array{T,2} ; sinon c'est plus ou moins juste gemm! )

Je serais heureux avec cela comme solution temporaire, et je peux partiellement le supporter dans les packages que je maintiens («partiel» en raison du problème des traits exceptionnels), bien que cela ajoute un autre nom au mélange: mul! , muladd! et maintenant matmul! .

... N'est-il pas temps pour quelqu'un d'afficher «le triage dit…» et de l'appeler?

En outre, il est vraiment étonnant que cette signature puisse être écrite et envoyée.

Le fait que vous puissiez envoyer proprement sur exactement cette signature n'est pas un argument pour en faire une méthode de mul! . Il peut alors également être clairement déconseillé au cas où une solution plus générale se produirait.

en faisant une méthode de mul!

Si nous choisissons mul!(C, A, B, α, β) il n'y a aucun moyen de le généraliser à mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) et autres sans casser la compatibilité. (Peut-être que c'est une "fonctionnalité" puisque nous sommes libérés de cette discussion pour toujours: sourire :).

Aussi, permettez-moi de noter que le type d'élément de délimitation à Number _et_ conserver la compatibilité avec le 3-arg actuel mul! (qui prend déjà en charge le type d'élément non Number ) introduira beaucoup de duplication.

Je n'ai aucune idée de ce que vous attendez de mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) à faire ... donc je pense que c'est une fonctionnalité.

Bosse. Cela me rend triste d'avoir à utiliser les fonctions BLAS partout pour obtenir de meilleures performances sur place.

@StefanKarpinski Pourriez-vous en

Nous pouvons discuter bien que je ne sois pas sûr du résultat qui va avoir sans quelques personnes linalg sur l'appel, ce qui n'est généralement pas le cas de nos jours. Pour être franc, j'ai passé pas mal de temps et d'efforts à essayer d'amener cette discussion à une sorte de résolution et chaque idée semble avoir été fermement rejetée d'une manière ou d'une autre, donc je viens de vérifier ce problème à ce point. Si quelqu'un pouvait faire un résumé de la question et expliquer pourquoi les diverses propositions sont inadéquates, ce serait utile pour avoir une discussion de triage productive. Sinon, je ne pense pas que nous pourrons faire grand-chose.

Je pense que l'absence de consensus signifie que ce n'est pas le bon moment pour intégrer cela dans StdLib.

Pourquoi pas simplement un package MatMul.jl qui implémente l'une des suggestions que les utilisateurs down peuvent utiliser? Je ne vois pas pourquoi être dans StdLib est si important dans la pratique. Je soutiendrai volontiers ceci dans les paquets que je maintiens.

Je pense juste à une belle version julienne de gemm! et gemv! correspondant à ce que nous avons déjà dans SparseArrays. Par @andreasnoack ci-dessus:

Je pensais que nous avions déjà choisi mul!(C, A, B, α, β) avec des valeurs par défaut pour α , β . Nous utilisons cette version dans

julia / stdlib / SparseArrays / src / linalg.jl

Lignes 32 à 50 de b8ca1a4

...
Je pense que certains packages utilisent également ce formulaire, mais je ne me souviens pas lequel sur ma tête.

Cette suggestion avait 7 pouces vers le haut et aucun pouce vers le bas. Pourquoi n'implémentons-nous pas simplement cette fonction pour les vecteurs / matrices denses? Ce serait une solution simple qui couvre le cas d'utilisation le plus courant, n'est-ce pas?

D'ACCORD. Donc, je suppose qu'il n'y a même pas de consensus pour savoir s'il y a consensus: sweat_smile:

_Je_ pensais que presque tout le monde [*] voulait cette API et c'est juste une question de nom de fonction et de signature. Comparé au fait de ne pas avoir cette API, je pensais que tout le monde était satisfait de l'une des options (disons mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) , muladd!(C, A, B, α, β) et mul!(C, A, B, α, β) ). À moins que quelqu'un ne puisse faire un argument convaincant qu'une certaine API est bien pire que de ne pas l'avoir, je serais heureux de tout ce que le triage décide.

@StefanKarpinski Mais n'hésitez pas à supprimer la balise triage si vous pensez que la discussion n'est pas encore suffisamment consolidée.

[*] OK, @dlfivefifty , je pense que vous mul! . Mais cela nécessiterait de changer l'interface 3-arg mul! partir de zéro, donc j'ai pensé que c'était bien au-delà de la portée de cette discussion (ce que j'ai interprété comme à propos de _ajouter_ une forme de variante 5-arg). Je pense que nous avons besoin de quelque chose qui fonctionne "suffisamment" jusqu'à ce que LazyArrays.jl arrive à maturité.

Pourquoi pas simplement un package MatMul.jl qui implémente l'une des suggestions que les utilisateurs down peuvent utiliser?

@dlfivefifty Je pense que l'avoir dans LinearAlgebra.jl est important car c'est une fonction d'interface (API surchargeable). De plus, puisque mul!(C::AbstractMatrix, A::AbstractVecOrMat, B::AbstractVecOrMat) est implémenté dans LinearAlgebra.jl, nous ne pourrons pas définir mul! en termes de MatMul.muladd! . Il y a bien sûr quelques solutions de contournement, mais il est beaucoup plus agréable d'avoir une implémentation simple, d'autant plus que cela nécessite "seulement" de décider du nom et de la signature.

Pourquoi n'implémentons-nous pas simplement cette fonction pour les vecteurs / matrices denses?

@chriscoey Malheureusement, ce n'est pas le seul favori de tout le monde: https://github.com/JuliaLang/julia/issues/23919#issuecomment -441717678. Voici mon résumé des avantages et des inconvénients de cette option et d'autres https://github.com/JuliaLang/julia/issues/23919#issuecomment -441865841. (Voir aussi les commentaires des autres)

À partir du triage: Il existe des plans à long terme pour avoir des API génériques de contraction de tenseur, y compris la prise en charge du compilateur et la sélection vers BLAS, mais à moyen terme, choisissez simplement l'API qui répond à votre besoin immédiat. Si cela correspond à BLAS, le choix des noms BLAS semble raisonnable.

@Keno , des informations que vous pouvez partager sur l'API générique de contraction des tenseurs et le support du compilateur? J'ai peut-être aussi des informations intéressantes à partager, mais pas (encore) en public.

Aucune conception d'API n'a été faite sur tout cela, juste un sens générique que nous devrions l'avoir. Je sais que vous avez travaillé sur certaines de ces choses, il serait donc bon d'avoir une session de conception au moment opportun, mais je ne pense pas que nous y soyons encore.

Si cela correspond à BLAS, le choix des noms BLAS semble raisonnable.

Ceci est complètement contraire à ce que nous avons fait jusqu'à présent pour les noms de fonctions génériques d'algèbre linéaire.

Quel est le plan pour les β == 0 forts / faibles dans la version générale proposée de BLAS.gemm!(α, A, B, β, C) ?

Si nous abaissons les appels à BLAS il se comportera comme un zéro fort, même si cela est maintenant incompatible avec lmul! . Je ne peux pas penser à une solution à cela en dehors de revenir à un generic_muladd! if β == 0 .

Quel est le plan pour fort / faible β == 0

Cela n'a été que brièvement discuté autour de mon commentaire sur https://github.com/JuliaLang/julia/issues/23919#issuecomment -430139849, donc le triage n'a probablement pas répondu à cette question.

@Keno Bien qu'il n'y ait pas encore eu de conception d'API, envisagez-vous que «les API incluant le support du compilateur et la sélection vers BLAS» seraient définies comme mutantes ou seraient-elles immuables, comme l'algèbre linéaire XLA, afin d'aider le compilateur? Pensez-vous que mul! et / ou muladd! feraient partie de ces API?

ping @Keno pour le @andreasnoack « est question https://github.com/JuliaLang/julia/issues/23919#issuecomment -475534454

Désolé, je devais revenir sur ce problème (en particulier j'ai demandé le triage), mais je ne savais pas quelle serait la prochaine action donnée à la décision du triage.

Si cela correspond à BLAS, le choix des noms BLAS semble raisonnable.

Comme @andreasnoack l'a souligné, nous ne pouvons pas utiliser (disons) gemm! parce que nous voulons prendre en charge la multiplication matrice-vecteur, etc. Mais je suppose que nous pouvons simplement ignorer cette décision de triage (qui dit simplement "Si cela correspond à BLAS" ; ce n'est pas le cas).

choisissez simplement l'API qui répond à vos besoins immédiats.

Donc, je suppose que nous pouvons suivre cette direction. Je pense que cela signifie oublier l'API basée sur les tuple suggérée par @StefanKarpinski et "juste" choisir l'un des mul! / muladd! / addmul! .

Nous revenons en quelque sorte à la discussion initiale. Mais je pense que c'est bien que nous ayons une contrainte de ne plus discuter d'API.

Une idée comment choisir un nom parmi mul! / muladd! / addmul! ?


@chriscoey Je pense qu'il vaut mieux discuter de la future API ailleurs. Ce problème est déjà très long et nous ne pourrons progresser que si nous nous concentrons sur une solution à moyen terme. Que diriez-vous d'ouvrir un nouveau numéro (ou un fil de discussion)?

Je suggère un seul tour de vote d'approbation avec un délai de 10 jours à partir de maintenant. Le vote d'approbation signifie: tout le monde vote pour toutes les options qu'il juge préférables à la poursuite de la discussion. Les gens qui préfèrent avoir leur nom le moins préféré maintenant plutôt qu'une discussion continue devraient voter pour les trois. Si aucune option ne reçoit une large approbation ou si le système de vote lui-même ne parvient pas à obtenir une large approbation, nous devons poursuivre la discussion. En cas de quasi-liens entre les options approuvées, @tkf décide (privilège de l'auteur PR).

+1: Je suis d'accord avec ce schéma de vote et j'ai émis mes votes d'approbation.
-1: Je ne suis pas d'accord avec ce schéma de vote. Si trop de personnes ou trop de personnes importantes choisissent cette option, le vote est sans objet.

Coeur: mul! est préférable à la poursuite de la discussion.
Rocket: muladd! est préférable à la poursuite de la discussion.
Hourra: addmul! est préférable à la poursuite de la discussion.

Je suggère provisoirement que 75% d'approbation et 5 devraient définitivement atteindre le quorum (c'est-à-dire 75% des personnes qui ont voté du tout, y compris en désaccord avec l'ensemble de la procédure de vote, et au moins 5 personnes ont approuvé l'option gagnante; si la participation est faible , alors 5/6 ou 6/8 font quorum mais l'unanimité 4/4 pourrait être considérée comme un échec).

Le gel des fonctionnalités pour la version 1.3 a lieu vers le 15 août: https://discourse.julialang.org/t/release-1-3-branch-date-approaching-aug-15/27233?u=chriscoey. J'espère que nous pourrons l'avoir fusionné d'ici là 😃. Merci à tous ceux qui ont déjà voté!

Nous devons également décider du comportement de β == 0 https://github.com/JuliaLang/julia/issues/23919#issuecomment -475420149 qui est orthogonal pour décider du nom. En outre, le conflit de fusion dans mon PR doit être résolu et le PR a besoin d'un examen des détails de mise en œuvre (par exemple, l'approche que j'ai utilisée là-bas pour gérer undef s dans le tableau de destination). Nous pouvons découvrir d'autres problèmes lors de l'examen. Donc, je ne suis pas sûr si cela peut entrer dans 1.3 ...

Re: β == 0 , je pense que le commentaire de @andreasnoack https://github.com/JuliaLang/julia/issues/23919#issuecomment -430139849 (mon résumé: β == 0 -handling devrait être BLAS -compatible pour tirer le meilleur parti de BLAS) a du sens. Il est difficile de trouver des opinions opposées autres que l' argument de β == 0 BLAS?

@simonbyrne En https://github.com/JuliaLang/julia/issues/23919#issuecomment -430375349, je ne pense pas que le branchement explicite soit un gros problème car il s'agit principalement d'un β != 0 ? rmul!(C, β) : fill!(C, zero(eltype(C))) onelinear. De plus, pour une implémentation très générique où vous devez gérer, par exemple C = Matrix{Any}(undef, 2, 2) , l'implémentation nécessite de toute façon une gestion explicite du "zéro fort" (voir la fonction d'aide _modify! dans mon PR https: // github .com / JuliaLang / julia / pull / 29634 / fichiers # diff-e5541a621163d78812e05b4ec9c33ef4R37). Donc, je pense que la manipulation de type BLAS est le meilleur choix ici. Qu'est-ce que tu penses?

Est-il possible d'avoir des zéros faibles de haute performance? Dans le sens où nous voudrions:

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

Autrement dit, nous aurions besoin de déterminer manuellement quelles lignes sont censées être NaN si nous descendons à BLAS (qui utilise un 0 fort).

@dlfivefifty Je pense que BLAS peut gérer NaN en A et B , mais pas en C ?

Je suppose qu'il n'y a aucun moyen de faire efficacement _C = α * A * B + 0 * C_ prenant en charge NaN en utilisant BLAS.gemm! etc. [1] et c'est de là que vient l'argument de

[1] vous devez économiser isnan.(C) quelque part et "polluer" C par la suite

@chethega cela fait plus de 10 jours depuis le vote

@chriscoey Vous avez raison, le vote est clos.

Je suis trop mauvais sur github pour obtenir une liste complète des personnes qui ont voté (ce serait nécessaire pour calculer combien de personnes ont voté). Cependant, quand je regarde les chiffres, il semble assez clair que mul! a un soutien écrasant (gérant probablement un quorum d'approbation de 75%), et le deuxième concurrent muladd! est bien en dessous de 50%.

Il n'y a pas eu une seule objection au système de vote. J'appelle le vote, mul! a gagné et le nom est décidé. @tkf peut continuer à le faire voler :)

@chethega Merci, c'était un bon plan pour décider cela!

BTW, je ne peux pas faire le rebase tout de suite (mais peut-être dans quelques semaines) donc si quelqu'un veut faire le rebase ou la réimplémentation, veuillez ne pas m'attendre.

Malheureusement, nous n'avons pas voté sur la sémantique NaN. Le gel des fonctionnalités aura lieu la semaine prochaine et nous n'avons pas assez de temps pour un vote significatif.

Je propose que nous ayons un référendum non contraignant pour recueillir un instantané de l'ambiance dans le fil.

Je vois les options suivantes:

  1. Continuez à discuter, espérons qu'un consensus sera atteint avant la date limite, ou que les personnes clés nous accordent une prolongation, ou quelque chose du genre. : tada: (modifier pour clarification: c'est l'option par défaut, c'est-à-dire que se passe-t-il si nous ne pouvons pas parvenir à un consensus sur une option différente. Le résultat le plus probable est que 5-arg mul! soit retardé jusqu'à 1.4.)
  2. Fusionner la nouvelle fonctionnalité, avec un comportement NaN non défini comme backstop. Dès que nous atteignons un consensus, nous mettons à jour le code et / ou la documentation pour obtenir le comportement NaN décidé (zéros forts ou faibles). Le backstop du comportement NaN défini par l' implémentation
  3. Gemm signifie Gemm! Fusionner pour 1.3 avec un engagement documenté à des zéros forts. :cœur:
  4. NaN signifie NaN ! Fusionner pour 1.3 avec un engagement documenté sur les zéros faibles. :yeux:
  5. Faites quelque chose, n'importe quoi, avant de frapper la falaise 1.3. :fusée:
  6. Rejetez le sondage. :pouces vers le bas:
  7. Inscrire
  8. Fil descendant proposé: disposition alternative du !(alpha === false) && iszero(alpha) && !all(isfinite, C) && throw(ArgumentError()) , et documentons que ce contrôle d'erreur est susceptible d'être retiré au profit de quelque chose d'autre. :confus:

N'hésitez pas à sélectionner plusieurs options, éventuellement contradictoires, et @tkf / triage n'hésitez pas à ignorer potentiellement le sondage.

Edit: Actuellement seulement: tada: (patience) et: rocket: (impatience) sont contradictoires, mais les deux sont compatibles avec tous les autres. Comme précisé dans le fil du fil, le triage comptera, espérons-le, le résultat du sondage à une date non spécifiée entre le mercredi 14 et le jeudi 15, et le prendra en considération d'une manière non spécifiée. Cela signifie à nouveau «vote d'approbation», c'est-à-dire sélectionner toutes les options que vous aimez, pas seulement votre préférée; il est entendu que: fusée: ne désapprouve pas: thumbsup :,: coeur :,: yeux: et: confus :. Je m'excuse que ce sondage soit plus précipité que le précédent.

Je voterais pour un zéro fort (: heart :) si c'était "Fusionner pour 1.x" au lieu de "Fusionner pour 1.3". Les options n'auraient-elles pas un sens si toutes les "Fusionner pour 1.3" étaient "Fusionner 1.x"?

Merci @chethega. @tkf J'ai vraiment besoin du nouveau mul! Dès que possible sans trop se soucier de la décision NaN (tant que les performances ne sont pas compromises).

Avez-vous vérifié LazyArrays.jl? Pour info, il a un très bon support de multiplication de matrice fusionnée. Vous pouvez également utiliser BLAS.gemm! etc. en toute sécurité car ce sont des méthodes publiques https://docs.julialang.org/en/latest/stdlib/LinearAlgebra/#LinearAlgebra.BLAS.gemm!

J'ai vraiment besoin du mul générique! puisque nous utilisons une grande variété d'anciennes matrices denses structurées, clairsemées et simples, pour représenter plus efficacement différents problèmes d'optimisation. Je suis ici pour la généricité et la rapidité.

Je vois. Et je viens de me rappeler que nous avons discuté de choses dans LazyArrays.jl alors bien sûr que vous le saviez déjà ...

En ce qui concerne "ASAP", le cycle de publication de quatre mois de Julia est, au moins en tant que conception, pour éviter le "merge rush" juste avant le gel des fonctionnalités. Je sais que ce n'est pas juste de ma part de dire cela parce que j'ai déjà essayé la même chose ... Mais je pense que quelqu'un doit le mentionner comme rappel. Le bon côté est que Julia est super facile à construire. Vous pouvez commencer à l'utiliser juste après sa fusion jusqu'à la prochaine version.

Edit: release -> merge

Merci. Je trouve que les délais sont des motivations utiles et j'aimerais éviter de laisser cela à nouveau obsolète. Je proposerais donc que nous essayions d'utiliser la date limite comme objectif.

C'est formidable que vous injectiez activement de l'énergie dans ce fil!

J'ai vraiment besoin du mul générique! puisque nous utilisons une grande variété d'anciennes matrices denses structurées, clairsemées et simples, pour représenter plus efficacement différents problèmes d'optimisation. Je suis ici pour la généricité et la rapidité.

Un mul! 5 arguments ne fonctionnera pas bien lorsque vous avez de nombreux types différents: vous aurez besoin de plusieurs remplacements combinatoires pour éviter toute ambiguïté. C'est l'une des motivations du système LazyArrays.jl MemoryLayout . Il est utilisé pour les matrices «structurées et éparses» précisément pour cette raison dans BandedMatrices.jl et BlockBandedMatrices.jl. (Ici même les sous-vues des matrices en bandes sont envoyées aux routines BLAS en bandes.)

Merci, je vais réessayer LazyArrays.

Étant donné que le mul 5-arg semble être généralement considéré comme un palliatif temporaire (jusqu'à ce qu'une solution comme LazyArrays puisse être utilisée en 2.0), je pense que nous pouvons viser à le fusionner sans nécessairement être la solution idéale ou parfaite pour le long terme.

@chethega quand pensez-vous que nous devrions compter les décomptes pour le nouveau vote non contraignant?

@tkf Bien sûr, vous avez raison de dire que les zéros forts / faibles / undef ont également un sens pour 1.x.

Cependant, je pense qu'il y a pas mal de gens qui préfèrent avoir 1,3 mul! maintenant plutôt que d'attendre 1,4 pour obtenir 5-arg mul! . S'il n'y avait pas de date limite, j'attendrais un peu plus et je prendrais un peu plus de temps pour réfléchir à la manière de mener un sondage approprié (au moins 10 jours pour voter). Plus important encore, nous ne pouvons pas avoir un vote significatif sans d'abord présenter et comparer les implémentations concurrentes sur la vitesse et l'élégance des zéros faibles / forts. Je soupçonne personnellement que les zéros faibles pourraient être rendus presque aussi vite que les zéros forts, en vérifiant d'abord iszero(alpha) , puis en scannant la matrice pour les valeurs !isfinite , et seulement ensuite en utilisant un chemin lent avec une allocation supplémentaire; mais je préfère quand même une sémantique forte zéro.

@chethega quand pensez-vous que nous devrions compter les décomptes pour le nouveau vote non contraignant?

Triage doit prendre une décision (retard / fort / faible / backstop) cette semaine pour le 1.3 alpha. Je pense que le jeudi 15 ou le mercredi 14 sont des options judicieuses pour le triage afin de faire un décompte et d'en tenir compte. Je ne pourrai probablement pas me joindre jeudi, alors quelqu'un d'autre devra compter.

De manière réaliste, il est normal d'être conservateur ici, de manquer la date limite, de poursuivre la discussion et d'attendre la 1.4.

D'un autre côté, nous sommes peut-être déjà parvenus à un consensus sans le remarquer: @andreasnoack a avancé de puissants arguments selon lesquels un coefficient nul devrait être un zéro fort. Il se peut qu'il ait réussi à convaincre tous les partisans faibles du zéro. Il se peut bien qu'il y ait une grande majorité qui veut vraiment 5-arg mul !, de préférence l'année dernière, et ne se soucie pas vraiment de ce petit détail. Si tel était le cas, il serait alors dommage de retarder davantage la fonctionnalité, simplement parce que personne ne veut fermer la discussion.

Pourquoi ne pas simplement lancer une erreur pour le moment:

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

Pourquoi ne pas simplement lancer une erreur pour le moment

J'ai ajouté cette option au sondage. Excellente idée de compromis!

Juste pour définir les attentes: le gel des fonctionnalités pour la 1.3 est dans trois jours, donc il n'y a pratiquement aucun moyen que cela puisse arriver à temps. Nous sommes assez stricts en ce qui concerne le gel des fonctionnalités et le branchement, car c'est la seule partie du cycle de publication dont nous pouvons vraiment contrôler le timing.

Le travail est déjà fait sur https://github.com/JuliaLang/julia/pull/29634 cependant. A juste besoin d'être ajusté et rebasé.

@tkf pour # 29634 pourriez-vous lister le travail qui reste à faire (y compris le changement de nom et la gestion des zéros en fonction du vote)? Je sais que vous êtes occupé alors peut-être que nous pourrons trouver un moyen de diviser les tâches restantes afin que le fardeau ne vous retombe plus.

Les TODO que je peux penser à ATM sont:

Mon PR implémente la sémantique BLAS du traitement β = 0 . Donc, une autre gestion, comme lancer une erreur, doit également être implémentée.

Mon PR implémente la sémantique BLAS du traitement β = 0 .

Désolé, ma mémoire était viciée; mon implémentation n'était pas cohérente et propage NaN _parfois_. Donc, TODO supplémentaire est de rendre le comportement de β = 0.0 cohérent.

Le type MulAddMul serait juste à usage interne, non?

Oui, c'est complètement un détail interne. Mes soucis étaient (1) il peut y avoir trop de spécialisations (beta = 0 etc. est encodé dans le paramètre type) et (2) cela diminue la lisibilité du code source.

Ce sont des préoccupations valables. Nous produisons déjà une tonne de spécialisations dans le code d'algèbre linéaire, il est donc bon de réfléchir si nous avons vraiment besoin de nous spécialiser ici. Ma pensée a généralement été que nous devrions optimiser pour les petites matrices car ce n'est pas gratuit (comme vous l'avez dit, cela complique le code source et pourrait augmenter les temps de compilation) et les gens sont mieux d'utiliser StaticArrays pour les petites multiplications de matrice. Par conséquent, je penche vers la simple vérification des valeurs au moment de l'exécution, mais nous pouvons toujours ajuster cela plus tard si nous changeons d'avis afin que nous ne devrions pas laisser cela causer des retards.

Les zéros souples FYI ont des implémentations simples:

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 Désolé, j'ai oublié que nous avions réellement besoin de la spécialisation pour optimiser la boucle la plus interne pour certaines matrices structurées comme mul!(C, A::BiTriSym, B, α, β) https://github.com/JuliaLang/julia/pull/29634#issuecomment -440510551. Certaines spécialisations peuvent être supprimées, mais c'est en fait plus de travail (donc des retards).

Par conséquent, je penche vers la simple vérification des valeurs au moment de l'exécution, mais nous pouvons toujours ajuster cela plus tard si nous changeons d'avis afin que nous ne devrions pas laisser cela causer des retards.

Génial!

@andreasnoack Merci beaucoup d'avoir révisé et fusionné ceci dans le temps!

Maintenant qu'il est fusionné pour 1.3, il a commencé me rend très nerveux au sujet de la mise en œuvre: smile :. J'apprécie que les gens ici puissent tester leur code d'algèbre linéaire plus en profondeur lorsque 1.3-rc sort!

Pas besoin de s'inquiéter, il y aura beaucoup de temps pour 1.3 RCs + PkgEvals pour éliminer les bugs.

Cette page vous a été utile?
0 / 5 - 0 notes