Julia: API умножения матриц

Созданный на 28 сент. 2017  ·  208Комментарии  ·  Источник: JuliaLang/julia

В настоящее время в разреженном коде matmult есть следующая строка:

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

Я предполагаю, что это означает, что мы хотим иметь более общие методы A_mul_B*!(α,A,B,β,C) = αAB + βC которые перезаписывают C (API BLAS gemm ) для плотных массивов. Так ли это до сих пор? (Также кажется возможным сохранить оба API, т.е. сохранить методы A_mul_B*!(C,A,B) , которые просто были бы определены как A_mul_B*!(C,A,B) = A_mul_B*!(1,A,B,0,C) .)

Я лично хотел бы, чтобы API gemm определен для всех типов массивов (это было выражено в другом месте ). Реализация этих методов для плотных массивов кажется довольно простой, поскольку они просто вызывают gemm и др. Напрямую. Редкий случай уже реализован. Единственная реальная модификация - это универсальный matmult в чистом виде julia, который не принимает аргументы α и β .

Это привело бы к универсальному коду, который работает с любым типом массива / числа. В настоящее время у меня есть простая реализация expm (после внесения изменений в _generic_matmatmult! ), которая работает с bigfloats и разреженными массивами.

linear algebra

Самый полезный комментарий

Я предлагаю провести один раунд одобрительного голосования с 10-дневным сроком. Утверждающее голосование означает: все голосуют за все варианты, которые они считают более предпочтительными, чем продолжение обсуждения. Люди, которые предпочли бы сейчас наименее предпочтительное имя, чем продолжение обсуждения, должны голосовать за всех трех. Если ни один из вариантов не получил широкого одобрения или сама схема голосования не находит широкого одобрения, то мы должны продолжить обсуждение. В случае почти совпадения между одобренными вариантами @tkf (право автора PR).

+1: Я согласен с этой схемой голосования и отдал свои голоса одобрения.
-1: Я не согласен с этой схемой голосования. Если слишком много или слишком важных людей выберут этот вариант, голосование будет спорным.

Сердце: mul! предпочтительнее продолжения обсуждения.
Ракета: muladd! предпочтительнее продолжения обсуждения.
Ура: addmul! предпочтительнее продолжения обсуждения.

Я предварительно предлагаю, чтобы 75% одобрения и 5 человек обязательно составляли кворум (т.е. 75% людей, которые проголосовали вообще, включая несогласие со всей процедурой голосования, и не менее 5 человек одобрили вариант победы; если участие низкое , то 5/6 или 6/8 составляют кворум, но единогласные 4/4 могут быть сочтены неудачей).

Все 208 Комментарий

Ref. № 9930, № 20053, № 23552. Лучший!

Спасибо за ссылки. Я полагаю, что эта проблема больше связана с добавлением методов gemm -style, чем с обновлением API, но ее можно закрыть, если мы считаем, что она все еще слишком похожа на # 9930.

В качестве отправной точки будет ли поддержка _generic_matmatmul! с gemm API? Это довольно простое изменение и чисто аддитивное / неразрывное, поскольку текущий метод просто реализуется с помощью α=1 и β=0 . Я могу сделать пиар. Я бы, наверное, пошел аналогично этой версии (в этой версии я вырезал все элементы транспонирования, потому что они мне не нужны, я бы не стал этого делать здесь).

Да. Это будет хорошим началом. Однако нам необходимо учитывать порядок аргументов. Первоначально я думал, что было бы более естественно следовать порядку BLAS, но мы довольно согласованы в том, что сначала нужно иметь выходные аргументы, что также имеет место для текущего трех аргументов A_mul_B! . Кроме того, как вы уже отметили, версия с тремя аргументами будет соответствовать версии с пятью аргументами с α=1 и β=0 а аргументы значения по умолчанию являются последними. Конечно, нам не обязательно использовать для этого синтаксис значений по умолчанию, но имеет смысл использовать его здесь.

Почему бы просто не ввести общую функцию gemm ?

Да. Это будет хорошим началом. Однако нам необходимо учитывать порядок аргументов. Первоначально я думал, что было более естественным следовать порядку BLAS, но мы довольно согласованы в том, чтобы сначала иметь выходные аргументы, что также относится к текущему трех аргументу A_mul_B !. Более того, как вы уже отметили, версия с тремя аргументами будет соответствовать версии с пятью аргументами с α = 1 и β = 0, а аргументы значения по умолчанию являются последними. Конечно, нам не обязательно использовать для этого синтаксис значений по умолчанию, но имеет смысл использовать его здесь.

Звучит неплохо. Мы можем продолжить обсуждение фактического порядка аргументов и переименования методов в # 9930. Это больше о просто ейся версии пяти аргументов доступна, так что я буду держать текущие Ax_mul_Bx!(α,A,B,β,C) интерфейса.

Почему бы просто не ввести общую функцию gemm?

Вы предлагаете переименовать _generic_matmatmul! в gemm! в дополнение к изменениям выше?

Чтобы быть более ясным, я действительно думаю, что у нас должен быть единственный метод mul(C,A,B,α=1,β=0) , а также ленивые транспонированные / сопряженные типы для отправки, но это будет другой PR.

Почему бы просто не ввести общую функцию gemm?

Я думаю, что gemm - неправильное употребление слова Джулия. В BLAS часть ge указывает, что матрицы являются общими , т.е. не имеют специальной структуры, первый m - это умножение, а список m - это матрица . В Julia часть ge (общая) закодирована в подписи, как и последняя m (матрица), поэтому я думаю, мы должны просто назвать ее mul! .

Обозначение mul!(α, A, B, β, C) из SparseArrays.jl

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

"доработанный" как официальный синтаксис? И это будет в дополнение к mul!(C, A, B) , lmul!(A, B) и rmul!(A,B) ?

Я не слишком большой поклонник того, чтобы A , B и C в разных порядках.

Нотация mul!(α, A, B, β, C) из SparseArrays.jl «завершена» как официальный синтаксис?

Я бы сказал нет. Первоначально мне нравилась идея следовать BLAS (и порядок также соответствовал тому, как это обычно записывается математически), но теперь я думаю, что имеет смысл просто добавить аргументы масштабирования в качестве необязательных четвертого и пятого аргументов.

Итак, чтобы уточнить, вам нужны необязательные аргументы в смысле

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

Другой вариант - необязательные аргументы ключевого слова.

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

но я не уверен, что люди слишком довольны юникодом.

Я очень доволен юникодом, но это правда, что мы всегда стараемся иметь опцию ascii, и здесь это возможно. Имена α и β также не являются супер-интуитивно понятными, если вы не знакомы с BLAS, поэтому я думаю, что использование позиционных аргументов является лучшим решением здесь.

На мой взгляд, более логичной номенклатурой было бы позволить muladd!(A, B, C, α=1, β=1) отображать различные подпрограммы BLAS, которые выполняют умножение и сложение . ( gemm как указано выше, но также, например, axpy когда A - скаляр.)

Тогда функция mul! могла бы иметь интерфейс типа mul!(Y, A, B, ...) принимающий произвольное количество аргументов (точно так же, как * ), если все промежуточные результаты могут быть сохранены в Y. ( Необязательный kwarg может указывать порядок умножения с разумным значением по умолчанию)

mul!(Y, A::AbstractVecOrMat, B:AbstractVecOrMat, α::Number) будет иметь реализацию по умолчанию muladd!(A, B, Y, α=α, β=0) , как и другие перестановки двух матриц / векторов и скаляра.

Еще одно голосование за определение mul!(C, A, B, α, β) для плотных матриц. Это позволило бы написать общий код для плотных и разреженных матриц. Я хотел определить такую ​​функцию в моем пакете нелинейных наименьших квадратов, но я предполагаю, что это пиратство типов.

Я также был соблазн написать mul!(C, A, B, α, β) методы для MixedModels пакета и участвовать в немного типа пиратства, но было бы гораздо лучше , если бы такие методы были в LinearAlgebra пакет. Наличие методов для универсального muladd! для этой операции меня тоже устраивает.

Я поддерживаю, хотя думаю, что у него, вероятно, должно быть другое имя, чем просто mul! . muladd! кажется разумным, но я, безусловно, открыт для предложений.

Может быть, mulinc! для умножения-приращения?

Может у нас получится что-то вроде addmul!(C, A, B, α=1, β=1) ?

Разве это не форма muladd! ? Или идея называть его addmul! состоит в том, что он изменяет аргумент добавления, а не аргумент умножения? Можно ли когда-нибудь видоизменить аргумент умножения?

Обратите внимание, что в некоторых случаях мы действительно изменяем не первые элементы, например lmul! и ldiv! , поэтому мы могли бы выполнять их в обычном порядке "muladd" (т.е. muladd!(A,B,C) ). Вопрос в том, в каком порядке должны идти α и β ? Один из вариантов - указать аргументы ключевого слова?

Разве не было бы неплохо, если бы вы оставили разработчикам возможность отправлять типы скаляров α и β? Конечным пользователям легко добавлять сахар.

Я думал, что мы уже остановились на mul!(C, A, B, α, β) со значениями по умолчанию для α , β . Мы используем эту версию в https://github.com/JuliaLang/julia/blob/b8ca1a499ff4044b9cb1ba3881d8c6fbb1f3c03b/stdlib/SparseArrays/src/linalg.jl#L32 -L50. Я думаю, что некоторые пакеты также используют эту форму, но я не помню, какие из них у меня в голове.

Благодаря! Было бы хорошо, если бы это было задокументировано.

Я думал, что мы уже остановились на mul!(C, A, B, α, β) со значениями по умолчанию для α , β .

SparseArrays использует его, но я не припомню, чтобы он где-либо обсуждался.

В некотором смысле имя muladd! более естественно, потому что это умножение с последующим сложением. Однако значения по умолчанию для α и β, muladd!(C, A, B, α=1, β=0) (обратите внимание, что значение по умолчанию для β равно нулю, а не единице), снова превращают его в mul!(C, A, B) .

Кажется, это случай педантизма и последовательности, называть ли это mul! или muladd! и я думаю, что случай существующего метода в SparseArrays будет аргументом в пользу mul! . Я оказываюсь в любопытном случае, когда отстаиваю последовательность, несмотря на мою любимую цитату Оскара Уайльда: «Последовательность - последнее прибежище лишенного воображения».

В некотором смысле имя muladd! более естественно, потому что это умножение с последующим сложением. Однако значения по умолчанию для α и β, muladd!(C, A, B, α=1, β=0) (обратите внимание, что значение по умолчанию для β равно нулю, а не единице), снова превращают его в mul!(C, A, B) .

Есть интересное исключение, когда C содержит Inf или NaN: теоретически, если β==0 , результат все равно должен быть NaN. На практике этого не происходит, потому что BLAS и наш код разреженной матрицы явно проверяют наличие β==0 затем заменяют его нулями.

Вы можете считать, что при наличии значений по умолчанию α=true, β=false поскольку true и false являются "сильными" 1 и 0 соответственно, в том смысле, что true*x всегда равно x и false*x всегда равно zero(x) .

lmul! также должен иметь такое исключительное поведение: https://github.com/JuliaLang/julia/issues/28972

true и false являются "сильными" 1 и 0 соответственно, в том смысле, что true*x всегда равно x а false*x всегда равно zero(x) .

Я этого не знал !:

julia> false*NaN
0.0

FWIW, я очень доволен удобочитаемостью синтаксиса LazyArrays.jl для этой операции:

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

За кулисами он снижается до mul!(y, A, x, α, β) для BLAS-совместимых массивов (полосатых и полосатых).

Я этого не знал!

Это часть того, что заставляет im = Complex(false, true) работать.

SparseArrays использует его, но я не припомню, чтобы он где-либо обсуждался.

Это обсуждалось выше в https://github.com/JuliaLang/julia/issues/23919#issuecomment -365463941 и реализовано в https://github.com/JuliaLang/julia/pull/26117 без каких-либо возражений. У нас нет версий α,β в плотном кейсе, поэтому единственное место в этом репо, где решение будет иметь немедленный эффект, - это SparseArrays .

А как насчет LinearAlgebra.BLAS.gemm! ? Разве его тоже нельзя обернуть как 5-арный mul! ?

Должен, но еще никто этого не сделал. В matmul.jl много методов.

Это обсуждалось выше в # 23919 (комментарий) и реализовано в # 26117 без каких-либо возражений.

Что ж, считайте это моим возражением. Я бы предпочел другое имя.

Почему это должно быть другое имя? И в плотном, и в разреженном случае основной алгоритм выполняет как умножение, так и сложение.

Если мы дадим этим функциям разные имена, у нас будет mul!(C,A,B) = dgemm(C,A,B,1,0) и muladd!(C,A,B,α, β) = dgemm(C,A,B,α, β) .

Единственное преимущество, которое я вижу, заключается в том, что мы действительно разделяем методы и сохраняем вызов if β==0 в случае C = A*B .

К вашему сведению, я начал работать над этим в # 29634, чтобы добавить интерфейс в matmul.jl . Надеюсь закончить к тому времени, когда будут определены имя и подпись :)

