Julia: API de multiplicación de matrices

Creado en 28 sept. 2017  ·  208Comentarios  ·  Fuente: JuliaLang/julia

Actualmente, existe la siguiente línea en el código matmult disperso:

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

Supongo que esto significa que queremos tener los métodos A_mul_B*!(α,A,B,β,C) = αAB + βC más generales que sobrescriban C (la API BLAS gemm ) para matrices densas. ¿Sigue siendo el caso? (También parece posible mantener ambas API, es decir, mantener los métodos A_mul_B*!(C,A,B) , que simplemente se definirían como A_mul_B*!(C,A,B) = A_mul_B*!(1,A,B,0,C) ).

Personalmente, me gustaría tener la API gemm definida para todos los tipos de matrices (esto se ha expresado en otra parte ). Implementar estos métodos para matrices densas parece bastante simple, ya que simplemente llamarían directamente a gemm et al. El caso disperso ya está implementado. La única modificación real sería el matmult genérico de julia puro, que no acepta los argumentos α y β .

Esto conduciría a un código genérico que funciona con cualquier tipo de matriz / número. Actualmente tengo una implementación simple de expm (después de haber hecho la modificación a _generic_matmatmult! ) que funciona con bigfloats y matrices dispersas.

linear algebra

Comentario más útil

Sugiero una sola ronda de votación de aprobación con un plazo de 10 días a partir de ahora. Voto de aprobación significa: Todos votan por todas las opciones que consideran preferibles a continuar la discusión. Las personas que prefieran tener su nombre menos preferido ahora que una discusión continua deberían votar por los tres. Si ninguna opción recibe una aprobación generalizada, o el esquema de votación en sí no logra una aprobación generalizada, entonces debemos continuar la discusión. En caso de casi empate entre las opciones aprobadas, @tkf decide (privilegio de autor de relaciones públicas).

+1: Estoy de acuerdo con este esquema de votación y he emitido mis votos de aprobación.
-1: No estoy de acuerdo con este esquema de votación. Si demasiadas personas o personas importantes seleccionan esta opción, la votación es discutible.

Corazón: mul! es preferible a la discusión continua.
Cohete: muladd! es preferible a la discusión continua.
Hurra: addmul! es preferible a la discusión continua.

Tentativamente sugiero que el 75% de aprobación y 5 definitivamente deberían formar un quórum (es decir, el 75% de las personas que han votado, incluido el desacuerdo con todo el procedimiento de votación, y al menos 5 personas han aprobado la opción ganadora; si la participación es baja , luego 5/6 o 6/8 hacen quórum, pero un 4/4 unánime podría considerarse un fracaso).

Todos 208 comentarios

Árbitro. # 9930, # 20053, # 23552. ¡Mejor!

Gracias por las referencias. Supongo que este problema tiene más que ver con agregar los métodos de estilo gemm que con una renovación de la API, pero se puede cerrar si creemos que todavía es demasiado similar a # 9930.

Como punto de partida, ¿habría soporte para que _generic_matmatmul! tenga la API gemm ? Es un cambio bastante simple, y puramente aditivo / no rompedor, ya que el método actual simplemente se implementaría con α=1 y β=0 . Puedo hacer las relaciones públicas. Probablemente iría de manera similar a esta versión (en esa versión corté todas las cosas de transposición porque no las necesitaba, no haría eso aquí).

Si. Sería un buen comienzo. Sin embargo, debemos considerar el orden de los argumentos. Originalmente, pensé que era más natural seguir el orden de BLAS, pero somos bastante consistentes en tener primero los argumentos de salida, que también es el caso de los tres argumentos actuales A_mul_B! . Además, como ya ha señalado, la versión de tres argumentos correspondería a la versión de cinco argumentos con α=1 y β=0 y los argumentos de valor predeterminado son los últimos. Por supuesto, no necesariamente tenemos que usar la sintaxis del valor predeterminado para esto, pero tendría sentido usarla aquí.

¿Por qué no introducir una función genérica gemm ?

Si. Sería un buen comienzo. Sin embargo, debemos considerar el orden de los argumentos. Originalmente, pensé que era más natural seguir el orden BLAS, pero somos bastante consistentes en tener primero los argumentos de salida, que también es el caso de los tres argumentos actuales A_mul_B !. Además y como ya has señalado, la versión de tres argumentos correspondería a la versión de cinco argumentos con α = 1 y β = 0 y los argumentos de valor predeterminado son los últimos. Por supuesto, no necesariamente tenemos que usar la sintaxis del valor predeterminado para esto, pero tendría sentido usarla aquí.

Suena bien. Podemos continuar la discusión sobre el orden de argumentos real y el cambio de nombre de métodos en # 9930. Se trata más de tener disponible la versión de cinco argumentos, así que mantendré la interfaz actual Ax_mul_Bx!(α,A,B,β,C) .

¿Por qué no introducir una función gemm genérica?

¿Sugieres cambiar el nombre de _generic_matmatmul! a gemm! además de los cambios anteriores?

Para ser más claro, creo que deberíamos terminar teniendo un solo método mul(C,A,B,α=1,β=0) , junto con tipos de transposición perezosa / adjuntos para enviar, pero ese será otro PR.

¿Por qué no introducir una función gemm genérica?

Creo que gemm es un nombre inapropiado en Julia. En BLAS, la parte ge indica que las matrices son generales , es decir, no tienen una estructura especial, el primer m es multiplicado y la lista m es matriz . En Julia, la parte ge (general) está codificada en la firma al igual que la última m (matriz), así que creo que deberíamos llamarla mul! .

Es la notación mul!(α, A, B, β, C) de SparseArrays.jl

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

"finalizado" como sintaxis oficial? Y esto sería además de mul!(C, A, B) , lmul!(A, B) y rmul!(A,B) ?

No soy demasiado fanático de tener A , B y C en diferentes órdenes.

¿La notación mul!(α, A, B, β, C) de SparseArrays.jl está "finalizada" como sintaxis oficial?

Yo diría que no. Originalmente, me gustó la idea de seguir BLAS (y el orden también coincidía con la forma en que esto generalmente se escribe matemáticamente) pero ahora creo que tiene sentido agregar los argumentos de escala como argumentos opcionales cuarto y quinto.

Entonces, solo para aclarar, le gustaría argumentos opcionales en el sentido

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

La otra opción serían los argumentos de palabras clave opcionales

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

pero no estoy seguro de que la gente esté muy contenta con Unicode.

Estoy muy contento con unicode pero es cierto que intentamos tener siempre una opción ascii y sería posible aquí. Los nombres α y β tampoco son súper intuitivos a menos que conozca BLAS, así que creo que usar argumentos posicionales es la mejor solución aquí.

En mi opinión, una nomenclatura más lógica sería permitir que muladd!(A, B, C, α=1, β=1) mapee las diversas rutinas BLAS que hacen multiplicaciones y sumas . ( gemm como arriba, pero también, por ejemplo, axpy cuando A es un escalar).

La función mul! podría tener una interfaz como mul!(Y, A, B, ...) tomando un número arbitrario de argumentos (como lo hace * ) siempre que todos los resultados intermedios se puedan almacenar en Y. ( Un kwarg opcional podría especificar el orden de multiplicación, con un valor predeterminado razonable)

mul!(Y, A::AbstractVecOrMat, B:AbstractVecOrMat, α::Number) tendría la implementación predeterminada muladd!(A, B, Y, α=α, β=0) , al igual que las otras permutaciones de dos matrices / vectores y un escalar.

Otro voto para tener mul!(C, A, B, α, β) definido para matrices densas. Esto permitiría escribir código genérico para matrices densas y dispersas. Quería definir dicha función en mi paquete de mínimos cuadrados no lineales, pero supongo que se trata de piratería de tipo.

También he tenido la tentación de escribir métodos mul!(C, A, B, α, β) para el paquete MixedModels y participar en un poco de piratería de tipo, pero sería mucho mejor si tales métodos estuvieran en el LinearAlgebra paquete. Tener métodos para un muladd! genérico para esta operación también estaría bien para mí.

Estoy a favor, aunque creo que probablemente debería tener un nombre diferente al de mul! . muladd! parece razonable, pero ciertamente estoy abierto a sugerencias.

¿Quizás mulinc! para multiplicar el incremento?

¿Quizás podamos tener algo como addmul!(C, A, B, α=1, β=1) ?

¿No es esta una forma de muladd! ? ¿O la idea detrás de llamarlo addmul! que muta el argumento de adición en lugar del argumento de multiplicación? ¿Alguna vez se mutaría el argumento de multiplicar?

Tenga en cuenta que, en algunos casos, mutamos elementos que no son primeros, por ejemplo, lmul! y ldiv! , por lo que podríamos hacerlo en el orden "muladd" habitual (es decir, muladd!(A,B,C) ). La pregunta es ¿qué orden deberían ir α y β ? ¿Una opción sería hacer argumentos de palabras clave?

¿No sería bueno si deja una opción a los implementadores para enviar en los tipos de escalares α y β? Es fácil agregar azúcares para los usuarios finales.

Pensé que ya nos habíamos decidido por mul!(C, A, B, α, β) con valores predeterminados para α , β . Estamos usando esta versión en https://github.com/JuliaLang/julia/blob/b8ca1a499ff4044b9cb1ba3881d8c6fbb1f3c03b/stdlib/SparseArrays/src/linalg.jl#L32 -L50. Creo que algunos paquetes también usan este formulario, pero no recuerdo cuál en la parte superior de mi cabeza.

¡Gracias! Sería bueno si eso estuviera documentado.

Pensé que ya nos habíamos decidido por mul!(C, A, B, α, β) con valores predeterminados para α , β .

SparseArrays lo usa, pero no recuerdo que se haya discutido en ninguna parte.

De alguna manera, el nombre muladd! es más natural porque es una multiplicación seguida de una suma. Sin embargo, los valores predeterminados de α y β, muladd!(C, A, B, α=1, β=0) (tenga en cuenta que el valor predeterminado de β es cero, no uno), vuelva a convertirlo en mul!(C, A, B) .

Parece ser un caso de pedantería vs consistencia si llamar a esto mul! o muladd! y creo que el caso del método existente en SparseArrays argumentaría a favor de mul! . Me encuentro en el curioso caso de defender la coherencia a pesar de mi cita favorita de Oscar Wilde: "La coherencia es el último refugio de los sin imaginación".

De alguna manera, el nombre muladd! es más natural porque es una multiplicación seguida de una suma. Sin embargo, los valores predeterminados de α y β, muladd!(C, A, B, α=1, β=0) (tenga en cuenta que el valor predeterminado de β es cero, no uno), vuelva a convertirlo en mul!(C, A, B) .

Hay una excepción interesante a esto cuando C contiene Infs o NaNs: teóricamente, si β==0 , el resultado debería seguir siendo NaNs. Esto no sucede en la práctica porque BLAS y nuestro código de matriz dispersa verifican explícitamente β==0 luego lo reemplazan con ceros.

Podría considerar que tener valores predeterminados de α=true, β=false ya que true y false son "fuertes" 1 y 0 respectivamente, en el sentido de que true*x es siempre x y false*x siempre son zero(x) .

lmul! también debería tener ese comportamiento excepcional: https://github.com/JuliaLang/julia/issues/28972

true y false son "fuertes" 1 y 0 respectivamente, en el sentido de que true*x es siempre x y false*x es siempre zero(x) .

¡No sabía eso !:

julia> false*NaN
0.0

FWIW, estoy bastante contento con la legibilidad de la sintaxis de LazyArrays.jl para esta operación:

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

Detrás de escena, se reduce a mul!(y, A, x, α, β) , para matrices compatibles con BLAS (en bandas y en zancadas).

¡No lo sabía!

Es parte de lo que hace que im = Complex(false, true) funcione.

SparseArrays lo usa, pero no recuerdo que se haya discutido en ninguna parte.

Se discutió anteriormente en https://github.com/JuliaLang/julia/issues/23919#issuecomment -365463941 y se implementó en https://github.com/JuliaLang/julia/pull/26117 sin ninguna objeción. No tenemos las versiones α,β en el caso denso, por lo que el único lugar en este repositorio donde una decisión tendría un efecto inmediato sería SparseArrays .

¿Qué pasa con LinearAlgebra.BLAS.gemm! ? ¿No debería estar envuelto como 5-ary mul! también?

Debería, pero nadie lo ha hecho todavía. Hay muchos métodos en matmul.jl .

Se discutió anteriormente en el # 23919 (comentario) y se implementó en el # 26117 sin ninguna objeción.

Bueno, considera esta mi objeción. Preferiría un nombre diferente.

¿Por qué sería un nombre diferente? Tanto en el caso denso como en el disperso, el algoritmo básico hace tanto la multiplicación como la suma.

Si le damos a esas funciones nombres diferentes, tendremos mul!(C,A,B) = dgemm(C,A,B,1,0) y muladd!(C,A,B,α, β) = dgemm(C,A,B,α, β) .

La única ventaja que veo es si realmente dividimos los métodos y guardamos una llamada if β==0 en el caso C = A*B .

FYI, comencé a trabajar en él en # 29634 para agregar la interfaz a matmul.jl . Espero terminarlo para cuando se decida el nombre y la firma :)

