Передача переменных конфигурации в функции, чтобы они вели себя как константы времени компиляции

В numba я хочу передать переменную конфигурации в функцию как константу времени компиляции. В частности, то, что я хочу сделать, это

    @njit
    def physics(config):
        flagA = config.flagA
        flagB = config.flagB
        aNumbaList = List()
        for i in range(100):
            if flagA:
                aNumbaList.append(i)
            else:
                aNumbaList.append(i/10)
        return aNumbaList

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

There are 2 candidate implementations:
                 - Of which 2 did not match due to:
                 ...
                 ...

Я просмотрел протокол одного из собраний Numba и обнаружил, что есть способ сделать это Встреча Numba: 05.03.2024 Я попробовал это, но все равно выдает ту же ошибку.

Вот код с сообщением об ошибке:

.. code:: ipython3

    from numba import jit, types, njit
    from numba.extending import overload
    from numba.typed import List
    import functools

.. code:: ipython3

    class Config():
        def __init__(self, flagA, flagB):
            self._flagA = flagA
            self._flagB = flagB
    
        @property
        def flagA(self):
            return self._flagA
    
        @property
        def flagB(self):
            return self._flagB

.. code:: ipython3

    @functools.cache
    def obj2strkeydict(obj, config_name):
    
        # unpack object to freevars and close over them
        tmp_a = obj.flagA
        tmp_b = obj.flagB
        assert isinstance(config_name, str)
        tmp_force_heterogeneous = config_name
    
        @njit
        def configurator():
            d = {'flagA': tmp_a,
                 'flagB': tmp_b,
                 'config_name': tmp_force_heterogeneous}
            return d
    
        # return a configuration function that returns a string-key-dict
        # representation of the configuration object.
        return configurator

.. code:: ipython3

    @njit
    def physics(cfig_func):
        config = cfig_func()
        flagA = config['flagA']
        flagB = config['flagB']
        aNumbaList = List()
        for i in range(100):
            if flagA:
                aNumbaList.append(i)
            else:
                aNumbaList.append(i/10)
        return aNumbaList

.. code:: ipython3

    def demo():
        configuration1 = Config(True, False)
        jit_config1 = obj2strkeydict(configuration1, 'config1')
        physics(jit_config1)

.. code:: ipython3

    demo()


::


    ---------------------------------------------------------------------------

    TypingError                               Traceback (most recent call last)

    Cell In[83], line 1
    ----> 1 demo()


    Cell In[82], line 4, in demo()
          2 configuration1 = Config(True, False)
          3 jit_config1 = obj2strkeydict(configuration1, 'config1')
    ----> 4 physics(jit_config1)


    File ~/anaconda3/envs/tardis/lib/python3.11/site-packages/numba/core/dispatcher.py:468, in _DispatcherBase._compile_for_args(self, *args, **kws)
        464         msg = (f"{str(e).rstrip()} \n\nThis error may have been caused "
        465                f"by the following argument(s):\n{args_str}\n")
        466         e.patch_message(msg)
    --> 468     error_rewrite(e, 'typing')
        469 except errors.UnsupportedError as e:
        470     # Something unsupported is present in the user code, add help info
        471     error_rewrite(e, 'unsupported_error')


    File ~/anaconda3/envs/tardis/lib/python3.11/site-packages/numba/core/dispatcher.py:409, in _DispatcherBase._compile_for_args.<locals>.error_rewrite(e, issue_type)
        407     raise e
        408 else:
    --> 409     raise e.with_traceback(None)


    TypingError: Failed in nopython mode pipeline (step: nopython frontend)
    - Resolution failure for literal arguments:
    No implementation of function Function(<function impl_append at 0x7fd87d253920>) found for signature:
    
     >>> impl_append(ListType[int64], float64)
    
    There are 2 candidate implementations:
          - Of which 2 did not match due to:
          Overload in function 'impl_append': File: numba/typed/listobject.py: Line 592.
            With argument(s): '(ListType[int64], float64)':
           Rejected as the implementation raised a specific error:
             TypingError: Failed in nopython mode pipeline (step: nopython frontend)
           No implementation of function Function(<intrinsic _cast>) found for signature:
    
            >>> _cast(float64, class(int64))
    
           There are 2 candidate implementations:
                 - Of which 2 did not match due to:
                 Intrinsic in function '_cast': File: numba/typed/typedobjectutils.py: Line 22.
                   With argument(s): '(float64, class(int64))':
                  Rejected as the implementation raised a specific error:
                    TypingError: cannot safely cast float64 to int64. Please cast explicitly.
             raised from /home/sam/anaconda3/envs/tardis/lib/python3.11/site-packages/numba/typed/typedobjectutils.py:75
           
           During: resolving callee type: Function(<intrinsic _cast>)
           During: typing of call at /home/sam/anaconda3/envs/tardis/lib/python3.11/site-packages/numba/typed/listobject.py (600)
           
           
           File "../anaconda3/envs/tardis/lib/python3.11/site-packages/numba/typed/listobject.py", line 600:
               def impl(l, item):
                   casteditem = _cast(item, itemty)
                   ^
    
      raised from /home/sam/anaconda3/envs/tardis/lib/python3.11/site-packages/numba/core/typeinfer.py:1086
    
    - Resolution failure for non-literal arguments:
    None
    
    During: resolving callee type: BoundFunction((<class 'numba.core.types.containers.ListType'>, 'append') for ListType[int64])
    During: typing of call at /tmp/ipykernel_9889/739598600.py (11)
    
    
    File "../../../tmp/ipykernel_9889/739598600.py", line 11:
    <source missing, REPL/exec in use?>

