Lapack: Потеря точности во всех неявных и явных запросах рабочей области одинарной точности

Созданный на 19 июл. 2021  ·  6Комментарии  ·  Источник: Reference-LAPACK/lapack

Всем привет,

недавно наткнулся на ошибку при использовании ssyevd через интерфейс lapacke.
Это указывает на проблему в интерфейсе лапака в целом. Это выглядит так:

Согласно стандартному интерфейсу Lapack, многие подпрограммы, такие как ssyevd, нужно вызывать дважды:
Один раз для того, чтобы спросить процедуру, сколько оперативной памяти ей нужно для определенного размера матрицы,
и только потом всерьез вызовите процедуру с необходимыми участками рабочей памяти
как параметры.

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

calculate memory       ->    store value in reference   ->  retrieve the value for use (allocation)
    int64                            float                              int64

Это int64 в случае интерфейса ilp64, иначе это будет int32.
Итак, по сути, мы имеем промежуточное сокращение значения памяти от
63 бит на 24 бит !!!
(или, точнее, до 24 бит между крайними установленными битами в представлении с плавающей запятой IEEE 754)
Даже в случае 32-битных целых чисел у вас есть сокращение с 31 бит до 24 бит.

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

Это происходит во всех подпрограммах одинарной точности (s / c) в Lapack, которые вычисляют
требование памяти в качестве промежуточного шага.
При переключении на двойную точность вместо этого будет использоваться ссылка с двойной точностью,
вместо этого увеличивая промежуточное значение до 53 бит, что все еще не близко к 64 битам
можно было бы предположить с 64-битным интерфейсом.

Обходной путь, четыре возможных способа:

  1. Если вы хотите использовать одиночные или сложные процедуры Lapack, не используйте автоматическое выделение памяти через интерфейс C lapacke.
  2. Если вы используете метод функции Lapack с двумя вызовами, для вычисления памяти используйте процедуру double (!)
  3. Взгляните на эталонную реализацию процедуры Lapack и самостоятельно рассчитайте необходимую память.
  4. Используйте только матрицы небольшого размера при использовании матриц с плавающей запятой / сложных матриц.

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

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

С уважением,

окислитель

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

Изначально изменение lwork на IN / OUT было бы хорошим решением, но оно не имеет обратной совместимости. Затем приложению необходимо знать, была ли версия LAPACK <= 3.10 (скажем) или> 3.10, чтобы узнать, где взять работу. Хуже того, бывают случаи, когда приложения передают константное значение - ожидая, что оно останется константным, - поэтому LAPACK, изменяя свое поведение, чтобы перезаписать это значение, будет очень вредным (UB). Например, в МАГМА:

    const magma_int_t ineg_one = -1;
    ...
            magma_int_t query_magma, query_lapack;
            magma_zgesdd( *jobz, M, N,
                          unused, lda, runused,
                          unused, ldu,
                          unused, ldv,
                          dummy, ineg_one,  // overwriting ineg_one would break MAGMA
                          #ifdef COMPLEX
                          runused,
                          #endif
                          iunused, &info );
            assert( info == 0 );
            query_magma = (magma_int_t) MAGMA_Z_REAL( dummy[0] );

Решение, которое я предложил несколько лет назад и реализовано в MAGMA, состоит просто в sgesdd и т. Д., Чтобы немного округлить lwork, возвращаемое в work [1], поэтому возвращаемое значение всегда> = предполагаемое значение. См. Https://bitbucket.org/icl/magma/src/master/control/magma_zauxiler.cpp и используйте в https://bitbucket.org/icl/magma/src/master/src/zgesdd.cpp. (См. Выпуск для сгенерированной версии с одинарной точностью.) В основном заменить

    WORK( 1 ) = MAXWRK

с

    WORK( 1 ) = lapack_roundup_lwork( MAXWRK )

где функция lapack_roundup_lwork немного округляет его, как это делает magma_*make_lwork . В MAGMA я округлял, умножая на (1 + eps), используя eps с одинарной точностью, но выполняя вычисления в двойном размере. Тогда существующие приложения будут вести себя правильно, без необходимости изменять запросы своей рабочей области.

После дополнительного тестирования я обнаружил, что для lwork> 2 ^ 54 необходимо использовать определение epsilon = 1.19e-07 (также известное как ulp) в C / C ++ / Fortran, а не определение LAPACK slamch ("eps") = 5.96e- 08 (он же округление единиц, u). Если вы используете ulp, похоже, что расчет может быть выполнен за один раз.

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

