Halo semua,
baru-baru ini saya menemukan kesalahan saat menggunakan ssyevd melalui antarmuka lapacke.
Ini menunjukkan masalah di antarmuka lapack secara umum. Ini berjalan seperti ini:
Menurut antarmuka standar lapack, banyak rutinitas seperti ssyevd yang harus Anda panggil dua kali:
Sekali untuk menanyakan rutin berapa banyak memori awal yang dibutuhkan untuk ukuran matriks tertentu,
dan baru kemudian panggil rutin dengan sungguh-sungguh dengan bagian memori awal yang diperlukan
sebagai parameter.
Jika Anda melihat lebih dekat pada panggilan pertama ini, yang seharusnya mengembalikan ukuran memori yang diperlukan, misalnya
untuk rutinitas seperti ssyevd, Anda melihat bahwa bahkan menurut dokumentasi lapack,
kebutuhan memori dilewatkan kembali melalui pointer ke nilai float .
Jadi saat menghitung memori, ia melewati serangkaian nilai:
calculate memory -> store value in reference -> retrieve the value for use (allocation)
int64 float int64
Ini adalah int64 dalam kasus antarmuka ilp64, jika tidak maka akan menjadi int32.
Jadi intinya, kami memiliki pemendekan perantara dari nilai memori dari
63 bit hingga 24 bit !!!
(atau lebih tepatnya, hingga 24bit antara set bit terluar dalam representasi float IEEE 754)
Bahkan dalam kasus bilangan bulat 32bit, Anda memiliki pemendekan dari 31 bit menjadi 24 bit.
Jadi, jika Anda akan memanggil perhitungan kebutuhan memori seperti di masa lalu 'dengan tangan'
Anda mungkin memiliki kesempatan untuk melihat di mana kesalahannya, tetapi Anda tetap tidak dapat mencegah korslet
ke nilai mengambang. Jika Anda menggunakan alokasi memori otomatis melalui antarmuka lapacke modern
Anda bahkan tidak tahu apa yang salah, karena rutinitas mengiklankan untuk berhati-hati
dari semua manajemen memori dengan sendirinya!
Ini terjadi di semua rutinitas presisi tunggal (s/c) di lapack, yang menghitung
kebutuhan memori sebagai langkah perantara.
Beralih ke presisi ganda kemudian akan menggunakan referensi presisi ganda,
meningkatkan nilai perantara menjadi 53 bit, yang masih belum mendekati 64 bit
orang akan berasumsi dengan antarmuka 64bit.
Solusi, empat cara yang mungkin:
Orang lain telah menemukan ini, tetapi tidak menindaklanjutinya ke penyebab sebenarnya, misalnya
build openBLAS dengan dukungan int64 gagal pada input yang valid untuk ssyevd
Kita harus menekankan, bahwa setidaknya dengan metode dua panggilan, ini bukan bug, tetapi cacat desain.
Dalam hal alokasi memori otomatis lapacke, itu harus dianggap sebagai bug yang cukup parah.
Salam,
pengoksidasi
Saya kira itu pasti masuk akal pada saat itu (untuk mengembalikan ukuran melalui penunjuk array kerja), tetapi saya ingin tahu apa yang membuat kita tidak mengubah penentu ukuran menjadi variabel masuk/keluar dan mengembalikan nilai yang tepat di sana juga? Penelepon "modern" kemudian akan memeriksanya terlebih dahulu dan menggunakan anggota array kerja hanya jika lwork masih -1, penelepon "lama" tidak akan melihat ada perubahan.
Juga mungkin LWORK yang diperlukan saat itu secara fisik terlalu besar untuk mengatasi masalah ini dan ilp64 membuatnya jelas. Saya memimpikan hari-hari di mana nilai NB diturunkan saat runtime alih-alih skema dua panggilan.
Berikut adalah dua utas diskusi yang terkait dengan ini:
https://icl.cs.utk.edu/lapack-forum/viewtopic.php?t=1418
http://icl.cs.utk.edu/lapack-forum/archives/lapack/msg00827.html
Ini terutama merupakan masalah untuk algoritma yang membutuhkan ruang kerja O(n^2). Untuk algoritme yang memerlukan ruang kerja O(n*nb), ini bukan masalah.
Ya, ini adalah kesalahan desain.
@martin-frbg: bagaimana perubahan yang Anda usulkan untuk antarmuka C _work? Kami memiliki LWORK sebagai INPUT hanya di sana. Mengubah LWORK menjadi INPUT/OUTPUT ada perubahan besar. Apakah Anda punya ide untuk menyelesaikan masalah ini? Lihat:
https://github.com/Reference-LAPACK/lapack/blob/aa631b4b4bd13f6ae2dbab9ae9da209e1e05b0fc/LAPACKE/src/lapacke_dgeqrf_work.c#L35
Saya berpikir bahwa kami juga dapat membuat beberapa subrutin alokasi ruang kerja seperti LAPACK_dgeqrf__workspace_query() dan ini akan mengembalikan ruang kerja yang diperlukan.
Welp, rencana licik saya tidak benar-benar bekerja ketika sadar ...
Tetapi ini sebenarnya adalah dua masalah yang saya pikir, satu ukuran kerja yang meluap dari lapack_int dan yang lainnya "hanya" salah representasi karena presisi terbatas - saya ingin tahu apakah mungkin untuk mengumpulkan ukuran yang dihitung untuk mengantisipasi yang terakhir dengan mengorbankan "beberapa" memori yang tidak terpakai?
Ya, ini adalah kesalahan desain.
Saya dapat melihat 2 kekurangan yang berbeda di sini:
Ide @martin-frbg adalah solusi yang baik untuk (1). Kode Fortran baru dapat menggunakan nilai kembalian LWORK alih-alih WORK(1). Kita dapat mencoba memodifikasi kode dengan beberapa prosedur penggantian (semi-)otomatis. Di ssyevd.f
, misalnya, kita bisa mengganti
ELSE IF( LQUERY ) THEN
RETURN
END IF
oleh
ELSE IF( LQUERY ) THEN
LWORK = LOPT
RETURN
END IF
Menambahkan LAPACKE_dgeqrf__work_query()
, seperti yang disarankan @langou , menyelesaikan (2), meskipun ada banyak pekerjaan yang terkait dengan modifikasi ini.
Mengubah lwork menjadi IN/OUT akan menjadi solusi yang bagus pada awalnya, tetapi tidak kompatibel ke belakang. Aplikasi kemudian harus mengetahui apakah versi LAPACK adalah <= 3.10 (katakanlah) atau > 3.10 untuk mengetahui di mana mendapatkan lwork. Lebih buruk lagi, ada kasus di mana aplikasi meneruskan nilai const — mengharapkannya tetap const — jadi LAPACK mengubah perilakunya untuk menimpa nilai itu akan sangat merugikan (UB). Misalnya, di MAGMA:
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] );
Solusi yang saya usulkan beberapa tahun yang lalu dan diimplementasikan di MAGMA hanya di sgesdd, dll., untuk membulatkan pekerjaan yang dikembalikan dalam pekerjaan[1] sedikit ke atas, sehingga nilai yang dikembalikan selalu >= nilai yang diinginkan. Lihat https://bitbucket.org/icl/magma/src/master/control/magma_zauxiliary.cpp dan gunakan di https://bitbucket.org/icl/magma/src/master/src/zgesdd.cpp. (Lihat rilis untuk versi presisi tunggal yang dihasilkan.) Pada dasarnya ganti
WORK( 1 ) = MAXWRK
dengan
WORK( 1 ) = lapack_roundup_lwork( MAXWRK )
di mana fungsi lapack_roundup_lwork
membulatkannya sedikit, seperti yang dilakukan magma_*make_lwork
. Di MAGMA, saya membulatkan dengan mengalikan dengan (1 + eps), menggunakan eps presisi tunggal tetapi melakukan perhitungan secara ganda. Kemudian aplikasi yang ada akan berperilaku dengan benar tanpa perlu mengubah kueri ruang kerjanya.
Setelah pengujian lebih lanjut, saya menemukan untuk lwork > 2^54, perlu menggunakan definisi C/C++/Fortran dari epsilon = 1.19e-07 (alias ulp), daripada definisi LAPACK slamch("eps") = 5.96e- 08 (alias pembulatan unit, u). Jika menggunakan ulp, sepertinya perhitungannya bisa dilakukan secara tunggal.
Komentar yang paling membantu
Mengubah lwork menjadi IN/OUT akan menjadi solusi yang bagus pada awalnya, tetapi tidak kompatibel ke belakang. Aplikasi kemudian harus mengetahui apakah versi LAPACK adalah <= 3.10 (katakanlah) atau > 3.10 untuk mengetahui di mana mendapatkan lwork. Lebih buruk lagi, ada kasus di mana aplikasi meneruskan nilai const — mengharapkannya tetap const — jadi LAPACK mengubah perilakunya untuk menimpa nilai itu akan sangat merugikan (UB). Misalnya, di MAGMA:
Solusi yang saya usulkan beberapa tahun yang lalu dan diimplementasikan di MAGMA hanya di sgesdd, dll., untuk membulatkan pekerjaan yang dikembalikan dalam pekerjaan[1] sedikit ke atas, sehingga nilai yang dikembalikan selalu >= nilai yang diinginkan. Lihat https://bitbucket.org/icl/magma/src/master/control/magma_zauxiliary.cpp dan gunakan di https://bitbucket.org/icl/magma/src/master/src/zgesdd.cpp. (Lihat rilis untuk versi presisi tunggal yang dihasilkan.) Pada dasarnya ganti
dengan
di mana fungsi
lapack_roundup_lwork
membulatkannya sedikit, seperti yang dilakukanmagma_*make_lwork
. Di MAGMA, saya membulatkan dengan mengalikan dengan (1 + eps), menggunakan eps presisi tunggal tetapi melakukan perhitungan secara ganda. Kemudian aplikasi yang ada akan berperilaku dengan benar tanpa perlu mengubah kueri ruang kerjanya.Setelah pengujian lebih lanjut, saya menemukan untuk lwork > 2^54, perlu menggunakan definisi C/C++/Fortran dari epsilon = 1.19e-07 (alias ulp), daripada definisi LAPACK slamch("eps") = 5.96e- 08 (alias pembulatan unit, u). Jika menggunakan ulp, sepertinya perhitungannya bisa dilakukan secara tunggal.