Преимущество muladd! будет заключаться в том, что у нас может быть тройной muladd!(A, B, C) (или muladd!(C, A, B) ?) Со значением по умолчанию α = β = true (как указано в исходном предложении https: //github.com/JuliaLang/julia/issues/23919#issuecomment-402953987). Метод muladd!(A, B, C) похож на muladd для Number s, поэтому я думаю, это более естественное имя, особенно если вы уже знаете muladd .

@andreasnoack Похоже, ваше предыдущее обсуждение muladd! ? (Наличие пятизначного mul! в SparseArrays может быть одним из них, но определить обратно совместимую оболочку несложно.)

Наличие mul! и muladd! кажется излишним, когда первое - это только второе со значениями по умолчанию для α и β . Более того, часть add была канонизирована BLAS. Если бы мы могли предложить надежное универсальное приложение линейной алгебры за muladd! , я бы хотел услышать об этом, но в противном случае я бы предпочел избежать избыточности.

Кроме того, я бы очень предпочел, чтобы мы держали свойство сильного нуля false отдельно от обсуждения mul! . IMO любое нулевое значение β должно быть сильным, как в BLAS, так и в текущих пяти аргументах mul! методах. Т.е. такое поведение должно быть следствием mul! а не типа β . С альтернативой было бы трудно работать. Например, mul!(Matrix{Float64}, Matrix{Float64}, Matrix{Float64}, 1.0, 0.0) ~ мог ~ не использовать BLAS.

Мы не можем изменить то, что делает BLAS, но _requiring_ строгого нулевого поведения для чисел с плавающей запятой действительно означает, что каждой реализации потребуется ветвь для проверки нуля.

Если бы мы могли придумать надежное универсальное приложение линейной алгебры для muladd!

@andreasnoack Под этим, я полагаю, вы имеете в виду "приложение для _три-аргументов_ muladd! ", поскольку в противном случае вы бы не согласились включить mul! с пятью аргументами?

Но я все же могу привести пример, в котором muladd!(A, B, C) полезно. Например, если вы хотите построить сеть «маленького мира», полезно иметь «ленивое» суммирование полосовой матрицы и разреженной матрицы. Затем вы можете написать что-то вроде:

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

Но я не против вручную написать там true s, так как я могу просто обернуть его для своего использования. Важнейшей целью здесь является наличие функции с пятью аргументами в качестве стабильного документированного API.

Возвращаясь к сути:

Наличие mul! и muladd! кажется излишним, когда первое - это только второе со значениями по умолчанию для α и β .

Но у нас есть некие * реализованные в терминах mul! с соответствующим инициализированным "значением по умолчанию" выходного массива. Думаю, есть ли такие "ярлыки" в Base и стандартных библиотеках? Я думаю, имеет смысл иметь и mul! и muladd! даже если mul! - это всего лишь сокращение от muladd! .

Я бы очень предпочел, чтобы мы держали свойство сильного нуля false отдельно от обсуждения mul!

Я согласен с тем, что было бы конструктивно сосредоточиться на обсуждении названия пяти аргументов версии умножения-сложения сначала ( mul! vs muladd! ).

Я не справился с задачей, когда попросил об общем случае использования, когда вам нужно muladd для общей работы с матрицами и числами. Номер версии будет muladd без восклицательного знака, поэтому то, что я спросил, не имело смысла.

Ваш пример можно было бы просто записать как

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

так что я все еще не вижу необходимости в muladd! . Вы просто думаете, что этот случай настолько распространен, что написать 1, 1 будет слишком многословно?

Но у нас есть некие * реализованные в терминах mul! с соответствующим инициализированным "значением по умолчанию" выходного массива. Думаю, есть ли такие "ярлыки" в Base и стандартных библиотеках?

Я не понимаю этого. Не могли бы вы уточнить? О каких ярлыках вы здесь говорите?

поэтому я все еще не вижу необходимости в muladd! . Вы просто думаете, что этот случай настолько распространен, что написать 1, 1 будет слишком многословно?

Я думаю, что muladd! также более наглядно показывает, что он на самом деле делает (хотя, возможно, это должно быть addmul! ).

У меня нет проблем с именем muladd! . Во-первых, я просто не думаю, что для этого нужны функции, а во-вторых, я не думаю, что отказ от mul! в пользу muladd! / addmul! стоит.

Вы просто думаете, что этот случай настолько распространен, что написание 1, 1 слишком многословно?

Нет. Я полностью согласен с вызовом функции с пятью аргументами, если это общедоступный API. Я просто попытался привести пример, в котором мне нужна только версия с тремя аргументами (так как я думал, что это ваш запрос).

О каких ярлыках вы здесь говорите?

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

Я думаю, что * определенное здесь, можно рассматривать как ярлык mul! . Это «просто» mul! со значением по умолчанию. Итак, почему бы не позволить mul! быть muladd! / addmul! со значениями по умолчанию?

Существуют также rmul! и lmul! определенные как аналогичные "горячие клавиши":

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

прекращение поддержки mul!

Я думал, что речь идет о добавлении нового интерфейса или нет. Если нам нужно отказаться от mul! для добавления нового API, я не думаю, что это того стоит.

Основные аргументы, которые я могу придумать, следующие:

  • концептуально форма с пятью аргументами делает больше, чем просто «умножение», и передает это более ясно.
  • тогда вы можете написать addmul!(C, A, B) вместо mul!(C,A,B,1,1) или mul!(C,A,B,true,true) .

Я думаю, что * определенное здесь, можно рассматривать как ярлык mul! . Это «просто» mul! со значением по умолчанию. Итак, почему бы не позволить мул! быть muladd! / addmul! со значениями по умолчанию?

Потому что * - это способ умножения матриц по умолчанию и то, как это сделало бы большинство пользователей. Для сравнения, muladd! не будет и близко к * по использованию. Более того, это даже существующий оператор, тогда как muladd! / addmul! будет новой функцией.

Не думайте, что rmul! и lmul! подходят этому шаблону, потому что они обычно не являются версиями значений по умолчанию неуместных методов mul! .

Саймон красиво резюмирует преимущества в посте выше. Вопрос в том, достаточно ли велики выгоды, чтобы оправдать дополнительную функцию переименования (что означает отказ от mul! ). Вот где мы не согласны. Не думаю, что оно того стоит.

Когда вы говорите, что переименование того не стоит, учли ли вы, что API не является полностью публичным? Я имею в виду, что этого нет в документации Джулии.

Я знаю, что LazyArrays.jl (и другие пакеты?) Уже использует его, поэтому слепое следование семверам было бы нехорошо. Но все же он не такой публичный, как другие функции.

mul! экспортируется из LinearAlgebra и широко используется, поэтому на данном этапе нам определенно придется отказаться от него. Жаль, что у нас не было этого обсуждения, когда A_mul_B! превратилось в mul! или, по крайней мере, до версии 0.7, потому что было бы гораздо лучше переименовать функцию.

Как насчет использования mul! на данный момент и обновления имени для LinearAlgebra v2.0 когда мы можем обновлять stdlibs отдельно?

LazyArrays.jl не использует mul! поскольку он не гибок для многих типов матриц (и вызывает ошибку замедления компилятора, когда вы переопределяете с помощью StridedArray s). Это дает альтернативную конструкцию вида

y .= Mul(A, x)

который я считаю более информативным. Аналогом с 5 аргументами является

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

Я бы поспорил в пользу отказа от mul! и перехода к LazyArrays.jl в LinearAlgebra.jl, но это будет непросто.

LowRankApprox.jl действительно использует mul! , но я мог бы изменить его, чтобы использовать подход LazyArrays.jl и тем самым избежать ошибки компилятора.

ХОРОШО. Я думал, было всего два предложения. Но, видимо, предложений примерно три ?:

  1. трех- и пятиаргументный mul!
  2. трех- и пятиаргументный muladd!
  3. три-аргумента mul! и пять аргументов muladd!

( muladd! может называться addmul! )

Я думал, что мы сравниваем 1 и 3. Теперь я понимаю, что @andreasnoack сравнивает 1 и 2.

Я бы сказал, что 2 вообще не вариант, поскольку mul! тремя аргументами - это общедоступный и широко используемый API. Под «API не полностью общедоступен» я имел в виду, что mul! пятью аргументами не задокументированы .

Да, я планировал сохранить mul! (как форму с 3 аргументами и, возможно, с 4 аргументами). Я думаю, что это того стоит, поскольку 3 arg mul! и addmul! будут вести себя по-разному, т.е. с учетом addmul!(C, A, B, α, β) мы будем иметь:

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)

Однако вы можете не захотеть реализовать их таким образом на практике, например, может быть проще просто использовать 4 аргумента mul! и addmul! отдельности и определить 5 аргументов addmul! как:

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

Удар!

Однако вы можете не захотеть реализовать их таким образом на практике, например, может быть проще просто использовать 4-arg mul! и аддмуль! отдельно и определите 5-аргументный addmul! в виде:
addmul!(C, A, B, α, β) = addmul!(C .= β .* C, A, B, α)

Почему бы сразу не сделать это оптимально? Смысл этого не в том, что вам нужно посетить элементы C только один раз, что определенно более эффективно для больших матриц. Кроме того, я не могу поверить, что код будет длиннее, если определить только 5 аргументов addmul! сравнению с 4 аргументами mul! и addmul! отдельности.

К вашему сведению, я изменил реализацию LinearAlgebra _generic_matmatmul! чтобы принимать 5-аргументы в LazyArrays: https://github.com/JuliaArrays/LazyArrays.jl/blob/8a50250fc6cf3f2402758088227769cf2derlas/linals/

Здесь он вызывается через:

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

но фактический код (в tiled_blasmul! ) можно было бы легко перевести обратно в LinearAlgebra.

Что можно сделать, чтобы ускорить этот процесс? Вещи, над которыми я работаю, действительно выиграют от использования унифицированного API умножения матриц с функцией mul + add.

Последняя версия Strided.jl теперь также поддерживает 5 аргументов mul!(C,A,B,α,β) , отправляя их в BLAS, когда это возможно, и используя свою собственную (многопоточную) реализацию в противном случае.

@Jutho отличный пакет! есть ли план действий на будущее? может быть план в конечном итоге объединить с LinearAlgebra?

Это никогда не было моим намерением, но я не против, если это когда-нибудь потребуется. Однако я думаю, что мое либеральное использование функций @generated (хотя есть только одна) в общей функциональности mapreduce может не подходить для Base.

Моя личная дорожная карта: в основном это низкоуровневый пакет, который будет использоваться пакетами более высокого уровня, то есть новой версией TensorOperations и некоторым другим пакетом, над которым я работаю. Тем не менее, некоторая дополнительная поддержка базовой линейной алгебры была бы хорошей (например, применение norm к StridedView настоящее время возвращается к довольно медленной реализации norm в Julia Base). И если у меня будет время и я научусь работать с графическими процессорами, попробуйте реализовать не менее общую mapreducekernel за GPUArray s.

Я думаю, что консенсус таков:

  1. Мы должны оставить mul!(C, A, B)
  2. Нам нужна _некоторая_ функция с 5 аргументами для inplace multiply-add C = αAB + βC

Я предлагаю сначала сосредоточиться на том, каким должно быть имя функции с 5 аргументами, а позже обсудить дополнительный API (например, с 3 и 4 аргументами addmul! ). Но это "особенность", которую мы получаем от _not_ использования mul! поэтому ее трудно не смешивать.

@andreasnoack Решено ли ваше беспокойство по поводу устаревания / переименования с помощью комментария @simonbyrne выше https://github.com/JuliaLang/julia/issues/23919#issuecomment -431046516? Думаю, в устаревании нет необходимости.

К вашему сведению, я только что закончил реализацию # 29634. Буду признателен, если кто-нибудь, знакомый с LinearAlgebra сможет его просмотреть.

Думаю, проще и лучше все называть mul! . Это также позволяет избежать устаревания. Если нам действительно нужно другое имя, лучше muladd .

При обсуждении mul! API, возможно, следует учесть еще кое-что:

Когда scale! ушел и погрузился в переход 0,6 -> 0,7, мне было немного грустно, потому что для меня скалярное умножение (свойство векторных пространств) сильно отличалось от умножения объектов на себя (свойство алгебр ). Тем не менее я полностью принял подход mul! и очень ценю возможность rmul!(vector,scalar) и lmul!(scalar,vector) когда умножение скаляров не коммутативно. Но теперь меня с каждым днем ​​все больше беспокоит неюлианское название двух других операций с векторным пространством на месте: axpy! и его обобщение axpby! . Могут ли они также быть поглощены в mul! / muladd! / addmul! . Хотя это немного странно, если один из двух множителей в A*B уже является скаляром, нет необходимости в дополнительном скалярном множителе α .
Но, может быть, тогда по аналогии с

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

также может быть

add!(Y, X, α, β)

заменить axpby! .

@andreasnoack Решено ли ваше беспокойство по поводу устаревания / переименования с помощью комментария @simonbyrne выше # 23919 (комментарий)? Думаю, в устаревании нет необходимости.

См. Последний абзац https://github.com/JuliaLang/julia/issues/23919#issuecomment -430952179. Я все еще считаю, что вводить новую функцию не стоит. Если мы все равно это сделаем, я думаю, нам следует отказаться от текущего 5-аргумента mul! .

@Jutho Я думаю, переименование acp(b)y! в add! было бы хорошей идеей.

См. Последний абзац в # 23919 (комментарий) . Я все еще считаю, что вводить новую функцию не стоит.

Да, я прочитал это и ответил, что mul! пятью аргументами не было задокументировано и не было частью общедоступного API. Итак, _технически_ в устаревании нет необходимости. См. Последний абзац https://github.com/JuliaLang/julia/issues/23919#issuecomment -430975159 (Конечно, в любом случае было бы неплохо отказаться от поддержки, поэтому я уже реализовал это в # 29634.)

Здесь я предполагаю, что объявление публичного API из-за документации одной подписи (например, mul!(C, A, B) ) не применяется к другим подписям (например, mul!(C, A, B, α, β) ). Если это _не_ дело, я думаю, что Джулия и ее stdlib выставляют слишком много внутренних компонентов. Например, вот документированная подпись Pkg.add

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

тогда как фактическое определение

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

Если наличие документации хотя бы одной подписи Pkg.add подразумевает, что другие подписи являются общедоступным API, Pkg.jl не может удалить поведение из-за деталей реализации без изменения основной версии, например: Pkg.add(...; mode = :develop) запускает Pkg.develop(...) ; все аргументы ключевого слова для Context! поддерживаются (что действительно может быть предназначено).

Но в любом случае это только мое впечатление. Считаете ли вы, что mul!(C, A, B, α, β) был таким же публичным, как mul!(C, A, B) ?

Я думаю, что мы говорим мимо друг друга. Я хочу сказать, что (все еще) не думаю, что введение другой функции того стоит. Отсюда моя ссылка на мой предыдущий комментарий. Это отдельно от обсуждения отказа от пяти аргументов mul! .

Однако, если мы решим добавить еще одну функцию, я думаю, было бы лучше отказаться от пяти аргументов mul! а не просто нарушать их. Конечно, он не так часто используется, как mul! тремя аргументами, но почему бы не отказаться от него, а не просто сломать его?

Это отдельно от обсуждения отказа от пяти аргументов mul! .

Моя интерпретация последнего абзаца вашего комментария https://github.com/JuliaLang/julia/issues/23919#issuecomment -430952179 заключалась в том, что вы признали преимущества @simonbyrne, перечисленные https://github.com/JuliaLang/julia/issues / 23919 # issuecomment -430809383 для новой функции с пятью аргументами, но считает, что они менее ценны по сравнению с сохранением _public API_ (как вы упомянули о «переименовании» и «устаревании»). Вот почему я подумал, что важно учитывать, был ли mul! с пятью аргументами общедоступным или нет.

