Pull to refresh
58
0
Alexander Kalinin @alec_kalinin

User

Send message
JIT код крайне эффективен, в основе тот же современный LLVM компилятор. Но я бы пока все-таки не советовал переписывать полностью код Python в этом стиле. Это лишает Python его преимуществ высокоуровневого динамического языка.

Эффективно это работает следующим образом. Выделяются критические к производительности участки кода, разнородные численные данные преобразаются к NumPy массивам, а далее расставляются типы и все JIT-компилируется.

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

При правильном проектировании и если использовать с самого начала аннотации типов все сводится только к расстановке jit в паре мест.
Да, все верно, последняя версия 0.52.0. Спасибо, поправил в новости.
Можно еще вот тут посмотреть эту позицию.

Это альтернативная открытая база 7-фигурок, но тут главная метрика DTZ вместо DTM.
Насколько я помню, впервые это правило отменили для мата двумя конями. Вообще считается, что мат двумя конями невозможен. Но (!) если добавить слабейшей стороне пешку, вроде как усиливая ее, мат становится возможен. Но далеко за пределами 50 ходов.

На этой позиции у сильнейшей стороны два коня. Результат ничья.

Но мы добавляем слабейшей стороне пешку. И получаем мат в 138 (!) ходов. Непостижимо для человека. Всего три фигуры. Но результат мат. При этом белые очень аккуратно обходят эту пешку. А черный король бродит по всей доске.
У меня тут есть несколько соображений.

1. Я не вижу в нейросетях чего-либо революционного. Это обычный эволюционный процесс накопления знаний и повышения эффективности обработки информации. Появление письменности, книгопечатание, библиотеки книг, библиотечные каталоги, регулярная научная периодика, онлайн каталоги + поиск, нейросети, и т.п. Это просто звенья одной цепи. В нашей временной перспективе это кажется прорывом. Но если так подумать, то гораздо большим прорывом в свое время была концепция публикации научных знаний в журналах, доступных всем.

2. В очень многих ситуациях человек просто не может постичь всю глубину шахмат. Вот простейшие примеры из 7-фигурной базы. Не считая королей, всего 5 фигур. Но мат достигается только 545 единственными ходами!

3. А игра уже меняется. Если лет десять назад считалось, что необходимо получить преимущество в дебюте, то сейчас ведущие гроссмейстеры идут на просто игровые позиции. И очень много компьютерных приемов я уже замечаю, например задвижение крайних пешек.
С момента AlphaZero в компьютерных шахматах появилось много интересных вещей.

1. Во первых это Leela Chess. Это открытая реализация нейросетевого подхода AlphaZero, активно развивающаяся, с большим количеством вариантов сетей.

И если смотреть по результатам компьютерного чемпионата TCEC она действительно сумела обыграть StockFish, но в последние два раза StockFish взял убедительный реванш. Так что на данном этапе StockFish пока превосходит нейросетки по типу AlphaZero.

2. Но совсем недавно появилась новая архитектура нейронной сети, работающая только на CPU. Она называется NNUE. Она позволяет улучшить alpha-beta отсечение с использованием нейросетевой оценки. Она работает только на CPU, так как копировать данные между CPU и GPU слишком накладно. Судя по последним данным, такой гибридный подход радикально усилил силу игры, почти на 100 ELO NNUE evaluation, что для текущих компьютерных шахмат серьезный прорыв.

Вот такая гибридная арихектура на CPU пока самая сильная на данный момент времени.

Вот график прироста ELO с использованием NNUE

image
Я бы еще добавил, что Python перешел на стабильные годовые циклы выпуска, см. PEP 602.

Каждая новая версия Python теперь будет выходить в октябре. Python 3.10 выйдет в октябре 2021, Python 3.11 в октябре 2022. А каждые два месяца будет выходить бакфикс. Для меня это великолепная новость, теперь можно четко планировать upgrade python окружения.
Огромное спасибо, погляжу! Я уже давно пытаюсь свалить с Питона куда-нибудь. Я научился писать быстрые программы на Python, с использованием cython, numpy, numba, jax, с-модули, etc… Но, честно говоря, хочется уже не зоопарка, а единой кодовой базы.

Julia выглядит как идеальный вариант, но пока мелкие шероховатости раздражают. Я пару раз пытался плотно пересесть на Julia, начиная с мелких скриптов. Но, то медленный старт немного достает, то недостаточная стабильность. Многие пакеты, активно развивающиеся год назад, сейчас почти заморожены. Мало хорошей документации, а GitHub копаться не всегда есть время.

Еще немного смущает порой агрессивный маркетинг и смущает попытка ранней коммерциализации Julia, т.е. Julia Computing предлагает Enterprise вариант для тех, кому нужна хорошая поддержка. Я бы наоборот, больший упор делал на честную работу с community. С горя я уже к фортрану начал присматриваться.

Очень надеюсь, что Julia постепенно наберет базу пользователей, развитое коммьюнити и стабилизирует все пакеты. Идея языка и сам подход мне очень нравятся!
Кстати, я недавно глянул на современный фортран. Я то думал, что это какой-то древний артефакт. Но оказалось это современный, активно развивающийся, предельно ясный язык, быстрый и параллельный.