Любая помощь или любая ссылка на соответствующий материал действительно мне помогут. Спасибо.

Вам следует создать один файл .py и протестировать его, а не фрагменты кода ipython (jupyter?). Если вы это сделаете, вы должны получить дополнительное сообщение об ошибке, которое будет гораздо более понятным.

ken 27.06.2024 08:46
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
1
79
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В Numba глобальные переменные являются константами времени компиляции, поэтому вы можете использовать их по своему усмотрению. Вот пример:

import numba as nb   # v0.58.1

flagA = True

@nb.njit
def physics(flagA):
    aNumbaList = nb.typed.List()
    for i in range(100):
        if flagA:
            aNumbaList.append(i)
        else:
            aNumbaList.append(i/10)
    return aNumbaList

Это работает без ошибок, а передача flagA в параметре приводит к ошибке, поскольку элементы в if и else относятся к разным типам.

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

Альтернативное решение — вернуть функцию, которая считывает переменную, определенную в родительской функции, чтобы она считалась глобальной для функции и, следовательно, константой времени компиляции. Переменная, прочитанная скомпилированной функцией, может быть передана в качестве параметра родительской функции. Вот пример:

import numba as nb

def make_physics(flagA):
    @nb.njit
    def fun():
        aNumbaList = nb.typed.List()
        for i in range(100):
            if flagA:
                aNumbaList.append(i)
            else:
                aNumbaList.append(i/10)
        return aNumbaList

    return fun

physics = make_physics(True)  # Compile a specialized function every time it is called
physics()                     # Call the compiled function generated just before

Это также не приводит к какой-либо ошибке и фактически работает так, как задумано. Вот сгенерированный ассемблерный код функции physics, показывающий, что проверка flagA во время выполнения в основном цикле отсутствует:

    [...]

    movq    %rax, %r12                 ; r12 = an allocated Python object (the list?)
    movq    24(%rax), %rax
    movq    %r14, (%rax)
    xorl    %ebx, %ebx                 ; i = 0
    movabsq $NRT_incref, %r13
    movabsq $numba_list_append, %rbp
    leaq    48(%rsp), %r15             ; (r15 is a pointer on i)

.LBB0_6:                               ; Main loop
    movq    %r12, %rcx                 
    callq   *%r13                      ; Call NRT_incref(r12)
    movq    %rbx, 48(%rsp)             
    movq    %r14, %rcx                 
    movq    %r15, %rdx                 
    callq   *%rbp                      ; Call numba_list_append(r14, pointer_of(i))
    testl   %eax, %eax                 
    jne .LBB0_7                        ; Stop the loop if numba_list_append returned a non-zero value
    incq    %rbx                       ; i += 1
    movq    %r12, %rcx                 
    movabsq $NRT_decref, %rax          
    callq   *%rax                      ; Call NRT_decref(r12)
    cmpq    $100, %rbx                 
    jne .LBB0_6                        ; Loop as long as i < 100

    [...]

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

Спасибо за подробный ответ, Джером. Итак, я попробовал подход с использованием глобальных переменных, и вот в чем проблема: пока ваша глобальная переменная одинакова во всей программе, она работает нормально, но если значение будет изменено в середине выполнения, программа будет использовать то же глобальное значение, что и раньше, и не обновлять его. Вам придется перекомпилировать его, чего Numba не рекомендует. Я попробую метод закрытия и вернусь к вам.

Sumit Gupta 30.06.2024 15:07

Я рассматриваю возможность использования перегрузки или сгенерированного_jit для возврата специализированных функций на основе значения конфигурации. Вы порекомендуете это? Кроме того, есть ли что-то, на что мне следует обратить внимание при их использовании, чтобы это не влияло на время выполнения программы?

Sumit Gupta 30.06.2024 15:20
generated_jit AFAIK устарел и теперь заменен на overload. Дело в том, что AFAIK overload ограничен перегрузкой на основе типов, тогда как вам, похоже, нужна перегрузка на основе значений. Поэтому я думаю, что ни один из двух не подходит для вашего случая использования. Кроме того, поведение generated_jit похоже на решение, представленное в ответе, за исключением того, что функция не всегда перекомпилируется при каждом вызове make_physics (я думаю, что она похожа на нее после добавления запоминания).
Jérôme Richard 30.06.2024 15:45

Я согласен насчет глобальных переменных, но в данном случае решение make_physics подходит. Вы можете вызывать его с другой конфигурацией и генерировать разные функции. На основе этого вы можете написать свою собственную перегрузку CPython. Мемоизацию можно легко поддержать с помощью @functools.lru_cache(maxsize=64) (кэшировать до 64 конфигураций — используйте None без привязки за счет, возможно, большого количества скомпилированных функций в памяти). Вы также можете предварительно скомпилировать все конфигурации вручную при запуске программы и в свою собственную систему кеширования (что невозможно с generated_jit).

Jérôme Richard 30.06.2024 15:52

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

Jérôme Richard 30.06.2024 15:55

Да. Если посмотреть на кодовую базу, сгенерированный_jit был удален начиная с Numba 0.59.0. Что касается использования перегрузки, один из способов заставить ее работать — передать переменные конфигурации как литералы Numba. Я могу использовать свойство .literal_value. Но я думаю, что решения make_physical кажутся простыми. Спасибо за вашу помощь.

Sumit Gupta 30.06.2024 17:04

Другие вопросы по теме

Похожие вопросы

Когда я запускаю поэтическую оболочку на MacOS с оболочкой ZSH, выдает ошибку «команда не найдена»
Развертывание веб-приложения Python (Dash) в Azure, конвейер работает слишком долго, а колесо создания сообщений для панд все еще работает. Как оптимизировать?
Как мне заставить селен последовательно находить и использовать элемент при создании викторины Kahoot?
Как извлечь подстроки непосредственно перед ближайшим знаком препинания?
Используя Postgres/Flask, как я могу запросить следующее появление запланированной задачи, когда дни/часы/минуты хранятся как целые числа в своих собственных столбцах?
Добавление ведущих нулей в столбцы данных при загрузке из файлов CSV с помощью pandas
Используйте Python для управления форматом json.dump() с помощью kwarg "default="?
Запросы Python и cURL никогда не возвращают ответ
Как объединить 2 кадра данных pandas на основе критериев
Преобразование плоского словаря с кортежами в качестве ключей во вложенный словарь