Но вы также упомянули оправдание наличия «дополнительной функции», о которой, я полагаю, вы сейчас говорите. Вы утверждаете, что вычисления _C = AB_ и _C = αAB + βC_ достаточно похожи, так что одно и то же имя может описывать оба? Я на самом деле не согласен, поскольку могут быть другие способы обобщения mul! с тремя аргументами: например, почему бы не mul!(y, A₁, A₂, ..., Aₙ, x) для _y = A₁ A₂ ⋯ Aₙ x_ https://github.com/JuliaLang/julia / issues / 23919 # issuecomment -402953987?

почему бы не отказаться от него, а не просто сломать?

Как я сказал в предыдущих комментариях, я согласен, что отказ от пяти аргументов mul! - это правильный шаг, _ если_ мы должны были ввести другую функцию. Этот код уже существует в моем PR # 29634.

Вы утверждаете, что вычисления C = AB и C = αAB + βC достаточно похожи, так что одно и то же имя может описывать оба?

Да, поскольку первое - это как раз второе с β=0 . Справедливо утверждать, что muladd! / addmul! - более точное название для C = αAB + βC но для того, чтобы добраться туда, потребуется либо ввести другую функцию умножения матриц ( muladd! / addmul! ) или переименование mul! и я не думаю, что сейчас это того стоит. Если бы это произошло весной, было бы легче подумать об изменении.

Я на самом деле не согласен, поскольку могут быть другие способы обобщения множителя с тремя аргументами !:

Джулии пришлось определить методы матричного умножения на месте без аргументов α и β но традиция матричного умножения действительно основана на BLAS-3, и там общая функция умножения матриц равна C = αAB + βC .

переименование mul!

Вы имеете в виду переименование в stdlib или в последующем пользовательском модуле / коде? Если вы имеете в виду первое, это уже сделано (для LinearAlgebra и SparseArrays) в # 29634, поэтому я не думаю, что вам нужно об этом беспокоиться. Если вы имеете в виду последнее, я думаю, что это снова сводится к обсуждению публично или нет.

традиция матричного умножения действительно основана на BLAS-3

Но Юлия уже отошла от соглашения об именах BLAS. Итак, не было бы неплохо иметь более описательное имя?

Вы имеете в виду переименование в stdlib или в последующем пользовательском модуле / коде?

29634 не переименовывает функцию mul! . Он добавляет новую функцию addmul! .

Но Юлия уже отошла от соглашения об именах BLAS.

Я не говорю о нейминге. По крайней мере, не совсем так, поскольку у Fortran 77 есть некоторые ограничения, которых у нас нет ни с точки зрения имен функций, ни с точки зрения отправки. Я говорю о том, что вычисляется. Общая функция умножения матриц в BLAS-3 вычисляет C = αAB + βC а в Julia это было mul! (fka A_mul_B! ).

Итак, не было бы неплохо иметь более описательное имя?

Было бы, и я говорил это несколько раз. Проблема в том, что не намного лучше иметь две функции умножения матриц, которые в основном делают одно и то же.

29634 не переименовывает функцию mul! . Он добавляет новую функцию addmul! .

Я хотел сказать, что mul! пятью аргументами было переименовано в addmul! .

Проблема в том, что не намного лучше иметь две функции умножения матриц, которые в основном делают одно и то же.

Я считаю, что, если они в основном такие же или нет, это несколько субъективно. Я думаю, что и _C = αAB + βC_, и _Y = A₁ A₂ ⋯ Aₙ X_ являются математически обоснованным обобщением _C = AB_. Если только _C = αAB + βC_ не является единственным обобщением, я не думаю, что этот аргумент достаточно силен. Это также зависит от того, знаете ли вы BLAS API, и я не уверен, что это базовые знания для типичных пользователей Julia.

Кроме того, _C = AB_ и _C = αAB + βC_ вычислительно сильно отличаются в том, что содержимое C используется или нет. Это параметр только вывода для первого и параметр ввода-вывода для второго. Я думаю, что эта разница заслуживает визуальной подсказки. Если я вижу, что mul!(some_func(...), ...) и mul! имеет форму с пятью аргументами, я должен подсчитать количество аргументов (что сложно, когда они являются результатами вызова функции, поскольку вам нужно сопоставить круглые скобки) чтобы узнать, выполняет ли some_func какое-то вычисление или просто распределение. Если у нас есть addmul! тогда я могу сразу ожидать, что some_func в mul!(some_func(...), ...) выполняет только распределение.

Я считаю, что, если они в основном такие же или нет, это несколько субъективно. Я думаю, что и C = αAB + βC, и Y = A₁ A₂ ⋯ Aₙ X являются математически верным обобщением C = AB. Я не думаю, что аргумент достаточно сильный, если только C = αAB + βC не является единственным обобщением.

Возможно, это не единственное обобщение, однако это обобщение, которое можно вычислить примерно с одинаковой стоимостью, и которое образует полезный примитив для построения других алгоритмов линейной алгебры. Во многих случаях мне хотелось иметь ненулевую бета-версию при реализации различных алгоритмов, связанных с линейной алгеброй, и мне всегда приходилось возвращаться к BLAS.gemm! . Другие обобщения, подобные тому, что вы упомянули, в любом случае нельзя рассчитать за один снимок без промежуточных временных интервалов, поэтому локальная версия гораздо менее полезна. Кроме того, они не так полезны, как примитивные операции.

Это также зависит от того, знаете ли вы BLAS API, и я не уверен, что это базовые знания для типичных пользователей Julia.

Пока аргументы по умолчанию α=1 и β=0 остаются на месте, три аргумента mul! будут делать то, что любой пользователь Julia разумно ожидал бы без фона BLAS. Для более продвинутых опций нужно обращаться к руководству, так как это касается любого языка и любой функции. Кроме того, этот единственный вызов mul! заменяет не только gemm но также gemv и trmv (которые, как ни странно, не имеют α и β parameters в BLAS API) и, возможно, многие другие.

Я согласен с тем, что BLAS-3 - правильное обобщение с точки зрения вычислений, и он очень хорошо составляет. Я поднимаю еще одно возможное обобщение только потому, что считаю его «недостаточно уникальным», чтобы оправдать использование того же имени. См. Также аргумент «только вывод против ввода-вывода» в последнем абзаце https://github.com/JuliaLang/julia/issues/23919#issuecomment -441267056. Я думаю, что другое имя упрощает чтение / просмотр кода.

Более того, этот единственный вызов mul! заменяет не только gemm но также gemv и trmv (которые, как ни странно, не имеют α и β parameters в BLAS API) и, возможно, многие другие.

Да, уже реализовано в # 29634 и готово к использованию, как только название будет определено (и будет рассмотрено)!

Мне сложно следить за этим разговором (он немного длинный и растянутый ... извините!), Является ли ведущее предложение чем-то вроде mul!(C, A, B; α=true, β=false) ?

Я не думаю, что аргумент ключевого слова для α и β находится в таблице. Например, @andreasnoack отклонил аргументы ключевого слова в https://github.com/JuliaLang/julia/issues/23919#issuecomment -365762889. @simonbyrne упомянул аргументы ключевых слов в https://github.com/JuliaLang/julia/issues/23919#issuecomment -426881998, но его последнее предложение https://github.com/JuliaLang/julia/issues/23919#issuecomment -431046516 является позиционным аргументы.

Мы еще не определились с названием (например, mul! vs addmul! vs muladd! ), и я думаю, что это центральная тема (или, по крайней мере, это мое желание).

Как вы обычно разрешаете подобные споры? Голосование? Сортировка?

является ли ведущее предложение чем-то вроде mul! (C, A, B; α = true, β = false)?

Мне это нравится, но без варгов.

Не видел ключевого слова. Я также не решаюсь добавлять ключевые слова Unicode. Я думаю, что позиционные аргументы с этими значениями по умолчанию подходят. Что касается предстоящего голосования, то я нахожусь на mul! . Я думаю, что это обобщение mul! , достаточно конкретное и полезное, чтобы не нуждаться в новом имени.

Просто для сбора данных (по крайней мере, на данный момент) проведем голосование:

Какое ваше любимое имя функции для _C = αAB + βC_?

  • : +1: mul!
  • : -1: addmul!
  • : smile: muladd!
  • : tada: еще что-то

Мне кажется, что addmul! описывает (A+B)C вместо AB + C .

Сначала я проголосовал за mul! затем я посмотрел на операцию и подумал: «Она выполняет умножение, а затем сложение, очевидно, мы должны назвать это muladd! . Теперь я не могу думать о том, чтобы назвать это на то, что он на месте, ясно указывает ! а масштабирующая часть кажется подходящей для аргументов ключевого слова.

он выполняет умножение, а затем добавление, очевидно, мы должны назвать это muladd!

Только если вы используете значение по умолчанию β=true , но тогда для любого другого значения это снова просто нечто более общее. Так какой смысл не называть его mul! , где любое другое значение, кроме значения по умолчанию β=false также дает вам нечто более общее? И как бы вы упорядочили аргументы по сравнению с muladd(x,y,z) = x*y + z ? Будет немного запутать, не так ли?

Я думаю, что muladd! имеет недостаток, заключающийся в том, что он звучит описательно, в то время как это не так: описательное имя было бы чем-то вроде scalemuladd! для упоминания части масштабирования.

Поэтому я предпочитаю mul! поскольку он достаточно невзрачный, чтобы не оправдать ожиданий.

Тем не менее, я называю ленивую версию в LazyArrays.jl MulAdd .

Я предпочитаю muladd! mul! потому что хорошо отличать функцию, которая никогда не использует значение C ( mul! ), от функции, которая его использует. ( muladd! ).

  • Технически это матричное умножение: [AC] * [Bα; Iβ] или, см. Комментарий ниже, [αA βC] * [B; Я]
  • ~ У нас уже есть 5-аргумент mul! для разреженных матриц, то же самое для плотного linalg будет согласованным ~ (не новый аргумент)

Так что я бы предпочел называть это mul! .

  • Технически это умножение матриц: [AC] * [Bα; Iβ]

... если eltype имеет коммутативное умножение

... если eltype коммутативен.

IIRC из обсуждения с @andreasnoack Джулия просто определяет gemm / gemv как y <- A * x * α + y * β потому что это имеет наибольший смысл.

@haampie Это хорошо! Поскольку я реализовал это наоборот в # 29634.

Это мало помогает, но

       C = α*A*B + β*C

это лучший способ, который я могу придумать, чтобы выразить операцию, и, следовательно, возможно, макрос <strong i="8">@call</strong> C = α*A*B + β*C или <strong i="10">@call_specialized</strong> ... или что-то в этом роде было бы естественным интерфейсом - также для подобных ситуаций. Тогда базовая функция может вызываться как угодно.

@mschauer LazyArrays.jl от @dlfivefifty имеет отличный синтаксис для вызова mul! 5 аргументами, как ваш синтаксис (и многое другое!).

Я думаю, нам нужно сначала создать функциональный API, чтобы авторы пакетов могли начать перегружать его для своих специализированных матриц. Тогда сообщество Julia может начать экспериментировать с сахарами для его вызова.

Только если вы используете значение по умолчанию β=true , но тогда для любого другого значения это снова просто нечто более общее. Так какой смысл не называть его mul! , где любое другое значение, кроме значения по умолчанию β=false также дает вам нечто более общее? И как бы вы упорядочили аргументы по сравнению с muladd(x,y,z) = x*y + z ? Будет немного запутать, не так ли?

Конечно, есть некоторое масштабирование, но «костяк» операции явно умножается и складывается. Мне также было бы хорошо, если бы muladd!(A, B, C, α=true, β=false) соответствовали подписи muladd . Конечно, нужно было бы задокументировать, но это само собой разумеется. Это действительно заставляет меня желать, чтобы muladd сначала взял добавочную часть, но корабль плыл на этой.

И как бы вы упорядочили аргументы по сравнению с muladd(x,y,z) = x*y + z ? Будет немного запутать, не так ли?

Вот почему я предпочитаю addmul! muladd! . Мы можем убедиться, что порядок аргументов не имеет ничего общего со скаляром muladd . (Хотя я предпочитаю muladd! mul! )

FWIW - это краткое изложение доводов до сих пор. (Я пытался быть нейтральным, но я за- muladd! / addmul! так что имейте это в виду ...)

Основное разногласие заключается в том, являются ли _C = AB_ и _C = αAB + βC_ достаточно разными, чтобы дать последнему новое имя.