Una ventaja de muladd! sería que podemos tener muladd!(A, B, C) ternario (o muladd!(C, A, B) ?) Con el valor predeterminado α = β = true (como se menciona en la sugerencia original https: //github.com/JuliaLang/julia/issues/23919#issuecomment-402953987). El método muladd!(A, B, C) es similar a muladd por Number s, así que supongo que es un nombre más natural, especialmente si ya conoces muladd .

@andreasnoack Parece que su discusión anterior trata sobre la firma del método y la preferencia de argumentos posicionales sobre los argumentos de palabras clave, no sobre el nombre del método. ¿Tiene alguna objeción por el nombre muladd! ? (La existencia de 5 arios mul! en SparseArrays podría ser una, pero definir el contenedor compatible con versiones anteriores no es difícil).

Tener tanto mul! como muladd! parece redundante cuando el primero es solo el último con valores predeterminados para α y β . Además, BLAS canonicalizó la parte add . Si pudiéramos crear una aplicación de álgebra lineal genérica creíble por muladd! , me gustaría escucharla, pero de lo contrario preferiría evitar la redundancia.

Además, preferiría que mantengamos la propiedad de cero fuerte de false separada de la discusión de mul! . En mi opinión, cualquier valor cero de β debería ser fuerte como lo es en BLAS y como lo es en los métodos actuales de cinco argumentos mul! . Es decir, este comportamiento debería ser una consecuencia de mul! y no del tipo β . Sería difícil trabajar con la alternativa. Por ejemplo, mul!(Matrix{Float64}, Matrix{Float64}, Matrix{Float64}, 1.0, 0.0) ~ podría ~ no pudo usar BLAS.

No podemos cambiar lo que hace BLAS, pero _requirir_ un comportamiento de cero fuerte para los flotantes significa que cada implementación necesitará una rama para verificar el cero.

Si pudiéramos crear una aplicación de álgebra lineal genérica creíble por muladd!

@andreasnoack Con esto, supongo que te refieres a "solicitud de _tres-argumentos_ muladd! " ya que de lo contrario no estarías de acuerdo en incluir cinco argumentos mul! ?

Pero todavía puedo dar un ejemplo en el que muladd!(A, B, C) es útil. Por ejemplo, si desea construir una red de "mundo pequeño", es útil tener una suma "perezosa" de una matriz con bandas y una matriz dispersa. Luego puede escribir algo como:

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

Pero no me importa escribir manualmente true s allí ya que simplemente puedo ajustarlo para mi uso. Tener la función de cinco argumentos como una API documentada estable es el objetivo más importante aquí.

Volviendo al grano:

Tener tanto mul! como muladd! parece redundante cuando el primero es solo el último con valores predeterminados para α y β .

Pero tenemos algunos * implementados en términos de mul! con el "valor predeterminado" de la matriz de salida inicializada apropiadamente. Creo que hay ejemplos de "atajos" de este tipo en Base y bibliotecas estándar. Creo que tiene sentido tener tanto mul! como muladd! aunque mul! es solo un atajo de muladd! .

Preferiría encarecidamente que mantengamos la propiedad de cero fuerte de false separada de la discusión de mul!

Estoy de acuerdo en que sería constructivo centrarse en discutir el nombre de la versión de cinco argumentos de la suma múltiple primero ( mul! vs muladd! ).

No hice un buen trabajo cuando pedí un caso de uso genérico en el que necesitabas muladd para trabajar de forma genérica en matrices y números. La versión numérica sería muladd sin el signo de exclamación, así que lo que pregunté no tenía sentido.

Tu ejemplo podría escribirse como

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

entonces todavía no veo la necesidad de muladd! . ¿Es solo que cree que este caso es tan común que escribir 1, 1 es demasiado detallado?

Pero tenemos algunos * implementados en términos de mul! con el "valor predeterminado" de la matriz de salida inicializada apropiadamente. Creo que hay ejemplos de "atajos" de este tipo en Base y bibliotecas estándar.

No entiendo este. ¿Podrías intentar elaborar? ¿Cuáles son los atajos de los que habla aquí?

entonces todavía no veo la necesidad de muladd! . ¿Es solo que cree que este caso es tan común que escribir 1, 1 es demasiado detallado?

Creo que muladd! también es más descriptivo en cuanto a lo que realmente hace (aunque quizás debería ser addmul! ).

No tengo ningún problema con el nombre muladd! . En primer lugar, no creo que debamos tener que hacer funciones para esto y, en segundo lugar, no creo que valga la pena desaprobar mul! a favor de muladd! / addmul! .

¿Es solo que cree que este caso es tan común que escribir 1, 1 es demasiado detallado?

No. Estoy totalmente de acuerdo con llamar a la función de cinco argumentos siempre que sea una API pública. Intenté dar un ejemplo en el que solo necesito una versión de tres argumentos (ya que pensé que esa era su solicitud).

¿Cuáles son los atajos de los que habla aquí?

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

Creo que * definido aquí puede considerarse un atajo de mul! . Es "solo" mul! con un valor predeterminado. Entonces, ¿por qué no dejar que mul! sea ​​un muladd! / addmul! con valores predeterminados?

También hay rmul! y lmul! definidos como "atajos" similares:

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

depreciando mul!

Pensé que la discusión era sobre agregar una nueva interfaz o no. Si necesitamos desaprobar mul! para agregar una nueva API, no creo que valga la pena.

Los principales argumentos en los que puedo pensar son:

  • conceptualmente, la forma de 5 argumentos hace más que simplemente "multiplicar", y transmite esto más claramente.
  • luego puede escribir addmul!(C, A, B) lugar de mul!(C,A,B,1,1) o mul!(C,A,B,true,true) .

Creo que * definido aquí puede considerarse un atajo de mul! . Es "solo" mul! con un valor predeterminado. Entonces, ¿por qué no dejas que mul? ser un muladd! / addmul! con valores predeterminados?

Porque * es la forma predeterminada de multiplicar matrices y cómo lo harían la mayoría de los usuarios. En comparación, muladd! no estaría ni cerca de * en uso. Además, es incluso un operador existente, mientras que muladd! / addmul! sería una función nueva.

No creo que rmul! y lmul! ajusten a este patrón porque generalmente no son versiones de valor predeterminado de métodos mul! fuera de lugar.

Simon resume muy bien los beneficios en la publicación de arriba. La pregunta es si los beneficios son lo suficientemente grandes como para justificar una función adicional de un cambio de nombre (lo que significa la desaprobación de mul! ). Es donde no estamos de acuerdo. No creo que valga la pena.

Cuando dices que no vale la pena cambiar el nombre, ¿tomaste en cuenta que la API no es completamente pública? Con eso, quiero decir que no está en la documentación de Julia.

Sé que LazyArrays.jl (¿y otros paquetes?) Ya lo usa tan ciegamente que seguir el semver no sería bueno. Pero aún así, no es tan público como otras funciones.

mul! se exporta desde LinearAlgebra y se usa ampliamente, por lo que definitivamente tendríamos que desaprobarlo en este momento. Es una pena que no tuviéramos esta discusión cuando A_mul_B! convirtió en mul! o al menos antes de 0.7 porque habría sido un momento mucho mejor para cambiar el nombre de la función.

¿Qué tal usar mul! por ahora y actualizar el nombre de LinearAlgebra v2.0 cuando podemos actualizar stdlibs por separado?

LazyArrays.jl no usa mul! ya que no es flexible para muchos tipos de matrices (y desencadena un error de lentitud del compilador cuando anula con StridedArray s). Da una construcción alternativa de la forma.

y .= Mul(A, x)

que encuentro es más descriptivo. El análogo de 5 argumentos es

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

Yo argumentaría a favor de desaprobar mul! y pasar al enfoque LazyArrays.jl en LinearAlgebra.jl, pero ese será un caso difícil de hacer.

LowRankApprox.jl usa mul! , pero podría cambiarlo para usar el enfoque LazyArrays.jl y así evitar el error del compilador.

OKAY. Pensé que solo había dos propuestas. ¿Pero aparentemente hay aproximadamente tres propuestas ?:

  1. tres y cinco argumentos mul!
  2. tres y cinco argumentos muladd!
  3. tres argumentos mul! y cinco argumentos muladd!

( muladd! pueden llamarse addmul! )

Estaba pensando que estamos comparando 1 y 3. Tengo entendido ahora que @andreasnoack ha estado comparando 1 y 2.

Yo diría que 2 no es una opción en absoluto, ya que mul! tres argumentos es una API pública y se usa ampliamente. Lo que quise decir con "la API no es completamente pública" es que mul! cinco argumentos no está documentado .

Sí, mi plan era mantener mul! (como una forma de 3 arg y posiblemente 4 arg). Creo que esto vale la pena ya que 3 arg mul! y addmul! tendrían un comportamiento diferente, es decir, dado addmul!(C, A, B, α, β) , tendríamos:

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)

Sin embargo, es posible que no desee implementarlos de esta manera en la práctica, por ejemplo, puede ser más simple solo los 4 argumentos mul! y addmul! separado, y definir los 5 argumentos addmul! como:

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

¡Bache!

Sin embargo, es posible que no desee implementarlos de esta manera en la práctica, por ejemplo, ¡puede ser más simple solo el mul de 4 argumentos! y addmul! por separado, y defina el addmul de 5 argumentos! como:
addmul!(C, A, B, α, β) = addmul!(C .= β .* C, A, B, α)

¿Por qué no hacerlo de forma óptima de inmediato? El punto de no hacerlo así es que solo necesita visitar los elementos de C una vez, lo que definitivamente es más eficiente para matrices grandes. Además, casi no puedo creer que el código sea más largo al definir solo los 5 argumentos addmul! versus los 4 argumentos mul! y addmul! separado.

Para su información, modifiqué la implementación de LinearAlgebra de _generic_matmatmul! para tomar 5 argumentos en LazyArrays: https://github.com/JuliaArrays/LazyArrays.jl/blob/8a50250fc6cf3f2402758088227769cf2de2e053/srcLm70ul.linalg

Aquí se llama vía:

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

pero el código real (en tiled_blasmul! ) sería fácil de traducir a LinearAlgebra.

¿Qué se puede hacer para intentar acelerar este proceso? Las cosas en las que estoy trabajando realmente se beneficiarían de una API de multiplicación de matrices unificada con mul + add en el lugar

La última versión de Strided.jl ahora también admite 5 argumentos mul!(C,A,B,α,β) , enviando a BLAS cuando sea posible y usando su propia implementación (multiproceso) de lo contrario.

@Jutho gran paquete! ¿Existe una hoja de ruta para lo que sigue? ¿Podría el plan ser eventualmente fusionarse con LinearAlgebra?

Esta nunca fue mi intención, pero no me opongo a ella si en algún momento se solicita. Sin embargo, creo que mi uso liberal de las funciones @generated (aunque solo hay una) en la funcionalidad general mapreduce podría no ser adecuado para Base.

Mi hoja de ruta personal: este es principalmente un paquete de bajo nivel para ser utilizado por paquetes de nivel superior, es decir, la nueva versión de TensorOperations y algún otro paquete en el que estoy trabajando. Sin embargo, sería bueno tener más soporte para el álgebra lineal básica (por ejemplo, aplicar norm a StridedView actualmente se reduce a una implementación bastante lenta de norm en Julia Base). Y si tengo tiempo y aprendo a trabajar con GPU, trato de implementar un mapreducekernel igualmente general por GPUArray s.

Creo que el consenso hasta ahora es:

  1. Deberíamos mantener mul!(C, A, B)
  2. Necesitamos _algunos_ funciones de 5 argumentos para multiplicar-agregar en lugar C = αAB + βC

Sugiero centrarse primero en cuál debería ser el nombre de la función de 5 argumentos y discutir API adicional más adelante (como 3 y 4 argumentos addmul! ). Pero esta es la "característica" que obtenemos de _no_ usando mul! por lo que es difícil no mezclar.

@andreasnoack es su preocupación acerca de desaprobación / Cambio de nombre resuelto por @simonbyrne 's comentario anterior https://github.com/JuliaLang/julia/issues/23919#issuecomment -431046516? Creo que no hay necesidad de desaprobación.

FYI, acabo de terminar la implementación # 29634. Agradezco que alguien familiarizado con LinearAlgebra pueda revisarlo.

Creo que es más simple y mejor nombrar todo mul! . También evita la depreciación. Si realmente queremos un nombre diferente, muladd es mejor.

Algo más a tener en cuenta cuando se habla de la API mul! :

Cuando scale! fue y quedó absorbido en la transición 0.6 -> 0.7, estaba un poco triste porque para mí, la multiplicación escalar (una propiedad de los espacios vectoriales) era muy diferente a la multiplicación de objetos en sí mismos (una propiedad de las álgebras ). No obstante, he adoptado completamente el enfoque mul! , y aprecio mucho la capacidad de rmul!(vector,scalar) y lmul!(scalar,vector) cuando la multiplicación de escalares no es conmutativa. Pero ahora cada día me molesta más el nombre no juliano de otras dos operaciones de espacio vectorial en el lugar: axpy! y su generalización axpby! . ¿Podrían estos también ser absorbidos en mul! / muladd! / addmul! ? Aunque es un poco extraño, si uno de los dos factores en A*B ya es un escalar, no hay necesidad de un factor escalar adicional α .
Pero tal vez entonces, en analogía con

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

también podría haber un

add!(Y, X, α, β)

para reemplazar axpby! .

@andreasnoack es su preocupación acerca de desaprobación / Cambio de nombre resuelto por @simonbyrne 's comentario anterior # 23919 (comentario)? Creo que no hay necesidad de desaprobación.

Consulte el último párrafo de https://github.com/JuliaLang/julia/issues/23919#issuecomment -430952179. Sigo pensando que no vale la pena introducir una nueva función. Si lo hacemos de todos modos, creo que deberíamos desaprobar los 5 argumentos actuales mul! .

@Jutho Creo que cambiar el nombre de acp(b)y! a add! sería una buena idea.

Vea el último párrafo del # 23919 (comentario) . Sigo pensando que no vale la pena introducir una nueva función.

Sí, lo leí y respondí que el mul! cinco argumentos no estaba documentado y no formaba parte de la API pública. Entonces, técnicamente, no hay necesidad de desaprobación. Vea el último párrafo de https://github.com/JuliaLang/julia/issues/23919#issuecomment -430975159 (Por supuesto, sería bueno tener una desaprobación de todos modos, así que ya lo implementé en # 29634).

Aquí, asumo que la declaración de la API pública debido a la documentación de una firma (por ejemplo, mul!(C, A, B) ) no se aplica a otras firmas (por ejemplo, mul!(C, A, B, α, β) ). Si no es el caso, creo que Julia y su stdlib están exponiendo demasiados aspectos internos. Por ejemplo, aquí está la firma documentada de Pkg.add

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

mientras que la definición real es

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 la existencia de la documentación de al menos una firma de Pkg.add implica que otras firmas son API públicas, Pkg.jl no puede eliminar el comportamiento debido a los detalles de implementación sin tocar la versión principal, por ejemplo: Pkg.add(...; mode = :develop) ejecuta Pkg.develop(...) ; todos los argumentos de palabras clave para Context! son compatibles (lo que en realidad puede ser la intención).

Pero esta es solo mi impresión de todos modos. ¿Considera que mul!(C, A, B, α, β) ha sido tan público como mul!(C, A, B) ?

Creo que estamos hablando entre nosotros. Lo que estoy diciendo es que (todavía) no creo que valga la pena introducir otra función. De ahí mi referencia a mi comentario anterior. Esto es independiente de la discusión sobre la desaprobación de mul! de cinco argumentos.

Sin embargo, si decidimos agregar otra función, creo que sería mejor desaprobar cinco argumentos mul! lugar de simplemente romperlos. Por supuesto, no se usa tan comúnmente como mul! tres argumentos, pero ¿por qué no desaprobarlo en lugar de simplemente romperlo?

Esto es independiente de la discusión sobre la desaprobación de mul! de cinco argumentos.

