Numpy: бессмысленное RuntimeWarning от скомпилированного clang np.float32 .__ mul__

Созданный на 27 апр. 2017  ·  53Комментарии  ·  Источник: numpy/numpy

В Sagemath мы встречаем в нашем билете № 22799

RuntimeWarning: invalid value encountered in multiply

при умножении числа numpy.float32 на данные, отличные от numpy; т.е. numpy должен не выполнять это умножение без уведомления, и это действительно так, если вы строите с помощью gcc, или если вместо np.float32 это np.float или np.float128 .

Точнее, вы получаете предупреждение от вызова Python

type(numpy.float32('1.5')).__mul__(numpy.float32('1.5'), x)

где x - одномерный многочлен Sagemath с коэффициентами типа RealField Sagemath. (и только этот конкретный тип данных вызывает это).
То есть потенциально такие бессмысленные предупреждения могут выдаваться вне Sagemath; мы можем воспроизвести его на OSX 11.12 с его стандартным cc (производным от clang 3.8), а также на Linux с clang 4.0 и на FreeBSD 11.0 с clang 4.0 или clang 3.7.

Возможно, мы сможем создать способ воспроизвести это за пределами Sagemath, хотя нам понадобятся несколько советов, где в numpy-коде фактически реализован этот __mul__ , чтобы увидеть, какие функции применяются к x ...

Мы видим это на numpy 1.11 и 1.12 тоже.

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

та же картина с numpy.float32('1.5').__mul__(x) а также с __add__ и __sub__ .

Этот тип ошибки типичен для массивов, содержащих nan или infinity . Что возвращает np.array(x) ?

@ eric-wieser: возвращает array(x, dtype=object) , без предупреждений.

np.multiply(np.float32('1.5'), x) дает такое же предупреждение?

numpy.multiply(numpy.float32('1.5'), x) дает такое же предупреждение.

А как насчет type(x).__rmul__(numpy.float32('1.5'), x) ?

Кроме того, если бы вы могли запустить warnings.filterwarnings('error') , вы бы получили полную трассировку стека

type(x).__rmul__(numpy.float32('1.5'), x)

TypeError: descriptor '__rmul__' requires a 'sage.structure.element.Element' object but received a 'numpy.float32'

x.__rmul__(numpy.float32('1.5')) проходит нормально.

Кажется, я забыл, как работает rmul . Я имел в виду type(x).__rmul__(x, numpy.float32('1.5')) , но я полагаю, что это делает то же самое, что и x.__rmul__ , если только x не действительно странно

Это тоже не работает? np.multiply(np.array(1.5, dtype=object), x) (на этот раз с filterwarnings , пожалуйста)

type(x).__rmul__(x,numpy.float32('1.5')) проходит без предупреждения.

И, кстати, установка warnings.filterwarnings('error') ничего интересного мне не дает,

---------------------------------------------------------------------------
RuntimeWarning                            Traceback (most recent call last)
<ipython-input-50-b3ece847d318> in <module>()
sage: np.multiply(np.array(1.5, dtype=object), x)
---------------------------------------------------------------------------
RuntimeWarning                            Traceback (most recent call last)
<ipython-input-52-706823a0b5a2> in <module>()
----> 1  np.multiply(np.array(RealNumber('1.5'), dtype=object), x)

RuntimeWarning: invalid value encountered in multiply

Хм, мудрец сделал то, чего я не ожидал. Такое же поведение с float('1.5') , я думаю?

Итак, я думаю, что происходит следующее:

  • numpy правильно использует цикл объектов в ufunc, который заканчивается просто вызовом PyNumber_Multiply
  • Внутри sage что-то устанавливает флаг ошибки в FPU (ошибка в sage?)
  • numpy выполняет свою обычную проверку флага fpu при выходе из ufunc (ошибка в циклах объектов?) И обнаруживает ошибку, оставленную их sage
sage: float(1.5).__mul__(x)
NotImplemented
sage: np.float(1.5).__mul__(x)
NotImplemented
sage: np.float32(1.5).__mul__(x)
/usr/home/dima/Sage/sage/src/bin/sage-ipython:1: RuntimeWarning: invalid value encountered in multiply
  #!/usr/bin/env python