Они достаточно похожи, потому что ...

  1. Это BLAS-3, и он прекрасно сочетается. Итак, _C = αAB + βC_ является очевидным обобщением _C = AB_ (https://github.com/JuliaLang/julia/issues/23919#issuecomment-441246606, https://github.com/JuliaLang/julia/issues/ 23919 # issuecomment-441312375 и т. Д.)

  2. _ "Недостатком muladd! является то, что он звучит описательно, когда это не так: описательное имя будет выглядеть примерно как scalemuladd! чтобы указать на масштабируемую часть." _ --- https://github.com/ JuliaLang / julia / issues / 23919 # issuecomment -441819470

  3. _ "Технически это матричное умножение: [AC] * [Bα; Iβ]" _ --- https://github.com/JuliaLang/julia/issues/23919#issuecomment -441825009

Они достаточно разные, потому что ...

  1. _C = αAB + βC_ больше, чем умножить (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 и т. д.).

  2. Могут быть и другие обобщения mul! например Y = A₁ A₂ ⋯ Aₙ X (https://github.com/JuliaLang/julia/issues/23919#issuecomment-402953987 и т. Д.)

  3. Только ввод и параметр ввода-вывода: сбивает с толку наличие функции, которая использует данные в C зависимости от количества аргументов (https://github.com/JuliaLang/julia/issues/23919#issuecomment -441267056, https://github.com/JuliaLang/julia/issues/23919#issuecomment-441824982)

Еще одна причина, по которой mul! лучше, потому что ...:

  1. В разреженной матрице он уже есть. Так что это хорошо для обратной совместимости. Встречный аргумент: mul! пятью аргументами не задокументирован, поэтому нам не нужно рассматривать его как общедоступный API.

и почему muladd! / addmul! лучше, потому что ...:

  1. У нас могут быть разные «удобные функции» с тремя или четырьмя аргументами для mul! и muladd! / addmul! отдельности (https://github.com/JuliaLang/julia/issues / 23919 # issuecomment-402953987, https://github.com/JuliaLang/julia/issues/23919#issuecomment-431046516 и т. Д.). Встречный аргумент: написание mul!(y, A, x, 1, 1) не слишком многословно по сравнению с mul!(y, A, x) (https://github.com/JuliaLang/julia/issues/23919#issuecomment-430674934 и т. Д.)

Спасибо за объективное резюме @tkf

Я также был бы в порядке с muladd! (A, B, C, α = true, β = false), чтобы соответствовать сигнатуре muladd.

Я надеюсь, что для функции с именем mulladd! по умолчанию будет β=true . Тем не менее, я думаю, что этот порядок аргументов, который продиктован muladd , будет очень запутанным по отношению к mul!(C,A,B)

Возможно, я ошибаюсь, но я думаю, что большинству людей / приложений / высокоуровневого кода (которые еще не удовлетворены только оператором умножения * ) требуется mul! . Возможность также смешивать βC с β=1 ( true ) или иным образом будет использоваться в коде нижнего уровня людьми, которые знают, что API BLAS для умножения матриц позволяет это. Я предполагаю, что эти люди будут искать эту функциональность в mul! , который является установленным интерфейсом Julia для gemm , gemv , ... Добавление нового имени (что сбивает с толку обратный порядок аргументов) не стоит того; Я не вижу выгоды?

Я предполагаю, что эти люди будут искать эту функциональность в mul! , который является установленным интерфейсом Julia для gemm , gemv , ... Добавление нового имени (что сбивает с толку обратный порядок аргументов) не стоит того; Я не вижу выгоды?

Я думаю, что обнаруживаемость не является большой проблемой, поскольку мы можем просто упомянуть muladd! в mul! docstring. Те, кто достаточно опытен, чтобы знать BLAS, знают, где искать API, верно?

Что касается аргументов позиционного и ключевого слова: здесь это еще не обсуждается, но я думаю, что C = αAB + βC с диагональной матрицей α можно реализовать так же эффективно и легко, как скалярный α . Такое расширение требует, чтобы мы могли отправлять по типу α что невозможно с аргументом ключевого слова.

Кроме того, для некоммутативного eltype вы можете эффективно вычислить C = ABα + Cβ , вызвав muladd!(α', B', A', β', C') (гипотетический порядок аргументов). Может потребоваться, чтобы вы могли отправлять на ленивых обертках Adjoint(α) и Adjoint(β) . (Я лично не использую некоммутативные числа в Джулии, так что это, вероятно, очень гипотетично.)

Я согласен с точкой зрения @Jutho , что эта функция умножения-сложения представляет собой низкоуровневый API для опытных программистов, таких как разработчики библиотек. Я думаю, что расширяемость имеет высокий приоритет для этого API, и позиционный аргумент - лучший выход.

Еще один аргумент в пользу отказа от аргумента ключевого слова - это то, что сказал @andreasnoack перед https://github.com/JuliaLang/julia/issues/23919#issuecomment -365762889:

Имена α и β также не будут интуитивно понятными, если вы не знаете BLAS.

@tkf , конечно, мой аргумент скорее β != 0 будет меньше, чем β == 0 , и те, кому это нужно, не удивятся, обнаружив это немного более общим поведение при mul! . Следовательно, я не вижу выгоды от выделения этого под новым именем, тем более что порядок аргументов перепутан (по крайней мере, с muladd! ). Если это должен быть новый метод, я также сочувствую вашему аргументу в пользу addmul! .

те, кому это нужно, не удивятся, обнаружив это несколько более общее поведение в mul! .

Я согласен с этим.

Поэтому я не вижу смысла выделять это под новым именем,

Если вы не видите какого-то вреда, я думаю, что это глобальный выигрыш, поскольку есть и другие люди, которые видят пользу.

тем более, что порядок аргументов перепутан (по крайней мере, с muladd! )

Думаю, вы сочтете это вредом, и я все понял. Просто я думаю, что другие преимущества для muladd! / addmul! более важны.

Думаю, вы сочтете это вредом, и я все понял. Просто я думаю, что у muladd! / Addmul есть другие преимущества! важнее.

Это действительно вред, вместе с тем фактом, что mul! всегда была единственной точкой входа в несколько операций BLAS, связанных с умножением, будь то ограничение, не предоставляя полный доступ к α и β. А теперь с muladd! будут две разные точки входа, в зависимости от небольшой разницы в запрошенной операции, которую можно легко захватить с помощью аргумента (и действительно, что захватывается аргументом в BLAS API) . Я думаю, что в Джулии было ошибкой не предлагать полный доступ к BLAS API (так что спасибо за исправление этого @tkf). Несмотря на это старое ужасное соглашение об именах Фортрана, я думаю, эти ребята знали, почему они так поступают. Но аналогично, я думаю, что это семейство операций (то есть 2-параметрическое семейство операций, параметризованных α и β) принадлежит одной точке входа, как и в BLAS.

На мой взгляд, наиболее действенным аргументом счетчика является разница между тем, будут ли доступны исходные данные в C . Но, учитывая тот факт, что Джулия приняла умножение на false как способ гарантировать нулевой результат, даже когда другой фактор равен NaN , я думаю, что об этом тоже позаботятся. Но, возможно, этот факт нужно сообщить / задокументировать лучше (я давно не читал документацию), и я также только недавно узнал об этом. (Вот почему в KrylovKit.jl мне требуется наличие метода fill! для инициализации произвольного векторного типа пользователя нулями. Но теперь я знаю, что могу просто rmul!(x,false) вместо этого, поэтому мне не нужно навязывать реализацию fill! ).

Просто я думаю, что у muladd! / Addmul есть другие преимущества! важнее.

Итак, позвольте мне перевернуть вопрос, в чем еще преимущества нового метода? Я снова прочитал ваше резюме, но вижу только точку доступа к C , которую я только что прокомментировал.

Сегодня утром я упомянул своей жене, что в сообществе Джулии в течение двух месяцев велся разговор о названии операции. Она предложила назвать его «Фред!» - без акронима, без глубокого смысла, просто хорошее имя. Просто выложу это от ее имени.

Хорошо, что она поставила восклицательный знак! 😄

Прежде всего, на всякий случай, позвольте мне пояснить, что меня беспокоит почти только читаемость кода, а не возможность записи или обнаружения. Вы пишете код один раз, но читаете много раз.

В чем еще преимущества нового метода?

Как вы прокомментировали, я считаю, что аргумент вывода и параметра ввода-вывода является наиболее важным. Но это лишь одна из причин, почему я думаю, что _C = αAB + βC_ отличается от _C = AB_. Я также думаю, что тот простой факт, что они различны в том смысле, что первое выражение является строгим «надмножеством» второго, требует четкого визуального указания в коде. Другое имя помогает программисту среднего уровня (или маленькому несознательному продвинутому программисту) просмотреть код и заметить, что он использует что-то более странное, чем mul! .

Я только что проверил опрос (вам нужно нажать «Загрузить еще» выше) еще раз, и похоже, что некоторые голоса переместились с mul! на muladd! ? В прошлый раз, когда я это видел, выигрывает mul! . Давайте запишем это здесь до того, как они двинулись: смеется:

  • mul! : 6
  • addmul! : 2
  • muladd! : 8
  • что-то еще: 1

Чуть более серьезно, я все еще думаю, что эти данные не показывают, что mul! или muladd! более ясны, чем другие. (Хотя это показывает, что addmul! - меньшинство: sob :)

Такое ощущение, что мы застряли. Как нам двигаться дальше?

Просто назовите это gemm! вместо этого?

Просто назовите это жемчужиной! вместо?

Надеюсь, это шутка ... если только вы не предлагаете gemm!(α, A::Matrix, x::Vector, β, y::Vector) = gemv!(α, A, x, β, y) для матрицы * вектора.

Не могли бы мы пока оставить интерфейс mul! который уже существует (с разреженными матрицами), чтобы мы могли объединить PR и получить улучшения, и беспокоиться о том, хотим ли мы добавить muladd! в другой PR ?

Может быть, здесь всем уже ясно, но я просто хотел подчеркнуть, что голосование - это не

  • mul! против muladd!

но

  • mul! vs ( mul! и muladd! )

т.е. наличие двух изменяющихся функций умножения вместо одной.

Я решил больше не публиковать сообщения, так как каждый раз, когда я писал в пользу mul! , голоса, казалось, менялись с mul! на ( mul! и muladd! ).

Однако у меня есть вопрос? Если мы пойдем с текущим большинством голосов, и у нас одновременно есть mul!(C,A,B) и muladd!(A,B,C,α=true,β=true) , и я хочу подготовить PR, который заменяет axpy! и axpby! на более юлианское имя add! , должно быть add!(y, x, α=true, β=true) или add!(x, y, α=true, β=true) (где, для ясности, y изменено). Или что-то другое?

Если это неочевидно, muladd!(A,B,C) нарушит соглашение, согласно которому измененные аргументы идут первыми .

Можно ли оставить интерфейс mul! который уже существует

@jebej Я думаю, что этот аргумент "обратной совместимости" широко обсуждается. Но это никого не убеждает (судя по опросу, это не только я).

беспокоиться о том, хотим ли мы добавить muladd! в другой PR?

Нарушать публичный API - это плохо. Итак, если мы скажем mul! тогда это будет mul! навсегда (хотя теоретически LinearAlgebra может изменить свою основную версию, чтобы сломать API).

Я хочу подготовить PR, который заменяет axpy! и axpby! более юлианским именем add! , если это будет add!(y, x, α=true, β=true) или add!(x, y, α=true, β=true)

@Jutho Спасибо, это было бы здорово! Я думаю, что выбор порядка аргументов будет простым, если мы определим сигнатуру вызова API умножения-сложения.

muladd!(A,B,C) нарушит соглашение, согласно которому измененные аргументы идут первыми .

@simonbyrne Но (как вы уже упоминали в https://github.com/JuliaLang/julia/issues/23919#issuecomment-426881998), lmul! и ldiv! изменяют не первый аргумент. Поэтому я думаю, что нам не нужно исключать muladd!(A,B,C,α,β) из выбора, а скорее считать это отрицательным моментом для этой подписи.

(Но я бы сказал, что нужно использовать muladd!(α, A, B, β, C) если у нас будет API "текстового порядка".)

Кстати, в результате голосования я не понимаю асимметрии muladd! и addmul! . Если вы напишете C = βC + αAB , мне кажется, что addmul! будет более естественным.

@tkf Это о том, какую операцию вы делаете в первую очередь. Мне addmul! предлагает сначала сложить, а затем умножить, как в (A+B)C . Конечно, это субъективно. Но хорошие имена должны обращаться к интуиции.

Ах, я понял.

Поскольку это все еще застряло, в моем предложении будет шаблон использования, состоящий из определений функций с (идя с @callexpr для второго)

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

и, возможно, более удобная форма для отправки (вторая - с @callname )

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

и звонки

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

и никому не нужно беспокоиться (или знать), как callexpr превращает алгебраические операции в уникальное имя функции (которое не зависит от символов аргументов, а только от операций и порядка операций).
Я немного подумал о реализации, и она должна быть вполне выполнимой.

@mschauer Я считаю, что это интересное направление. Вы можете открыть новый выпуск? Предлагаемый вами API может решить многие другие проблемы. Я думаю, что его нужно пройти через тщательный процесс проектирования, а не на решение единственной проблемы, которую он может решить.

Так что до меня дошли слухи, что на следующей неделе функциональность 1.1 будет заморожена. Хотя до следующего второстепенного релиза «всего» четыре месяца, было бы очень хорошо, если бы мы могли иметь его в 1.1 ...

В любом случае, нам также необходимо определить сигнатуру вызова (порядок аргументов и ключевых слов или нет) перед объединением PR.

Итак, давайте проведем голосование еще раз (я обнаружил, что это хороший стимулятор).

_Если_ мы используем muladd! для _C = ABα + Cβ_, какая ваша любимая сигнатура вызова?

  • : +1: muladd!(C, A, B, α, β)
  • : -1: muladd!(A, B, C, α, β)
  • : smile: muladd!(C, A, B; α, β) (например: +1:, но с аргументами ключевого слова)
  • : tada: muladd!(A, B, C; α, β) (например: -1:, но с аргументами ключевого слова)
  • : confused: muladd!(A, B, α, C, β)
  • : heart: еще что-то

Если у вас есть другие имена аргументов ключевых слов, проголосуйте за те, которые используют α и β а затем прокомментируйте, какие имена лучше.

Поскольку мы еще не решили, какое имя должно быть, нам нужно сделать это и для mul! :

_Если_ мы используем mul! для _C = ABα + Cβ_, какая ваша любимая сигнатура вызова?

  • : +1: mul!(C, A, B, α, β)
  • : -1: mul!(A, B, C, α, β)
  • : smile: mul!(C, A, B; α, β) (например: +1:, но с аргументами ключевого слова)
  • : tada: mul!(A, B, C; α, β) (это невозможно)
  • : confused: mul!(A, B, α, C, β)
  • : heart: еще что-то

ПРИМЕЧАНИЕ. Мы не меняем существующий API mul!(C, A, B)

ПРИМЕЧАНИЕ. Мы не меняем существующий API mul!(C, A, B)

Я не уделил этому факту должного внимания - у нас уже есть mul! и вот что это означает:

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

Вычисляет произведение матрица-матрица или матрица-вектор A*B и сохраняет результат в Y , перезаписывая существующее значение Y . Обратите внимание, что Y не должен иметь псевдонима ни A ни B .

Учитывая это, кажется очень естественным просто расширить это так:

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

Вычисляет произведение матрица-матрица или матрица-вектор A*B и сохраняет результат в Y , перезаписывая существующее значение Y . Обратите внимание, что Y не должен иметь псевдонима ни A или B . Если скалярное значение, α , поставляется, то α*A*B вычисляется вместо A*B . Если предоставляется скалярное значение, β , вместо него вычисляется α*A*B + β*Y . К этим вариантам применяется такое же ограничение на псевдонимы.

Однако я думаю, что это вызывает серьезную озабоченность: для mul!(Y, A, B, C, D) кажется по крайней мере таким же естественным вычисление A*B*C*D на месте в Y - и это общее понятие очень сильно противоречит mul!(Y, A, B, α, β) вычисления α*A*B + β*C . Более того, мне кажется, что вычисление A*B*C*D в Y - это вещь, которую было бы полезно и возможно делать эффективно, избегая промежуточных распределений, поэтому я действительно не хотел бы блокировать это значение .

Имея в виду это другое естественное обобщение mul! , вот еще одна мысль:

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

Это вписывается в общую модель mul!(out, args...) где вы вычисляете и записываете в out , умножая args вместе. Он полагается на диспетчеризацию, чтобы иметь дело со скалярным значением α вместо того, чтобы делать его особым случаем - это просто еще одна вещь, которую вы умножаете. Когда α - это скаляр, а A , B и Y - матрицы, мы можем отправить в BLAS, чтобы сделать это очень эффективно. В противном случае у нас может быть общая реализация.

Более того, если вы находитесь в некоммутативном поле (например, кватернионы), вы можете контролировать, с какой стороны происходит масштабирование с помощью α : mul!(Y, A, B, α) масштабируется на α на справа вместо левого:

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

Да, мы не можем вызывать BLAS для кватернионов, но он общий, и мы, вероятно, все еще можем делать это достаточно эффективно (возможно, даже как-то преобразовав его в некоторые вызовы BLAS).

Предполагая, что такой подход для Y .= α*A*B следующий вопрос: а как насчет масштабирования и увеличения Y ? Я начал думать о ключевых словах для этого, но потом мне пришло в голову некоммутативное поле, которое показалось мне слишком неудобным и ограниченным. Итак, я начал думать об этом API вместо этого - что сначала кажется немного странным, но терпите меня:

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

Немного странно, но работает. А в некоммутативном поле вы можете попросить умножить Y на β справа следующим образом:

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

В общем, в некоммутативном поле вы можете масштабировать как влево, так и вправо следующим образом:

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

Теперь, конечно, это немного странно, и для этого нет операции BLAS, но это обобщение GEMM, которое позволяет нам выразить _lot_ вещей и которое мы можем тривиально отправить в операции BLAS, даже не делая никаких неприятных if / else ветви.

Мне очень нравится предложение @StefanKarpinski в качестве базового вызова API, но я также думаю о том, действительно ли мы хотим таким образом

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

Тогда основная функция будет примерно такой, как предлагает @StefanKarpinski .

Но здесь следует пойти дальше. Я действительно думаю, что если вы создадите для него API и универсальную функцию, кто-то создаст библиотеку Julia, которая будет делать это эффективно, поэтому я согласен с тем, что здесь не следует просто придерживаться BLAS. Такие вещи, как MatrixChainMultiply.jl, уже создают DSL для множественных матричных вычислений, а DiffEq делает свое дело с выражениями аффинных операторов. Если у нас есть только одно представление для аффинного выражения в Base, мы можем определить, что вся наша работа будет относиться к одному и тому же.

@dlfivefifty раньше изучал ленивую линейную алгебру, я думаю, что здесь действительно стоит возродить. Построение ленивых представлений широковещательной передачи имело решающее значение для того, чтобы поэлементные операции работали с абстрактными массивами и альтернативным вычислительным оборудованием. То же самое нам понадобится для линейной алгебры. Представление линейных алгебраических выражений позволило бы нам определять новые ядра BLAS «на лету» из Julia BLAS или передавать уравнения в GPU / TPU.

По сути, все вычисления в научных вычислениях сводятся к поэлементным и линейным алгебраическим операциям, поэтому их высокоуровневое описание кажется полезным для создания инструментов для метапрограммирования и изучения новых конструкций.

Я должен был бы больше подумать об этом предложении, но пока я просто прокомментирую, что я не думаю, что вы хотели бы вычислять A*B*C без временного. Мне кажется, что придется заплатить большим количеством арифметических операций, чтобы избежать временного.

Не думаю, что вам захочется вычислять A*B*C без временного.

Однако для mul! вас уже есть выходной массив. Я не уверен, помогает это или нет. В любом случае это похоже на деталь реализации. API mul!(Y, A, B, C...) выражает то, что вы хотите вычислить, и позволяет реализации выбрать лучший способ сделать это, что и было здесь общей целью.

Мне очень нравится предложение @StefanKarpinski в качестве базового вызова API, но я также думаю о том, действительно ли мы хотим таким образом

@ChrisRackauckas : Я думаю, что вещи, в которые вы попадаете, можно и нужно исследовать во внешних пакетах - лень, написание вычислений, которые вы хотите, и разрешение некоторой оптимизации, выбирающей части, которые соответствуют определенным алгебраическим шаблонам, которые он знает, как оптимизировать, и т. Д. Такое использование mul! похоже на типичную, но простую для понимания операцию, которую мы хотим на этом уровне.

Обратите внимание на то, что по поводу mul!(Y, α, A, B) не может быть реальных споров Y .= α*A*B поскольку что еще это будет означать? Итак, для меня единственный открытый вопрос заключается в том, является ли использование кортежа с матрицей и левым и / или правым скалярами разумным способом выразить, что мы хотим увеличивать и масштабировать выходной массив. Общие случаи:

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

Для первого аргумента ничего другого не допускается. Это может быть принято как более общее соглашение для других операций, когда имеет смысл либо перезаписывать, либо накапливать в выходном массиве, необязательно в сочетании с масштабированием.

Мне не пришло в голову «слить» mul!(out, args...) и GEMM-подобный интерфейс! Мне нравится его расширяемость (но тогда начните писать ответ ниже, и теперь я не уверен ...)

Но меня беспокоит то, что его легко использовать в качестве интерфейса для перегрузки. Нам нужно полагаться на систему типов, чтобы она хорошо работала с вложенными кортежами. Вложенные кортежи работают так же хорошо, как плоские кортежи в системе типов Джулии? Мне интересно, является ли что-то вроде « Tuple{Tuple{A1,B1},C1,D1} более конкретным, чем Tuple{Tuple{A2,B2},C2,D2} iff Tuple{A1,B1,C1,D1} более конкретным, чем Tuple{A2,B2,C2,D2} ». В противном случае было бы сложно использовать в качестве API перегрузки.

Обратите внимание, что нам необходимо выполнить диспетчеризацию скалярных типов, чтобы использовать переинтерпретацию для сложных матриц (это из PR # 29634, поэтому не обращайте внимания на имя функции):

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

Другое беспокойство заключается в том, что это несколько ограниченный интерфейс для исполнителя графа вычислений. Я думаю, что основная цель интерфейса умножения-сложения - предоставить перегрузочный API, позволяющий разработчикам библиотеки определять небольшое повторно используемое вычислительное ядро, которое может быть эффективно реализовано. Это означает, что мы можем реализовать только _C = ABα_, а не, например, _αAB_ (см. Https://github.com/JuliaLang/julia/pull/29634#issuecomment-443103667). Поддержка _α₁ABα₂_ для некоммутативного eltype требует либо временного массива, либо увеличения количества арифметических операций. Неясно, какой из пользователей хочет, и в идеале это должно быть настраиваемым. На этом этапе нам нужно представление графа вычислений, отделенное от механизма выполнения. Я думаю, что это лучше изучить во внешних пакетах (например, LazyArrays.jl, MappedArrays.jl). Однако, если мы сможем найти стратегию реализации, которая в какой-то момент покрывает большую часть сценария использования, использование mul! в качестве основной точки входа будет иметь смысл. Я думаю, что это еще одна причина отдать предпочтение muladd! ; выделение места для будущего API вызовов.

Мне нужно было бы больше подумать об этом предложении, но пока я просто прокомментирую, что я не думаю, что вы хотели бы вычислять A B C без временного. Мне кажется, что придется заплатить большим количеством арифметических операций, чтобы избежать временного.

Вы действительно можете доказать, что любое сокращение произвольного числа тензоров, самый эффективный способ оценить все это всегда - это использование попарных сокращений. Так что умножение нескольких матриц - это просто частный случай этого, вы должны умножать их попарно (лучший порядок, конечно, нетривиальная проблема). Вот почему я думаю, что mul!(Y,X1,X2,X3...) не такой уж полезный примитив. И, в конце концов, это то, что я думаю, mul! , это примитивная операция, которую разработчики могут перегрузить для своих конкретных типов. Затем любая более сложная операция может быть написана с использованием конструкции более высокого уровня, например, с использованием макросов, и, например, может быть построен граф вычислений, который в конце оценивается путем вызова примитивных операций, таких как mul! . Конечно, этот примитив может быть достаточно общим, чтобы включать случаи, подобные некоммутативному, о котором упоминает

Пока не задействовано умножение матриц / сжатие тензора, верно, что мышление в терминах примитивных операций не так полезно, и может быть полезно объединить все вместе, как это делает широковещательная передача.

В общем, я согласен, что было бы хорошо иметь тип графа ленивого представления / вычислений по умолчанию в Base, но я не думаю, что mul! - способ его построить.

@tkf :

Но меня беспокоит то, что его легко использовать в качестве интерфейса для перегрузки. Нам нужно полагаться на систему типов, чтобы она хорошо работала с вложенными кортежами. Вложенные кортежи работают так же хорошо, как плоские кортежи в системе типов Джулии?

Да, в этом плане у нас все хорошо. Я не уверен, в чем заключается вложение, но передача некоторых вещей в кортеж столь же эффективна, как и передача их всех в качестве непосредственных аргументов - это реализовано точно так же.

Это означает, что мы можем реализовать только _C = ABα_, а не, например, _αAB_

Я запуталась ... можно писать mul!(C, A, B, α) и mul!(C, α, A, B) . Можно даже написать mul!(C, α₁, A, α₂, B, α₃) . Похоже, что это самый гибкий универсальный API умножения матриц, который был предложен до сих пор.

Другое беспокойство заключается в том, что это несколько ограниченный интерфейс для исполнителя графа вычислений. Я думаю, что основная цель интерфейса умножения-сложения - предоставить перегрузочный API, позволяющий разработчикам библиотеки определять небольшое повторно используемое вычислительное ядро, которое может быть эффективно реализовано.

На этом этапе нам нужно представление графа вычислений, отделенное от механизма выполнения.

Это может быть так, но здесь не место для этого - это можно и нужно разрабатывать во внешних пакетах. Все, что нам нужно для решения этой конкретной проблемы, - это API умножения матриц, который обобщает то, что может быть отправлено операциям BLAS - что в значительной степени именно то, что он делает.

@Jutho

Так что умножение нескольких матриц - это просто частный случай этого, вы должны умножать их попарно (лучший порядок, конечно, нетривиальная проблема). Вот почему я думаю, что mul!(Y,X1,X2,X3...) не такой уж полезный примитив.

Операция mul! позволит реализации выбрать порядок умножения, что является полезным свойством. Действительно, возможность потенциально сделать это - вот почему мы в первую очередь сделали синтаксический анализ операции * как n-арный, и те же рассуждения еще больше применимы к mul! поскольку, если вы ее , вы, по-видимому, достаточно заботитесь о производительности.

В общем, я не могу сказать, за или против моего предложения по mul! .

Я не уверен, где происходит вложение, но передача некоторых вещей в кортеж так же эффективна, как передача их всех в качестве непосредственных аргументов

Я не беспокоился об эффективности, а скорее о неоднозначности диспетчеризации и метода, поскольку даже текущая LinearAlgebra несколько хрупка (что может быть связано с отсутствием у меня понимания системы типов; иногда это меня удивляет). Я упоминал о вложенном кортеже, так как думал, что разрешение метода достигается путем пересечения типа кортежа всех позиционных аргументов. Это дает вам плоский кортеж. Если вы используете кортеж в первом аргументе, у вас есть вложенный кортеж.

Это означает, что мы можем реализовать только _C = ABα_, а не, например, _αAB_

Я запуталась ... можно писать mul!(C, A, B, α) и mul!(C, α, A, B) .

Я хотел сказать, что «мы можем реализовать _C = ABα_ так же эффективно, как _C = AB_, а другие кандидаты, такие как _αAB_, не могут быть реализованы эффективно во всех комбинациях типов матриц». (Под эффективностью я подразумеваю большую временную сложность O.) Я не уверен, что это действительно так, но, по крайней мере, для разреженной матрицы другие два варианта отсутствуют.

На этом этапе нам нужно представление графа вычислений, отделенное от механизма выполнения.

Это может быть так, но здесь не место для этого - это можно и нужно разрабатывать во внешних пакетах.

Это именно моя точка зрения. Я предлагаю рассматривать этот API как минимальный строительный блок для такого использования (конечно, это не вся цель). Внедрение и дизайн vararg mul! могут быть выполнены после того, как люди изучат пространство дизайна во внешних пакетах.

Диспетчеризация по типам даже для текущего mul! уже «сломана»: наблюдается комбинаторный рост переопределений неоднозначности, необходимых для работы с типами составных массивов, такими как SubArray и Adjoint .

Решение состоит в использовании трейтов, и LazyArrays.jl имеет доказательную концептуальную версию mul! с трейтами.

Но это скорее обсуждение реализации, чем API. Но использование кортежей для группировки терминов кажется неправильным: разве не для этого существует система типов? В этом случае вы получите решение LazyArrays.jl.

Муль! Операция позволит реализации выбирать порядок умножения, что является полезным свойством. Действительно, возможность потенциально сделать это была причиной того, что мы изначально сделали синтаксический анализ операции * как n-арный, и те же рассуждения еще больше применимы к mul! поскольку, если вы его используете, вы, вероятно, достаточно заботитесь о производительности.

То, что * разбирается как n -ary, чрезвычайно полезно. Я использую его в TensorOperations.jl для реализации макроса @tensoropt , который действительно оптимизирует порядок сжатия. Я считаю, что n -арная версия mul! менее полезна, потому что с точки зрения эффективности нет особого смысла в предоставлении заранее выделенного места для размещения результата, если все промежуточные массивы по-прежнему должны быть размещены внутри функции, а затем gc'ed. Фактически, в TensorOperations.jl несколько человек заметили, что выделение больших временных файлов - одно из тех мест, где gc Джулии работает очень плохо (довольно часто это приводит к временам gc в 50%).

Следовательно, я бы ограничил mul! тем, что является действительно примитивной операцией, что также рекомендуется @tkf, если я правильно понимаю: умножение двух матриц на третью с возможно скалярными коэффициентами. Да, мы можем придумать наиболее общий способ сделать это для некоммутативных алгебр, но сейчас я думаю, что насущной необходимостью является удобный доступ к функциям, предоставляемым BLAS (gemm, gemv, ...), которые mul! Джулии mul! . Я не против подумать о лучшем API, чтобы убедиться, что в будущем он действительно поддерживает эту примитивную операцию для более общих числовых типов.

Мне не нравится ваше предложение с кортежами, но я могу предвидеть возможную путаницу
Ограничение от случая 4 до случая 2 из 3, по-видимому, подразумевает значения по умолчанию β₁ = 1 и β₂ = 1 (или фактически true ). Но тогда, если ни один из них не указан, это внезапно означает β₁ = β₂ = 0 ( false ). Конечно, синтаксис немного отличается, поскольку вы пишете mul!(Y, args...) , а не mul!((Y,), args...) . В конце концов, это вопрос документации, поэтому я просто хотел указать на это.

Итак, в общем, нет, я на самом деле не против этого синтаксиса, хотя это новый тип парадигмы, который вводится, и, вероятно, его также следует придерживаться в других местах. Я возражаю против того, чтобы сразу же обобщить это на умножение произвольного числа матриц, в котором, как указывалось выше, я не вижу пользы.

@dlfivefifty : Но это скорее обсуждение реализации, чем API. Но использование кортежей для группировки терминов кажется неправильным: разве не для этого существует система типов? В этом случае вы получите решение LazyArrays.jl.

Но мы не собираемся использовать здесь полные ленивые массивы - для этого уже есть LazyArrays. Между тем, нам нужен способ выразить масштабирование Y . Использование кортежей кажется простым и легким подходом к выражению этой небольшой структуры. У кого-нибудь есть другие предложения? У нас могут быть ключевые слова lscale и / или rscale для β₁ и β₂ , но это не кажется более элегантным, и мы потеряем возможность отправка по этому поводу, что не критично, но приятно иметь.

@Jutho : Следовательно, я бы ограничил mul! тем, что является действительно примитивной операцией, что также рекомендуется @tkf, если я правильно понимаю: умножение двух матриц на третью с возможно скалярными коэффициентами. Да, мы можем придумать наиболее общий способ сделать это для некоммутативных алгебр, но сейчас я думаю, что насущной необходимостью является удобный доступ к функциям, предоставляемым BLAS (gemm, gemv, ...), которые mul! Джулии mul! .

Я согласен с определением лишь небольшого подмножества операций для mul! , возможно, даже тех, которые структурно соответствуют действительным вызовам BLAS. Это было бы:

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

С какой целью? Зачем допускать столько вариаций в способах выражения операций BLAS?

  1. Поскольку это позволяет людям выражать свое намерение - если намерение состоит в том, чтобы умножаться слева или справа, или и то, и другое, почему бы не позволить людям выразить это и выбрать правильную реализацию?

  2. У нас могут быть универсальные резервные варианты, которые делают правильные вещи даже для некоммутативных типов элементов.

Весь смысл этого выпуска в том, чтобы получить обобщение умножения матриц на месте, которое включает gemm! будучи более универсальным, чем gemm !. Иначе почему бы просто не продолжить писать gemm! ?

Но мы не собираемся здесь использовать ленивые массивы

Я не говорю «полные ленивые массивы», я предлагаю ленивый в том же смысле, что и Broadcasted , который в конечном итоге удаляется во время компиляции. По сути, я бы добавил Applied для представления ленивого применения функции, и вместо использования кортежей (которые не содержат контекста) у вас будет что-то вроде

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

Это можно было бы покрыть сахаром, как нотация .* трансляции, чтобы сделать ее более читаемой, но я думаю, что это сразу ясно, что требуется, в отличие от предложения на основе кортежей.

@StefanKarpinski , как было сказано ранее, я, конечно же, согласен с тем, что нам следует подумать об интерфейсе, который соответствует требованиям будущего и правильно обобщается на другие типы чисел. Единственная проблема, я думаю, в том, что ваш список неполный. В принципе, у вас могло быть:

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

и все его сокращенные версии, т.е. если все 5 скалярных аргументов могут отсутствовать, это 2 ^ 5 = 32 различных возможности. И это в сочетании со всеми возможностями различных матриц или векторов.

Я согласен с @dlfivefifty в том, что похожий на широковещательный, более осуществим.

Да, я понял, что упустил некоторые варианты, но 32 метода не кажутся мне такими уж сумасшедшими, в конце концов, нам не нужно писать их вручную. Добавление «широковещательной системы» или системы отложенной оценки, которая позволяет нам писать materialize!(applied(+, applied(*, α, A, B), applied(*, β, C))) кажется гораздо более серьезным дополнением и выходом за рамки этой проблемы. Все, что нам нужно, это какой-то способ написания общего умножения матриц, который является универсальным и позволяет нам отправлять данные в BLAS. Если мы все не можем согласиться с этим, я склонен позволить людям продолжать звонить gemm! напрямую.

Да, наверное, правда; Я предположил, что будет легче, если скалярные аргументы будут возвращаться назад, чтобы легко обеспечить значения по умолчанию. Но если с помощью некоторого метапрограммирования @eval мы можем легко сгенерировать все 32 определения, это одинаково хорошо. (Обратите внимание, как вы наверняка знаете, mul - это не только gemm! но также gemv и trmm и ...).

Добавлю, что это не просто оболочка BLAS. В stdlib есть и другие специализированные методы pure-Julia. Кроме того, важно иметь это в качестве API перегрузки: авторы пакетов могут определять mul! для своих специальных типов матриц.

Думаю, это моя позиция:

  1. Мы могли бы также поддержать mul!(C, A, B, a, b) сейчас, поскольку он уже существует в SparseArrays.jl
  2. Мы не должны ничего делать, потому что диспетчеризация по типам матриц плохо масштабируется. (Как сопровождающий BandedMatrices.jl, BlockArrays.jl, LowRankApprox.jl и т. Д., Я могу утверждать это по своему опыту.)
  3. Дизайн на основе черт хорошо масштабируется, но было бы лучше пойти ва-банк и сделать широковещательную рассылку типа Applied , поскольку шаблон проектирования уже установлен. Придется подождать до Julia 2.0, а прототип будет разрабатываться в LazyArrays.jl, который соответствует моим потребностям.

@dlfivefifty Считаете ли вы, что сложность устранения неоднозначности mul!((Y, β), α, A, B) API такая же, как в mul!(Y, A, B, α, β) ? Рассмотрение оболочек матриц, таких как Transpose представляет сложность, включая 2- и 3-кортежи, похоже, еще больше увеличивает сложность (хотя я знаю, что кортеж - это особый случай в системе типов Джулии).

  1. Мы могли бы также поддержать mul!(C, A, B, a, b) сейчас, поскольку он уже существует в SparseArrays.jl

Тот факт, что кто-то решил, что mul!(C, A, B, a, b) должен означать C .= b*C + a*A*B не обдумав это до конца, категорически не является хорошей причиной для удвоения этого. Если mul! - это версия * тогда я не понимаю, как mul!(out, args...) может означать что-либо, кроме out .= *(args...) . Честно говоря, именно так вы получаете систему, состоящую из плохо продуманных и несовместимых API, которые существуют только по исторической случайности. Функция mul! не экспортируется из SparseArrays _и этот конкретный метод не задокументирован, так что это действительно самая ненадежная причина закрепить непродуманный метод, который, вероятно, был добавлен только потому, что функция не была непублично! Я предлагаю исправить эту ошибку и вместо этого удалить / переименовать этот метод mul! .

Из остальной части этого обсуждения кажется, что мы не должны делать ничего другого, поскольку все заинтересованные стороны хотят сделать что-то более интересное с чертами и / или ленью за пределами стандартной библиотеки. Меня это устраивает, так как удалять вещи всегда приятно.

Из остальной части этого обсуждения кажется, что мы не должны делать ничего другого, поскольку все заинтересованные стороны хотят сделать что-то более интересное с чертами и / или ленью за пределами стандартной библиотеки. Меня это устраивает, так как удалять вещи всегда приятно.

Похоже, тебе уже немного надоело, и это понятно. Однако я не думаю, что этот вывод верен. Если вы уверены, что текущее предложение может быть реализовано масштабируемым образом и по-прежнему позволяет разработчикам пакетов перегрузить это определение для своих собственных матричных и векторных типов (как указано в @tkf), это было бы отличным способом вперед.

В частности, я думаю, что разработчикам пакетов нужно всего лишь реализовать:

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

и, возможно, например,

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

в то время как Julia Base (точнее, стандартная библиотека LinearAlgebra) заботится об обработке всех значений по умолчанию и т. д.

Я бы подумал, что разработчикам пакетов нужно только реализовать:

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

Я бы предложил задокументировать

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

как подпись для перегрузки. Это потому, что другие местоположения для α меняют большую временную сложность O. См. Https://github.com/JuliaLang/julia/pull/29634#issuecomment -443103667. Это дает некоммутативные числа не первоклассной обработки. Но AFAICT здесь на самом деле никто не использует некоммутативные числа, и я думаю, что нам следует подождать, пока не возникнет реальная необходимость.

В подходе @StefanKarpinski мне нравится то, что мы можем реализовать специализированный метод для mul!((Y, β), α::Diagonal, A, B) для _some_ матричного типа A (например, Adjoint{_,<:SparseMatrixCSC} ) без изменения временной сложности. . (Это важно для моего приложения.) Конечно, этот путь потребует дальнейшего обсуждения в API, особенно того, как запрашивать существование специализированного метода. Тем не менее, возможность расширения API - это здорово.

Если кто-нибудь прояснит мое беспокойство по поводу неоднозначности методов, я буду полностью сторонником подхода группы за кортежем.

Это потому, что другие местоположения для α меняют большую временную сложность O.

Это какая то разреженная матрица конкретно? Я не совсем аргумент, особенно для плотных матриц. В реализации, на которую вы ссылаетесь, вы показываете случай, когда α находится между A и B ?

Я бы подумал, что разработчикам пакетов нужно только реализовать ...

Это очень упрощенно. Предположим, у нас есть матрица, которая ведет себя как матрица с полосами, например PseudoBlockMatrix из BlockArrays.jl. Чтобы полностью поддерживать gemm! нам нужно переопределить каждую перестановку PseudoBlockMatrix с (1) самим собой, (2) StridedMatrix , (3) Adjoint s самого себя , (4) Transpose s самого себя, (5) Adjoint s из StridedMatrix , (6) Transpose s из StridedMatrix , и, возможно, другие. Это уже 6 ^ 3 = 216 различных комбинаций. Затем вы хотите поддержать trmm! и вы должны сделать то же самое с UpperTriangular , UnitUpperTriangular , их сопряженными, их транспозициями и т. Д. Затем gsmm! с Symmetric и Hermitian .

Но во многих приложениях мы хотим работать не только с матрицами, но и с их подпредставлениями, особенно для блочных матриц, где мы хотим работать с блоками. Теперь нам нужно добавить каждую перестановку представлений нашей матрицы вместе с 6 комбинациями выше.

Теперь у нас есть тысячи переопределений, включая StridedMatrix , который представляет собой очень сложный тип объединения. Это слишком много для компилятора, поэтому время using занимает минуты, а не секунды.

На этом этапе становится понятно, что текущий mul! и, как следствие, предлагаемые расширения mul! имеют изъян по конструкции, и разработчики пакетов не должны возиться с этим. К счастью, LazyArrays.jl предоставляет временный обходной путь с использованием трейтов.

Так что я вроде как согласен с @StefanKarpinski в том, чтобы оставить все как есть до тех пор, пока не будет проведен более существенный редизайн, поскольку тратить усилия на то, что несовершенно по дизайну, - не лучшее использование времени.

@dlfivefifty , я имел в виду только то, как обрабатываются скалярные аргументы. Все сложности с разными типами матриц, которые уже существуют для mul!(C,A,B) , конечно же, останутся.

Это потому, что другие местоположения для α меняют большую временную сложность O.

Это какая то разреженная матрица конкретно? Я не совсем аргумент, особенно для плотных матриц. В реализации, на которую вы ссылаетесь, вы показываете случай, когда α находится между A и B ?

@Jutho Я думаю, что вы не можете поместить α во внутреннюю позицию самого цикла. Например, в этом случае вы можете поддерживать α₁*A*B*α₃ но не A*α₂*B

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

Я думаю, что по крайней мере α₁ или α₂ в α₁*A*α₂*B*α₃ должно быть 1 чтобы избежать увеличения асимптотической временной сложности.

@dlfivefifty Но даже LazyArrays.jl нуждается в некоторых примитивных функциях для отправки, верно? Насколько я понимаю, это решает проблему «диспетчерского ада», но не уменьшает количество вычислительных «ядер», которые люди должны реализовать.

Нет, «примитива» не существует, так же как в Broadcasted нет «примитива». Но да, на данный момент это не решает «ядерный» вопрос. Я думаю, что следующим шагом будет переделка его для использования ленивого типа Applied с ApplyStyle . Тогда может быть MulAddStyle для распознавания BLAS-подобных операций, причем порядок не имеет значения.

Я бы назвал materialize! или copyto! примитивом. По крайней мере, это строительный блок для механизма вещания. Точно так же, я полагаю, LazyArrays.jl в какой-то момент должен снизить свое ленивое представление до функций с циклами или ccall s до внешних библиотек, верно? Будет ли плохо, если имя такой функции будет mul! ?

Это очень упрощенно. Предположим, у нас есть матрица, которая ведет себя как матрица с полосами, например PseudoBlockMatrix из BlockArrays.jl. Чтобы полностью поддержать gemm! нам нужно переопределить каждую перестановку PseudoBlockMatrix с помощью (1) самого себя, (2) StridedMatrix, (3) Adjoints самого себя, (4) Transposes самого себя, (5) Adjoints of StridedMatrix, (6) Transposes of StridedMatrix и, возможно, других . Это уже 6 ^ 3 = 216 различных комбинаций. Тогда вы хотите поддержать trmm! и вы должны сделать то же самое с UpperTriangular, UnitUpperTriangular, их смежными, их транспозициями и т. д. Тогда гсм! с симметричным и эрмитовым.
Но во многих приложениях мы хотим работать не только с матрицами, но и с их подпредставлениями, особенно для блочных матриц, где мы хотим работать с блоками. Теперь нам нужно добавить каждую перестановку представлений нашей матрицы вместе с 6 комбинациями выше.
Теперь у нас есть тысячи переопределений, включая StridedMatrix, который представляет собой очень сложный тип объединения. Это слишком много для компилятора, поэтому время использования занимает минуты, а не секунды.

Я, конечно, согласен с тем, что текущее объединение типов StridedArray является серьезным недостатком дизайна. Я поддерживал вашу попытку исправить это в какой-то момент.

В Strided.jl я реализую mul! тогда, когда все задействованные матрицы относятся к моему собственному типу (Abstract)StridedView , и всякий раз, когда есть некоторое смешение типов A, B и C, я позволяю Джулии Base / LinearAlgebra справится с этим. Конечно, это должно использоваться в макросреде @strided , которая пытается преобразовать все возможные базовые типы в тип StridedView . Здесь StridedView может представлять подпредставления, транспозиции и присоединения, а также определенные изменения формы, все с одним и тем же (параметрическим) типом. В целом, полный код умножения составляет около 100 строк:
https://github.com/Jutho/Strided.jl/blob/master/src/abstractstridedview.jl#L46 -L147
Собственный резервный вариант Julia на случай, если BLAS не применяется, реализован с использованием более общей функциональности mapreducedim! предоставляемой этим пакетом, и не менее эффективен, чем функция в LinearAlgebra ; но он также многопоточный.

Я думаю, что по крайней мере α₁ или α₂ в α₁*A*α₂*B*α₃ должно быть 1, чтобы избежать увеличения асимптотической временной сложности.

@tkf , я бы предположил, что если эти скалярные коэффициенты принимают значение по умолчанию one(T) или, что еще лучше, true , постоянное распространение и оптимизация компилятора автоматически устранят это умножение. во внутреннем самом цикле, когда он не работает. Так что все же было бы удобно просто определить самую общую форму.

Я не уверен, можем ли мы полагаться на постоянное распространение, чтобы исключить все умножения на 1 ( true ). Например, eltype может быть Matrix . В этом случае я думаю, что true * x (где x::Matrix ) должен создать копию x выделенную в куче. Может Джулия сотворить чудо, чтобы это устранить?

@Jutho Я думаю, что этот тест показывает, что Джулия не может исключить промежуточные умножения в некоторых случаях:

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)

Хороший тест. Я также уже заметил, что это действительно не устранит эти распределения с помощью некоторых подобных экспериментов. Для таких случаев может быть полезно определить одноэлементный тип One специального назначения, который просто определяет *(::One, x::Any) = x и *(x::Any, ::One) = x , и о котором ни один пользовательский тип никогда не нуждается. Тогда значение по умолчанию, по крайней мере для α₂ , может быть One() .

Ах да, это умно! Сначала я подумал, что теперь у меня все в порядке с поддержкой α₁ * A * α₂ * B * α₃ но потом я обнаружил другую проблему: математически неоднозначно, что мы должны делать, когда (скажем) A представляет собой матрицу-матрицу и α₁ - это матрица. Это не будет проблемой, если мы _ никогда_ не будем поддерживать нескалярные аргументы в позициях α . Однако это делает невозможным представление Y .= β₁*Y*β₂ + *(args...) в качестве ментальной модели mul!((β₁, Y, β₂), args...) . Кроме того, было бы очень хорошо, если бы диагональные матрицы можно было передать в α₁ или α₂ поскольку это может быть вычислено почти «бесплатно» иногда (что важно в приложениях). Думаю, есть два пути:

(1) Используйте mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) но при перегрузке метода аргументы α и β должны принимать Diagonal . Путь вызова легко определить, чтобы код конечного пользователя мог вызывать его через скалярные значения. Но для того, чтобы это работало эффективно, в LinearAlgebra должна быть реализована «версия O (1)» Diagonal(fill(λ, n)) https://github.com/JuliaLang/julia/pull/30298#discussion_r239845163 . Обратите внимание, что реализации для скалярной и диагональной α не сильно отличаются; часто это просто замена α и α.diag[i] . Так что я не думаю, что это большая нагрузка для авторов пакета.

Это решает неоднозначность, о которой я упоминал выше, потому что теперь вы можете вызвать mul!(Y, α * I, A, B) когда A - это матрица-матрица, а α - это матрица, которую следует рассматривать как eltype из A .

(2) Может быть, указанный выше путь (1) все еще слишком сложен? Если да, то сейчас используйте muladd! . Если мы когда-нибудь захотим поддержать mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) , переход на него с сохранением обратной совместимости не составит труда.

На этом этапе я должен задаться вопросом, не должны ли мы просто иметь очень ограниченный и специализированный matmul!(C, A, B, α, β) который определен для работы только для этой общей подписи:

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

Кроме того, действительно удивительно, что эта подпись может быть написана и отправлена.

По сути, это мое предложение (2), верно? (Я предполагаю, что A :: Matrix{T} буквально не означает Core.Array{T,2} ; в противном случае это более или менее просто gemm! )

Я был бы счастлив, если бы это было временным решением, и я мог бы частично поддерживать его в поддерживаемых мной пакетах («частично» из-за нерешенной проблемы с характеристиками), хотя оно добавляет еще одно имя в смесь: mul! , muladd! а теперь matmul! .

... Не пора ли кому-нибудь написать «сортировка говорит ...» и назвать это?

Кроме того, действительно удивительно, что эта подпись может быть написана и отправлена.

Разве это не факт, что вы можете чисто отправить именно эту подпись как аргумент для того, чтобы просто сделать это методом mul! . Затем его также можно полностью исключить, если появится какое-то более общее решение.

делая это методом mul!

Если мы пойдем с mul!(C, A, B, α, β) мы не сможем обобщить его на mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) и тому подобное без нарушения совместимости. (Может быть, это «особенность», потому что мы навсегда избавлены от этой дискуссии: smile :).

Кроме того, позвольте мне отметить, что ограничивающий тип элемента Number _и_ сохраняя совместимость с текущим 3-аргументом mul! (который уже поддерживает не Number тип элемента) внесет много дублирования.

Я понятия не имею, что вы ожидаете от mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) ... так что я думаю, что это особенность.

Удар. Мне грустно использовать функции BLAS повсюду, чтобы повысить производительность на месте.

@StefanKarpinski Не могли бы вы поднять это в порядке сортировки?

Мы можем обсудить, хотя я не уверен, какой результат будет без присутствия некоторых линалгов, которых обычно не бывает в наши дни. Откровенно говоря, я потратил сумму справедливого времени и усилий, пытаясь получить эту дискуссию в какое-то разрешении и каждую идею, кажется, были стойко отклонено, так или иначе, так что я очень много раз проверил из этого вопроса на эта точка. Если бы кто-то смог кратко описать проблему и почему различные предложения неадекватны, это было бы полезно для продуктивного обсуждения сортировки. В противном случае я не думаю, что мы сможем много сделать.

Я думаю, что отсутствие консенсуса означает, что сейчас не время включать это в StdLib.

Почему не просто пакет MatMul.jl, реализующий одно из предложений, которые могут использовать неработающие пользователи? Я не понимаю, почему использование StdLib так важно на практике. Я с радостью поддержу это в поддерживаемых мной пакетах.

Я думаю просто хорошая юлианская версия gemm! и гемв! соответствует тому, что у нас уже есть в SparseArrays. Согласно @andreasnoack выше:

Я думал, что мы уже остановились на mul!(C, A, B, α, β) со значениями по умолчанию для α , β . Мы используем эту версию в

Юлия / stdlib / SparseArrays / src / linalg.jl

Строки с 32 по 50 в b8ca1a4

...
Я думаю, что некоторые пакеты также используют эту форму, но я не помню, какие из них у меня в голове.

У этого предложения было семь больших пальцев вверх и ни одного большого пальца вниз. Почему бы нам просто не реализовать эту функцию для плотных векторов / матриц? Это было бы простое решение, охватывающее наиболее распространенный вариант использования, верно?

ХОРОШО. Так что, я думаю, нет даже единого мнения о том, есть ли консенсус: sweat_smile:

_I_ думал, что почти всем [*] нужен этот API, и дело только в имени функции и сигнатуре. По сравнению с отсутствием этого API, я думал, что всех устраивает любой из вариантов (скажем, mul!((β₁, Y, β₂), α₁, A, α₂, B, α₃) , muladd!(C, A, B, α, β) и mul!(C, A, B, α, β) ). Если кто-то не сможет убедительно аргументировать, что определенный API намного хуже, чем _не_ его не иметь, я был бы доволен любым решением сортировки.

@StefanKarpinski Но, пожалуйста, удалите тег triage если вы считаете, что обсуждение еще недостаточно консолидировано.

[*] Хорошо, @dlfivefifty , я думаю, вы сомневаетесь даже в текущем 3-аргументе mul! . Но это потребовало бы изменения интерфейса 3-arg mul! с нуля, поэтому я подумал, что это выходит за рамки этого обсуждения (которое я интерпретировал как _добавление_ некоторой формы варианта 5-arg). Я думаю, нам нужно что-то, что работает «достаточно», пока LazyArrays.jl не созреет.

Почему не просто пакет MatMul.jl, реализующий одно из предложений, которые могут использовать неработающие пользователи?

@dlfivefifty Я думаю, что наличие его в LinearAlgebra.jl важно, потому что это интерфейсная функция (перегружаемый API). Кроме того, поскольку mul!(C::AbstractMatrix, A::AbstractVecOrMat, B::AbstractVecOrMat) реализовано в LinearAlgebra.jl, мы не сможем определить mul! в терминах MatMul.muladd! . Есть, конечно, некоторые обходные пути, но гораздо приятнее иметь простую реализацию, особенно с учетом того, что это «всего лишь» требует определения имени и подписи.

Почему бы нам просто не реализовать эту функцию для плотных векторов / матриц?

@chriscoey К сожалению, это не единственный фаворит для всех: https://github.com/JuliaLang/julia/issues/23919#issuecomment -441717678. Вот мое резюме плюсов и минусов для этого и других вариантов https://github.com/JuliaLang/julia/issues/23919#issuecomment -441865841. (Смотрите также комментарии других людей)

Из сортировки: есть долгосрочные планы по созданию универсальных API-интерфейсов сжатия тензорного типа, включая поддержку компилятора и выбор в BLAS, но в среднесрочной перспективе просто выберите любой API, удовлетворяющий ваши непосредственные потребности. Если он соответствует BLAS, выбор имен BLAS кажется разумным.

@Keno , вы можете поделиться какой-либо информацией об общем API сжатия тензора и поддержке компилятора? У меня тоже может быть интересная информация, которой я могу поделиться, хотя и не публично (пока).

Ни для чего из этого не было разработано API, просто общее ощущение, что оно должно быть у нас. Я знаю, что вы работали над некоторыми из этих вещей, поэтому было бы неплохо провести сеанс дизайна в подходящий момент, но я не думаю, что мы еще там.

Если он соответствует BLAS, выбор имен BLAS кажется разумным.

Это полностью противоречит тому, что мы делали до сих пор для общих имен функций линейной алгебры.

Каков план для сильного / слабого β == 0 в предлагаемой общей версии BLAS.gemm!(α, A, B, β, C) ?

Если мы снизим до BLAS вызовов, он будет вести себя как сильный ноль, даже если теперь это несовместимо с lmul! . Я не могу придумать решения этой проблемы, кроме возврата к generic_muladd! if β == 0 .

Какой план на сильный / слабый β == 0

Это было кратко обсуждено в моем комментарии в https://github.com/JuliaLang/julia/issues/23919#issuecomment -430139849, поэтому сортировка, вероятно, не решала этот вопрос.

@Keno Хотя еще не было дизайна API, представляете ли вы, что «API, включая поддержку компилятора и выбор в BLAS», будут определены как изменяемые или будут неизменными, как линейная алгебра XLA, чтобы помочь компилятор? То есть, как вы думаете, mul! и / или muladd! будут частью таких API?

пинг @Keno для @andreasnoack «S вопрос https://github.com/JuliaLang/julia/issues/23919#issuecomment -475534454

Извините, я должен был вернуться к этой проблеме (особенно я запросил сортировку), но я не знал, какое следующее действие будет вынесено на основании сортировки.

Если он соответствует BLAS, выбор имен BLAS кажется разумным.

Как указал @andreasnoack , мы не можем использовать (скажем) gemm! потому что мы хотим поддерживать умножение матрицы на вектор и т. Д. Но я думаю, мы можем просто проигнорировать это решение о сортировке (которое просто говорит: «Если оно соответствует BLAS» ; это не так).

просто выберите любой API, отвечающий вашим непосредственным потребностям.

Итак, я думаю, мы можем следовать в этом направлении. Я думаю, это означает забыть об API на основе кортежей, предложенном @StefanKarpinski, и «просто» выбрать один из mul! / muladd! / addmul! .

Мы как бы возвращаемся к первоначальному обсуждению. Но я думаю, что это хорошо, что у нас есть ограничение, чтобы больше не обсуждать API.

Есть идеи, как выбрать имя из mul! / muladd! / addmul! ?


@chriscoey Я думаю, что будущее API лучше обсудить в другом месте. Этот вопрос уже очень длинный, и мы не сможем добиться прогресса, если не сосредоточимся на среднесрочном решении. Как насчет открытия нового выпуска (или обсуждения темы)?

Я предлагаю провести один раунд одобрительного голосования с 10-дневным сроком. Утверждающее голосование означает: все голосуют за все варианты, которые они считают более предпочтительными, чем продолжение обсуждения. Люди, которые предпочли бы сейчас наименее предпочтительное имя, чем продолжение обсуждения, должны голосовать за всех трех. Если ни один из вариантов не получил широкого одобрения или сама схема голосования не находит широкого одобрения, то мы должны продолжить обсуждение. В случае почти совпадения между одобренными вариантами @tkf (право автора PR).

+1: Я согласен с этой схемой голосования и отдал свои голоса одобрения.
-1: Я не согласен с этой схемой голосования. Если слишком много или слишком важных людей выберут этот вариант, голосование будет спорным.

Сердце: mul! предпочтительнее продолжения обсуждения.
Ракета: muladd! предпочтительнее продолжения обсуждения.
Ура: addmul! предпочтительнее продолжения обсуждения.

Я предварительно предлагаю, чтобы 75% одобрения и 5 человек обязательно составляли кворум (т.е. 75% людей, которые проголосовали вообще, включая несогласие со всей процедурой голосования, и не менее 5 человек одобрили вариант победы; если участие низкое , то 5/6 или 6/8 составляют кворум, но единогласные 4/4 могут быть сочтены неудачей).

Замораживание функций для версии 1.3 примерно 15 августа: https://discourse.julialang.org/t/release-1-3-branch-date-approaching-aug-15/27233?u=chriscoey. Надеюсь, к тому времени мы сможем объединить его. Спасибо всем, кто уже проголосовал!

Нам все еще нужно определить поведение β == 0 https://github.com/JuliaLang/julia/issues/23919#issuecomment -475420149, который ортогонален определению имени. Кроме того, конфликт слияния в моем PR должен быть разрешен, и PR требует некоторого обзора деталей реализации (например, подхода, который я использовал там для обработки undef s в целевом массиве). Во время проверки мы можем обнаружить другие проблемы. Так что я не уверен, сможет ли он попасть в 1.3 ....

Re: β == 0 , я думаю , @andreasnoack «s комментарий https://github.com/JuliaLang/julia/issues/23919#issuecomment -430139849 (мое резюме: β == 0 -Обработка должны быть BLAS -совместимость для максимального использования BLAS) имеет смысл. Трудно найти противоположные мнения, кроме аргумента @simonbyrne чуть ниже («каждой реализации потребуется ветка для проверки нуля»). Есть ли другие аргументы против BLAS-подобной обработки β == 0 ?

@simonbyrne Что касается вашего комментария https://github.com/JuliaLang/julia/issues/23919#issuecomment -430375349, я не думаю, что явное ветвление является большой проблемой, потому что это в основном однолинейные β != 0 ? rmul!(C, β) : fill!(C, zero(eltype(C))) . Кроме того, для очень общей реализации, где вам нужно обрабатывать, например, C = Matrix{Any}(undef, 2, 2) , реализация в любом случае требует явной обработки "строгого нуля" (см. Вспомогательную функцию _modify! в моем PR https: // github .com / JuliaLang / julia / pull / 29634 / files # diff-e5541a621163d78812e05b4ec9c33ef4R37). Так что я думаю, что управление, подобное BLAS, - лучший выбор здесь. Что вы думаете?

Возможно ли иметь высокопроизводительные слабые нули? В том смысле, что мы хотели бы:

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

То есть нам нужно будет вручную определить, какие строки должны быть NaN если мы опустимся до BLAS (который использует строгий 0).

@dlfivefifty Я думаю, BLAS может обрабатывать NaN в A и B , но не в C ?

Я полагаю, что нет способа эффективно использовать NaN-осведомленность _C = α * A * B + 0 * C_, используя BLAS.gemm! и т. Д. [1], и вот откуда аргумент @andreasnoack .

[1] вам нужно где-то сохранить isnan.(C) и потом "загрязнить" C

@chethega прошло уже более 10 дней с момента голосования

@chriscoey Вы правы, голосование закрыто.

Я слишком плохо разбираюсь в github, чтобы получить полный список проголосовавших (это потребовалось бы, чтобы подсчитать, сколько людей вообще проголосовало). Однако, когда я смотрю на цифры, становится совершенно ясно, что mul! имеет подавляющую поддержку (вероятно, управляет кворумом утверждения 75%), а второй претендент muladd! намного ниже 50%.

К схеме голосования не было ни одного возражения. Я объявляю голосование, mul! выиграл, и имя определено. @tkf может заставить его летать :)

@chethega Спасибо, это был хороший

Кстати, я не могу выполнить перебазирование сразу (но, возможно, в течение нескольких недель), поэтому, если кто-то захочет выполнить перебазирование или повторную реализацию, пожалуйста, не ждите меня.

К сожалению, у нас не было голосования по NaN-семантике. Замораживание функций произойдет на следующей неделе, и у нас нет времени для полноценного голосования.

Предлагаю провести необязательный референдум, собрать снимок настроения в ветке.

Я вижу следующие варианты:

  1. Продолжайте обсуждение, надеюсь, что консенсус будет достигнут до крайнего срока, или основные люди предоставят нам продление или что-то в этом роде. : tada: (отредактируйте для пояснения: это вариант по умолчанию, то есть что произойдет, если мы не сможем достичь консенсуса по другому варианту. Наиболее вероятным результатом является то, что 5 аргументов mul! откладываются до версии 1.4.)
  2. Объедините новую функцию с неопределенным NaN-поведением в качестве упора. Как только мы достигаем консенсуса, мы обновляем код и / или документацию, чтобы получить определенное поведение NaN (сильные и слабые нули). Поддержка неопределенного NaN-поведения,
  3. Гемм означает Гемм! Слияние для 1.3 с задокументированной приверженностью строгим нулям. :сердце:
  4. NaN означает NaN ! Слияние для 1.3 с задокументированной приверженностью слабым нулям. :глаза:
  5. Сделайте что-нибудь, хоть что-нибудь, прежде чем упасть на 1,3-й обрыв. : ракета:
  6. Отклонить опрос. : палец вниз:
  7. Написать в
  8. Предлагаемая нижняя нить : альтернативное расположение !(alpha === false) && iszero(alpha) && !all(isfinite, C) && throw(ArgumentError()) и документируем, что эта проверка ошибок, вероятно, будет отменена в пользу чего-то другого. :смущенный:

Не стесняйтесь выбирать несколько, возможно, противоречащих друг другу вариантов, и @tkf / triage не стесняйтесь потенциально игнорировать опрос.

Изменить: в настоящее время только: tada: (терпение) и: rocket: (нетерпение) противоречат друг другу, но оба совместимы со всеми остальными. Как было разъяснено ниже, сортировка, как мы надеемся, будет подсчитывать результаты опроса в неустановленный день между 14-м и 15-м числами среды и принимать его во внимание каким-то неопределенным образом. Это опять же означает «одобрительное голосование», то есть выберите все варианты, которые вам нравятся, а не только ваш любимый; понятно, что: rocket: не осуждает: thumbsup :,: heart :,: eyes: и: confused :. Прошу прощения за то, что этот опрос был более поспешным, чем предыдущий.

Я бы проголосовал за сильный ноль (: heart :), если бы это было «Объединить для 1.x» вместо «Объединить для 1.3». Разве варианты не имели бы смысла, если бы все «Объединить для 1.3» были «Объединить 1.x»?

Спасибо @chethega. @tkf Мне действительно нужен новый мул! Как можно скорее, не слишком заботясь о решении NaN (при условии, что производительность не снижена).

Вы проверяли LazyArrays.jl? К вашему сведению, у него действительно хорошая поддержка умножения слитных матриц. Вы также можете безопасно использовать BLAS.gemm! и т. Д., Поскольку они являются общедоступными методами https://docs.julialang.org/en/latest/stdlib/LinearAlgebra/#LinearAlgebra.BLAS.gemm!

Мне действительно нужен универсальный мул! поскольку мы используем большое количество структурированных, разреженных и простых старых плотных матриц для наиболее эффективного представления различных задач оптимизации. Я здесь за универсальность и скорость.

Понимаю. И я только что вспомнил, что мы обсуждали вещи в LazyArrays.jl, так что, конечно, вы это уже знали ...

Что касается «как можно скорее», четырехмесячный цикл выпуска Джулии , по крайней мере, в плане дизайна, позволяет избежать «спешки слияния» непосредственно перед замораживанием функций. Я знаю, что это несправедливо с моей стороны, потому что я пробовал то же самое раньше ... Но я думаю, что кому-то нужно упомянуть это как напоминание. Яркая сторона в том, что Юлию очень легко построить. Вы можете начать использовать его сразу после слияния до следующего выпуска.

Изменить: выпуск -> слияние

Благодарю. Я считаю дедлайны полезными мотиваторами, и мне бы хотелось, чтобы это снова не стало устаревшим. Поэтому я предлагаю, чтобы мы попытались использовать крайний срок в качестве цели.

Здорово, что вы активно вкладываете энергию в эту ветку!

Мне действительно нужен универсальный мул! поскольку мы используем большое количество структурированных, разреженных и простых старых плотных матриц для наиболее эффективного представления различных задач оптимизации. Я здесь за универсальность и скорость.

mul! 5 аргументами не будет работать, если у вас много разных типов: вам понадобится комбинаторно много переопределений, чтобы избежать двусмысленности. Это одна из мотиваций системы LazyArrays.jl MemoryLayout . Именно по этой причине он используется для «структурированных и разреженных» матриц в BandedMatrices.jl и BlockBandedMatrices.jl. (Здесь даже подвиды ленточных матриц отправляются подпрограммам ленточного BLAS.)

Спасибо, еще раз попробую LazyArrays.

Поскольку 5-arg mul, по-видимому, обычно рассматривается как временное решение (до тех пор, пока какое-либо решение, такое как LazyArrays, не будет использовано в 2.0), я думаю, что мы можем стремиться к его объединению, не обязательно являясь идеальным или идеальным решением в долгосрочной перспективе.

@chethega, когда, по вашему мнению, мы должны подсчитывать результаты нового необязательного голосования?

@tkf Конечно, вы правы, что нули strong / weak / undef имеют смысл и для 1.x.

Однако я думаю, что есть немало людей, которые предпочли бы сейчас 1.3 mul! чем ждать до 1.4, чтобы получить 5-аргумент mul! . Если бы крайнего срока не было, я бы подождал еще немного и подумал бы, как провести правильный опрос (минимум 10 дней на голосование). Что наиболее важно, у нас не может быть значимого голосования без предварительного представления и тестирования конкурирующих реализаций на скорость и элегантность слабых / сильных нулей. Я лично подозреваю, что слабые нули можно сделать почти так же быстро, как и сильные, если сначала проверить iszero(alpha) , затем просмотреть матрицу на предмет значений !isfinite и только потом использовать медленный путь с дополнительным распределением; но я все равно предпочитаю строгую нулевую семантику.

@chethega, когда, по вашему мнению, мы должны подсчитывать результаты нового необязательного голосования?

На этой неделе сортировка должна принять решение (задержка / сильная / слабая / задержка) для альфа 1.3. Я думаю, что четверг 15-го или среда 14-го - это разумные варианты сортировки, чтобы сделать подсчет и принять его во внимание. Я, вероятно, не смогу присоединиться к нам в четверг, так что кто-то другой должен будет считать.

На самом деле, здесь нормально быть консервативным, пропустить крайний срок, продолжить обсуждение и дождаться версии 1.4.

С другой стороны, мы, возможно, уже достигли консенсуса, не заметив этого: @andreasnoack привел несколько

Почему бы сейчас просто не выдать ошибку:

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

Почему бы сейчас просто не выдать ошибку

Я добавил эту опцию в опрос. Отличная идея компромисса!

Просто чтобы оправдать ожидания: заморозка функций для 1.3 произойдет через три дня, так что, по сути, нет возможности сделать это вовремя. Мы довольно строго относимся к зависанию функций и ветвлению, поскольку это единственная часть цикла выпуска, время которой мы действительно можем контролировать.

Однако работа уже проделана на https://github.com/JuliaLang/julia/pull/29634 . Просто нужно настроить и перебазировать.

@tkf for # 29634 Не могли бы вы перечислить работу, которую еще предстоит сделать (включая переименование и обработку нулей в соответствии с голосованием)? Я знаю, что вы заняты, поэтому, возможно, мы найдем способ разделить оставшиеся задачи, чтобы это бремя больше не ложилось на вас.

Я могу думать об ATM:

  • перебазировать
  • переименовать addmul! -> mul!

    • и настройте код, предполагая, что новый API не будет называться mul! (например, <strong i="15">@deprecate</strong> mul!(C, A, B, α, β) addmul!(C, A, B, α, β) )

  • получить согласие основного разработчика на стратегию реализации MulAddMul т. д .; см. https://github.com/JuliaLang/julia/pull/29634#issuecomment -440510551. @andreasnoack , у тебя была возможность посмотреть на это?
  • в идеале добавлять тесты с помощью Quaternion ; см. https://github.com/JuliaLang/julia/pull/29634#issuecomment -443379914

Мой PR реализует семантику BLAS обработки β = 0 . Так что другая обработка, такая как выдача ошибки, также должна быть реализована.

Мой PR реализует семантику BLAS обработки β = 0 .

Извините, моя память устарела; моя реализация была непоследовательной и иногда использовала NaN. Таким образом, дополнительное TODO - сделать поведение β = 0.0 согласованным.

Тип MulAddMul только для внутреннего использования, верно?

Да, это полностью внутренняя деталь. Я беспокоился о том, что (1) может быть слишком много специализаций (бета = 0 и т. Д. Закодировано в параметре типа) и (2) это снижает читабельность исходного кода.

Это серьезные опасения. Мы уже создали массу специализаций в коде линейной алгебры, так что неплохо подумать, действительно ли нам нужно здесь специализироваться. Я обычно думал, что мы должны оптимизировать для небольших матриц, поскольку это не бесплатно (как вы сказали, это усложняет исходный код и может увеличить время компиляции), и людям лучше использовать StaticArrays для небольших умножений матриц. Следовательно, я склоняюсь к простой проверке значений во время выполнения, но мы всегда можем изменить это позже, если передумаем, поэтому мы не должны допустить, чтобы это приводило к задержкам.

К вашему сведению, мягкие нули имеют простую реализацию:

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 Извините, я забыл, что на самом деле нам нужна была специализация для оптимизации самого внутреннего цикла для некоторых структурированных матриц, таких как mul!(C, A::BiTriSym, B, α, β) https://github.com/JuliaLang/julia/pull/29634#issuecomment -440510551. Некоторые специализации можно удалить, но на самом деле это больше работы (поэтому задержки).

Следовательно, я склоняюсь к простой проверке значений во время выполнения, но мы всегда можем изменить это позже, если передумаем, поэтому мы не должны допустить, чтобы это приводило к задержкам.

Большой!

@andreasnoack Большое спасибо за просмотр и слияние вовремя!

Теперь, когда он объединен для 1.3, я очень нервничаю по поводу реализации: smile :. Я ценю, если люди здесь смогут более тщательно протестировать свой код линейной алгебры, когда выйдет 1.3-rc!

Не беспокойтесь, у 1.3 RC + PkgEval будет достаточно времени, чтобы избавиться от ошибок.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги