Lapack: فقدان الدقة في جميع استعلامات مساحة العمل الفردية ، الضمنية والصريحة

تم إنشاؤها على ١٩ يوليو ٢٠٢١  ·  6تعليقات  ·  مصدر: Reference-LAPACK/lapack

مرحبا جميعا،

لقد عثرت مؤخرًا على خطأ عند استخدام ssyevd عبر واجهة lapacke.
يشير إلى وجود مشكلة في واجهة lapack بشكل عام. يذهب مثل هذا:

وفقًا لواجهة 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. إذا كنت ترغب في استخدام إجراءات فردية أو معقدة ، فلا تستخدم التخصيص التلقائي للذاكرة عبر واجهة C lapacke
  2. إذا كنت تستخدم طريقة دالة lapack ثنائية الاستدعاء ، لحساب الذاكرة ، استخدم روتين double (!)
  3. ألقِ نظرة على التنفيذ المرجعي لروتين الثغرة واحسب الذاكرة المطلوبة بنفسك
  4. استخدم فقط أحجام مصفوفة صغيرة عند استخدام مصفوفات عائمة / معقدة

لقد عثر أشخاص آخرون على هذا الأمر ، لكنهم لم يتابعوه للوصول إلى السبب الحقيقي ، على سبيل المثال
فشل إنشاء openBLAS مع دعم int64 على إدخال صالح لـ ssyevd

يجب على المرء أن يؤكد ، على الأقل مع طريقة النداء الثنائي ، هذا ليس خطأ ، ولكنه عيب في التصميم.
في حالة التخصيص التلقائي للذاكرة lapacke ، يجب اعتباره خطأً شديدًا إلى حد ما.

يعتبر،

مؤكسد

Bug

التعليق الأكثر فائدة

قد يكون تغيير Lwork ليكون IN / OUT حلاً جيدًا في الأصل ، ولكنه ليس متوافقًا مع الإصدارات السابقة. سيتعين على التطبيق بعد ذلك معرفة ما إذا كان إصدار LAPACK <= 3.10 (على سبيل المثال) أو> 3.10 لمعرفة مكان الحصول على lwork. والأسوأ من ذلك ، أن هناك حالات تمرر فيها التطبيقات قيمة ثابتة - وتتوقع أن تظل ثابتة - لذا فإن تغيير LAPACK لسلوكه لاستبدال تلك القيمة سيكون ضارًا جدًا (UB). على سبيل المثال ، في 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] );

الحل الذي اقترحته منذ بضع سنوات ونفذته في MAGMA هو ببساطة في sgesdd ، وما إلى ذلك ، لتقريب العمل الذي تم إرجاعه في العمل [1] قليلاً ، وبالتالي فإن القيمة المعادة هي دائمًا> = القيمة المقصودة. راجع https://bitbucket.org/icl/magma/src/master/control/magma_zauxiliary.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 ، يجب استخدام تعريف C / C ++ / Fortran لـ epsilon = 1.19e-07 (المعروف أيضًا باسم ulp) ، بدلاً من تعريف LAPACK slamch ("eps") = 5.96e- 08 (ويعرف أيضًا باسم جولة الوحدة ، ش). في حالة استخدام 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 كمدخل هناك فقط. تغيير LWORK إلى INPUT / OUTPUT هناك تغيير كبير. هل لديك فكرة لحل هذه المشكلة؟ يرى:
https://github.com/Reference-LAPACK/lapack/blob/aa631b4b4bd13f6ae2dbab9ae9da209e1e05b0fc/LAPACKE/src/lapacke_dgeqrf_work.c#L35

كنت أفكر في أنه يمكننا أيضًا إنشاء بعض الإجراءات الفرعية لتخصيص مساحة العمل مثل LAPACK_dgeqrf__workspace_query () وهذا سيعيد مساحة العمل المطلوبة.

ويلب ، خطتي الماكرة لا تعمل حقًا عندما تكون رصينة ...
لكن هذه في الواقع مشكلتان على ما أعتقد ، إحداهما حجم العمل يغرق في lapack_int والأخرى "فقط" تحريف بسبب الدقة المحدودة - أتساءل عما إذا كان من الممكن تقريب الحجم المحسوب لتوقع الأخير على حساب "بعض" الذاكرة غير المستخدمة؟

نعم ، هذا عيب في التصميم.

يمكنني رؤية عيبين مختلفين هنا:

  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 لمعرفة مكان الحصول على lwork. والأسوأ من ذلك ، أن هناك حالات تمرر فيها التطبيقات قيمة ثابتة - وتتوقع أن تظل ثابتة - لذا فإن تغيير LAPACK لسلوكه لاستبدال تلك القيمة سيكون ضارًا جدًا (UB). على سبيل المثال ، في 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] );

الحل الذي اقترحته منذ بضع سنوات ونفذته في MAGMA هو ببساطة في sgesdd ، وما إلى ذلك ، لتقريب العمل الذي تم إرجاعه في العمل [1] قليلاً ، وبالتالي فإن القيمة المعادة هي دائمًا> = القيمة المقصودة. راجع https://bitbucket.org/icl/magma/src/master/control/magma_zauxiliary.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 ، يجب استخدام تعريف C / C ++ / Fortran لـ epsilon = 1.19e-07 (المعروف أيضًا باسم ulp) ، بدلاً من تعريف LAPACK slamch ("eps") = 5.96e- 08 (ويعرف أيضًا باسم جولة الوحدة ، ش). في حالة استخدام ulp ، يبدو أن الحساب يمكن إجراؤه بمفرده.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

JHenneberg picture JHenneberg  ·  10تعليقات

hokb picture hokb  ·  16تعليقات

christoph-conrads picture christoph-conrads  ·  26تعليقات

Dichloromethane picture Dichloromethane  ·  11تعليقات

Peter9606 picture Peter9606  ·  7تعليقات