Mi interpretación del último párrafo de su comentario https://github.com/JuliaLang/julia/issues/23919#issuecomment -430952179 fue que reconoció los beneficios que @simonbyrne enumeró https://github.com/JuliaLang/julia/issues / 23919 # issuecomment -430809383 para una nueva función de cinco argumentos, pero consideró que eran menos valiosos en comparación con mantener la _public API_ (como mencionó "cambio de nombre" y "obsolescencia"). Por eso pensé que era importante considerar si el mul! cinco argumentos ha sido público o no.

Pero también mencionaste la justificación de tener "una función extra" que, supongo, es a lo que te refieres ahora. ¿Está argumentando que los cálculos _C = AB_ y _C = αAB + βC_ son lo suficientemente similares como para que el mismo nombre pueda describir a ambos? De hecho, no estoy de acuerdo, ya que puede haber otras formas de generalizar tres argumentos mul! : por ejemplo, ¿por qué no mul!(y, A₁, A₂, ..., Aₙ, x) para _y = A₁ A₂ ⋯ Aₙ x_ https://github.com/JuliaLang/julia / issues / 23919 # issuecomment -402953987?

¿Por qué no desaprobarlo en lugar de simplemente romperlo?

Como dije en los comentarios anteriores, estoy de acuerdo en desaprobar los cinco argumentos mul! es lo correcto _si_ tuviéramos que introducir otra función. Este código ya existe en mi PR # 29634.

¿Está argumentando que los cálculos C = AB y C = αAB + βC son lo suficientemente similares como para que el mismo nombre pueda describir a ambos?

Sí, ya que el primero es solo el último con β=0 . Es justo argumentar que muladd! / addmul! es un nombre más preciso para C = αAB + βC pero llegar allí requeriría la introducción de otra función de multiplicación de matrices ( muladd! / addmul! ) o renombrar mul! y no creo que valga la pena ahora. Si esto hubiera surgido en la primavera, habría sido más fácil considerar un cambio.

De hecho, no estoy de acuerdo ya que puede haber otras formas de generalizar mul de tres argumentos:

Julia definió los métodos de multiplicación de matrices en el lugar sin los argumentos α y β pero la tradición de multiplicación de matrices se basa realmente en BLAS-3 y allí la función general de multiplicación de matrices es C = αAB + βC .

renombrar mul!

¿Te refieres a cambiarle el nombre en stdlib o en el módulo / código de usuario posterior? Si te refieres al primero, ya está hecho (para LinearAlgebra y SparseArrays) en # 29634, así que no creo que debas preocuparte por eso. Si te refieres a lo último, creo que nuevamente se reduce a una discusión pública o no.

la tradición de la multiplicación de matrices se basa realmente en BLAS-3

Pero Julia ya se apartó de la convención de nomenclatura de BLAS. Entonces, ¿no sería bueno tener un nombre más descriptivo?

¿Te refieres a cambiarle el nombre en stdlib o en el módulo / código de usuario posterior?

29634 no cambia el nombre de la función mul! . Agrega la nueva función addmul! .

Pero Julia ya se apartó de la convención de nomenclatura de BLAS.

No me refiero al nombramiento. Al menos no exactamente, ya que Fortran 77 tiene algunas limitaciones que no tenemos tanto en términos de nombres de funciones como de envío. Hablo de lo que se está calculando. La función general de multiplicación de matrices en BLAS-3 calcula C = αAB + βC y en Julia, ha sido mul! (fka A_mul_B! ).

Entonces, ¿no sería bueno tener un nombre más descriptivo?

Lo haría, y lo he dicho varias veces. El problema es que no es mucho mejor que debamos tener dos funciones de multiplicación de matrices que básicamente hacen lo mismo.

29634 no cambia el nombre de la función mul! . Agrega la nueva función addmul! .

Lo que quise decir es que mul! cinco argumentos fue renombrado a addmul! .

El problema es que no es mucho mejor que debamos tener dos funciones de multiplicación de matrices que básicamente hacen lo mismo.

Siento que si son básicamente iguales o no es algo subjetivo. Creo que tanto _C = αAB + βC_ como _Y = A₁ A₂ ⋯ Aₙ X_ son una generalización matemáticamente válida de _C = AB_. A menos que _C = αAB + βC_ sea la generalización única, no creo que el argumento sea lo suficientemente fuerte. También depende de si conoce BLAS API y no estoy seguro si ese es el conocimiento básico para los usuarios típicos de Julia.

Además, _C = AB_ y _C = αAB + βC_ son computacionalmente muy diferentes en que el contenido de C se usa o no. Es un parámetro de solo salida para el primero y un parámetro de entrada-salida para el segundo. Creo que esta diferencia merece una pista visual. Si veo mul!(some_func(...), ...) y mul! tiene la forma de cinco argumentos, tengo que contar el número de argumentos (lo cual es difícil cuando son el resultado de la llamada a la función ya que tiene que hacer coincidir los paréntesis) para ver si some_func hace algún cálculo o simplemente una asignación. Si tenemos addmul! entonces puedo esperar inmediatamente que some_func en mul!(some_func(...), ...) solo haga la asignación.

Siento que si son básicamente iguales o no es algo subjetivo. Creo que tanto C = αAB + βC como Y = A₁ A₂ ⋯ Aₙ X son una generalización matemáticamente válida de C = AB. A menos que C = αAB + βC sea la generalización única, no creo que el argumento sea lo suficientemente fuerte.

Puede que no sea la generalización única, sin embargo, es una generalización que se puede calcular a un costo aproximadamente idéntico y que forma una primitiva útil para construir otros algoritmos de álgebra lineal. En muchas ocasiones he querido tener una beta distinta de cero al implementar varios algoritmos relacionados con el álgebra lineal, y siempre tuve que recurrir a BLAS.gemm! . Otras generalizaciones como la que menciona no se pueden calcular de todos modos en una sola toma sin temporales intermedios, por lo que una versión en el lugar es mucho menos útil. Además, no son tan útiles en general como una operación primitiva.

También depende de si conoce BLAS API y no estoy seguro si ese es el conocimiento básico para los usuarios típicos de Julia.

Siempre que los argumentos predeterminados α=1 y β=0 sigan en su lugar, los tres argumentos mul! harán lo que cualquier usuario de Julia esperaría razonablemente sin los antecedentes de BLAS. Para las opciones más avanzadas hay que consultar el manual, como se tendría que hacer con cualquier idioma y función. Además, esta única llamada mul! no solo reemplaza a gemm sino también a gemv y trmv (que curiosamente no tiene α y β parámetros en la API BLAS) y probablemente muchos otros.

Estoy de acuerdo en que BLAS-3 es la generalización correcta en términos de cálculo y se compone muy bien. Estoy planteando otra posible generalización solo porque creo que no es "lo suficientemente única" para justificar el uso del mismo nombre. Consulte también el argumento de solo salida frente a entrada-salida en el último párrafo de https://github.com/JuliaLang/julia/issues/23919#issuecomment -441267056. Creo que un nombre diferente facilita la lectura / revisión del código.

Además, esta única llamada mul! no solo reemplaza a gemm sino también a gemv y trmv (que curiosamente no tiene α y β parámetros en la API BLAS) y probablemente muchos otros.

Sí, ya implementado en # 29634 y está listo para funcionar una vez que se decida el nombre (y se revise)

Estoy teniendo dificultades para seguir esta conversación (es un poco larga y extensa ... ¡lo siento!), ¿La propuesta principal es algo como mul!(C, A, B; α=true, β=false) ?

No creo que el argumento de palabra clave para α y β esté sobre la mesa. Por ejemplo, @andreasnoack descartó los argumentos de palabras clave en https://github.com/JuliaLang/julia/issues/23919#issuecomment -365762889. @simonbyrne mencionó argumentos de palabras clave en https://github.com/JuliaLang/julia/issues/23919#issuecomment -426881998 pero su última sugerencia https://github.com/JuliaLang/julia/issues/23919#issuecomment -431046516 es posicional argumentos.

Aún no hemos decidido el nombre (es decir, mul! vs addmul! vs muladd! ) y creo que ese es el tema central (o al menos ese es mi deseo).

¿Cómo resuelves habitualmente este tipo de controversias? ¿Votación? ¿Triaje?

¿Es la propuesta principal algo así como mul! (C, A, B; α = verdadero, β = falso)?

Me gusta esto, pero sin los kwargs.

No vi la palabra clave. También dudo en agregar palabras clave Unicode. Creo que los argumentos posicionales con estos valores predeterminados están bien. En cuanto a la próxima votación, la mía está en mul! . Creo que esta es una generalización de mul! que es lo suficientemente específica y útil como para no necesitar un nombre nuevo.

Solo para recopilar datos (al menos por el momento), hagamos la votación:

¿Cuál es el nombre de su función favorita para _C = αAB + βC_?

  • : +1: mul!
  • : -1: addmul!
  • : sonrisa: muladd!
  • : tada: algo más

Para mí, addmul! parece describir (A+B)C lugar de AB + C .

Al principio voté por mul! luego miré la operación y pensé "está haciendo una multiplicación y luego una suma, obviamente deberíamos llamarlo muladd! . Ahora no puedo pensar en llamarlo El hecho de que esté en su lugar está claramente indicado por ! y la parte de escala parece adecuada para argumentos de palabras clave.

está haciendo una multiplicación y luego una suma, obviamente deberíamos llamarlo muladd!

Solo si usa el valor predeterminado β=true , pero luego para cualquier otro valor es simplemente algo más general nuevamente. Entonces, ¿cuál es el punto de no llamarlo mul! , donde cualquier otro valor que no sea el valor predeterminado β=false también te da algo más general? ¿Y cómo ordenarías los argumentos, en comparación con muladd(x,y,z) = x*y + z ? Será algo confuso, ¿no?

Creo que muladd! tiene el inconveniente de sonar descriptivo cuando no lo es: un nombre descriptivo sería algo como scalemuladd! para mencionar la parte de escala.

Así que prefiero mul! ya que es lo suficientemente anodino como para no generar expectativas.

Dicho esto, llamo a la versión perezosa en LazyArrays.jl MulAdd .

Prefiero muladd! a mul! porque es bueno diferenciar una función que nunca usa el valor de C ( mul! ) de una función que lo usa ( muladd! ).

  • Técnicamente es una multiplicación de matrices: [AC] * [Bα; Iβ] o, véase el comentario a continuación, [αA βC] * [B; YO]
  • ~ Ya tenemos un mul! de 5 argumentos para matrices dispersas, lo mismo para linalg denso sería consistente ~ (no es un argumento nuevo)

Entonces estaría a favor de llamarlo mul! .

  • Técnicamente es una multiplicación de matrices: [AC] * [Bα; Iβ]

... si eltype tiene multiplicación conmutativa

... si eltype es conmutativo.

IIRC de una discusión con @andreasnoack Julia simplemente define gemm / gemv como y <- A * x * α + y * β porque eso tiene más sentido.

@haampie ¡ Eso es bueno! Como lo implementé al revés en # 29634.

Esto es de ayuda limitada, pero

       C = α*A*B + β*C

es la mejor manera que se me ocurre para expresar la operación y, por lo tanto, tal vez una macro <strong i="8">@call</strong> C = α*A*B + β*C o <strong i="10">@call_specialized</strong> ... o algo por el estilo sería una interfaz natural, también para situaciones similares. Entonces la función subyacente se puede llamar como sea.

@mschauer LazyArrays.jl de @dlfivefifty tiene una gran sintaxis para llamar a 5 argumentos mul! como su sintaxis (¡y más!).

Creo que primero debemos establecer una API basada en funciones para que los autores de paquetes puedan comenzar a sobrecargarla para sus matrices especializadas. Entonces la comunidad de Julia puede comenzar a experimentar azúcares para llamarlo.

Solo si usa el valor predeterminado β=true , pero luego para cualquier otro valor es simplemente algo más general nuevamente. Entonces, ¿cuál es el punto de no llamarlo mul! , donde cualquier otro valor que no sea el valor predeterminado β=false también te da algo más general? ¿Y cómo ordenarías los argumentos, en comparación con muladd(x,y,z) = x*y + z ? Será algo confuso, ¿no?

Seguro que hay algo de escala ahí, pero los "huesos" de la operación claramente se multiplican y suman. También estaría bien con muladd!(A, B, C, α=true, β=false) para que coincida con la firma de muladd . Tendría que estar documentado, por supuesto, pero no hace falta decirlo. Me hace desear que muladd tomara la parte aditiva primero, pero el barco ya navegó en esa.

¿Y cómo ordenarías los argumentos en comparación con muladd(x,y,z) = x*y + z ? Será algo confuso, ¿no?

Esta es la razón por la que prefiero addmul! sobre muladd! . Podemos asegurarnos de que el orden de los argumentos no tiene nada que ver con el escalar muladd . (Aunque prefiero muladd! sobre mul! )

FWIW aquí es un resumen de los argumentos hasta ahora. (Traté de ser neutral pero soy pro muladd! / addmul! así que tenlo en cuenta ...)

El principal desacuerdo es que si _C = AB_ y _C = αAB + βC_ son lo suficientemente diferentes como para dar un nuevo nombre a este último.