1.50000000000000*x
sage: np.float64(1.5).__mul__(x)
/usr/home/dima/Sage/sage/src/bin/sage-ipython:1: RuntimeWarning: invalid value encountered in multiply
  #!/usr/bin/env python
1.50000000000000*x

Стоит отметить, что np.float is float из соображений совместимости

почему np.float32(1.5).__mul__(x) возвращает NotImplemented ?

Потому что он знает, что может обрабатывать его как np.multiply с помощью цикла объекта, а затем пытается снова с float * x внутри этого цикла. К сожалению, оболочка этого цикла подбирает флаги FPU, установленные sage.

Если вы присмотритесь, вы обнаружите, что x.__rmul__ все еще вызывается глубже в стеке.

sage: np.float32(1.5)*x
/usr/home/dima/Sage/sage/src/bin/sage-ipython:1: RuntimeWarning: invalid value encountered in multiply
  #!/usr/bin/env python
1.50000000000000*x
sage: np.float128(1.5)*x
1.50000000000000*x
sage: np.float64(1.5)*x
/usr/home/dima/Sage/sage/src/bin/sage-ipython:1: RuntimeWarning: invalid value encountered in multiply
  #!/usr/bin/env python
1.50000000000000*x

так что похоже, что np.float128 в порядке, но

sage: np.float128(1.5).__mul__(x)
/usr/home/dima/Sage/sage/src/bin/sage-ipython:1: RuntimeWarning: invalid value encountered in multiply
  #!/usr/bin/env python
1.50000000000000*x

Это странно. Возможно, ошибка компилятора (поскольку мы никогда не видим ее в gcc), но в каком месте?

Кажется, что флаг FPU по какой-то причине установлен на clang, но не на gcc в коде sage. Numpy виноват в том, что шумел об этом, но я очень сомневаюсь, что виноват в том, что установил его в первую очередь.

К сожалению, numpy не предоставляет никакого способа явно запросить флаги FPU - это было бы очень полезно для разделения проблемы в sage пополам.

Я предполагаю, что это вызывает такое же предупреждение (для этого требуется numpy 1.12, я думаю)

mul = np.vectorize(x.__rmul__)
mul(float('1.5'))

не совсем то же, но близко:

/usr/home/dima/Sage/sage/local/lib/python2.7/site-packages/numpy/lib/function_base.py:2652: RuntimeWarning: invalid value encountered in __rmul__ (vectorized)
  outputs = ufunc(*inputs)
array(1.50000000000000*x, dtype=object)

Хорошо, хорошо, похоже, это указывает на то, что нет ничего конкретного с np.float32 или np.float64 , это более общий механизм numpy для генерации предупреждений.

Не знаю, сочтут ли авторы компилятора это ошибкой. Предупреждение работает так: есть несколько волшебных флагов состояния, которые отслеживает процессор, которые автоматически устанавливаются при возникновении соответствующего события. Numpy очищает их перед началом вычислений, а затем снова проверяет их в конце. Итак, где-то между этими точками сборка, сгенерированная clang, выполняет некоторые вычисления, в которых используется NaN. Но это сложно отследить (поскольку фактическая установка флага полностью выполняется аппаратно), и большую часть времени люди не беспокоятся о том, как их код влияет на флаги fpu. (Реализации Libm также заведомо непоследовательны в отношении того, устанавливают ли они эти флаги.) И точные результаты во многом зависят от конкретного создаваемого asm, поэтому неудивительно, что вы видите его только в определенных конфигурациях, а не в других.

Да, это подтверждает мои подозрения и дает вам возможность отладки. Этот код

def check_fpu(f):
    @functools.wraps(f)
    def wrapped(*args, **kwargs):
        excluded = list(range(len(args))) + list(kwargs.keys())
        fvec = np.vectorize(f, excluded=excluded)
        return fvec(*args, **kwargs)
    return wrapped

Применительно к функции Python позволяет изолировать предупреждения внутри этого фрагмента кода.

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

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

Также есть довольно хороший аргумент, что numpy не должен даже пытаться проверять эти флаги в циклах объектов. (Или целочисленные циклы в этом отношении, но это сложно, потому что способ, которым мы сообщаем о целочисленном переполнении, довольно грубый и использует флаги fpu.) Это единственное, что я могу придумать, что numpy может здесь сделать.