Я вот подумываю, когда тормоза Питона меня окончательно достанут, писать модули на фортране.
Те, кто работает плотно с Julia, подскажите, ее уже можно использовать в небольших standalone скриптах?

Я год назад пробовал написать небольшой standalone скрипт, который запускается по таймеру, собирает из API json'ы, делает расчет, пишет в файл и завершает работу.

К моему удивлению этот скрипт работал каждый раз секунд около 20, тогда как «медленный» python работал какие-то миллисекунды. Я написал в форумы, мне посоветовали запускать мой код из REPL, который все время висит в памяти. Я тогда немного удивился и забил на julia.

Подскажите, сейчас это починили?
Я бы попробовал это реализовать двумя путями.

Если оставаться чисто в парадигме вложенных классов, я бы это сделал через @jitclass, как в jitclass.py и в binarytree.py.

Другой вариант вместо python классов использовать Numpy Strutured Array и их поддержку в Numba, structures.py.
Пока не могут, так как эти аннотации пришли в python из необходимости существенно ускорить математические вычисления, основанные на numpy array. Не все типы python пока еще поддерживаются. Да и тайпхинты пока все еще в процессе развития.

Но в ближайших планах как раз и перейти на тайпхинты с поддержкой все большего количество чистых пайтоновских структур данных.
С одной стороны вы правы. Под капотом все тоже компилирование в нейтив. Но, с точки зрения программиста, ситуация меняется. Если раньше нужно было писать брутальный C модуль или использовать cython с его несколько извращенным синтаксисом, то теперь с numba достаточно подключения библиотеки и аннотации типов. И формально программист не выходит за пределы логики python.

И я вижу, как это дальше развивается. Numba тесно интегрируется с аннотацией типов python и поддерживает все больше и больше чистых python структур, а не только numpy array. Все те функции python, для которых возможно полное выведение типов, будут скомпилированы в native и свободны от GIL, при этом будут бесшовно сшиваться с python интерпретаторами.

Это похоже на концепцию языка julia, где авторы создали динамический язык, способный к автоматическому выведению типов. Правда в julia они начали с того, что обрезали все те возможности языка, для которых такое выведение типов невозможно.

Python идет примерно по той же дороге, выделяя то подмножество языка, для которого это возможно, и шаг за шагом расширяет это подмножество.
Да, там 0.047. Спасибо за правку!
Допустим у нас в python интерпретаторе есть python функция, для которой мы знаем
  1. Типы и структуру входных и выходных параметров.
  2. Типы переменных в самой функции строго зависят от входных параметров или являются элементарными типами.

Тогда для такой функции мы можем выйти из python интепретатора, освободить GIL, скомпилировать код в нэйтив для данной платформы, выполнить его и вернуться обратно в интепретатор. Самый удобный пакет сейчас для такого это numba, которая основана на LLVM.

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

Для примера, рассмотрим следующую задачу.

Постановка задачи
Даны два набора из N точек в трехмерном пространстве. Необходимо посчитать матрицу попарных регуляризированных обратных расстояний. В нашем пример N = 5000.

def main():
    """ For the given points set calculate RBF matrix
    base on regularized inverse distances.
    
    Input data:
    -----------
    
    p = [[p0_x, p0_y, p0_y],
         [p0_x, p0_y, p0_y],
         ...
         [pN_x, pN_y, pN_y]]
    
    q = [[q0_x, q1_x, ..., qN_x],
         [q0_y, q1_x, ..., qN_x],
         [q0_z, q1_z, ..., qN_z]]
         
    Output data:
    -----------
    
    R = [[f(p0, q0), f(p0, q1), ..., f(p0, qN)],
         [f(p1, q0), f(p1, q1), ..., f(p1, qN)],
         ...
         [f(pN, q0), f(pN, q1), ..., f(pN, qN)]],
    where f(p, q) = 1 / (1 + |q - p|)
    """

    N = 5000

    p = np.random.rand(N, 3)
    q = np.random.rand(3, N)


Чистый код на Python
def get_R_py(p, q):
    R = np.empty((p.shape[0], q.shape[1]))

    for i in range(p.shape[0]):
        for j in range(q.shape[1]):
            rx = p[i, 0] - q[0, j]
            ry = p[i, 1] - q[1, j]
            rz = p[i, 2] - q[2, j]
            R[i, j] = 1 / (1 + math.sqrt(rx * rx + ry * ry + rz * rz))

    return R


Время работы: 100.479 c.

Это очень долго. Теперь начинается магия!

Последовательная реализация с аннотацией типов
from numba import float64, jit

@jit(float64[:, :](float64[:, :], float64[:, :]), nopython=True, nogil=True)
def get_R_numba_sp(p, q):
    R = np.empty((p.shape[0], q.shape[1]))

    for i in range(p.shape[0]):
        for j in range(q.shape[1]):
            rx = p[i, 0] - q[0, j]
            ry = p[i, 1] - q[1, j]
            rz = p[i, 2] - q[2, j]
            R[i, j] = 1 / (1 + math.sqrt(rx * rx + ry * ry + rz * rz))

    return R