Son bastante similares porque ...

  1. Es BLAS-3 y se puede componer muy bien. Entonces, _C = αAB + βC_ es una generalización obvia de _C = AB_ (https://github.com/JuliaLang/julia/issues/23919#issuecomment-441246606, https://github.com/JuliaLang/julia/issues/ 23919 # issuecomment-441312375, etc.)

  2. _ " muladd! tiene el inconveniente de sonar descriptivo cuando no lo es: un nombre descriptivo sería algo así como scalemuladd! para mencionar la parte de escala". _ --- https://github.com/ JuliaLang / julia / issues / 23919 # issuecomment -441819470

  3. _ "Técnicamente es una multiplicación de matrices: [AC] * [Bα; Iβ]" _ --- https://github.com/JuliaLang/julia/issues/23919#issuecomment -441825009

Son lo suficientemente diferentes porque ...

  1. _C = αAB + βC_ es más que multiplicar (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. Podría haber otras generalizaciones de mul! como Y = A₁ A₂ ⋯ Aₙ X (https://github.com/JuliaLang/julia/issues/23919#issuecomment-402953987 etc.)

  3. Parámetro de entrada solo vs entrada-salida: es confuso tener una función que use los datos en C según la cantidad de argumentos (https://github.com/JuliaLang/julia/issues/23919#issuecomment -441267056, https://github.com/JuliaLang/julia/issues/23919#issuecomment-441824982)

Otra razón por la que mul! es mejor porque ...:

  1. Sparse matrix ya lo tiene. Por lo que es bueno para la compatibilidad con versiones anteriores. Contraargumento: cinco argumentos mul! no están documentados, por lo que no es necesario considerarlos como una API pública.

y por qué muladd! / addmul! es mejor porque ...:

  1. Podemos tener diferentes "funciones útiles" de tres o cuatro argumentos por mul! y muladd! / addmul! separado (https://github.com/JuliaLang/julia/issues / 23919 # issuecomment-402953987, https://github.com/JuliaLang/julia/issues/23919#issuecomment-431046516, etc.). Contraargumento: escribir mul!(y, A, x, 1, 1) no es muy detallado en comparación con mul!(y, A, x) (https://github.com/JuliaLang/julia/issues/23919#issuecomment-430674934, etc.)

Gracias por el resumen objetivo @tkf

¡También estaría bien con muladd! (A, B, C, α = true, β = false) para que coincida con la firma de muladd.

Espero que para una función llamada mulladd! el valor predeterminado sea β=true . Aún así, creo que este orden de argumentos, que se dicta a partir de muladd , será muy confuso en relación con mul!(C,A,B)

Tal vez esté equivocado, pero creo que la mayoría de las personas / aplicaciones / códigos de alto nivel (que no están satisfechos con solo el operador de multiplicación * ) necesitan mul! . La capacidad de mezclar también βC , con β=1 ( true ) o de otra manera, será utilizada en el código de nivel inferior, por personas que saben que la API BLAS para la multiplicación de matrices permite esta. Supongo que estas personas buscarían esta funcionalidad en mul! , que es la interfaz de Julia establecida para gemm , gemv , ... Agregar un nuevo nombre (que confunde orden de argumentos opuesto) no parece que valga la pena; ¿No veo la ganancia?

Supongo que estas personas buscarían esta funcionalidad en mul! , que es la interfaz de Julia establecida para gemm , gemv , ... Agregar un nuevo nombre (que confunde orden de argumentos opuesto) no parece valer la pena; ¿No veo la ganancia?

Creo que la capacidad de descubrimiento no es un gran problema, ya que simplemente podemos mencionar muladd! en mul! docstring. Aquellos que tengan la habilidad suficiente para conocer BLAS sabrían dónde buscar una API, ¿verdad?

Con respecto a los argumentos posicionales frente a los de palabras clave: aún no se discute aquí, pero creo que C = αAB + βC con α como una matriz diagonal se puede implementar tan eficiente y fácilmente como escalar α . Tal extensión requiere que podamos enviar en el tipo de α cual es imposible con el argumento de palabra clave.

Además, para eltype no conmutativo, es posible que desee calcular de manera eficiente C = ABα + Cβ llamando muladd!(α', B', A', β', C') (orden hipotético de argumentos). Es posible que requiera que pueda enviar en contenedores perezosos Adjoint(α) y Adjoint(β) . (No uso números no conmutativos en Julia personalmente, por lo que probablemente sea muy hipotético).

Estoy de acuerdo con el punto de @Jutho de que esta función de adición múltiple es una API de bajo nivel para programadores expertos como implementadores de bibliotecas. Creo que la extensibilidad tiene una alta prioridad para esta API y el argumento posicional es el camino a seguir.

Otro argumento para evitar el argumento de palabras clave es lo que dijo @andreasnoack antes de https://github.com/JuliaLang/julia/issues/23919#issuecomment -365762889:

Los nombres α y β tampoco son súper intuitivos a menos que conozca BLAS

@tkf , claro, mi argumento es más bien: el número de usos reales de β != 0 será menor que los de β == 0 , y aquellos que lo necesiten no se sorprenderán al encontrar esto un poco más general comportamiento bajo mul! . Por lo tanto, no veo la ganancia de separar esto con un nuevo nombre, especialmente porque el orden de los argumentos está desordenado (con muladd! al menos). Si tiene que ser un método nuevo, también simpatizo con su argumento de addmul! .

aquellos que lo necesiten no se sorprenderán al encontrar este comportamiento un poco más general en mul! .

Estoy de acuerdo con este punto.

Por lo tanto, no veo la ganancia de separar esto con un nuevo nombre,

A menos que vea algunos daños, creo que es una ganancia global ya que hay otras personas que ven beneficios.

especialmente porque el orden de los argumentos está desordenado (con muladd! al menos)

Supongo que lo considerarías un daño y entiendo el punto. Es solo que creo que otros beneficios de muladd! / addmul! son más importantes.

Supongo que lo considerarías un daño y entiendo el punto. Es solo que creo que hay otros beneficios para muladd! / Addmul! son más importantes.

De hecho, ese es el daño, junto con el hecho de que mul! siempre ha sido el único punto de entrada a varias operaciones BLAS relacionadas con la multiplicación, ya sea que esté restringido al no dar acceso completo a α y β. Y ahora, con muladd! , habría dos puntos de entrada diferentes, dependiendo de solo una pequeña diferencia en la operación solicitada que se puede capturar fácilmente mediante un argumento (y, de hecho, que se captura mediante un argumento en la API BLAS) . Creo que fue un error de Julia en primer lugar no ofrecer acceso completo a la API BLAS (así que gracias por arreglar eso @tkf). A pesar de su vieja y horrible convención de nombres de fortran, supongo que estos tipos sabían por qué estaban haciendo las cosas de esta manera. Pero de la misma manera, creo que esta familia de operaciones (es decir, la familia de operaciones de 2 parámetros parametrizadas por α y β) pertenece a un solo punto de entrada, como lo están en BLAS.

En mi opinión, el argumento contrario más válido es la diferencia entre si se accederá o no a los datos originales en C . Pero dado el hecho de que Julia ha adoptado la multiplicación con false como una forma de garantizar un resultado cero, incluso cuando el otro factor es NaN , creo que esto también se ha solucionado. Pero tal vez este hecho deba comunicarse / documentarse mejor (ha pasado un tiempo desde que leí la documentación), y también lo aprendí recientemente. (Es por eso que en KrylovKit.jl, necesito la existencia de un método fill! , para inicializar un tipo de usuario arbitrario similar a un vector con ceros. Pero ahora sé que puedo solo rmul!(x,false) lugar, por lo que no necesito imponer que se implemente fill! ).

Es solo que creo que hay otros beneficios para muladd! / Addmul! son más importantes.

Así que permítanme invertir la pregunta, ¿cuáles son estos otros beneficios de tener un nuevo método? He leído tu resumen de nuevo, pero solo veo el punto de acceder a C , que acabo de comentar.

Le mencioné a mi esposa esta mañana que ha habido una conversación de dos meses en la comunidad de Julia sobre el nombramiento de una operación. Ella sugirió que se llamara "¡Fred!" - sin siglas, sin significado profundo, solo un buen nombre. Solo pongo esto en su nombre.

¡Qué bueno que haya incluido un signo de exclamación! 😄

En primer lugar, por si acaso, permítanme aclarar que mi preocupación casi solo proviene de la legibilidad del código, no de la escritura o la capacidad de descubrimiento. Escribes código una vez pero lo lees muchas veces.

¿Cuáles son estos otros beneficios de tener un nuevo método?

Como comentaste, creo que el argumento del parámetro de salida vs entrada-salida es el más importante. Pero esta es solo una de las razones por las que creo que _C = αAB + βC_ es diferente de _C = AB_. También creo que el simple hecho de que sean diferentes en el sentido de que la expresión anterior es el "superconjunto" estricto de la última necesita una indicación visual clara en el código. Un nombre diferente ayuda a un programador intermedio (o programador avanzado poco enfocado) a leer el código y darse cuenta de que está usando algo más extraño que mul! .

Acabo de revisar la encuesta (debe hacer clic en "Cargar más" arriba) nuevamente y parece que algunos votos se movieron de mul! a muladd! ? La última vez que lo vi, mul! estaba ganando. Grabemos aquí antes de que se muden: riendo:

  • mul! : 6
  • addmul! : 2
  • muladd! : 8
  • algo más: 1

Un poco más en serio, sigo pensando que estos datos no muestran que mul! o muladd! sea ​​más claro que el otro. (Aunque muestra que addmul! es una minoría: sollozo :)

Se siente como si estuviéramos estancados. ¿Cómo seguimos adelante?

¿Simplemente llámalo gemm! lugar?

¡Solo llámalo gemm! ¿en lugar?

Espero que esto sea una broma ... a menos que esté proponiendo gemm!(α, A::Matrix, x::Vector, β, y::Vector) = gemv!(α, A, x, β, y) para matriz * vector.

¿Podríamos dejar la interfaz mul! que ya existe (con matrices escasas) por ahora para que podamos fusionar el PR y tener las mejoras, y preocuparnos por si queremos agregar muladd! en otro PR? ?

Tal vez ya esté claro para todos aquí, pero solo quería enfatizar que la votación no es

  • mul! frente a muladd!

pero

  • mul! vs ( mul! y muladd! )

es decir, tener dos funciones de multiplicación mutantes en lugar de una sola.

Decidí no publicar más ya que cada vez que publicaba a favor de mul! , los votos parecían moverse de mul! a ( mul! y muladd! ).

Sin embargo, ¿tengo una pregunta? Si vamos con el voto de la mayoría actual, y simultáneamente tenemos mul!(C,A,B) y muladd!(A,B,C,α=true,β=true) , y quiero preparar un PR que reemplace axpy! y axpby! con un nombre más juliano add! , debería ser add!(y, x, α=true, β=true) o add!(x, y, α=true, β=true) (donde, para mayor claridad, y está mutado). ¿O algo mas?

En caso de que no sea obvio, muladd!(A,B,C) violaría la convención de que los argumentos mutados van primero .

¿Podríamos dejar la interfaz mul! que ya existe?

@jebej Creo que este argumento de "compatibilidad con versiones anteriores" se discute ampliamente. Sin embargo, no convence a nadie (al mirar la encuesta, no soy solo yo).

¿Preocuparse por si queremos agregar muladd! en otro PR?

Es malo romper la API pública. Entonces, si decimos mul! entonces es mul! para siempre (aunque en teoría LinearAlgebra puede superar su versión principal para romper la API).

Quiero preparar un PR que reemplace axpy! y axpby! con un nombre más juliano add! , debería ser add!(y, x, α=true, β=true) o add!(x, y, α=true, β=true)

@Jutho Gracias, ¡sería genial! Creo que elegir el orden de los argumentos sería sencillo una vez que decidiéramos la firma de llamada de la API de adición múltiple.

muladd!(A,B,C) violaría la convención de que los argumentos mutados van primero .

@simonbyrne Pero (como ya mencionaste en https://github.com/JuliaLang/julia/issues/23919#issuecomment-426881998), lmul! y ldiv! mutan que no es el primer argumento. Por tanto, creo que no es necesario excluir muladd!(A,B,C,α,β) de la elección, sino contarlo como un punto negativo para esta firma.

(Pero yo diría que vayamos con muladd!(α, A, B, β, C) si vamos a tener una API de "orden textual").

Por cierto, una cosa que no entiendo por el resultado de la votación es la asimetría de muladd! y addmul! . Si escribe C = βC + αAB , creo que addmul! es más natural.

@tkf Se trata de qué operación haces primero. Para mí, addmul! sugiere que primero haga una suma y luego multiplique, como en (A+B)C . Por supuesto que es subjetivo. Pero los buenos nombres deberían apelar a la intuición.

Ah, entiendo ese punto.

Como esto todavía está atascado, mi propuesta tendría el patrón de uso que consta de definiciones de función con (yendo con @callexpr para el segundo)

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

y tal vez un formulario más amigable para el envío (con @callname para el segundo)

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

y llama

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

y nadie tiene que preocuparse (o saber) cómo callexpr transforma las operaciones algebraicas en un nombre de función único (que no depende de los símbolos del argumento, solo de las operaciones y el orden de las operaciones).
Pensé un poco en la implementación y debería ser factible.

@mschauer Creo que es una dirección interesante. ¿Puedes abrir una nueva edición? La API que propone puede resolver muchos otros problemas. Creo que debe pasar por un proceso de diseño cuidadoso en lugar de resolver una sola instancia del problema que puede resolver.

Así que escuché el rumor de que la función de congelación de 1.1 es la próxima semana. Aunque faltan "sólo" cuatro meses para el próximo lanzamiento menor, sería muy bueno si pudiéramos tenerlo en 1.1 ...

De todos modos, también necesitamos decidir la firma de la llamada (orden de argumentos y palabra clave o no) antes de fusionar el PR.

Así que vamos a votar de nuevo (ya que he descubierto que es un buen estimulante).

_Si_ usamos muladd! para _C = ABα + Cβ_, ¿cuál es su firma de llamada favorita?

  • : +1: muladd!(C, A, B, α, β)
  • : -1: muladd!(A, B, C, α, β)
  • : sonrisa: muladd!(C, A, B; α, β) (como: +1 :, pero con argumentos de palabra clave)
  • : tada: muladd!(A, B, C; α, β) (como: -1 :, pero con argumentos de palabras clave)
  • : confuso: muladd!(A, B, α, C, β)
  • : corazón: algo más

Si tiene otros nombres de argumentos de palabras clave en mente, vote por los que usan α y β y luego comente qué nombres son mejores.

Como no hemos decidido cuál debería ser el nombre, también debemos hacerlo por mul! :

_Si_ usamos mul! para _C = ABα + Cβ_, ¿cuál es su firma de llamada favorita?

  • : +1: mul!(C, A, B, α, β)
  • : -1: mul!(A, B, C, α, β)
  • : sonrisa: mul!(C, A, B; α, β) (como: +1 :, pero con argumentos de palabra clave)
  • : tada: mul!(A, B, C; α, β) (esto es imposible)
  • : confuso: mul!(A, B, α, C, β)
  • : corazón: algo más

NOTA: No cambiaremos la API existente mul!(C, A, B)

NOTA: No cambiaremos la API existente mul!(C, A, B)

No había prestado suficiente atención a este hecho; ya tenemos mul! y esto es lo que significa:

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

Calcula el producto matriz-matriz o matriz-vector A*B y almacena el resultado en Y , sobrescribiendo el valor existente de Y . Tenga en cuenta que Y no debe tener un alias con A o B .

Dado eso, parece muy natural expandir eso de esta manera:

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

Calcula el producto matriz-matriz o matriz-vector A*B y almacena el resultado en Y , sobrescribiendo el valor existente de Y . Tenga en cuenta que Y no debe tener un alias con A o B . Si se proporciona un valor escalar, α , entonces se calcula α*A*B lugar de A*B . Si se proporciona un valor escalar, β , entonces se calcula α*A*B + β*Y su lugar. La misma restricción de alias se aplica a estas variantes.

Sin embargo, creo que hay una gran preocupación con esto: parece al menos tan natural que mul!(Y, A, B, C, D) calcular A*B*C*D en su lugar en Y y esa noción genérica choca muy mal con mul!(Y, A, B, α, β) computación α*A*B + β*C . Además, me parece que calcular A*B*C*D en Y es algo que sería útil y posible de hacer de manera eficiente, evitando asignaciones intermedias, por lo que realmente no querría bloquear ese significado .

Con esa otra generalización natural de mul! en mente, aquí hay otro pensamiento:

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

Esto encaja en el modelo general de mul!(out, args...) donde calcula y escribe en out multiplicando args juntos. Se basa en el envío para hacer frente a que α sea ​​escalar en lugar de convertirlo en un caso especial; es solo otra cosa que estás multiplicando. Cuando α es un escalar y A , B y Y son matrices, podemos enviar a BLAS para hacer esto de manera súper eficiente. De lo contrario, podemos tener una implementación genérica.

Además, si está en un campo no conmutativo (por ejemplo, cuaterniones), puede controlar de qué lado ocurre la escala en α : mul!(Y, A, B, α) escala en α en la derecha en lugar de la izquierda:

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

Sí, no podemos llamar a BLAS para cuaterniones, pero es genérico y probablemente aún podamos hacerlo de manera razonablemente eficiente (tal vez incluso transformándolo en algunas llamadas BLAS de alguna manera).

Suponiendo ese enfoque para Y .= α*A*B la siguiente pregunta es: ¿qué hay de escalar e incrementar Y ? Empecé a pensar en palabras clave para eso, pero luego me vino a la mente el campo no conmutativo que se sentía demasiado incómodo y limitado. Entonces, comencé a pensar en esta API en su lugar, lo que parece un poco extraño al principio, pero tengan paciencia conmigo:

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

Un poco extraño, pero funciona. Y en un campo no conmutativo, puede solicitar multiplicar Y por β a la derecha así:

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

En total generalidad en un campo no conmutativo, podría escalar tanto a la izquierda como a la derecha de esta manera:

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

Ahora, por supuesto, esto es un poco extraño y no hay una operación BLAS para esto, pero es una generalización de GEMM que nos permite expresar un _mucha_ de cosas y que podemos enviar a las operaciones BLAS de manera trivial sin siquiera hacer ningún if / else desagradable. ramas.

Realmente me gusta la sugerencia de @StefanKarpinski como una llamada API subyacente, pero también estoy pensando si así es como realmente queremos exponer las cosas a los usuarios. En mi opinión, al final debería verse simple, como una macro asociada:

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

La función subyacente sería algo como lo que propone

Pero deberíamos ir más lejos aquí. Realmente creo que, si crea una API para ella y una función genérica, alguien creará una biblioteca de Julia que lo haga de manera eficiente, así que estoy de acuerdo en que no deberíamos limitarnos a BLAS aquí. Cosas como MatrixChainMultiply.jl ya están construyendo DSL para cálculos matriciales múltiples y DiffEq está haciendo lo suyo con expresiones de operadores afines. Si solo tenemos una representación para una expresión afín en Base, podemos definir todo nuestro trabajo para que esté en la misma cosa.

@dlfivefifty examinó antes el álgebra lineal perezosa, creo que realmente debería revivirse aquí. La construcción de representaciones perezosas de transmisión fue crucial para que las operaciones de elementos funcionen en arreglos abstractos y en hardware computacional alternativo. Necesitamos lo mismo para el álgebra lineal. Una representación de expresiones algebraicas lineales nos permitiría definir nuevos núcleos BLAS sobre la marcha desde un Julia BLAS o transferir las ecuaciones a una GPU / TPU.

Esencialmente, todos los cálculos en la computación científica se reducen a operaciones algebraicas lineales y por elementos, por lo que tener una descripción de alto nivel de ambos parece fundamental para construir herramientas para metaprogramar y explorar nuevos diseños.

Tendría que pensar más en esta propuesta pero, por ahora, solo comentaré que no creo que le gustaría calcular A*B*C sin un temporal. Me parece que tendrías que pagar con muchas operaciones aritméticas para evitar lo temporal.

No creo que le gustaría calcular A*B*C sin un archivo temporal.

Sin embargo, para mul! ya tiene una matriz de salida. No estoy seguro de si eso ayuda o no. En cualquier caso, parece un detalle de implementación. La API mul!(Y, A, B, C...) expresa lo que desea calcular y permite que la implementación elija la mejor manera de hacerlo, que era el objetivo general aquí.

Realmente me gusta la sugerencia de @StefanKarpinski como una llamada API subyacente, pero también estoy pensando si así es como realmente queremos exponer las cosas a los usuarios.

@ChrisRackauckas : Creo que las cosas en las que te estás metiendo pueden y deben explorarse en paquetes externos: pereza, escribir el cálculo que deseas y dejar que algún tipo de optimización pase para seleccionar piezas que coincidan con ciertos patrones algebraicos que sabe cómo optimizar, etc. Usar mul! esta manera parece el tipo de operación genérica pero fácil de entender que queremos en este nivel.

Tenga en cuenta que no hay un debate real sobre mul!(Y, α, A, B) ; tiene que significar básicamente Y .= α*A*B ya que ¿qué más significaría? Entonces, para mí, la única pregunta abierta aquí es si usar una tupla con una matriz y escalares izquierdo y / o derecho es una forma razonable de expresar que queremos incrementar y escalar la matriz de salida. Los casos generales serían:

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

No se permitiría nada más para el primer argumento. Esto podría adoptarse como una convención más general para otras operaciones en las que tenga sentido sobrescribir o acumular en la matriz de salida, opcionalmente combinado con escalado.

¡No se me ocurrió "fusionar" mul!(out, args...) y una interfaz similar a GEMM! Me gusta la extensibilidad de la misma (pero luego empiezo a escribir la respuesta a continuación y ahora no estoy seguro ...)

Pero mi preocupación es que si es fácil de usar como interfaz de sobrecarga. Necesitamos confiar en el sistema de tipos para que funcione bien con tuplas anidadas. ¿Las tuplas anidadas funcionan tan bien como las tuplas planas en el sistema de tipos de Julia? Me pregunto si algo como " Tuple{Tuple{A1,B1},C1,D1} es más específico que Tuple{Tuple{A2,B2},C2,D2} iff Tuple{A1,B1,C1,D1} es más específico que Tuple{A2,B2,C2,D2} ". De lo contrario, sería complicado usarlo como API de sobrecarga.

Tenga en cuenta que necesitamos enviar en tipos escalares para usar el truco de reinterpretación para las matrices complejas (esto es de PR # 29634, así que no preste atención al nombre de la función):

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

Otra preocupación es que se trata de una interfaz algo limitada para un ejecutor de gráficos de cálculo. Creo que el propósito principal de la interfaz de adición múltiple es proporcionar una API de sobrecarga para permitir que los implementadores de bibliotecas definan un pequeño núcleo de cálculo reutilizable que se pueda implementar de manera eficiente. Significa que solo podemos implementar _C = ABα_ y no, por ejemplo, _αAB_ (ver https://github.com/JuliaLang/julia/pull/29634#issuecomment-443103667). El soporte de _α₁ABα₂_ para eltype no conmutativo requiere una matriz temporal o aumentar el número de operaciones aritméticas. No está claro qué quiere un usuario e idealmente esto debería ser configurable. En este punto, necesitamos una representación gráfica de cálculo separada del mecanismo de ejecución. Creo que es mejor explorar esto en paquetes externos (por ejemplo, LazyArrays.jl, MappedArrays.jl). Sin embargo, si podemos encontrar una estrategia de implementación que cubra la mayor parte del caso de uso en algún momento, tendría sentido usar mul! como punto de entrada principal. Creo que esta es otra razón más para favorecer muladd! ; Asignar un espacio para la futura API de llamada.

Tendría que pensar más en esta propuesta pero, por ahora, solo comentaré que no creo que le gustaría calcular A B C sin un temporal. Me parece que tendrías que pagar con muchas operaciones aritméticas para evitar lo temporal.

De hecho, puede probar que cualquier contracción de un número arbitrario de tensores, la forma más eficiente de evaluar todo el asunto siempre es usando contracciones por pares. Entonces, multiplicar varias matrices es solo un caso especial de eso, debe multiplicarlas por pares (el mejor orden es, por supuesto, un problema no trivial). Por eso creo que mul!(Y,X1,X2,X3...) no es una primitiva tan útil. Y al final, eso es lo que creo que es mul! , es una operación primitiva que los desarrolladores pueden sobrecargar para sus tipos específicos. Cualquier operación más complicada se puede escribir usando una construcción de nivel superior, por ejemplo, usando macros, y por ejemplo se construye un gráfico de cálculo, que al final se evalúa llamando a operaciones primitivas como mul! . Eso sí, ese primitivo podría ser lo suficientemente general como para incluir casos como el no conmutativo que menciona @StefanKarpinski .

Siempre que no se trate de multiplicación de matrices / contracción de tensor, es cierto que pensar en términos de operaciones primitivas no es tan útil y puede ser beneficioso fusionar todo como lo hace la radiodifusión.

En general, estoy de acuerdo en que sería bueno tener un tipo de gráfico de representación / cálculo perezoso predeterminado en Base, pero no creo que mul! sea ​​la forma de construirlo.

@tkf :

Pero mi preocupación es que si es fácil de usar como interfaz de sobrecarga. Necesitamos confiar en el sistema de tipos para que funcione bien con tuplas anidadas. ¿Las tuplas anidadas funcionan tan bien como las tuplas planas en el sistema de tipos de Julia?

Sí, todos somos buenos en ese frente. No estoy seguro de dónde entra el anidamiento, pero pasar algunas cosas en una tupla es tan eficiente como pasarlas todas como argumentos inmediatos; se implementa exactamente de la misma manera.

Significa que solo podemos implementar _C = ABα_ y no, por ejemplo, _αAB_

Estoy confundido ... puedes escribir mul!(C, A, B, α) y mul!(C, α, A, B) . Incluso podrías escribir mul!(C, α₁, A, α₂, B, α₃) . Esta parece ser, con mucho, la API de multiplicación de matrices genérica más flexible que se ha propuesto hasta ahora.

Otra preocupación es que se trata de una interfaz algo limitada para un ejecutor de gráficos de cálculo. Creo que el propósito principal de la interfaz de adición múltiple es proporcionar una API de sobrecarga para permitir que los implementadores de bibliotecas definan un pequeño núcleo de cálculo reutilizable que se pueda implementar de manera eficiente.

En este punto, necesitamos una representación gráfica de cálculo separada del mecanismo de ejecución.

Ese puede ser el caso, pero este no es el lugar para ello, eso puede y debe desarrollarse en paquetes externos. Todo lo que necesitamos para resolver este problema específico es una API de multiplicación de matrices que generalice lo que se puede enviar a las operaciones BLAS, que es exactamente lo que hace.

@Jutho

Entonces, multiplicar varias matrices es solo un caso especial de eso, debe multiplicarlas por pares (el mejor orden es, por supuesto, un problema no trivial). Por eso creo que mul!(Y,X1,X2,X3...) no es una primitiva tan útil.

La operación mul! permitiría a la implementación elegir el orden de multiplicación, que es una propiedad útil. De hecho, la capacidad de hacerlo potencialmente fue la razón por la que hicimos el análisis de la operación * como n-ary en primer lugar y el mismo razonamiento se aplica aún más a mul! ya que si lo está usando , presumiblemente le importa lo suficiente el rendimiento.

En general, no puedo decir si está argumentando a favor o en contra de mi propuesta de mul! .

No estoy seguro de dónde entra el anidamiento, pero pasar algunas cosas en una tupla es tan eficiente como pasarlas todas como argumentos inmediatos.

No me preocupaba la eficiencia, sino el despacho y las ambigüedades de los métodos, ya que incluso LinearAlgebra actual es algo frágil (lo que puede deberse a la falta de mi comprensión del sistema de tipos; a veces me sorprende). Estaba mencionando la tupla anidada ya que pensé que la resolución del método se realiza mediante la intersección del tipo de tupla de todos los argumentos posicionales. Eso te da una tupla plana. Si usa una tupla en el primer argumento, tiene una tupla anidada.

Significa que solo podemos implementar _C = ABα_ y no, por ejemplo, _αAB_

Estoy confundido ... puedes escribir mul!(C, A, B, α) y mul!(C, α, A, B) .

Quería decir "solo podemos implementar _C = ABα_ tan eficientemente como _C = AB_ y otros candidatos como _αAB_ no se pueden implementar de manera eficiente en todas las combinaciones de tipos de matriz". (Por eficiencia me refiero a la gran complejidad del tiempo O). No estoy muy seguro de que este sea realmente el caso, pero al menos para la matriz dispersa, otras dos opciones están descartadas.

En este punto, necesitamos una representación gráfica de cálculo separada del mecanismo de ejecución.

Ese puede ser el caso, pero este no es el lugar para ello, eso puede y debe desarrollarse en paquetes externos.

Ese es exactamente mi punto. Sugiero ver esta API como un bloque de construcción mínimo para tal uso (por supuesto, ese no es todo el propósito). La implementación y el diseño de vararg mul! se pueden realizar después de que la gente haya explorado el espacio de diseño en paquetes externos.

El envío de tipos incluso para el actual mul! ya está "roto": hay un crecimiento combinatorio en las anulaciones de ambigüedad necesarias para trabajar con tipos de arreglos componibles como SubArray y Adjoint .

La solución es usar rasgos, y LazyArrays.jl tiene una versión de prueba de concepto de mul! con rasgos.

Pero esto es más una discusión sobre implementación que API. Pero usar tuplas para agrupar términos se siente mal: ¿no es para eso para lo que está el sistema de tipos? En cuyo caso llegará a la solución LazyArrays.jl.

¡El mul! La operación permitiría a la implementación elegir el orden de multiplicación, que es una propiedad útil. De hecho, la capacidad de hacer eso potencialmente fue la razón por la que hicimos la operación * parse como n-ary en primer lugar y el mismo razonamiento se aplica aún más a mul! ya que si lo está usando, presumiblemente se preocupa lo suficiente por el rendimiento.

Que * analice como n -ary es extremadamente útil. Lo uso en TensorOperations.jl para implementar la macro @tensoropt , que de hecho optimiza el orden de contracción. Que encuentro una versión n -ary de mul! menos útil es porque tiene poco sentido, desde una perspectiva de eficiencia, en proporcionar un lugar preasignado para poner el resultado, si todos los intermedios las matrices todavía tienen que ser asignadas dentro de la función y luego gc'ed. De hecho, en TensorOperations.jl, varias personas notaron que la asignación de temporarios grandes es uno de los lugares donde el gc de Julia se está desempeñando realmente mal (lo que a menudo lleva a tiempos de gc del 50%).

Por lo tanto, restringiría mul! a lo que es realmente una operación primitiva, como también lo defiende @tkf si entiendo correctamente: multiplicar dos matrices en una tercera, con coeficientes posiblemente escalares. Sí, podemos pensar en la forma más general de hacer esto para álgebras no conmutativas, pero por ahora creo que la necesidad inmediata es un acceso conveniente a la funcionalidad proporcionada por BLAS (gemm, gemv, ...) que Julia's mul! Carece de envoltorio

No me desagrada tu propuesta con tuplas, pero puedo prever una posible confusión
Restringir del caso 4 al caso 2 de 3 parece implicar valores predeterminados β₁ = 1 y β₂ = 1 (o en realidad true ). Pero luego, si no se especifica ninguno, de repente significa β₁ = β₂ = 0 ( false ). Claro, la sintaxis es ligeramente diferente, ya que escribe mul!(Y, args...) , no mul!((Y,), args...) . Al final, es una cuestión de documentación, así que solo quería señalar esto.

Entonces, en resumen, no, no me opongo realmente a esta sintaxis, aunque es un nuevo tipo de paradigma que se está introduciendo y que probablemente también debería seguirse en otros lugares. A lo que me opongo es a querer generalizar inmediatamente esto a la multiplicación de un número arbitrario de matrices, lo que, como se argumentó anteriormente, no veo el beneficio de.

@dlfivefifty : Pero esta es más una discusión sobre implementación que API. Pero usar tuplas para agrupar términos se siente mal: ¿no es para eso para lo que está el sistema de tipos? En cuyo caso llegará a la solución LazyArrays.jl.

Pero no vamos a utilizar arreglos perezosos completos aquí, ya hay LazyArrays para eso. Mientras tanto, necesitamos alguna forma de expresar la escala Y . Usar tuplas parece un enfoque simple y ligero para expresar esa pequeña cantidad de estructura. ¿Alguien tiene alguna otra sugerencia? Podríamos tener lscale y / o rscale palabras clave para β₁ y β₂ , pero eso no se siente más elegante y perderíamos la capacidad de despacho sobre eso, lo cual no es crucial pero es bueno tenerlo.

@Jutho : Por lo tanto, restringiría mul! a lo que es verdaderamente una operación primitiva, como también lo defiende @tkf si lo entiendo correctamente: multiplicar dos matrices en una tercera, posiblemente con coeficientes escalares. Sí, podemos pensar en la forma más general de hacer esto para álgebras no conmutativas, pero por ahora creo que la necesidad inmediata es un acceso conveniente a la funcionalidad proporcionada por BLAS (gemm, gemv, ...) que Julia's mul! Carece de envoltorio

Estoy bien con solo definir un pequeño subconjunto de operaciones para mul! , quizás incluso solo las que corresponden estructuralmente a llamadas BLAS válidas. Eso sería:

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

¿A que final? ¿Por qué permitir tanta variación en cómo expresar las operaciones BLAS?

  1. Porque permite a las personas expresar su intención, si la intención es multiplicar a la izquierda, a la derecha o a ambos, ¿por qué no permitir que la gente exprese eso y elija la implementación correcta?

  2. Podemos tener alternativas genéricas que hacen lo correcto incluso para tipos de elementos no conmutativos.

El objetivo de este número es tener una generalización de la multiplicación de matrices en el lugar que subsume gemm! ¡siendo más genérico que gemm !. De lo contrario, ¿por qué no seguir escribiendo gemm! ?

Pero no vamos a utilizar arreglos perezosos completos aquí

No estoy diciendo "matrices perezosas completas", sugiero perezoso en el mismo sentido que Broadcasted , que finalmente se elimina en tiempo de compilación. Básicamente, agregaría un Applied para representar la aplicación perezosa de una función y en lugar de usar tuplas (que no contienen contexto) tendrías algo como

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

Esto podría estar cubierto de azúcar como la notación .* transmisión para que sea más legible, pero creo que esto es inmediatamente claro lo que se quiere, a diferencia de la propuesta basada en tuplas.

@StefanKarpinski , como dije antes, ciertamente estoy de acuerdo en que deberíamos contemplar una interfaz que sea a prueba de futuro y se generalice correctamente a otros tipos de números. El único problema, creo, es que su lista no está completa. En principio, podrías tener:

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

y todas las versiones reducidas del mismo, es decir, si los 5 argumentos escalares pueden estar ausentes, eso es 2 ^ 5 = 32 posibilidades diferentes. Y esto combinado con todas las posibilidades de diferentes matrices o vectores.

Estoy de acuerdo con @dlfivefifty en que un enfoque similar al de una transmisión es más factible.

Sí, me di cuenta de que omití algunas de las opciones, pero 32 métodos no me parecen una locura, después de todo, no tenemos que escribirlos a mano. La adición de un "sistema de emisión similar" o un sistema de evaluación perezosa que nos permite escribir materialize!(applied(+, applied(*, α, A, B), applied(*, β, C))) se parece como una adición mucho más grande y muy lejos del alcance de este problema. Todo lo que queremos es alguna forma de deletrear la multiplicación de matrices general que sea genérica y que nos permita enviarla a BLAS. Si no podemos estar todos de acuerdo en eso, me inclino a dejar que la gente continúe llamando directamente al gemm! .

Sí, probablemente sea cierto; Supuse que sería más fácil con argumentos escalares en la parte posterior, proporcionar fácilmente valores predeterminados. Pero si con algo de metaprogramación @eval podemos generar fácilmente las 32 definiciones, eso es igualmente bueno. (Tenga en cuenta que, como seguramente sabrá, mul no solo es gemm! sino también gemv y trmm y ...).

Permítanme agregar que no es solo una envoltura BLAS. Hay otros métodos especializados de pura Julia en stdlib. Además, es importante tener esto como una API de sobrecarga: los autores de paquetes pueden definir mul! para sus tipos de matrices especiales.

Supongo que esta es mi postura:

  1. También podríamos admitir mul!(C, A, B, a, b) ahora, ya que ya existe en SparseArrays.jl
  2. No deberíamos hacer nada más porque el envío en tipos de matriz no se escala bien. (Como mantenedor de BandedMatrices.jl, BlockArrays.jl, LowRankApprox.jl, etc. Puedo afirmar eso por experiencia).
  3. El diseño basado en rasgos se escala bien, pero sería mejor ir con todo y hacer una transmisión como Applied , ya que el patrón de diseño ya está establecido. Esto tendrá que esperar hasta Julia 2.0, con un prototipo que se sigue desarrollando en LazyArrays.jl que se adapta a mis necesidades.

@dlfivefifty ¿Crees que la dificultad en la desambiguación de mul!((Y, β), α, A, B) API es igual a la de mul!(Y, A, B, α, β) ? Teniendo en cuenta envoltorios de matriz como Transpose introduce la dificultad, incluir 2 y 3 tuplas suena como aumentar más la dificultad (aunque sé que la tupla es un caso especial en el sistema de tipos de Julia).

  1. También podríamos admitir mul!(C, A, B, a, b) ahora, ya que ya existe en SparseArrays.jl

El hecho de que alguien haya decidido que mul!(C, A, B, a, b) debería significar C .= b*C + a*A*B sin pensarlo bien no es una buena razón para duplicar esto. Si mul! es la versión local de * entonces no veo cómo mul!(out, args...) puede significar algo más que out .= *(args...) . Francamente, así es como terminas con un sistema que es un desastre de API inconsistentes mal pensadas que solo existen por accidente histórico. La función mul! no se exporta desde SparseArrays _y_ ese método en particular no está documentado, por lo que esta es realmente la razón más endeble posible para consolidar un método mal concebido que probablemente solo se agregó porque la función no estaba ¡No es público! Propongo que deshagamos ese error y eliminemos / cambiemos el nombre de ese método de mul! lugar.

Del resto de esta discusión, parece que no deberíamos hacer nada más, ya que todas las partes interesadas quieren hacer algo más elegante con rasgos y / o pereza fuera de la biblioteca estándar. Estoy bien con eso, ya que borrar cosas siempre es bueno.

Del resto de esta discusión, parece que no deberíamos hacer nada más, ya que todas las partes interesadas quieren hacer algo más elegante con rasgos y / o pereza fuera de la biblioteca estándar. Estoy bien con eso, ya que borrar cosas siempre es bueno.

Parece que te estás hartando un poco, lo cual es comprensible. Sin embargo, no creo que esa conclusión sea cierta. Si está seguro de que la sugerencia actual se puede implementar de una manera que sea escalable y aún así sea conveniente para los desarrolladores de paquetes sobrecargar esa definición para sus propios tipos de matriz y vector (como lo menciona @tkf), esa sería una excelente manera adelante.

En particular, creo que los desarrolladores de paquetes solo necesitan implementar:

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

y tal vez, por ejemplo,

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

mientras que Julia Base (o más bien, la biblioteca estándar LinearAlgebra) se encarga de manejar todos los valores predeterminados, etc.

Creo que los desarrolladores de paquetes solo necesitan implementar:

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

Sugeriría documentar

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

como la firma a sobrecargar. Esto se debe a que otras ubicaciones para α cambian la complejidad del tiempo O grande. Ver: https://github.com/JuliaLang/julia/pull/29634#issuecomment -443103667. Esto le da a los números no conmutativos un tratamiento que no es de primera clase. Pero AFAICT, nadie aquí está usando números no conmutativos y creo que deberíamos esperar hasta que haya una necesidad real.

Una cosa que me gusta del enfoque de mul!((Y, β), α::Diagonal, A, B) para _algunos_ tipos de matriz de A (por ejemplo, Adjoint{_,<:SparseMatrixCSC} ) sin cambiar la complejidad del tiempo . (Esto es importante para mi aplicación). Por supuesto, seguir este camino requeriría una mayor discusión en la API, especialmente sobre cómo consultar la existencia de un método especializado. Aún así, tener la oportunidad de ampliar la API es genial.

Si alguien aclara mi preocupación por las ambigüedades de los métodos, estaré a favor del enfoque de grupo por tupla.

Esto se debe a que otras ubicaciones para α cambian la complejidad del tiempo O grande.

¿Es esto algo de matriz escasa específicamente? No entiendo el argumento, especialmente no para matrices densas. En la implementación a la que se vincula, muestra un caso en el que α está entre A y B ?

Creo que los desarrolladores de paquetes solo necesitan implementar ...

Esto está muy simplificado. Supongamos que tenemos una matriz que se comporta como una matriz escalonada, como PseudoBlockMatrix de BlockArrays.jl. Para soportar completamente gemm! necesitamos anular cada permutación de PseudoBlockMatrix con (1) en sí mismo, (2) StridedMatrix , (3) Adjoint s de sí , (4) Transpose s de sí mismo, (5) Adjoint s de StridedMatrix , (6) Transpose s de StridedMatrix , y posiblemente otros. Esto ya es 6 ^ 3 = 216 combinaciones diferentes. Entonces desea admitir trmm! y debe hacer lo mismo con UpperTriangular , UnitUpperTriangular , sus adjuntos, sus transposiciones, etc. Luego gsmm! con Symmetric y Hermitian .

Pero en muchas aplicaciones no solo queremos trabajar con las matrices, sino también con sus subvistas, particularmente para las matrices de bloques donde queremos trabajar con bloques. Ahora necesitamos agregar cada permuación de vistas de nuestra matriz junto con las 6 combinaciones anteriores.

Ahora tenemos miles de anulaciones, que involucran StridedMatrix , que es un tipo de unión muy complicado. Esto es demasiado para el compilador, lo que hace que el tiempo de using tome minutos, en lugar de segundos.

En este punto, uno se da cuenta de que el actual mul! y, por extensión, las extensiones propuestas de mul! , tiene fallas de diseño y, por lo tanto, los desarrolladores de paquetes no deberían preocuparse por él. Afortunadamente, LazyArrays.jl proporciona una solución temporal utilizando rasgos.

Así que estoy de acuerdo con @StefanKarpinski en dejar las cosas como están hasta que se persiga un rediseño más sustancial, ya que gastar esfuerzos en algo que tiene fallas de diseño no es un buen uso del tiempo de nadie.

@dlfivefifty , me refería solo a cómo se manejan los argumentos escalares. Por supuesto, se mantendrán todas las complicaciones con diferentes tipos de matrices que ya existen actualmente para mul!(C,A,B) .

Esto se debe a que otras ubicaciones para α cambian la complejidad del tiempo O grande.

¿Es esto algo de matriz escasa específicamente? No entiendo el argumento, especialmente no para matrices densas. En la implementación a la que se vincula, muestra un caso en el que α está entre A y B ?

@Jutho Creo que, en general, no se puede poner α en la posición más interna del bucle. Por ejemplo, en este caso puede admitir α₁*A*B*α₃ pero no A*α₂*B

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

Creo que al menos α₁ o α₂ en α₁*A*α₂*B*α₃ tiene que ser 1 para evitar aumentar la complejidad del tiempo asintótico.

@dlfivefifty Pero incluso LazyArrays.jl necesita algunas funciones primitivas para enviar, ¿verdad? Tengo entendido que resuelve el "infierno de despacho" pero no disminuye el número de "núcleos" de computación que la gente tiene que implementar.

No, no hay "primitivo" de la misma manera que Broadcasted no tiene un "primitivo". Pero sí, por el momento, no resuelve la cuestión del "núcleo". Creo que el siguiente paso es rediseñarlo para usar un tipo Applied perezoso con un ApplyStyle . Entonces podría haber MulAddStyle para reconocer operaciones similares a BLAS, de una manera que el orden no importa.

Llamaría materialize! o copyto! una primitiva. Al menos es el componente básico del mecanismo de transmisión. Del mismo modo, supongo que LazyArrays.jl tiene que reducir su representación perezosa a funciones con bucles o ccall s a bibliotecas externas en algún momento, ¿verdad? ¿Sería malo si el nombre de dicha función fuera mul! ?

Esto está muy simplificado. Supongamos que tenemos una matriz que se comporta como una matriz escalonada, como PseudoBlockMatrix de BlockArrays.jl. Para apoyar completamente a gemm! Necesitamos anular cada permutación de PseudoBlockMatrix con (1) sí mismo, (2) StridedMatrix, (3) Adjoints de sí mismo, (4) Transpose de sí mismo, (5) Adjoints de StridedMatrix, (6) Transposed de StridedMatrix, y posiblemente otros . Esto ya es 6 ^ 3 = 216 combinaciones diferentes. ¡Entonces quieres apoyar a trmm! y tienes que hacer lo mismo con UpperTriangular, UnitUpperTriangular, sus adjuntos, sus transposiciones, etc. ¡Entonces gsmm! con Simétrico y Hermitiano.
Pero en muchas aplicaciones no solo queremos trabajar con las matrices, sino también con sus subvistas, particularmente para las matrices de bloques donde queremos trabajar con bloques. Ahora necesitamos agregar cada permuación de vistas de nuestra matriz junto con las 6 combinaciones anteriores.
Ahora tenemos miles de anulaciones, que involucran a StridedMatrix, que es un tipo de unión muy complicado. Esto es demasiado para el compilador, lo que hace que el tiempo de uso tome minutos, en lugar de segundos.

Ciertamente estoy de acuerdo en que la unión de tipo StridedArray actual es un defecto de diseño importante. Apoyé tu intento de solucionar este problema en algún momento.

En Strided.jl, solo implemento mul! cuando todas las matrices involucradas son de mi propio tipo personalizado (Abstract)StridedView , siempre que haya alguna mezcla en los tipos de A, B y C, dejo que Julia Base / LinearAlgebra maneja esto. Por supuesto, esto se debe usar en un entorno macro @strided , que intenta convertir todos los tipos base posibles en el tipo StridedView . Aquí, StridedView puede representar subvistas, transposiciones y adjuntas, y ciertas remodelaciones, todas con el mismo tipo (paramétrico). En general, el código de multiplicación completo es de aproximadamente 100 líneas:
https://github.com/Jutho/Strided.jl/blob/master/src/abstractstridedview.jl#L46 -L147
La alternativa nativa de Julia en caso de que BLAS no se aplique se implementa utilizando la funcionalidad mapreducedim! más general proporcionada por ese paquete, y no es menos eficiente que la de LinearAlgebra ; pero también es multiproceso.

Creo que al menos α₁ o α₂ en α₁*A*α₂*B*α₃ tiene que ser 1 para evitar aumentar la complejidad del tiempo asintótico.

@tkf , supongo que si estos coeficientes escalares toman un valor predeterminado one(T) , o mejor aún, true , la propagación constante y las optimizaciones del compilador eliminarán automáticamente esa multiplicación. en el bucle más interno cuando no hay operación. Por lo que aún sería conveniente tener que definir la forma más general.

No estoy seguro de si podemos confiar en la propagación constante para eliminar todas las multiplicaciones por 1 ( true ). Por ejemplo, eltype puede ser Matrix . En ese caso, creo que true * x (donde x::Matrix ) tiene que crear una copia asignada al montón de x . ¿Puede Julia hacer algo de magia para eliminar eso?

@Jutho Creo que este punto de referencia muestra que Julia no puede eliminar las multiplicaciones intermedias en algunos casos:

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)

Buen punto de referencia. También ya había notado que de hecho no eliminará estas asignaciones mediante experimentos similares. Para tales casos, podría ser útil definir un tipo único de propósito especial One que solo defina *(::One, x::Any) = x y *(x::Any, ::One) = x , y que ningún tipo de usuario necesita ahora. Entonces, el valor predeterminado, al menos para α₂ , podría ser One() .

¡Ah, sí, eso es inteligente! Primero pensé que ahora estaba bien con el soporte de α₁ * A * α₂ * B * α₃ pero luego creo que encontré otro problema: es matemáticamente ambiguo lo que deberíamos hacer cuando (digamos) A es una matriz de matriz y α₁ es una matriz. No será un problema si _nunca_ apoyamos argumentos no escalares en posiciones α . Sin embargo, hace imposible presentar Y .= β₁*Y*β₂ + *(args...) como el modelo mental de mul!((β₁, Y, β₂), args...) . Además, sería muy bueno si las matrices diagonales se pudieran pasar a α₁ o α₂ ya que a veces se pueden calcular casi "gratis" (y esto es importante en las aplicaciones). Creo que hay dos rutas:

(1) Vaya con mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) pero al sobrecargar el método, los argumentos α y β deben aceptar Diagonal . Es fácil definir una ruta de llamada para que el código del usuario final aún pueda invocarla mediante valores escalares. Pero para que esto funcione de manera eficiente, la "versión O (1)" de Diagonal(fill(λ, n)) https://github.com/JuliaLang/julia/pull/30298#discussion_r239845163 debe implementarse en LinearAlgebra. Tenga en cuenta que las implementaciones escalares y diagonales α no son muy diferentes; a menudo es solo intercambiar α y α.diag[i] . Así que no creo que sea una gran carga para los autores del paquete.

Esto resuelve la ambigüedad que mencioné anteriormente porque ahora puede llamar a mul!(Y, α * I, A, B) cuando A es una matriz de matriz y α es una matriz que debe tratarse como eltype de A .

(2) ¿Quizás la ruta anterior (1) todavía es demasiado compleja? Si es así, vaya con muladd! por ahora. Si alguna vez queremos admitir mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) , migrar a él manteniendo la compatibilidad con versiones anteriores no es difícil.

En este punto, me pregunto si no deberíamos tener un matmul!(C, A, B, α, β) muy restringido y especializado que solo está definido para funcionar para esta firma general:

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

Además, es realmente sorprendente que esta firma se pueda escribir y enviar.

Esa es esencialmente mi sugerencia (2), ¿verdad? (Supongo que A :: Matrix{T} no significa literalmente Core.Array{T,2} ; de lo contrario, es más o menos solo gemm! )

Yo sería feliz con eso como una solución temporal, y puede soportar parcialmente en los paquetes mantengo ( “parcial” por el tema de los rasgos excepcionales), aunque se suma otro nombre a la mezcla: mul! , muladd! y ahora matmul! .

... ¿No es hora de que alguien para enviar “triaje dice ...” y lo llaman?

Además, es realmente sorprendente que esta firma se pueda escribir y enviar.

¿No es el hecho de que puedas enviar limpiamente exactamente con esta firma un argumento para hacer de este un método de mul! ? Luego, también se puede desaprobar claramente en caso de que surja alguna solución más general.

haciendo de esto un método de mul!

Si vamos con mul!(C, A, B, α, β) no hay forma de generalizarlo a mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) y similares sin romper la compatibilidad. (Tal vez sea una "característica" ya que estamos libres de esta discusión para siempre: sonríe :).

Además, permítame señalar que el tipo de elemento delimitador a Number _and_ mantener la compatibilidad con el 3-arg actual mul! (que ya admite el tipo de elemento no Number ) introducirá mucho de duplicación.

No tengo idea de lo que esperarías que hiciera mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) ... así que creo que es una característica.

Bache. Me entristece tener que usar las funciones BLAS en todas partes para obtener un mejor rendimiento en el lugar.

@StefanKarpinski ¿Podrías mencionarlo en la clasificación?

Podemos discutir, aunque no estoy seguro de qué resultado va a tener sin algunas personas de linalg en la llamada, que normalmente no hay en estos días. Para ser franco, dediqué una buena cantidad de tiempo y esfuerzo a tratar de que esta discusión llegara a algún tipo de resolución y cada idea parece haber sido rechazada firmemente de una forma u otra, así que prácticamente acabo de revisar este problema en este punto. Si alguien pudiera hacer un resumen del problema y de por qué las diversas propuestas son inadecuadas, sería útil para tener una discusión de triaje productiva. De lo contrario, no creo que podamos hacer mucho.

Creo que la falta de consenso significa que no es el momento adecuado para incorporar esto en StdLib.

¿Por qué no solo un paquete MatMul.jl que implementa una de las sugerencias que los usuarios pueden usar? No veo por qué estar en StdLib es tan importante en la práctica. Con mucho gusto apoyaré esto en los paquetes que mantengo.

¡Estoy pensando en una bonita versión juliana de gemm! y gemv! coincidiendo con lo que ya tenemos en SparseArrays. Por @andreasnoack arriba:

Pensé que ya nos habíamos decidido por mul!(C, A, B, α, β) con valores predeterminados para α , β . Estamos usando esta versión en

julia / stdlib / SparseArrays / src / linalg.jl

Líneas 32 a 50 en b8ca1a4

...
Creo que algunos paquetes también usan este formulario, pero no recuerdo cuál en la parte superior de mi cabeza.

Esa sugerencia tenía 7 pulgares hacia arriba y ningún pulgar hacia abajo. ¿Por qué no implementamos esa función para vectores / matrices densos? Esa sería una solución simple que cubre el caso de uso más común, ¿verdad?

OKAY. Así que supongo que ni siquiera hay consenso sobre si hay consenso: sweat_smile:

_Pensaba que casi todo el mundo [*] quería esta API y es solo una cuestión del nombre de la función y la firma. En comparación con _no_ tener esta API, pensé que todos están contentos con cualquiera de las opciones (digamos mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) , muladd!(C, A, B, α, β) y mul!(C, A, B, α, β) ). A menos que alguien pueda presentar un argumento convincente de que una determinada API es mucho peor que _no_ tenerla, me complacería con lo que decida la clasificación.

@StefanKarpinski Pero no dudes en eliminar la etiqueta triage si crees que la discusión aún no está suficientemente consolidada.

[*] De acuerdo, @dlfivefifty , creo que tienes dudas incluso sobre el mul! actual de 3 argumentos . Pero eso requeriría cambiar la interfaz mul! argumentos desde cero, así que pensé que eso está mucho más allá del alcance de esta discusión (que, he estado interpretando como _adding_ alguna forma de variante de 5 argumentos). Creo que necesitamos algo que funcione "lo suficiente" hasta que LazyArrays.jl madure.

¿Por qué no solo un paquete MatMul.jl que implementa una de las sugerencias que los usuarios pueden usar?

@dlfivefifty Creo que tenerlo en LinearAlgebra.jl es importante porque es una función de interfaz (API sobrecargable). Además, dado que mul!(C::AbstractMatrix, A::AbstractVecOrMat, B::AbstractVecOrMat) está implementado en LinearAlgebra.jl, no podremos definir mul! en términos de MatMul.muladd! . Por supuesto, existen algunas soluciones, pero es mucho mejor tener una implementación sencilla, especialmente considerando que "solo" requiere decidir el nombre y la firma.

¿Por qué no implementamos esa función para vectores / matrices densos?

@chriscoey Desafortunadamente, este no es el único favorito para todos: https://github.com/JuliaLang/julia/issues/23919#issuecomment -441717678. Aquí está mi resumen de los pros y los contras de esta y otras opciones https://github.com/JuliaLang/julia/issues/23919#issuecomment -441865841. (Vea también los comentarios de otras personas)

Desde la clasificación: hay planes a largo plazo para tener API de contracción de tensor genéricas, incluido el soporte del compilador y la selección de BLAS, pero a medio plazo, simplemente elija la API que aborde su necesidad inmediata. Si coincide con BLAS, la elección de nombres BLAS parece razonable.

@Keno , ¿alguna información que pueda compartir sobre la API de contracción de tensor genérico y el soporte del compilador? También podría tener información interesante para compartir, aunque no en público (todavía).

No se ha realizado ningún diseño de API en nada de eso, solo un sentido genérico de que deberíamos tenerlo. Soy consciente de que ha estado trabajando en algunas de estas cosas, por lo que sería bueno tener una sesión de diseño en el momento adecuado, pero no creo que estemos allí todavía.

Si coincide con BLAS, la elección de nombres BLAS parece razonable.

Esto es completamente contrario a lo que hemos estado haciendo hasta ahora con los nombres de funciones genéricas de álgebra lineal.

¿Cuál es el plan para β == 0 fuerte / débil en la versión general propuesta de BLAS.gemm!(α, A, B, β, C) ?

Si bajamos a BLAS llamadas, se comportará como un cero fuerte, aunque esto ahora es inconsistente con lmul! . No puedo pensar en una solución para esto aparte de volver a un generic_muladd! if β == 0 .

¿Cuál es el plan para β == 0 fuerte / débil?

Solo se discutió brevemente en torno a mi comentario en https://github.com/JuliaLang/julia/issues/23919#issuecomment -430139849, por lo que la clasificación probablemente no abordó esa pregunta.

@Keno Aunque todavía no ha habido ningún diseño de API, ¿imagina que "las API, incluido el soporte del compilador y la selección de BLAS", se definirían como mutantes o serían inmutables, como el álgebra lineal de XLA, para ayudar al ¿compilador? Es decir, ¿cree que mul! y / o muladd! serían parte de dichas API?

@Keno de ping para el @andreasnoack Es cuestión de https://github.com/JuliaLang/julia/issues/23919#issuecomment -475534454

Lo siento, estaba destinado a volver a este tema (especialmente solicité el triaje) pero no sabía cuál sería la siguiente acción dada la decisión del triaje.

Si coincide con BLAS, la elección de nombres BLAS parece razonable.

Como señaló @andreasnoack , no podemos usar (digamos) gemm! porque queremos admitir la multiplicación matriz-vector, etc. Pero supongo que podemos ignorar esta decisión de clasificación (que solo dice "Si coincide con BLAS" ; no lo hace).

simplemente elija la API que responda a sus necesidades inmediatas.

Entonces, supongo que podemos seguir esta dirección. Creo que significa olvidarse de la API basada en tuplas sugerida por @StefanKarpinski y "simplemente" elegir una de mul! / muladd! / addmul! .

Volvemos a la discusión original. Pero creo que es bueno que tengamos una restricción para no discutir más sobre API.

¿Alguna idea de cómo elegir un nombre de mul! / muladd! / addmul! ?


@chriscoey Creo que es mejor discutir la API futura en otro lugar. Este problema ya es muy largo y no podremos hacer ningún progreso a menos que nos enfoquemos en una solución a mediano plazo. ¿Qué tal abrir un nuevo tema (o hilo de discurso)?

Sugiero una sola ronda de votación de aprobación con un plazo de 10 días a partir de ahora. Voto de aprobación significa: Todos votan por todas las opciones que consideran preferibles a continuar la discusión. Las personas que prefieran tener su nombre menos preferido ahora que una discusión continua deberían votar por los tres. Si ninguna opción recibe una aprobación generalizada, o el esquema de votación en sí no logra una aprobación generalizada, entonces debemos continuar la discusión. En caso de casi empate entre las opciones aprobadas, @tkf decide (privilegio de autor de relaciones públicas).

+1: Estoy de acuerdo con este esquema de votación y he emitido mis votos de aprobación.
-1: No estoy de acuerdo con este esquema de votación. Si demasiadas personas o personas importantes seleccionan esta opción, la votación es discutible.

Corazón: mul! es preferible a la discusión continua.
Cohete: muladd! es preferible a la discusión continua.
Hurra: addmul! es preferible a la discusión continua.

Tentativamente sugiero que el 75% de aprobación y 5 definitivamente deberían formar un quórum (es decir, el 75% de las personas que han votado, incluido el desacuerdo con todo el procedimiento de votación, y al menos 5 personas han aprobado la opción ganadora; si la participación es baja , luego 5/6 o 6/8 hacen quórum, pero un 4/4 unánime podría considerarse un fracaso).

La congelación de funciones para 1.3 es alrededor del 15 de agosto: https://discourse.julialang.org/t/release-1-3-branch-date-approaching-aug-15/27233?u=chriscoey. Espero que podamos fusionarlo para entonces 😃. ¡Gracias a todos los que ya han votado!

También necesitamos decidir el comportamiento de β == 0 https://github.com/JuliaLang/julia/issues/23919#issuecomment -475420149 que es ortogonal para decidir el nombre. Además, el conflicto de fusión en mi RP debe resolverse y el RP necesita una revisión sobre los detalles de implementación (por ejemplo, el enfoque que usé allí para manejar undef s en la matriz de destino). Es posible que descubramos otros problemas durante la revisión. Entonces, no estoy seguro de si puede entrar en 1.3 ...

Re: β == 0 , creo @andreasnoack 's comentario https://github.com/JuliaLang/julia/issues/23919#issuecomment -430139849 (mi resumen: β == 0 -el hacerse cargo debería ser BLAS -compatible para aprovechar BLAS tanto como sea posible) tiene sentido. Es difícil encontrar opiniones opuestas que no sean el argumento de β == 0 similar a BLAS?

@simonbyrne Con respecto a su comentario https://github.com/JuliaLang/julia/issues/23919#issuecomment -430375349, no creo que la ramificación explícita sea un gran problema porque en su mayoría es solo un β != 0 ? rmul!(C, β) : fill!(C, zero(eltype(C))) . Además, para una implementación muy genérica donde necesita manejar, por ejemplo, C = Matrix{Any}(undef, 2, 2) , la implementación requiere un manejo explícito de "cero fuerte" de todos modos (consulte la función auxiliar _modify! en mi PR https: // github .com / JuliaLang / julia / pull / 29634 / files # diff-e5541a621163d78812e05b4ec9c33ef4R37). Entonces, creo que el manejo similar al BLAS es la mejor opción aquí. ¿Qué piensas?

¿Es posible tener ceros débiles de alto rendimiento? En el sentido en que quisiéramos:

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

Es decir, necesitaríamos determinar manualmente qué filas deben ser NaN si bajamos a BLAS (que usa un fuerte 0).

@dlfivefifty Creo que BLAS puede manejar NaN en A y B , pero no en C ?

Supongo que no hay forma de hacer _C = α * A * B + 0 * C_ consciente de NaN de manera eficiente usando BLAS.gemm! etc. [1] y de ahí es de donde viene el argumento de

[1] necesitas ahorrar isnan.(C) algún lugar y "contaminar" C después

@chethega han pasado más de 10 días desde la votación

@chriscoey Tienes razón, la votación está cerrada.

Soy muy malo en github para obtener una lista completa de las personas que votaron (eso sería necesario para calcular cuántas personas emitieron votos). Sin embargo, cuando analizo los números, parece bastante claro que mul! tiene un apoyo abrumador (probablemente manejando un quórum de aprobación del 75%), y el segundo contendiente muladd! está muy por debajo del 50%.

No hubo una sola objeción al esquema de votación. Llamo a la votación, mul! ha ganado y el nombre está decidido. @tkf puede seguir haciéndolo volar :)

@chethega Gracias, ¡fue un buen plan para decidir esto!

Por cierto, no puedo hacer el rebase de inmediato (pero tal vez dentro de unas semanas), así que si alguien quiere hacer el rebase o la reimplementación, no me espere.

Desafortunadamente, no tuvimos una votación sobre la semántica de NaN. La congelación de funciones es la próxima semana y no tenemos tiempo suficiente para una votación significativa.

Propongo que tengamos un referéndum no vinculante para recopilar una instantánea del estado de ánimo en el hilo.

Veo las siguientes opciones:

  1. Continúe discutiendo, espere que se llegue a un consenso de alguna manera antes de la fecha límite, o que las personas principales nos otorguen una extensión, o algo. : tada: (editar para aclarar: esta es la opción predeterminada, es decir, qué sucede si no podemos llegar a un consenso sobre una opción diferente. El resultado más probable es que 5-arg mul! se retrase hasta 1.4.)
  2. Fusionar la nueva función, con un comportamiento NaN indefinido como respaldo. Tan pronto como llegamos a un consenso, actualizamos el código y / o los documentos para obtener el comportamiento de NaN decidido (ceros fuertes vs débiles). El respaldo del comportamiento de NaN definido por la implementación indefinido podría terminar indefinido, pero eso no se puede votar hoy. (implemente lo que sea más rápido; los usuarios que se preocupan deben llamar a diferentes métodos). :Pulgares hacia arriba:
  3. ¡Gemm significa Gemm! Fusionar para 1.3 con un compromiso documentado con ceros fuertes. :corazón:
  4. NaN significa NaN ! Fusionar para 1.3 con compromiso documentado con ceros débiles. : ojos:
  5. Haz algo, cualquier cosa, antes de llegar al acantilado 1.3. :cohete:
  6. Rechaza la encuesta. :pulgares abajo:
  7. Escribir en
  8. Rosca descendente propuesta: disposición alternativa del !(alpha === false) && iszero(alpha) && !all(isfinite, C) && throw(ArgumentError()) y documentamos que es probable que esta comprobación de errores se elimine en favor de otra cosa. :confuso:

Siéntase libre de seleccionar múltiples opciones, posiblemente contradictorias, y @tkf / triage puede ignorar potencialmente la encuesta.

Editar: Actualmente solo: tada: (paciencia) y: rocket: (impaciencia) son contradictorios, pero ambos son compatibles con todos los demás. Como se ha aclarado en el hilo inferior, es de esperar que la clasificación cuente el resultado de la encuesta en una fecha no especificada entre el miércoles 14 y el jueves 15, y lo tenga en cuenta de alguna manera no especificada. De nuevo, esto se entiende como "votación de aprobación", es decir, seleccione todas las opciones que desee, no solo su favorita; se entiende que: cohete: no desaprueba: pulgar hacia arriba :,: corazón :,: ojos: y: confundido :

Votaría por un cero fuerte (: heart :) si fuera "Fusionar para 1.x" en lugar de "Fusionar para 1.3". ¿No tendrían sentido las opciones si todos los "Fusionar para 1.3" son "Fusionar 1.x"?

Gracias @chethega. @tkf ¡Estoy en la posición de necesitar el nuevo mul! Lo antes posible sin preocuparse demasiado por la decisión de NaN (siempre que el rendimiento no se vea comprometido).

¿Revisó LazyArrays.jl? FYI, tiene un soporte de multiplicación de matrices fusionadas realmente bueno. ¡También puede usar BLAS.gemm! etc. de forma segura ya que son métodos públicos https://docs.julialang.org/en/latest/stdlib/LinearAlgebra/#LinearAlgebra.BLAS.gemm!

¡Realmente necesito el mul genérico! ya que utilizamos una amplia variedad de matrices densas antiguas estructuradas y escasas y simples, para representar diferentes problemas de optimización de la manera más eficiente. Estoy aquí por la genéricaidad y la velocidad.

Veo. Y acabo de recordar que discutimos cosas en LazyArrays.jl así que, por supuesto, ya lo sabías ...

Con respecto a "ASAP", el ciclo de lanzamiento de cuatro meses de Julia es, al menos como un diseño, para evitar "merge rush" justo antes de la congelación de funciones. Sé que no es justo para mí decir esto porque he intentado lo mismo antes ... Pero creo que alguien debe mencionar esto como recordatorio. El lado positivo es que Julia es muy fácil de construir. Puede comenzar a usarlo justo después de que se fusione hasta la próxima versión.

Editar: liberar -> fusionar

Gracias. Encuentro que los plazos son motivadores útiles y me gustaría evitar que esto vuelva a quedar obsoleto. Por tanto, propondría que intentemos utilizar el plazo como objetivo.

¡Es genial que estés inyectando energía activamente a este hilo!

¡Realmente necesito el mul genérico! ya que utilizamos una amplia variedad de matrices densas antiguas estructuradas y escasas y simples, para representar diferentes problemas de optimización de la manera más eficiente. Estoy aquí por la genéricaidad y la velocidad.

Un mul! 5 argumentos no funcionará bien cuando tenga muchos tipos diferentes: necesitará combinatoriamente muchas anulaciones para evitar la ambigüedad. Esta es una de las motivaciones detrás del sistema LazyArrays.jl MemoryLayout . Se utiliza para matrices "estructuradas y dispersas" precisamente por esta razón en BandedMatrices.jl y BlockBandedMatrices.jl. (Aquí incluso las sub vistas de matrices con bandas se envían a rutinas BLAS con bandas).

Gracias, intentaré LazyArrays de nuevo.

Dado que el mul de 5 argumentos parece ser considerado generalmente como una solución temporal (hasta que alguna solución como LazyArrays pueda usarse en 2.0), creo que podemos aspirar a fusionarlo sin que necesariamente sea la solución ideal o perfecta a largo plazo.

@chethega, ¿ cuándo crees que deberíamos contar los recuentos para el nuevo voto no vinculante?

@tkf Seguro, tienes razón en que los ceros fuertes / débiles / indefinidos también tienen sentido para 1.x.

Sin embargo, creo que hay bastantes personas que preferirían tener 1.3 mul! ahora que esperar hasta 1.4 para obtener 5-arg mul! . Si no hubiera una fecha límite, esperaría un poco más y me tomaría más tiempo para pensar cómo realizar una encuesta adecuada (al menos 10 días para votar). Más importante aún, no podemos tener un voto significativo sin primero presentar y comparar implementaciones de la competencia sobre la velocidad y elegancia de los ceros débiles / fuertes. Personalmente sospecho que los ceros débiles podrían hacerse casi tan rápido como los ceros fuertes, primero verificando iszero(alpha) , luego escaneando la matriz en busca de valores de !isfinite , y solo luego usando una ruta lenta con asignación adicional; pero prefiero la semántica cero fuerte de todos modos.

@chethega, ¿ cuándo crees que deberíamos contar los recuentos para el nuevo voto no vinculante?

Triage debe tomar una decisión (retraso / fuerte / débil / backstop) esta semana para el alfa 1.3. Creo que el jueves 15 o el miércoles 14 son opciones sensatas para que el triaje haga un recuento y lo tenga en cuenta. Probablemente no pueda unirme el jueves, así que alguien más tendrá que contar.

Siendo realistas, está bien ser conservador aquí, no cumplir con la fecha límite, continuar la discusión y esperar la 1.4.

Por otro lado, es posible que ya hayamos llegado a un consenso sin darnos cuenta:

¿Por qué no lanzar un error por ahora?

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

¿Por qué no lanzar un error por ahora?

Agregué esa opción a la encuesta. ¡Gran idea de compromiso!

Solo para establecer expectativas: la función de congelación para 1.3 es en tres días, por lo que básicamente no hay forma de que esto llegue a tiempo. Somos bastante estrictos con respecto a la congelación de funciones y la ramificación, ya que es la única parte del ciclo de lanzamiento en la que realmente podemos controlar el tiempo.

Sin embargo, el trabajo ya está hecho en https://github.com/JuliaLang/julia/pull/29634 . Solo necesita ser ajustado y reajustado.

@tkf para # 29634 ¿podría enumerar el trabajo que queda por hacer (incluido el cambio de nombre y el manejo de ceros de acuerdo con la votación)? Sé que estás ocupado, así que tal vez podamos encontrar una manera de dividir los todos restantes para que la carga no vuelva a caer sobre ti.

Los TODO que puedo pensar en ATM son:

Mi PR implementa la semántica BLAS del manejo β = 0 . Por lo tanto, también se debe implementar otro manejo, como lanzar un error.

Mi PR implementa la semántica BLAS del manejo β = 0 .

Lo siento, mi memoria estaba rancia; mi implementación no fue consistente y propaga NaN _a veces_. Entonces, TODO adicional es hacer que el comportamiento de β = 0.0 consistente.

El tipo MulAddMul sería solo para uso interno, ¿verdad?

Sí, es un detalle completamente interno. Mis preocupaciones eran (1) puede haber demasiadas especializaciones (beta = 0, etc. está codificado en el parámetro de tipo) y (2) disminuye la legibilidad del código fuente.

Esas son preocupaciones válidas. Ya producimos un montón de especializaciones en el código de álgebra lineal, por lo que es bueno pensar bien si realmente necesitamos especializarnos aquí. En general, mi pensamiento ha sido que deberíamos optimizar para matrices pequeñas ya que no es gratis (como dijiste, complica el código fuente y podría aumentar los tiempos de compilación) y la gente está mejor usando StaticArrays para multiplicaciones de matrices pequeñas. Por lo tanto, me inclino por verificar los valores en tiempo de ejecución, pero siempre podemos ajustar esto más tarde si cambiamos de opinión, por lo que no debemos permitir que esto cause retrasos.

Para su información, los ceros suaves tienen implementaciones 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 Lo siento, olvidé que en realidad necesitábamos la especialización para optimizar el bucle más interno para algunas matrices estructuradas como mul!(C, A::BiTriSym, B, α, β) https://github.com/JuliaLang/julia/pull/29634#issuecomment -440510551. Algunas especializaciones se pueden eliminar, pero en realidad eso es más trabajo (por lo que retrasos).

Por lo tanto, me inclino por verificar los valores en tiempo de ejecución, pero siempre podemos ajustar esto más tarde si cambiamos de opinión, por lo que no debemos permitir que esto cause retrasos.

¡Excelente!

@andreasnoack ¡ Muchas gracias por revisar y fusionar esto a tiempo!

Ahora que se fusionó para la versión 1.3, me puso muy nervioso la implementación: smile :

No se preocupe, habrá mucho tiempo para que 1.3 RCs + PkgEvals eliminen los errores.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

Keno picture Keno  ·  3Comentarios

ararslan picture ararslan  ·  3Comentarios

iamed2 picture iamed2  ·  3Comentarios

manor picture manor  ·  3Comentarios

felixrehren picture felixrehren  ·  3Comentarios