check_fpu() есть опечатка, там должно быть fvec = np.vectorize(f, excluded=exclude) .
И мы на python2: import functools32 as functools .

functools.wraps не нужен Python 3, не так ли?

Я получаю сообщение об ошибке, если использую функции python2 в вызове setattr()

AttributeError: 'method-wrapper' object has no attribute '__module__'

Да, я предполагаю, что это какая-то библиотека с множественной точностью, реализующая арифметику для коэффициентов в RealField которая устанавливает флаг FPU. Компилируются ли базовые библиотеки с тем же компилятором, что и numpy, в каждом из разных обстоятельств? Или только numpy перестраивается с разными компиляторами?

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

Это для протокола MPFR.

Мы пытаемся портировать Sagemath на clang + gfortran (в основном на OSX и FreeBSD, платформы, где clang является основным компилятором), чтобы его сборка и запуск на OSX выполнялись проще и быстрее (FreeBSD - это скорее инструмент для получения аналогичной среды без хлопоты OSX и оборудования Apple).

Все сравнения, о которых я здесь сообщаю, относятся к полным сборкам с clang / clang +++ gfortran в отличие от gcc / g +++ gfortran.

оболочка, кажется, сообщает нам, что x.__rmul__ устанавливает флаг FPU

check_fpu(x.__rmul__)(np.float32('1.5'))

выводит предупреждение, а x.__rmul__(np.float32('1.5')) - нет.

В самом деле, я предполагал, что x.__rmul__ был написан на Python, и что его исходный код можно разделить пополам, чтобы определить, какой бит конкретно устанавливает флаг.

x.__rmul__ находится в Cython, но все же это небольшой фрагмент кода, который нужно исследовать.

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

@jdemeyer IMHO numpy предупреждение выдается намного позже в пути кода, то есть это результат явной проверки флагов FPU, а не набора прерываний.

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

@jdemeyer будет ли код Cython между sig_on() / sig_off() из cysignals вызывать исключение, если поднят флаг FPU? Или это зависит от флага?

cysignals вызовет исключение, если SIGFPE поднято, что может произойти, если поднят флаг FPU, в зависимости от конфигурации FPU. Но по умолчанию это не так.

Аналогичное предупреждение: RuntimeWarning: invalid value encountered in greater is
исходящий из np.float64(5)>e . Здесь e - это константа Sagemath, определяющая основание натурального логарифма 2.71828 ..., и поэтому на пути к вычислению этого значения в True она должна быть "преобразована" (конечно, e "знает "его численное приближение, это e.n() ) к числу.
Это приближение типа RealField уже упоминалось выше (так что, возможно, это предупреждение тесно связано).

И снова возникает вопрос: что делает numpy для вычисления np.float64(5)>e ?
Или, что то же самое, предупреждение появляется из np.float64(5).__gt__(e) , так что можно также начать оттуда.

Обратите внимание, что type(e) - это sage.symbolic.constants_c.E ; это в основном какой-то (почти) фиктивный класс
упаковка символических выражений Sagemath ( SR ).

Нет предупреждений от np.float64(5).__gt__(e.n()) или np.float64(5)>e.n() .
По сути, то же самое (то же предупреждение / шаблон предупреждения) произойдет, если вы замените e на pi (с очевидным pi.n()==3.1.415... ).
pi имеет тип SR , т.е. sage.symbolic.expression.Expression .

Ответ здесь тот же - numpy вызывает np.greater с помощью цикла объекта. На нижнем уровне это вызывает e.__lt__(5.0) . Но опять же, он проверяет флаги FPU до и после и замечает, что что-то не так.

Большинство арифметических / логических операторов ndarray (за исключением - и divmod ) делегируются ufuncs. При вызове с объектами sage это вызовет циклы O (object) для этих ufunc. Эти циклы объектов будут перебирать массив (который в данном случае равен 0d), запускать обычный оператор python для элементов, _ но проверять флаги FPU, когда он это делает _.

Итак, еще раз, мудрец устанавливает эти флаги. Возможно, это признак ошибки, а может, и нет.

Я думаю, здесь есть хороший аргумент, что numpy не должен проверять флаги fpu для этих случаев. @njsmith , как вы думаете, нам следует продолжить удаление проверки типов объектов?