Я предполагаю, что в то время это должно было иметь смысл (возвращать размер через указатель рабочего массива), но мне интересно, что мешает нам превратить спецификатор размера в переменную ввода / вывода и передать туда точное значение? «Современный» вызывающий объект сначала проверит это и обратится к члену рабочего массива, только если lwork все еще будет -1, «старый» вызывающий не заметит изменений.

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

Вот две темы обсуждения, связанные с этим:
https://icl.cs.utk.edu/lapack-forum/viewtopic.php?t=1418
http://icl.cs.utk.edu/lapack-forum/archives/lapack/msg00827.html

Это особенно проблема для алгоритмов, которым требуется рабочее пространство O (n ^ 2). Для алгоритма, для которого требуется рабочее пространство O (n * nb), это не проблема.

Да, это недоработка конструкции.

@ martin-frbg: как бы вы предложили изменение интерфейса C _work? Только там у нас есть LWORK как INPUT. При изменении LWORK на INPUT / OUTPUT происходит серьезное изменение. У вас есть идея решить эту проблему? Видеть:
https://github.com/Reference-LAPACK/lapack/blob/aa631b4b4bd13f6ae2dbab9ae9da209e1e05b0fc/LAPACKE/src/lapacke_dgeqrf_work.c#L35

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

Хорошо, мой хитрый план не работает, когда я трезв ...
Но на самом деле, я думаю, это две проблемы: одна - размер работы, превышающий lapack_int, а другая - "всего лишь" искажение из-за ограниченной точности - мне интересно, можно ли округлить рассчитанный размер, чтобы предвидеть последнее, за счет "какая-то" неиспользуемая память?

Да, это недоработка конструкции.

Здесь я вижу 2 разных недостатка:

  1. В LAPACK: процедуры возвращают размер работы с использованием реальной переменной.
  2. В LAPACKE: подпрограммы распространяют брешь из LAPACK.

Идея @martin-frbg - хорошее решение (1). Новый код Fortran может использовать возвращаемое значение LWORK вместо WORK (1). Мы можем попробовать изменить код с помощью некоторой (полу) автоматической процедуры замены. Например, в ssyevd.f мы могли бы заменить

  ELSE IF( LQUERY ) THEN
     RETURN
  END IF

по

  ELSE IF( LQUERY ) THEN
     LWORK = LOPT
     RETURN
  END IF

Добавление LAPACKE_dgeqrf__work_query() , как предлагает @langou , решает (2), хотя с этой модификацией связано много работы.

Изначально изменение lwork на IN / OUT было бы хорошим решением, но оно не имеет обратной совместимости. Затем приложению необходимо знать, была ли версия LAPACK <= 3.10 (скажем) или> 3.10, чтобы узнать, где взять работу. Хуже того, бывают случаи, когда приложения передают константное значение - ожидая, что оно останется константным, - поэтому LAPACK, изменяя свое поведение, чтобы перезаписать это значение, будет очень вредным (UB). Например, в МАГМА:

    const magma_int_t ineg_one = -1;
    ...
            magma_int_t query_magma, query_lapack;
            magma_zgesdd( *jobz, M, N,
                          unused, lda, runused,
                          unused, ldu,
                          unused, ldv,
                          dummy, ineg_one,  // overwriting ineg_one would break MAGMA
                          #ifdef COMPLEX
                          runused,
                          #endif
                          iunused, &info );
            assert( info == 0 );
            query_magma = (magma_int_t) MAGMA_Z_REAL( dummy[0] );

Решение, которое я предложил несколько лет назад и реализовано в MAGMA, состоит просто в sgesdd и т. Д., Чтобы немного округлить lwork, возвращаемое в work [1], поэтому возвращаемое значение всегда> = предполагаемое значение. См. Https://bitbucket.org/icl/magma/src/master/control/magma_zauxiler.cpp и используйте в https://bitbucket.org/icl/magma/src/master/src/zgesdd.cpp. (См. Выпуск для сгенерированной версии с одинарной точностью.) В основном заменить

    WORK( 1 ) = MAXWRK

с

    WORK( 1 ) = lapack_roundup_lwork( MAXWRK )

где функция lapack_roundup_lwork немного округляет его, как это делает magma_*make_lwork . В MAGMA я округлял, умножая на (1 + eps), используя eps с одинарной точностью, но выполняя вычисления в двойном размере. Тогда существующие приложения будут вести себя правильно, без необходимости изменять запросы своей рабочей области.

После дополнительного тестирования я обнаружил, что для lwork> 2 ^ 54 необходимо использовать определение epsilon = 1.19e-07 (также известное как ulp) в C / C ++ / Fortran, а не определение LAPACK slamch ("eps") = 5.96e- 08 (он же округление единиц, u). Если вы используете ulp, похоже, что расчет может быть выполнен за один раз.

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