#let text-indentation = 1.25cm #let text-size = 14pt #let leading = 1em #let department = "высокопроизводительных вычислений" #let topic = "История развития языков и моделей программирования для высокопроизводительных вычислений" #let author = "Горбацевич Андрей Анатольевич" #let field-of-study = "2.3 Информационные технологии и телекоммуникации" #let program = "2.3.1 Системный анализ, управление и обработка информации, статистика" #set text( font: "Times New Roman", size: text-size, lang: "ru" ) #show heading: set text(size: text-size) #set page( paper: "a4", margin: ( top: 2cm, bottom: 2cm, left: 3cm, right: 1cm ), numbering: "1" ) #show "〜": h(text-indentation) #{ set page( numbering: {}, footer: "Красноярск 2025" ) set align(center) set par(leading: leading) text("Министерство науки и высшего образования РФ\n") set par(leading: leading / 2) [ Федеральное государственное автономное образовательное учреждение высшего образования "Сибирский федеральный университет" ] set par(leading: leading) text("Кафедра " + department) v(leading*8) [*РЕФЕРАТ*] v(0pt) text("по теме: " + ["#topic"]) v(leading*2) set align(left) text("Подготовил " + author) v(0pt) text("Направление подготовки: " + field-of-study) v(0pt) text("Программа: " + program) v(leading*8) table( rows: 6, columns: (auto, 1fr, auto, 1fr), stroke: none, inset: 0%, [Проверил], [`______________________________`], [/], [`______________________________`], [], [#pad(top: 0.5em, align(center,text(size: text-size/3*2, [_(ФИО преподавателя)_])))], [], [#pad(top: 0.5em, align(center,text(size: text-size/3*2, [_(зачтено/не зачтено)_])))] ) v(leading*3) table( rows: 6, columns: (1fr, 1fr, 2fr), stroke: none, inset: 0%, [Отдел аспирантуры], [], [], [], [#pad(top: 0.5em, align(center, text(size: text-size/3*2, [_(печать)_])))], [], ) } #pagebreak() #set heading( numbering: "1.1.1.1", ) #show heading.where(level: 1): set block(below: leading*2) #show heading.where(level: 2): set block(above: leading*2, below: leading*2) #show heading.where(level: 3): set block(above: leading*2, below: leading*2) #show heading: it => pad(left: text-indentation, it) #show figure: it => pad(top: leading, bottom: leading, it) #set par( justify: true, leading: leading, first-line-indent: ( amount: text-indentation, all: true ), ) #align( center, outline( title: pad( left: -text-indentation, bottom: text-size, "СОДЕРЖАНИЕ" ) ) ) #pagebreak() #align( center, pad( left: -text-indentation, bottom: text-size, heading( numbering: none, "Введение" ) ) ) *Актуальность темы*: развитие технологий тесно связано с ростом объёмов обрабатываемых данных, особенно в сфере искусственного интеллекта. В связи с этим история высокопроизводительных вычислений становится как никогда актуальной. Рост массивов данных, развитие ИИ и необходимость ускорения вычислений делают историю языков и моделей программирования для HPC особенно важной. Эволюция подходов позволяет оптимизировать параллельные вычисления на суперкомпьютерах и ускорителях вроде GPU. Ограничения аппаратного обеспечения и растая сложность задач, требующих распределённой обработки, ведут к появлению новых вычислительных моделей. Первым высокоуровневым языком для HPC стал Fortran, разработанный IBM в 1954 году под руководством Джона Бэкуса, который обеспечил эффективность на быстро эволюционирующем оборудовании и остаётся стандартом для бенчмарков TOP500@noauthor_frequently_nodate. В 1960–1970-х годах последовательное программирование уступило место параллельным подходам из-за роста многоядерных систем@noauthor_history_nodate. В контексте развития высокопроизводительных вычислений особую роль сыграли параллельные модели и языки программирования. Уже в 1990-е годы сформировались ключевые подходы -- *message passing* и *shared memory*@noauthor_mpi_nodate. Стандарт MPI (1994 @noauthor_mpi_nodate) стал основой для распределённых систем и фактически закрепился как де-факто стандарт параллельных вычислений на кластерах. Параллельно развивался OpenMP (1997 -- для Fortran и 1998 -- для C/C++), ориентированный на многопоточное программирование для многоядерных CPU. Следующий значимый этап связан с появлением GPU-программирования. В 2007 году NVIDIA представила CUDA@noauthor_cuda_nodate, впервые предложив удобную модель для унифицированных вычислений на GPU. Её развитие было дополнено открытым стандартом OpenCL@noauthor_opencl_nodate (2008), получившим поддержку NVIDIA и AMD. Параллельно появился Chapel -- язык, создававшийся Cray в рамках программы DARPA HPCS (2002–2012) и ориентированный на высокопроизводительную параллельность в масштабах суперкомпьютеров@balaji_chapel_2015. Эти модели существенно расширили возможности обработки данных, обеспечив переносимость и адаптивность к гетерогенным вычислительным системам. В итоге эволюция -- от классических решений вроде Fortran до современных GPU-ориентированных моделей CUDA и OpenCL -- значительно упростила разработку высокопроизводительных алгоритмов. Смещение парадигм в сторону *параллелизма данных* и *гетерогенных вычислений* определяет современное состояние HPC и формирует требования к моделям программирования будущего. #pagebreak() = Ранние языки для научных расчётов == Fortran -- первый язык для научных вычислений Fortran (аббревиатура от *FOR*-mula *TRAN*-slation, "транслятор формул") разработан командой IBM под руководством Джона Бэкуса в 1954–1957 годах. Первая версия (Fortran I) выпущена в 1957 для компьютера IBM 704@noauthor_fortran_nodate. Проект начался из-за того, что написание программ в машинных кодах было крайне трудоёмким, как характеризовал этот процесс сам Бэкус: "Рукопашный бой с машиной@noauthor_fortran_nodate." Ключевой проблемой был разрыв между мышлением учёных и инженеров -- формулами -- и тем, что понимала машина -- машинные коды. Fortran был сделан так, что код на этом языке был довольно близок к математической нотации (@listing-1). #figure( ```f90 Y = A*X**2 + B*X + C ```, caption: [Пример кода на языке Fortran] ) Такой подход позволил уменьшить как объёмы кода, так и снизить количество ошибок при его написании. Компиляторы Fortran принесли новые виды оптимизации кода: развёртку циклов, распределение регистров, оптимизация повторяющихся подвыражений и другие. Что особенно важно -- генерируемый код был сопоставим по эффективности с ассемблерным кодом, написанным вручную, а иногда даже быстрее. Таким образом, Fortran стал фундаментом для высокопроизводительных вычислений: #list( [ стандартизация научных расчётов: общий язык для численного программирования; ], [ библиотеки: BLAS, LINPACK/LAPACK и другие фундаментальные библиотеки написаны на Fortran; ], [ портируемость: код можно было переносить между разными компьютерами, для которых был доступен компилятор Fortran; ], [ долголетие: современные версии до сих пор используются в суперкомпьютерных центрах. ], marker: "-", indent: text-indentation, ) При этом разработка и улучшение Fortran не остановилась, отдельные его модули и библиотеки до сих пор используются для высокопроизводительных вычислений, а последняя версия языка была выпущена в 2023 году. == Алгол, Лисп и компиляторы Но не Фортраном единым замыкается разработка программ. === Algol В 1958 году комитетом европейских и американских учёных на съезде в институте ETH в Цюрихе был разработан язык программирования ALGOL (аббревиатура от *ALGO*-rithmic *L*-anguage, "алгоритмический язык"), точнее его вариант ALGOL 58 @noauthor_why_nodate. В ходе разработки языка были выведены его ключевые концепции: #list( [ блочная структура программ; ], [ разрешение рекурсии; ], [ формальное описание синтаксиса в форме BNF-нотации. ], marker: "-", indent: text-indentation, ) Многи последующие императивные языки программирования позаимствовали синтаксис Алгола -- но стоит отметить, что сам язык широкого распространения за пределами научных вычислений не получил. === Lisp В 1985 году Джон Маккарти представил миру Lisp (аббревиатура от *Lis*-t *P*-rocessing) -- язык программирования для символьных вычислений и ИИ @wexelblat_history_1978. Особенностями языка можно назвать: #list( [ "функции первого класса" -- их можно передавать, возвращать и проводить композицию; ], [ каррирование -- преобразование функции множества аргументов в набор вложенных функций; ], [ отсутсвие циклов -- их заменили рекурсией; ], [ универсальная структура данных -- список; ], [ символьное дифференцирование; ], marker: "-", indent: text-indentation, ) Lisp позже повлияли на Python (list comprehensions), JavaScript (функции высшего порядка), языки типа Haskell, Scala и другие. Также этот язык является прародителем функционального программирования, хотя он и является мультипарадигменным. === Оптимизирующая компиляция Как уже было сказано ранее, компилторы стали применять систематические оптимизации. Ключевые достижения: #list( [ компилятор Fortran мог генерировать более эффективный ассемблерный код, по сравнению с человеком; ], [ анализ потока данных @hecht_flow_1977; ], [ алгоритмы оптимизации графов (например, "алгоритм Хайтина" для раскраски графов и последующего распределения регистров); ], marker: "-", indent: text-indentation, ) Для HPC оптимизирующие компиляторы стали критическим звеном между абстрактным кодом и эффективным использованием железа. Современные компиляторы (GCC, LLVM, Intel ICC) применяют сотни проходов оптимизации: векторизацию, предсказание переходов, межпроцедурную оптимизацию, полиэдрическую оптимизацию для вложенных циклов, однако база была заложена давно. #pagebreak() = Векторные- и суперкомпьютеры == Концепция векторных вычислений Векторные вычисления представляют собой фундаментальную ступень развития высокопроизводительных вычислительных систем, которая позволила достичь качественного скачка в производительности при решении задач, требующих обработки больших однородных массивов данных. === Суперкомпьютеры Cray и становление векторной обработки Компания Cray Research, основанная Сеймуром Креем в 1972 году, стала пионером в области векторных суперкомпьютеров. Первая машина серии Cray-1, представленная в 1976 году, продемонстрировала революционный подход к организации вычислений@noauthor__nodate. Архитектура системы была построена на принципе конвейерной обработки векторных данных, что позволяло выполнять одну операцию над множеством элементов данных одновременно. Главная память машины состояла из 16 независимых блоков по 64 тысячи машинных слов каждый, а 12 функциональных модулей позволяли выполнять логические, скалярные и векторные операции над этими данными с высокой степенью параллелизма. Ключевое преимущество векторной архитектуры заключалось в том, что одна векторная инструкция заменяла целый цикл скалярных операций. Это сокращало количество необходимых к исполнению команд и устраняло накладные расходы на управление циклами. Для научных и инженерных расчетов, где типичными являются операции с матрицами и массивами данных, такой подход обеспечивал многократное ускорение по сравнению со скалярными процессорами того времени. === Модель SIMD как основа векторизации Концептуальной основой векторных вычислений стала модель SIMD, сформулированная в таксономии Флинна@flynn_very_1966. В своей ранней реализации эта модель предполагала, что один поток команд управляет обработкой множества потоков данных параллельно. Векторный процессор фактически реализовывал данную парадигму аппаратно: единственная векторная инструкция запускала идентичную операцию над всеми элементами векторного регистра. Важно отметить, что ранняя форма SIMD в векторных суперкомпьютерах и современные векторные расширения процессоров существенно отличаются. Векторные машины работали с векторами переменной длины и обладали специализированными конвейерами для различных типов операций, а SIMD-расширения современных процессоров оперируют фиксированными по размеру векторами для исполнения этих операций ничего кроме самого процессора не требуется. === Векторные расширения в языке Fortran Параллельно с развитием аппаратных векторных систем происходила эволюция программных средств, позволяющих эффективно использовать возможности векторной обработки@chang_support_2004. Язык Fortran в стандарте Fortran 90 получил расширение в виде конструкции для работы с массивами как с едиными объектами, что позволяло программистам того времени выражать векторные операции в знакомой математической нотации. Компиляторы Fortran получили возможность автоматической векторизации циклов, распознавая паттерны кода, которые могли быть преобразованы в векторные инструкции@allen_automatic_1987. Это означало, что существующий код мог быть оптимизирован для векторных архитектур без полного переписывания, хотя наибольшая эффективность достигалась при использовании явных векторных конструкций языка. Появление директив компилятора также позволило программистам явно управлять процессом векторизации, указывая компилятору на возможности параллелизма в коде. Развитие векторных вычислений заложило основу для современных параллельных архитектур и продолжает оставаться актуальным в контексте современных процессоров с SIMD-расширениями и графических ускорителей, использующих сходные принципы обработки данных. == Новые компиляторные оптимизации Параллельно с развитием векторных архитектур происходила модернизация в технологиях компиляции, направленная на максимальное использование возможностей современного оборудования. Компиляторы превращались из простых трансляторов высокоуровневого кода в машинный в сложные системы оптимизации, способные радикально трансформировать программу для достижения максимальной производительности. Центральной задачей стала векторизация циклов. Компиляторы научились анализировать структуру циклов и определять, можно ли безопасно преобразовать последовательные итерации в единую векторную операцию. Для успешной векторизации цикла компилятору необходимо доказать, что между итерациями отсутствуют зависимости, так как в обратном случае применение векторных оперций приведёт к некорректным результатам вычисления. Неполное разворачивание циклов стало одной из ключевых техник оптимизации -- компилятор мог группировать несколько итераций цикла, что давало ему возможность переупорядочить инструкции и уже затем, возможно, заменить их векторными операциями. Цикл всё ещё присутствует, но он может быть значительно меньше оригинального варианта. Компиляторы, такие как для TI ASC и Cray Fortran, анализировали циклы на отсутствие зависимостей данных между итерациями, чтобы преобразовать последовательный код в векторные операции@morgan_compiling_2018. Это обеспечивало безопасность параллелизма без изменения результатов программы. Методы включали решение систем диофантовых уравнений для доказательства независимости. Программисты использовали директивы (например, Cray VECTOR directives@noauthor_vectorization_nodate) для подсказок компилятору о безопасной векторизации при неполном статическом анализе. Межпроцедурный анализ учитывал вызовы функций для глобальной векторизации, как в Convex Application Compiler. Эти нововведения легли в основу современных компиляторов. #pagebreak() = Эра массового параллелизма и распределённых систем == Переход к архитектуре MIMD Сдвиг в сторону MIMD@flynn_very_1966 означал отход от прежних парадигм векторной обработки и одиночных инструкций. Этот переход был обусловлен как технологической осуществимостью, так и признанием того факта, что многие реальные вычислительные задачи требовали гибкости в том, как различные элементы конвейера обработки могли работать независимо друг от друга. В этот период начали появляться многоядерные системы, хотя по сравнению с современными реализациями они были еще в зачаточном состоянии. Ранние кластеры собирались из стандартных рабочих станций, объединяемых сетевой инфраструктурой. Оказалось, что не обязательно нужны специализированные монолитные суперкомпьютеры для получения больших вычислительных мощностей. Первые такие кластерные системы заложили основу для современных архитектур центров обработки данных и инфраструктуры облачных вычислений. == Языки программирования и параллельные вычисления С расширением возможностей аппаратного обеспечения все более актуальной становилась задача выражения параллельных вычислений в языках программирования. Появилось несколько подходов, каждый из которых отражал различные философские представления о том, как программисты должны концептуализировать параллельное выполнение кода. === Occam и модель CSP Occam, разработанный в начале 1980-х годов группой учёных из Оксфордского университета, являлся реализацией модели CSP (Communicating Sequential Processes, Теория Взаимодействующих Последовательных Процессов) -- сформулированной Тони Хоаром в нескольких работах@hoare_communicating_1978. В Occam процессы считаются фундаментальными единицами, коммуницирующими друг с другом посредством "передачи сообщений", что предоставляет сильную теоретическую основу для рассуждений о параллельном поведении программ. Однако CSP, в разрезе параллельного программирования, повлияла не только на Occam, но и заложила концепции, применяемые в параллельном программировании по сей день: такие как синхронная передача сообщений через каналы и отсутствие общих переменных между процессами. Эти идеи активно применяются в современных языках, включая Go (горутины и каналы), Erlang, Limbo и `core.async` в Clojure. === High Performance Fortran (HPF) и его ограничения HPF стал попыткой расширения языка программирования Fortran путём добавления директив, позволяющих компиляторам автоматически разделять код для последующего его исполнения на распределённых системах. И хотя в проект были вложены немалые ресурсы, он столкнулся с проблемой чистой компиляторной оптимизации. Оказалось, что автоматически распределять векторные операции по несколькуим хостам -- не такая простая задача, что привело к ограниченному использованию HPF, по сравнению с другими, более "ручными" вариантами. === Message Passing Interface (MPI) В 1994 году появился стандарт MPI, который вместо попыток автоматической параллелизации или создания нового языка программирования предоставлял стандартизированную библиотеку обмена сообщениями между процессами. Такой подход, в противопоставление HPF, предоставлял разработчикам полный прямой контроль над межпроцессорным взаимодействием, оставляя, однако, сложные механизмы передачи сообщений "за кадром" разработки. Такой подход также упростил переносимость кода между платформами. В общем и целом, всё вышеобозначенное привело к большой популярности и всеобщему признанию MPI. === OpenMP OpenMP появился как дополнение к MPI -- в этой библиотеки реализовывался параллелизм через общую память с помощью директив для компилятора, вставляемых в код программы. Такой подход OpenMP снижал сложность внедрения многопоточного выполнения в рамках одной машины. При этом различия между моделью распределённых вычислений MPI и подходом OpenMP позволяло использовать их вместе для ещё большего ускорения вычислений. == Что считать "естественной" моделью параллелизма? Распространение подходов к параллельному программированию отражало более глубокие, неразрешённые вопросы о фундаментальной природе параллельных вычислений. Исследователи и практики вели -- и продолжают вести -- длительные дискуссии о том, следует ли выражать параллелизм в алгоритме или же стоит полагаться на аппаратно-ориентированные подходы. Сторонники алгоритмического подхода утверждают, что параллельные вычисления представляют собой принципиально иную логику, требующую явного выражения в моделях программирования. Полагается, что программист должен понимать и контролировать стратегии декомпозиции, обмен данными и требования к синхронизации. Эта точка зрения проявляется в системах, подобных MPI, где параллелизм выражается явно, в коде. Аппаратно-ориентированный подход стремится сделать параллелизм более неявным, перекладывая распараллеливание на "плечи" компилятора и среды исполнения. Эта философия лежала, например, в HPF. Сторонники такого подхода считают, что отделение алгоритмической корректности от производительности параллельного исполнения сделает параллельные вычисления более доступными для широкого круга разработчиков. Эти философские различия были не просто академическими спорами, но действительно отражали неопределённость относительно того, как будет развиваться параллельная вычислительная парадигма. Эти вопросы -- об уровнях абстракции, ответственности программиста и взаимосвязи между алгоритмами и архитектурой, -- продолжают находить отклик в современных обсуждениях проектирования параллельных и распределённых систем. #pagebreak() = Появление GPGPU и ломка парадигм == Исторические предпосылки К началу 2000-х годов графические процессоры превратились в высокоспециализированные устройства, предназначенные исключительно для обработки графики и визуализации. Их архитектура развивалась по траектории, радикально отличавшейся от центральных процессоров: вместо сложных систем предсказания ветвлений и многоуровневых кэшей GPU делали ставку на массовый параллелизм. Типичный графический процессор того времени содержал десятки -- затем и сотни -- простых вычислительных ядер, способных одновременно обрабатывать множество пикселей или вершин геометрии. Ключевой концепцией стала модель SIMT (Single Instruction, Multiple Threads), которая представляла собой эволюцию классической SIMD-архитектуры. Ключевым различием между SIMD и SIMT является "строгое исполнение" (lockstep execution) -- что означает выполнение одного и того же выражения (инструкции) всеми процессорами одновременно. Эта модель идеально подходила для графических задач, где миллионы пикселей требовали одинаковой обработки с разными входными данными. Исследовательское сообщество увидело потенциал использования GPU для неграфических вычислений еще в начале 2000-х. Первые работы демонстрировали, что с помощью графических API, такие как OpenGL и DirectX, можно производить произвольные вычислений через манипуляции с текстурами и шейдерными программами. Однако этот подход требовал глубокой переработки алгоритмов вычислительных задач, дабы их можно было "уложить" в графические примитивы, что создавало значительный концептуальный барьер для программистов. == Появление CUDA В ноябре 2006 года NVIDIA представила CUDA, которая изменила подход к программированию графических процессоров для неграфических нужд. CUDA обеспечила прямой доступ к вычислительным ресурсам GPU через расширение языка C, освободив разработчиков от необходимости работать через графические API. Появление CUDA обозначило переход от косвенного использования графического конвейера к явной вычислительной модели@nickolls_gpu_2010@garland_parallel_2008. Архитектура CUDA ввела иерархическую модель организации вычислений, которая отражала аппаратную структуру GPU. Программист организовывал вычисления в виде сетки блоков, где каждый блок содержал группу потоков, способных взаимодействовать через быструю разделяемую память@nickolls_scalable_2008. Варп (warp) -- группы из 32 потоков, выполняющихся синхронно на одном мультипроцессоре -- стал основной единицей выполнения. Эффективное программирование требовало понимания того, как варпы выполняют инструкции и как расхождение потоков ветвлений внутри варпа может снизить производительность. NVIDIA приняла решение создать закрытую экосистему вокруг CUDA. Технология работала исключительно на GPU их производства, в то же время компания инвестировала значительные ресурсы в развитие библиотек, инструментов разработки и образовательных программ. Этот подход отражал убеждение, что вертикальная интеграция аппаратного обеспечения и программного стека позволит достичь максимальной производительности и ускорить внедрение новых возможностей. NVIDIA развивала богатую экосистему специализированных библиотек, таких как cuBLAS для линейной алгебры и cuFFT для преобразований Фурье, которые обеспечивали оптимизированные реализации распространенных алгоритмов. Согласно исследованиям начала эры CUDA, производительность GPU для определенных классов задач могла превышать производительность центральных процессоров на порядки величины, что стимулировало стремительное принятие технологии в научных вычислениях и машинном обучении. == OpenCL как альтернативная модель В ответ на доминирование CUDA консорциум Khronos Group в 2008 году представил OpenCL (Open Computing Language) как открытый стандарт для гетерогенных параллельных вычислений@munshi_opencl_2009. OpenCL воплощал в себе переносимость и универсальность, позволяя одному и тому же коду выполняться на GPU различных производителей, центральных процессорах, и других ускорителях. Стандарт поддерживался коалицией компаний, включая AMD, Intel, Apple и даже NVIDIA. Архитектура OpenCL строилась вокруг абстрактной модели выполнения, которая не привязывалась к специфике конкретного оборудования. Программисты описывали вычисления в терминах "рабочих групп" и "рабочих элементов", которые затем выполнялись на аппаратном обеспечении драйвером и компилятором. Эта модель обеспечивала гибкость и создавала дополнительные уровни абстракции между программой и железом. Конкуренция между CUDA и OpenCL отражала противоречие в индустрии высокопроизводительных вычислений. CUDA предлагала более тесную интеграцию с аппаратным обеспечением NVIDIA, что часто приводило к превосходной производительности и более удобным инструментам разработки, а библиотеки CUDA были глубоко оптимизированы под конкретные архитектуры GPU -- поэтому компания могла быстро внедрять поддержку новых аппаратных функций. В то же время OpenCL обещала переносимость кода между различными платформами, что было критически важно для организаций, использующих гетерогенную инфраструктуру или стремящихся избежать привязки к одному поставщику. Практический опыт показал, что достижение сопоставимой производительности на OpenCL часто требовало куда больших усилий по оптимизации под каждую конкретную платформу, что частично нивелировало преимущества переносимости. Тем не менее OpenCL сохранила свою нишу в приложениях, где важна поддержка широкого спектра оборудования, таких как системы компьютерного зрения или графических приложениях, работающих на различных платформах. #pagebreak() = Развитие высокоуровневых моделей и DSL для HPC == "Наращивание" абстракций с TensorFlow, PyTorch, MLIR и TVM Взрывной рост машинного обучения и нейронных сетей ознаменовал трансформацию высокопроизводительных вычислений начиная с около 2010-го года. Традиционная модель разработки, требовавшая от программиста глубокого понимания аппаратной архитектуры и ручной оптимизации каждого аспекта параллельного кода, оказалась неподходящей для новой волны исследователей и инженеров в области ИИ. Это привело к созданию фреймворков, которые не просто упростили программирование, но кардинально изменили саму философию взаимодействия разработчика с вычислительными ресурсами. Фреймворк TensorFlow, представленный Google в 2015 году, стал одним из первых масштабных проектов, реализовавших концепцию вычислительного графа как высокоуровневой абстракции над гетерогенными вычислительными архитектурами. Система позволяла описывать алгоритмы машинного обучения в терминах операций над тензорами, автоматически распределяя вычисления между центральными процессорами и графическими ускорителями. Ключевым достижением TensorFlow стала реализация автоматического дифференцирования и оптимизация графа вычислений на этапе компиляции@abadi_tensorflow:_2016. Параллельно развивался PyTorch, выпущенный Facebook AI Research в 2016 году и предложивший альтернативную парадигму динамических вычислительных графов, которые строятся в процессе исполнения программы. Этот подход обеспечил большую гибкость для исследовательских задач и упростил отладку, сохранив при этом способность к эффективному выполнению на GPU через систему автоматической генерации CUDA-кернелов. Архитектура PyTorch продемонстрировала, что высокий уровень абстракции не обязательно требует жертв в производительности, если промежуточные представления спроектированы правильно@paszke_pytorch:_2019. Критической проблемой обоих фреймворков стала их привязанность к специфичным доменам и ограниченная переносимость оптимизаций между различными типами операций и аппаратных платформ. Ответом на эту проблему стало создание MLIR (Multi-Level Intermediate Representation), анонсированного командой Google в 2019 году. MLIR представил иерархическую систему промежуточных представлений, где каждый уровень соответствует определенному уровню абстракции от высокоуровневых операций машинного обучения до низкоуровневых инструкций целевой архитектуры. Фундаментальная идея MLIR заключалась в создании расширяемой инфраструктуры, позволяющей различным доменно-специфичным языкам и компиляторам совместно использовать общие оптимизационные проходы и трансформации@lattner_mlir:_2020. Проект TVM, начатый исследователями из университета Вашингтона в 2017 году, сфокусировался на автоматической оптимизации и генерации кода для разнородных аппаратных платформ. TVM ввел концепцию автоматического планирования через систему template-based code generation с последующим автоматическим тюнингом производительности. Система использует машинное обучение для предсказания оптимальных конфигураций кернелов на основе характеристик операций и целевого оборудования@chen_tvm:_2018. Роль промежуточных представлений в этой эволюции крайне велика -- IR стали не просто техническим артефактом процесса компиляции, но ключевым механизмом, позволяющим разделить спецификацию алгоритма от деталей его реализации на конкретной аппаратной платформе. Многоуровневая структура современных IR позволяет проводить оптимизации на различных уровнях абстракции: от алгебраических упрощений и операторного слияния на верхних уровнях до оптимизации использования памяти и векторизации на нижних. Это создало возможность для композиции оптимизаций, где высокоуровневые трансформации открывают возможности для низкоуровневых, и наоборот. Автоматическая генерация CUDA и OpenCL кернелов достигла уровня, когда для многих классов операций генерируемый код сравним или превосходит по производительности код, написанный вручную экспертами. Это стало возможным благодаря систематическому исследованию пространства оптимизаций, включающего выбор размера блоков, стратегии использования разделяемой памяти, развертывание циклов и другие параметры. Современные системы используют комбинацию аналитических моделей производительности и эмпирического профилирования для навигации в экспоненциально большом пространстве возможных конфигураций. == Эволюция "параллелизма по умолчанию" Концепция "параллелизма по умолчанию" претерпела существенную эволюцию от идеи явного параллельного программирования к модели, где параллелизм извлекается и управляется автоматически компиляционной инфраструктурой. Этот переход отражает фундаментальное изменение в распределении ответственности между программистом и инструментарием. Ранние попытки автоматического параллелизма -- которые обозревались мной ранее -- демонстрировали ограниченную эффективность из-за сложности анализа зависимостей и недостатка информации о намерениях программиста. Новая волна систем автотюнинга изменила подход: вместо попыток автоматически обнаружить параллелизм в последовательном коде, системы стали требовать от программиста явной спецификации параллельной структуры алгоритма на высоком уровне абстракции, беря на себя ответственность за выбор оптимальных параметров исполнения. Проект Halide, представленный MIT в 2012 году, стал пионером в области декларативного программирования для обработки изображений. Halide ввел разделение алгоритма и расписание его исполнения: программист описывает вычисление математически, а затем отдельно выбирает стратегию параллелизации, тайлинга и векторизации. Разработчики Halide поняли, что для многих классов алгоритмов существует множество функционально эквивалентных, но различающихся по производительности способов организации вычислений@ragan-kelley_halide:_2013. Системы автотюнинга, такие как OpenTuner@ansel_opentuner:_2014 и AutoTVM, развили эту идею дальше, применяя методы машинного обучения для автоматического исследования пространства возможных расписаний выполнения. Вместо того чтобы требовать от программиста вручную специфицировать оптимальное расписание, эти системы могут автоматически найти близкое к оптимальному решение через итеративное профилирование различных конфигураций. Этот подход особенно ценен при портировании кода на новые аппаратные платформы, где интуиция, разработанная для одной архитектуры, может не работать для другой. Современные системы также интегрируют адаптивные стратегии исполнения, где решения об оптимизации принимаются частично во время выполнения программы на основе фактических характеристик данных и текущей загрузки системы. Это особенно важно для гетерогенных систем, где оптимальное распределение работы между CPU и GPU может зависеть от размера входных данных и доступности вычислительных ресурсов в конкретный момент времени. == Изменение философии программирования Трансформация подхода к высокопроизводительному программированию в 2010-х годах отражает более глубокие изменения в философии разработки программного обеспечения. Традиционная модель HPC-программирования предполагала, что оптимальная производительность достигается только через глубокое понимание аппаратной архитектуры и тщательную ручную оптимизацию каждого аспекта кода. Программист был обязан управлять иерархией памяти, распределением работы между вычислительными блоками, векторизацией и множеством других деталей низкого уровня. Это демократизировало доступ к вычислительной мощности и ускорило инновации в прикладных областях. Новая парадигма переносит фокус программиста с оптимизации реализации на спецификацию модели и алгоритма. Программист определяет математическую структуру вычислений, а используемая инфраструктура берет на себя ответственность за оптимизацию исполнения. Это изменение не означает, что производительность стала менее важной, но что методы её достижения фундаментально изменились. Критическим фактором, делающим этот подход возможным, стало развитие компиляторов, способных эффективно транслировать высокоуровневые спецификации в оптимизированный машинный код. Современные компиляторы для машинного обучения используют комбинацию классических оптимизационных техник, эвристик, основанных на доменных знаниях, и методов машинного обучения для навигации в сложном пространстве возможных реализаций. Система Tiramisu, например, использует полиэдральную модель компиляции для автоматической оптимизации линейной алгебры и свёрточных операций@baghdadi_tiramisu:_2019 Однако важно признать, что этот переход не является абсолютным. Для критических по производительности приложений или при работе с новыми, еще не поддержанными стандартными фреймворками паттернами вычислений, знание низкоуровневых деталей остается необходимым. Более того, разработка самих компиляционных инфраструктур требует глубокого понимания как высокоуровневых абстракций, так и низкоуровневых деталей аппаратной реализации. Можно говорить о специализации ролей: большинство программистов работают на уровне высокоуровневых абстракций, в то время как относительно небольшая группа экспертов развивает инфраструктуру, делающую это возможным. Эволюция также изменила критерии оценки успешности программных решений. Если ранее основной метрикой было достижение максимальной производительности на конкретной аппаратной конфигурации, то современные системы оцениваются по способности обеспечить хорошую производительность на широком спектре архитектур с минимальными модификациями кода. "Портативность" производительности стала не менее важной, чем абсолютная производительность. Будущее развитие этого направления, вероятно, будет характеризоваться дальнейшим повышением уровня абстракции и интеграцией более продвинутых методов автоматической оптимизации, включая использование самих нейронных сетей для предсказания оптимальных стратегий компиляции. #pagebreak() = Смена парадигм: историко-философский анализ == Подход Куна: парадигмы и научные революции Концепция научных революций Томаса Куна, изложенная в его фундаментальной работе "Структура научных революций"@kuhn_structure_2009. Согласно Куну, научное знание развивается не линейно и кумулятивно, а через периоды "нормальной науки", прерываемые радикальными сменами парадигм. В контексте HPC подобная динамика проявляется с особенной отчетливостью: #list( [ переход на векторизацию в контексте высокопроизводительных вычислений; ], [ затем переход к массовому параллелизму; ], [ потом пришло поколение вычислений на GPGPU; ], [ приход DSL и автогенерации кода мы видим в настоящее время. ], marker: "-", indent: text-indentation, ) == Проблема "аппаратно-зависимого программирования" Фундаментальная проблема в истории HPC заключается в некоторой перпендикулярности взглядов, между стремлением к универсальности программных абстракций и неизбежной зависимостью от специфики аппаратного обеспечения. Этот конфликт имеет глубокие философские корни, восходящие к дихотомии между идеальным и материальным, между формой и содержанием. История демонстрирует циклический паттерн: каждая новая эпоха начинается с повышения уровня абстракции, но постепенно программисты вынуждены спускаться к более низким уровням для достижения приемлемой производительности. Переход от Fortran к MPI казался шагом назад в плане удобства программирования, но был необходим для эффективного использования распределенных систем. Аналогично, CUDA представляет собой относительно низкоуровневый интерфейс по сравнению с традиционными последовательными языками, но обеспечивает контроль, необходимый для эффективного использования GPU. Современные попытки создания универсальных моделей, такие как OpenCL и SYCL, стремятся найти баланс между портируемостью и производительностью. Однако практика показывает, что достижение производительности, сопоставимой с платформо-специфичными решениями, часто требует использования расширений и оптимизаций, специфичных для конкретного оборудования. Это подтверждает тезис о том, что физические ограничения аппаратного обеспечения фундаментально влияют на структуру эффективных вычислений. == Текущие дискуссии в научном сообществе Современный ландшафт высокопроизводительных вычислений характеризуется напряженными дебатами относительно будущего программных моделей и философии развития технологий. Центральная линия этих дискуссий проходит между проприетарными и открытыми стандартами, что отражает более широкий конфликт между коммерческими интересами и научными идеалами открытости и воспроизводимости. CUDA быстро стала де-факто стандартом для программирования GPU в научных вычислениях и машинном обучении. Однако проприетарная природа CUDA создает существенные проблемы для научного сообщества. Исследования, основанные на CUDA, привязаны к оборудованию NVIDIA, что ограничивает воспроизводимость и создает зависимость от единственного производителя. Кроме того, закрытость CUDA противоречит принципам открытой науки, где воспроизводимость и независимая верификация являются фундаментальными ценностями. OpenCL был задуман как универсальная альтернатива проприетарным решениям. Философия OpenCL воплощает идеалы открытости и портируемости: единый код может исполняться на процессорах различных производителей, включая CPU, GPU и FPGA. Однако на практике OpenCL столкнулся с серьезными трудностями -- различия в реализациях между производителями, отсутствие единой экосистемы инструментов и библиотек, а также часто более низкая производительность по сравнению с платформо-специфичными решениями ограничили его принятие. SYCL, разработанный Khronos Group и впервые выпущенный в 2014 году, представляет собой попытку модернизировать подход OpenCL через использование современных возможностей C++@reinders_data_2021. SYCL стремится соединить удобство высокоуровневого программирования с производительностью и портируемостью. Проекты, основанные на SYCL, -- например, oneAPI от Intel, -- представляют видение будущего, в котором единая кодовая база может эффективно исполняться на разнородных архитектурах. Однако остается открытым вопрос, сможет ли SYCL преодолеть фундаментальное противоречие между универсальностью и оптимальной производительностью. Дискуссия о будущем низкоуровневых языков программирования отражает более глубокий философский вопрос о природе вычислительной абстракции. С одной стороны, существует тенденция к повышению уровня абстракции, воплощенная в развитии DSL и автоматической генерации кода. Halide, TVM и подобные системы демонстрируют, что для многих классов приложений возможно автоматически генерировать высокоэффективный код из высокоуровневых спецификаций. С другой стороны, существуют веские аргументы в пользу сохранения значимости низкоуровневого программирования. Появление новых архитектур, таких как квантовые компьютеры, нейроморфные системы и специализированные ускорители для машинного обучения, требует глубокого понимания аппаратного уровня для эффективного использования этих систем. Современное состояние можно охарактеризовать как период плюрализма и экспериментирования. Научное сообщество признает, что различные подходы имеют свои области применимости, и вместо поиска единственного универсального решения наблюдается развитие экосистемы взаимодополняющих технологий. Этот плюрализм отражает зрелость области, признающей сложность и многообразие вычислительных задач, но также создает вызовы для обучения специалистов и поддержания кодовых баз. #pagebreak() = Связь исторической эволюции с современной научной задачей == Необходимость новых абстракций для гибридных CPU+GPU систем Современные гетерогенные вычислительные системы представляют собой качественно новую парадигму, требующую переосмысления традиционных подходов к программированию и оптимизации. В отличие от классических параллельных архитектур, где все процессорные элементы были однородными, гибридные CPU+GPU системы характеризуются радикальной асимметрией вычислительных ресурсов@mittal_survey_2015. Центральные процессоры оптимизированы для выполнения сложной логики управления, работы с нерегулярными структурами данных и минимизации задержек отдельных операций. Графические процессоры, напротив, достигают максимальной производительности на массово параллельных задачах с регулярными паттернами доступа к памяти и высокой арифметической интенсивностью@kirk_programming_2017. Проблема усугубляется сложностью иерархии памяти в гетерогенных системах. Традиционные модели программирования не предоставляют эффективных механизмов для управления перемещением данных между различными адресными пространствами CPU и GPU, что может приводить к значительным накладным расходам. Современные абстракции должны обеспечивать автоматическую оптимизацию передачи данных, учитывая как объемы копируемой информации, так и возможности перекрытия коммуникаций с вычислениями. Для задач машинного обучения и глубокого обучения, где инференс моделей требует выполнения последовательности разнородных операций, необходимы абстракции, способные автоматически распределять различные слои нейронных сетей между CPU и GPU в зависимости от их характеристик@jia_beyond_2018. Некоторые операции, такие как небольшие полносвязные слои или нормализация батчей малого размера, могут эффективнее выполняться на CPU, в то время как крупные свёрточные слои лучше ложатся на массивно параллельные ядра GPU. #pagebreak() #align( center, pad( left: -text-indentation, bottom: text-size, heading( numbering: none, "СПИСОК СОКРАЩЕНИЙ" ) ) ) ИИ -- искуственный интеллект HPC (High-Performance Computing) -- высокопроизводительные вычисления GPU (Graphics Processing Unit) -- графический процессор SIMD (Single Instruction, Multiple Data) -- вычислительный подход, когда одна инструкция исполняется над некоторым массивом данных в один проход MIMD (Multiple Instruction, Multiple Data) -- вычислительный подход, когда множество инструкций исполняется над множеством потоков данных CUDA (Compute Unified Device Architecture) -- программно-аппаратная архитектура параллельных вычислений, которая позволяет существенно увеличить вычислительную производительность благодаря использованию графических процессоров фирмы NVIDIA IR (Intermediate Representation) -- промежуточное представление высокоуровневого кода, который можно оптимизировать и компилировать в нативный код любой поддерживаемой компилятором платформы #pagebreak() #align( center, pad( left: -text-indentation, bottom: text-size, heading( numbering: none, "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ" ) ) ) #bibliography( "references.bib", title: none, style: "gost-r-7-0-5-2008-numeric-alphabetical.csl" )