Фактически, e.__lt__(5.0) является символическим выражением:

sage: type(e.__lt__(np.float32(5.0)))
<type 'sage.symbolic.expression.Expression'>
sage: e.__lt__(np.float32(5.0))
e < 5.0
sage: bool(e.__lt__(np.float32(5.0)))  # this is how it's evaluated
True

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

sage: check_fpu(e.__lt__)(np.float32(5.0))
e < 5.0

Я смог связать нашу проблему в Sagemath с конкретным расширением C, используя модуль fpectl Python (который в некоторой степени, но не полностью, сломан во FreeBSD). На самом деле это было очень быстро, как только мне удалось его установить.

IMHO fpectl настолько полезен, что его нужно исправить ; возможно, даже используется в numpy вместо или в дополнение к np.seterr() , поскольку он обеспечивает лучшую детализацию скомпилированных компонентов.

Разница между подходом fpectl и np.seterr:

np.seterr запускает цикл ufunc, а затем проверяет, установлены ли какие-либо флаги.

fpectl делает некоторую магию, чтобы сделать так, чтобы всякий раз, когда происходит операция, вызывающая установку одного из флагов, оборудование генерирует прерывание, ядро ​​преобразует это в SIGFPE, доставленное процессу, и устанавливает Обработчик SIGFPE, который longjmp s прямо из обработчика сигнала в код обработки ошибок.

Некоторыми недостатками подхода fpectl являются: (a) он вообще не работает в Windows, (b) он нарушает код, который внутренне вызывает временную установку одного из этих флагов, а затем очищает его (это является законным, и я полагаю, что существуют библиотеки libm, которые это делают), (c) longjmp невероятно хрупкое; в основном вы рискуете получить segfault каждый раз, когда это делаете. Это определенно не может быть общим решением для произвольных пользовательских функций.

Учитывая все это, я не думаю, что numpy собирается переключиться.

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

В любом случае, похоже, что исходная проблема решена

Мы уверены, что не хотим отключать проверку флагов FPU для циклов объектов? Это могло бы показаться довольно разумным изменением для numpy.

@ Эрик-Визер: о, это интересная идея, да. может быть стоит открыть вопрос по этому поводу :-). «Правильно», однако, довольно сложно - в идеале мы не должны использовать особый корпус для объекта dtype (подумайте о пользовательских dtypes), и целочисленные циклы также не должны его использовать (это может быть настоящей оптимизацией на некоторых архитектурах, где проверка / очистка флагов FPU выполняется очень медленно), но целочисленные циклы действительно нуждаются в способе явно сигнализировать о целочисленных ошибках, что они в настоящее время делают, явно устанавливая флаги FPU, ... Я не уверен, что это тот случай, когда легко -висящий фрукт?

Или я неправильно понял, и sage только идентифицировал проблему, и им все еще нужно небольшое изменение, чтобы исправить это?

@njsmith : Я не понимаю, почему вы говорите, что это не работает в Windows. (Впрочем, это было бы правильно в эпоху до C99). Современные функции обработки FPU (fenv) доступны, как только ваш компилятор C станет совместимым со стандартом C99. Помимо fenv все, что ему нужно, это setjmp / longjmp (опять же, стандартная функция C).

Мне также любопытно услышать о libm, которая вызывает одно из исключений FE в ходе нормальной работы.
(Если это не классифицировано как ошибка).

@dimpase : вам также нужна поддержка SIGFPE, которая не указана в C99. (Ну, C99 говорит, что должен быть SIGFPE, но это для деления на ноль - он не указывает никакого способа подключить его к исключениям с плавающей запятой.) Тем не менее, похоже, что я неправильно запомнил, и хотя Windows не поддерживает сигналы, MSVCRT эмулирует SIGFPE с использованием структурированной обработки исключений и предоставляет нестандартную функцию _control_fp для включения ее для определенных исключений fp, поэтому поддержка Windows на самом деле не является препятствием. OTOH это не имеет особого значения, поскольку longjmp определенно не происходит без очень веской причины :-)

И FWIW, если libm вызвала исключение FE, а затем очистила его снова, я не могу понять, почему они сочли бы это ошибкой. Я не уверен, что существуют какие-либо такие реализации, но это правдоподобно, и если они есть, то способ, которым мы узнаем, - это b / c, кто-то говорит нам, что numpy не работает на платформе X, и единственное исправление - отменить изменение вы предложили.