Время работы: 0.154 с, ускорее примерно в 700 раз. И все, что мы для этого сделали, по большому счету, только проставили типы.

Параллельная реализация с аннотацией типов
Ну ладно, а как на счет параллельности и GIL? Почти без изменений:

@jit(float64[:, :](float64[:, :], float64[:, :]), nopython=True, nogil=True, parallel=True)
def get_R_numba_mp(p, q):
    R = np.empty((p.shape[0], q.shape[1]))

    for i in prange(p.shape[0]):
        for j in range(q.shape[1]):
            rx = p[i, 0] - q[0, j]
            ry = p[i, 1] - q[1, j]
            rz = p[i, 2] - q[2, j]

            R[i, j] = 1 / (1 + math.sqrt(rx * rx + ry * ry + rz * rz))

    return R

Мы добавили а) parallel=True в аннотацию и б) во внешнем цикле range заменили на prange.

Время работы: 0.47 с, ускорение по сравнения с последовательной версией примерно в 3.3 раза, что неплохо для моего четырехядерного лаптопа.

И что дальше?
Уже как несколько лет это работает идеально для математики и numpy array. Но разработчики numba пошли гораздо дальше. Очень важный момент, что такой же аннотацией типов код можно скомплировать и для GPU. Кроме того, стали поддерживаться почти все python типы, включая типизированные List, Dict, а также спецификации для классов, что я показывал выше. А в планах тесная интеграция с python type annotations.

Сейчас репозиторий на GitHub очень активный, около 170 контрибьюторов. И мне нравится, как развивается этот проект.
Сейчас Python очень активно развивается, большей частью в сторону решения своих главных проблем с производительностью и GIL. Например, уже сейчас стало очень просто расставить типизацию в ключевых местах и получить компилированый код, свободный от GIL:

from numba import int32, deferred_type, optional
from numba.experimental import jitclass

node_type = deferred_type()

spec = OrderedDict()
spec['data'] = int32
spec['next'] = optional(node_type)

@jitclass(spec)
class LinkedNode(object):
    def __init__(self, data, next):
        self.data = data
        self.next = next

    def prepend(self, data):
        return LinkedNode(data, self)

node_type.define(LinkedNode.class_type.instance_type)

С учетом активно развивающейся концепции аннотации типов и усовершенствованием парсера синтаксиса, мы скором получим динамический язык, который путем расстановки типов в ключевых местах (и их автоматического вывода) можно ускорить практически до скорости языка C.
И еще раз о скорости Python. Не так давно тут была статья с хорошим бенчмарком на многих языках программирования.

Так вот, Python с jit компиляцией по скорости был на уровне C, Go и Rust. Поэтому миф о медленном Python давно уже пора забыть. Правда тут в другом.

На Python можно писать медленно, но в 2020 году нет никаких проблем оптимизировать критические участки кода парой аннотаций для jit комплиции.

Программирование на Python научных задач сейчас во многом иерархично. На верхнем уровне вы концентрируетесь только на сути задачи, не тратя ограниченные ресурсы головы на сугубо технические вещи. После того, как алгоритм готов, критические части обычно можно переформулировать на языке линейной алгебры, что дает а) гораздо более понятный во многом декларативный код и б) производительность близкую к C. Но если что-то сложно ложится на язык линейной алгебры, тогда ладно. Надо добавить типы и выполнить jit-компиляцию.

Поэтому с точки зрения скорости разработки и производительности конечного года Python сейчас во многом уникальный инструмент.

Не говоря о том, что почти любая научная C/Fortran библиотека имеет обвязку на Python. И как дополнительный бонус крайне удобные Python ноутбуки и вменяемая система управления пакетами.
Проблемы скорости и параллельного выполнения давного уже решаются jit компиляцией. Вот простейший пример:
@numba.jit(nopython=True, parallel=True)
def logistic_regression(Y, X, w, iterations):
    for i in range(iterations):
        w -= np.dot(((1.0 /
              (1.0 + np.exp(-Y * np.dot(X, w)))
              - 1.0) * Y), X)
    return w
Тут «вычисления» как-то слишком широко получились. Численного анализа в питоне почти нет, если сравнивать с джулией.
Ну как сказать… Вот, например, потрясающая библотека на Python quadpy, где собраны более 1500 методов численного интегрирования. Пока такого в Julia я не встречал. Или библиотека cvxopt, одна из важнейших библиотек для решения задач Конвексной оптимизации. Интересно, что реализация на Julia вызывает реализацию на Python через PyCall.
В питоне автоматом тоже ничего не работает, там надо или njit расставлять, или векторизовать, или типы прописыать.
Ну в питоне то к этому все готовы изначально. А вот Julia позиционируется двусмысленно. С одной стороны типы необязательны. Но если вы хотите быстрый код, но нужно писать уже по другому. Сделали бы тогда типы обязательными.
Для математиков это скорее Wolfram Mathematica, Maple или на крайний случай Matlab. A в Julia математик может много всего странного написать.

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

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Registered
Activity