Olá pessoal,
Recentemente, encontrei um erro ao usar o ssyevd por meio da interface do lapacke.
Isso aponta para um problema na interface do lapack em geral. É assim:
De acordo com a interface padrão do lapack, muitas rotinas como ssyevd você tem que chamar duas vezes:
Uma vez, para perguntar à rotina de quanta memória temporária ela precisa para um determinado tamanho de matriz,
e só então chamar a rotina a sério com as seções de memória de rascunho necessárias
como parâmetros.
Se você olhar atentamente para esta primeira chamada, que deve retornar o tamanho de memória necessário, por exemplo
para uma rotina como ssyevd, você vê que mesmo de acordo com a documentação do lapack,
o requisito de memória é passado de volta por meio de um ponteiro para um valor flutuante .
Portanto, ao calcular a memória, ela passa por uma série de valores:
calculate memory -> store value in reference -> retrieve the value for use (allocation)
int64 float int64
É int64 no caso de uma interface ilp64, caso contrário, seria int32.
Então, em essência, temos um encurtamento intermediário do valor da memória de
63 bits a 24 bits !!!
(ou mais preciso, para 24 bits entre os bits definidos mais externos na representação flutuante IEEE 754)
Mesmo no caso de inteiros de 32 bits, você tem um encurtamento de 31 bits para 24 bits.
Então, se você chamar o cálculo do requisito de memória como no passado 'manualmente'
você pode ter a chance de ver onde está errado, mas ainda assim você não pode evitar o curto-circuito
para um valor flutuante. Se você usar a alocação automática de memória por meio da interface lapacke moderna
você nem tem ideia do que pode estar errado, pois a rotina anuncia para cuidar
de todo o gerenciamento de memória por si só!
Isso acontece em todas as rotinas de precisão simples (s / c) em lapack, que calculam
o requisito de memória como uma etapa intermediária.
Mudar para precisão dupla usaria, em vez disso, uma referência de precisão dupla,
aumentando o valor intermediário para 53 bits em vez disso, que ainda não está perto dos 64 bits
seria de se supor com uma interface de 64 bits.
Solução alternativa, quatro maneiras possíveis:
Outras pessoas tropeçaram nisso, mas não seguiram até a causa real, por exemplo,
A compilação openBLAS com suporte a int64 falha na entrada válida para ssyevd
É preciso enfatizar que, pelo menos com o método de duas chamadas, isso não é um bug, mas uma falha de design.
No caso da alocação automática de memória lapacke, ela deve ser considerada um bug bastante grave.
Cumprimentos,
oxidante
Acho que deve ter feito sentido na época (retornar o tamanho por meio do ponteiro da matriz de trabalho), mas me pergunto o que nos impede de transformar o especificador de tamanho em uma variável de entrada / saída e retornar o valor exato lá também? Um chamador "moderno" verificaria isso primeiro e recorreria ao membro da matriz work apenas se lwork ainda fosse -1, um chamador "antigo" não notaria nenhuma mudança.
Também provavelmente o LWORK necessário naquela época era fisicamente grande demais para acertar esse problema e o ilp64 tornou isso óbvio. Estou sonhando com os dias em que o valor NB é derivado em tempo de execução, em vez do esquema de duas chamadas.
Aqui estão dois tópicos de discussão relacionados a isso:
https://icl.cs.utk.edu/lapack-forum/viewtopic.php?t=1418
http://icl.cs.utk.edu/lapack-forum/archives/lapack/msg00827.html
Isso é especialmente um problema para algoritmos que requerem um espaço de trabalho O (n ^ 2). Para algoritmos que requerem um espaço de trabalho O (n * nb), isso é menos problemático.
Sim, esta é uma falha de design.
@ martin-frbg: como seria a mudança proposta para a interface C _work? Temos LWORK como INPUT apenas lá. Mudar LWORK para INPUT / OUTPUT ocorre uma grande mudança. Você tem uma ideia para resolver este problema? Ver:
https://github.com/Reference-LAPACK/lapack/blob/aa631b4b4bd13f6ae2dbab9ae9da209e1e05b0fc/LAPACKE/src/lapacke_dgeqrf_work.c#L35
Eu estava pensando que também poderíamos criar algumas sub-rotinas de alocação de espaço de trabalho, como LAPACK_dgeqrf__workspace_query () e isso retornaria o espaço de trabalho necessário.
Bem, meu plano astuto realmente não funciona quando sóbrio ...
Mas esses são, na verdade, dois problemas, eu acho, um o tamanho do trabalho transbordando um lapack_int e o outro "apenas" uma representação incorreta devido à precisão limitada - eu me pergunto se seria possível arredondar o tamanho calculado para antecipar o último às custas de "alguma" memória não utilizada?
Sim, esta é uma falha de design.
Posso ver 2 falhas diferentes aqui:
A ideia de @martin-frbg é uma boa solução para (1). O novo código Fortran poderia usar o valor de retorno de LWORK em vez de WORK (1). Podemos tentar modificar o código com algum procedimento (semi-) automático de substituição. Em ssyevd.f
, por exemplo, poderíamos substituir
ELSE IF( LQUERY ) THEN
RETURN
END IF
de
ELSE IF( LQUERY ) THEN
LWORK = LOPT
RETURN
END IF
Adicionar LAPACKE_dgeqrf__work_query()
, como @langou sugere, resolve (2), embora haja muito trabalho associado a essa modificação.
Mudar lwork para IN / OUT seria uma boa solução originalmente, mas não é compatível com versões anteriores. O aplicativo teria então que saber se a versão do LAPACK era <= 3,10 (digamos) ou> 3,10 para saber onde obter o trabalho. Pior, há casos em que os aplicativos passam um valor const - esperando que ele permaneça constante - então LAPACK mudar seu comportamento para sobrescrever esse valor seria muito prejudicial (UB). Por exemplo, em 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] );
A solução que propus há alguns anos e implementei no MAGMA é simplesmente no sgesdd, etc., para arredondar um pouco o trabalho retornado no trabalho [1], de forma que o valor retornado seja sempre> = o valor pretendido. Consulte https://bitbucket.org/icl/magma/src/master/control/magma_zauxiliary.cpp e use em https://bitbucket.org/icl/magma/src/master/src/zgesdd.cpp. (Veja um release para a versão de precisão única gerada.) Substitua basicamente
WORK( 1 ) = MAXWRK
com
WORK( 1 ) = lapack_roundup_lwork( MAXWRK )
onde a função lapack_roundup_lwork
arredonda ligeiramente, como magma_*make_lwork
faz. No MAGMA, eu arredondei multiplicando por (1 + eps), usando eps de precisão simples, mas fazendo o cálculo em dobro. Assim, os aplicativos existentes se comportarão corretamente sem a necessidade de alterar suas consultas de espaço de trabalho.
Após mais testes, descobri que para lwork> 2 ^ 54, ele precisa usar a definição C / C ++ / Fortran de epsilon = 1.19e-07 (também conhecida como ulp), em vez da definição LAPACK slamch ("eps") = 5,96e- 08 (também conhecido como arredondamento de unidade, u). Se estiver usando o ulp, parece que o cálculo pode ser feito de uma só vez.
Comentários muito úteis
Mudar lwork para IN / OUT seria uma boa solução originalmente, mas não é compatível com versões anteriores. O aplicativo teria então que saber se a versão do LAPACK era <= 3,10 (digamos) ou> 3,10 para saber onde obter o trabalho. Pior, há casos em que os aplicativos passam um valor const - esperando que ele permaneça constante - então LAPACK mudar seu comportamento para sobrescrever esse valor seria muito prejudicial (UB). Por exemplo, em MAGMA:
A solução que propus há alguns anos e implementei no MAGMA é simplesmente no sgesdd, etc., para arredondar um pouco o trabalho retornado no trabalho [1], de forma que o valor retornado seja sempre> = o valor pretendido. Consulte https://bitbucket.org/icl/magma/src/master/control/magma_zauxiliary.cpp e use em https://bitbucket.org/icl/magma/src/master/src/zgesdd.cpp. (Veja um release para a versão de precisão única gerada.) Substitua basicamente
com
onde a função
lapack_roundup_lwork
arredonda ligeiramente, comomagma_*make_lwork
faz. No MAGMA, eu arredondei multiplicando por (1 + eps), usando eps de precisão simples, mas fazendo o cálculo em dobro. Assim, os aplicativos existentes se comportarão corretamente sem a necessidade de alterar suas consultas de espaço de trabalho.Após mais testes, descobri que para lwork> 2 ^ 54, ele precisa usar a definição C / C ++ / Fortran de epsilon = 1.19e-07 (também conhecida como ulp), em vez da definição LAPACK slamch ("eps") = 5,96e- 08 (também conhecido como arredondamento de unidade, u). Se estiver usando o ulp, parece que o cálculo pode ser feito de uma só vez.