Можете ли вы ответить на вопрос, который я задал в конце моего предыдущего комментария?

@njsmith : если libm (или любой другой пользовательский код) должен вызвать исключение FE и обработать его, он установит свой собственный обработчик исключений FE, сохранив предыдущий и восстановив предыдущий при выходе.
Так что это не проблема, если основной код играет по правилам.

Что касается поддержки MS для этого, они поставляют fenv.h начиная с Visual C (++) 2013 года или около того .
Это специально предназначено для использования с setjmp / longjmp (надеюсь, то, как именно это делается, не должно сильно беспокоить).

Что касается RuntimeWarning numpy:

  • если вы думаете, что пользовательский код может быстро играть с флагами FP, то эти предупреждения должны быть не обязательными.
  • в противном случае они могут оставаться стандартными, но, по крайней мере, способ добраться до сути того места, откуда они пришли, имеет решающее значение для их полезности; (улучшено) fpectl - быстрый способ достичь последнего. Использование внешних инструментов (таких как отладчики, позволяющие настроить код для проверки чего-либо после каждой инструкции) сложнее, медленнее и подвержено ошибкам - например, нередко то, что ошибка появляется только в оптимизированном двоичном файле, уходит, как только вы попытаетесь найти его в хорошо отлаживаемом двоичном файле.
  • в любом случае, RuntimeWarning должна быть отключена.

Что касается этой проблемы в Sage - все еще исправляется (надеюсь, она ограничена некоторыми проблемами только в MPFR ).

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

если libm (или любой другой пользовательский код) должен вызвать исключение FE и обработать его, он установит свой собственный обработчик исключений FE, сохранив предыдущий,

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

Что касается поддержки MS для этого, они поставляют fenv.h начиная с Visual C (++) 2013 года или около того.
Это специально предназначено для использования с setjmp / longjmp

Я не могу понять, о чем вы здесь говорите. Afaict, стандартные функции в fenv.h полезны только для реализации функций в стиле numpy, а MS придерживается стандарта. Я не вижу там никаких функций, которые можно было бы использовать с setjmp / longjmp.

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

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

способ добраться до сути того места, откуда они пришли, имеет решающее значение для их полезности; (улучшено) fpectl - быстрый способ добиться последнего.

Вы буквально первый человек примерно за десятилетие, которому нужен SIGFPE для отладки такого рода проблем, и, снова посмотрев на комментарии к мудрым ошибкам, похоже, что у вас на самом деле не работает fpectl? Это не должно вызывать дамп ядра. (Похоже, что cysignals переопределяет код fpectl, поэтому он даже не запускается.)

Если это произойдет снова, вам нужно сделать один вызов C для включения SIGFPE, а затем использовать отладчик для получения трассировки стека. Для получения трассировки стека вам не нужна отладочная сборка; все, что вам нужно сделать, это не удалять символы. И эй, теперь мы знаем, может ли это повториться снова.

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

Наконец, оказывается, что все сводится к давней ошибке в компиляторе clang C. По сути, в определенном диапазоне double назначение, как в

unsigned long a;
double b;
...
a = b;

повышает FE_INVALID (а иногда и FE_INEXACT ); Кстати, это также влияет на другие типы данных с плавающей запятой. Отличный MPFR (конечно, MPFR должен копировать двойные числа в их числа с произвольной точностью) люди предоставили обходной путь, исправив это для sage.

Между прочим, связанная с этим еще более давняя (с 2010 года, с десятком закрытых дубликатов) ошибка clang fenv.h для правильной работы fpectl в настоящий момент. . Clang не полностью совместим с C99 в этом отношении и очень стесняется этого.

Не уверен, хочет ли numpy что-нибудь с этим сделать; возможно, будет полезно замечание о том, что RuntimeWarning может быть вызвано просто ошибкой компилятора (цитируя "clang bug 8100", это довольно яркий пример).

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

Суть ошибки 8100 заключается в том, что clang не заботится о правильной компиляции операций FP; хотя юрист может не согласиться. :-)

Хорошо, уже упомянутая ошибка 17686 точно актуальна.

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