From 3141b9839b834bde0ff6c3ab7a3d7866846ffb24 Mon Sep 17 00:00:00 2001 From: Arity-T Date: Wed, 14 Jan 2026 13:06:35 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=82=D1=87=D1=91=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- report/.gitignore | 4 + report/report.tex | 3619 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3623 insertions(+) create mode 100644 report/.gitignore create mode 100644 report/report.tex diff --git a/report/.gitignore b/report/.gitignore new file mode 100644 index 0000000..2d7fe8f --- /dev/null +++ b/report/.gitignore @@ -0,0 +1,4 @@ +* + +!.gitignore +!report.tex \ No newline at end of file diff --git a/report/report.tex b/report/report.tex new file mode 100644 index 0000000..dc763fe --- /dev/null +++ b/report/report.tex @@ -0,0 +1,3619 @@ +\documentclass[a4paper, final]{article} +\usepackage[14pt]{extsizes} +\usepackage[T2A]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage[russian]{babel} +\usepackage{amsmath} +\usepackage{subfig} +\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry} +\usepackage{ragged2e} +\usepackage{setspace} +\usepackage{float} +\usepackage{stackengine} +\usepackage{indentfirst} +\usepackage{moreverb} +\renewcommand\verbatimtabsize{4\relax} +\renewcommand\listingoffset{0.2em} +\renewcommand{\arraystretch}{1.4} +\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} +\usepackage{listingsutf8} +\usepackage{xcolor} +\usepackage{listings} +\usepackage{hyperref} +\usepackage{enumitem} +\usepackage{verbatim} +\usepackage{tikz} +\usepackage{amssymb} +\usepackage{pdflscape} %для pdf +\usepackage{pdfpages} %для pdf +\usepackage{graphicx} +\usepackage{tabularx} +\usepackage{multirow} +\usepackage{cmap} +\usepackage{longtable} +\usepackage{booktabs} + +\definecolor{apricot}{HTML}{FFF0DA} +\definecolor{mygreen}{rgb}{0,0.6,0} +\definecolor{string}{HTML}{B40000} % цвет строк в коде +\definecolor{comment}{HTML}{008000} % цвет комментариев в коде +\definecolor{keyword}{HTML}{1A00FF} % цвет ключевых слов в коде +\definecolor{morecomment}{HTML}{8000FF} % цвет include и других элементов в коде +\definecolor{captiontext}{HTML}{FFFFFF} % цвет текста заголовка в коде +\definecolor{captionbk}{HTML}{999999} % цвет фона заголовка в коде +\definecolor{bk}{HTML}{FFFFFF} % цвет фона в коде +\definecolor{frame}{HTML}{999999} % цвет рамки в коде +\definecolor{brackets}{HTML}{B40000} % цвет скобок в коде + +\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях + +\hypersetup{colorlinks, + allcolors=[RGB]{061 087 255}} + + + +\textheight=24cm +\textwidth=16cm +\oddsidemargin=0pt +\topmargin=-1.5cm +\parindent=24pt +\parskip=0pt +\tolerance=2000 +\flushbottom + + +\lstset{ + language=Java, % Язык кода по умолчанию + morekeywords={*,...}, % если хотите добавить ключевые слова, то добавляйте + % Цвета + keywordstyle=\color{keyword}\ttfamily\bfseries, + %stringstyle=\color{string}\ttfamily, + stringstyle=\ttfamily\color{red!50!brown}, + commentstyle=\color{comment}\ttfamily, + morecomment=[l][\color{morecomment}]{\#}, + % Настройки отображения + breaklines=true, % Перенос длинных строк + basicstyle=\ttfamily\footnotesize, % Шрифт для отображения кода + backgroundcolor=\color{bk}, % Цвет фона кода + frame=single,xleftmargin=\fboxsep,xrightmargin=-\fboxsep, % Рамка, подогнанная к заголовку + rulecolor=\color{frame}, % Цвет рамки + tabsize=3, % Размер табуляции в пробелах + % Настройка отображения номеров строк. Если не нужно, то удалите весь блок + numbers=left, % Слева отображаются номера строк + stepnumber=1, % Каждую строку нумеровать + numbersep=5pt, % Отступ от кода + numberstyle=\small\color{black}, % Стиль написания номеров строк + % Для отображения русского языка + extendedchars=true, + showstringspaces=false, % не показывать пробелы в виде подчёркиваний + keepspaces=true, % сохранять исходное количество пробелов + columns=fullflexible, + literate={\\}{{\textbackslash}}1 + {Ö}{{\"O}}1 + {+}{{\texttt{+}}}1 + {-}{{\texttt{-}}}1 + {*}{{\texttt{*}}}1 + {/}{{\texttt{/}}}1 + {|}{{\texttt{|}}}1 + {(}{{\texttt{(}}}1 + {)}{{\texttt{)}}}1 + {[}{{[}}1 + {]}{{]}}1 + {^}{{\texttt{\^}}}1 + {\$}{{\texttt{\$}}}1 + {~}{{\textasciitilde}}1 + {а}{{\selectfont\char224}}1 + {б}{{\selectfont\char225}}1 + {в}{{\selectfont\char226}}1 + {г}{{\selectfont\char227}}1 + {д}{{\selectfont\char228}}1 + {е}{{\selectfont\char229}}1 + {ё}{{\"e}}1 + {ж}{{\selectfont\char230}}1 + {з}{{\selectfont\char231}}1 + {и}{{\selectfont\char232}}1 + {й}{{\selectfont\char233}}1 + {к}{{\selectfont\char234}}1 + {л}{{\selectfont\char235}}1 + {м}{{\selectfont\char236}}1 + {н}{{\selectfont\char237}}1 + {о}{{\selectfont\char238}}1 + {п}{{\selectfont\char239}}1 + {р}{{\selectfont\char240}}1 + {с}{{\selectfont\char241}}1 + {т}{{\selectfont\char242}}1 + {у}{{\selectfont\char243}}1 + {ф}{{\selectfont\char244}}1 + {х}{{\selectfont\char245}}1 + {ц}{{\selectfont\char246}}1 + {ч}{{\selectfont\char247}}1 + {ш}{{\selectfont\char248}}1 + {щ}{{\selectfont\char249}}1 + {ъ}{{\selectfont\char250}}1 + {ы}{{\selectfont\char251}}1 + {ь}{{\selectfont\char252}}1 + {э}{{\selectfont\char253}}1 + {ю}{{\selectfont\char254}}1 + {я}{{\selectfont\char255}}1 + {А}{{\selectfont\char192}}1 + {Б}{{\selectfont\char193}}1 + {В}{{\selectfont\char194}}1 + {Г}{{\selectfont\char195}}1 + {Д}{{\selectfont\char196}}1 + {Е}{{\selectfont\char197}}1 + {Ё}{{\"E}}1 + {Ж}{{\selectfont\char198}}1 + {З}{{\selectfont\char199}}1 + {И}{{\selectfont\char200}}1 + {Й}{{\selectfont\char201}}1 + {К}{{\selectfont\char202}}1 + {Л}{{\selectfont\char203}}1 + {М}{{\selectfont\char204}}1 + {Н}{{\selectfont\char205}}1 + {О}{{\selectfont\char206}}1 + {П}{{\selectfont\char207}}1 + {Р}{{\selectfont\char208}}1 + {С}{{\selectfont\char209}}1 + {Т}{{\selectfont\char210}}1 + {У}{{\selectfont\char211}}1 + {Ф}{{\selectfont\char212}}1 + {Х}{{\selectfont\char213}}1 + {Ц}{{\selectfont\char214}}1 + {Ч}{{\selectfont\char215}}1 + {Ш}{{\selectfont\char216}}1 + {Щ}{{\selectfont\char217}}1 + {Ъ}{{\selectfont\char218}}1 + {Ы}{{\selectfont\char219}}1 + {Ь}{{\selectfont\char220}}1 + {Э}{{\selectfont\char221}}1 + {Ю}{{\selectfont\char222}}1 + {Я}{{\selectfont\char223}}1 + {\#}{{\texttt{\#}}}1 + {\{}{{{\color{brackets}\{}}}1 % Цвет скобок { + {\}}{{{\color{brackets}\}}}}1 % Цвет скобок } +} + + +\begin{document} + +\newcommand{\smalltexttt}[1]{\texttt{\small #1}} + +\begin{center} +\hfill \break +\hfill \break +\normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\ + федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]} +\normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt] +\normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt] +\normalsize{Направление: 02.03.01 Математика и компьютерные науки}\\ + +\hfill \break +\hfill \break +\hfill \break +\large{<<Архитектура суперкомпьютерных систем>>}\\ +\large{Отчет по выполению лабораторной работы}\\ +\large{Вариант 18}\\ +\hfill \break +\vspace{2cm} + + +\end{center} + +\small{ +\begin{tabular}{lrrl} +\!\!\!Студент, & \hspace{2cm} & & \\ +\!\!\!группы 5130201/20101 & \hspace{2cm} & \underline{\hspace{3cm}} & Тищенко А. А. \\\\ +\!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Чуватов М. В.\\\\ +&&\hspace{5cm} +\end{tabular} +\begin{flushright} +<<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2026г. +\end{flushright} +} + + +\hfill \break +\begin{center} \small{Санкт-Петербург, 2026} \end{center} +\thispagestyle{empty} + +\begin{center} +\section*{РЕФЕРАТ} +\end{center} +% 79 с., 1 кн., 2 рис., 5 табл., 15 источн., 3 прил. + + +ГЕТЕРОГЕННЫЕ ВЫЧИСЛИТЕЛЬНЫЕ СИСТЕМЫ, ПАРАЛЛЕЛЬНОЕ ВЫЧИСЛЕНИЕ, MPI, GPU-ВЫЧИСЛЕНИЯ, OPENMPI, HYPER-V, CUDA, ВРЕМЕННЫЕ РЯДЫ, АГРЕГАЦИЯ ДАННЫХ. + + +Объектом исследования в текущей работе является гетерогенный вычислительный кластер, использующий +ресурсы GPU и CPU вычислителей. Так как ресурсы физических суперкомпьютерных систем зачастую не доступны, кластер такого рода самостоятельно создается с использованием доступных вычислительных ресурсов. +Целью работы является создание, настройка и тестирование высокопроизводительного вычислительного кластера, способного эффективно выполнять задачи параллельных вычислений с использованием разнородных аппаратных ресурсов. + + +В разработанной системе воссоздается окружение суперкомпьютерного вычислителя, использующего разные узлы (виртуальные машины) и конфигурацию slurm. + + +На базе реализованного кластера разработано параллельное приложение, использующее технологии CUDA и OpenMPI, выполняющее анализ временных рядов исторических данных о стоимости Bitcoin с целью выявления интервалов значительного изменения цены на основе агрегированных дневных статистик. + +\newpage + +\begin{center} +\tableofcontents +\end{center} + +\newpage + +\begin{center} +\section*{ТЕРМИНЫ И ОПРЕДЕЛЕНИЯ} +\end{center} +\addcontentsline{toc}{section}{ТЕРМИНЫ И ОПРЕДЕЛЕНИЯ} +\begin{enumerate} + \item \textbf{Гетерогенные вычислительные системы} — электронные системы, использующие различные типы вычислительных блоков. Они позволяют эффективно решать задачи за счёт использования компонентов с различными архитектурами. + \item \textbf{GPU (Graphics Processing Unit)} — графический процессор, предназначенный для параллельной обработки данных, особенно эффективен для вычислений с высокой степенью параллелизма. + \item \textbf{CPU (Central Processing Unit)} — центральный процессор общего назначения, оптимизированный для последовательных вычислений. + \item \textbf{Hyper-V} — технология виртуализации от Microsoft, позволяющая создавать и управлять виртуальными машинами на Windows. + \item \textbf{NFS (Network File System)} — протокол сетевой файловой системы, позволяющий разделять данные между узлами кластера. + \item \textbf{MPI (Message Passing Interface)} — стандарт взаимодействия между процессами в параллельных вычислительных системах. + \item \textbf{Slurm} — менеджер ресурсов и планировщик задач для кластерных систем. + \item \textbf{Контейнеризация} — технология виртуализации, которая позволяет изолировать программное обеспечение в контейнерах для повышения переносимости и устранения конфликтов версий. + \item \textbf{CUDA (Compute Unified Device Architecture)} — программная платформа от NVIDIA для разработки параллельных приложений на графических процессорах. + \item \textbf{OpenMPI} — высокопроизводительная реализация стандарта MPI, обеспечивающая взаимодействие между процессами в распределённых системах. + \item \textbf{MUNGE} — инструмент аутентификации, используемый для обеспечения безопасности в вычислительных кластерах. + \item \textbf{Rank} — идентификатор процесса в системе MPI, используемый для определения роли процесса. + \item \textbf{MPI\_Send, MPI\_Recv} — функции MPI для отправки и получения данных между процессами. +\end{enumerate} + +\newpage +\begin{center} +\section*{ПЕРЕЧЕНЬ СОКРАЩЕНИЙ И ОБОЗНАЧЕНИЙ} +\end{center} +\addcontentsline{toc}{section}{ПЕРЕЧЕНЬ СОКРАЩЕНИЙ И ОБОЗНАЧЕНИЙ} + +\begin{enumerate} + \item \textbf{GPU} — Graphics Processing Unit (графический процессор). + \item \textbf{CPU} — Central Processing Unit (центральный процессор). + \item \textbf{NFS} — Network File System (сетевая файловая система). + \item \textbf{MPI} — Message Passing Interface (интерфейс передачи сообщений). + \item \textbf{Slurm} — Simple Linux Utility for Resource Management (утилита управления ресурсами для Linux). + \item \textbf{CUDA} — Compute Unified Device Architecture (единая архитектура вычислений от NVIDIA). + \item \textbf{OpenMPI} — Open Message Passing Interface (реализация интерфейса передачи сообщений). + \item \textbf{MUNGE} — MUNGE Uid 'N' Gid Emporium (инструмент аутентификации для кластеров). + \item \textbf{HPC} — High-Performance Computing (высокопроизводительные вычисления). + \item \textbf{SSH} — Secure Shell (безопасная оболочка). + \item \textbf{RAM} — Random Access Memory (оперативная память). + \item \textbf{TCP/IP} — Transmission Control Protocol/Internet Protocol (протокол управления передачей/интернет-протокол). + \item \textbf{OS} — Operating System (операционная система). +\end{enumerate} + +\newpage +\begin{center} +\section*{ВВЕДЕНИЕ} +\end{center} +\addcontentsline{toc}{section}{ВВЕДЕНИЕ} + +Использование графических ускорителей (GPU) наряду с центральными процессорами (CPU) является распространенной практикой в области параллельных вычислений на гетерогенных платформах. Гетерогенные вычислительные системы объединяют различные типы вычислительных блоков, что позволяет эффективно решать задачи, выбирая оптимальный вычислительный ресурс для каждого этапа обработки данных. + +GPU и CPU имеют различную архитектуру и изначально проектировались для решения разных классов задач. GPU обладает большим количеством простых вычислительных ядер, оптимизированных для массово-параллельных операций, в то время как CPU имеет меньше ядер, но с более сложной логикой и лучшей производительностью на одно ядро. Совместное использование GPU и CPU осложняется рядом особенностей: они имеют раздельную память, различные адресные пространства, и для передачи данных требуется явное копирование через системные вызовы. Однако при правильном распределении задач и росте объёма данных использование GPU может значительно ускорить вычисления. + +Обеспечение взаимодействия между узлами вычислительного кластера осуществляется с помощью Message Passing Interface (MPI) — стандарта передачи сообщений между процессами. Основными реализациями являются Open MPI и MPICH, которые позволяют процессам синхронизироваться и обмениваться данными. + +Для GPU-вычислений используются специализированные технологии, такие как CUDA (для устройств NVIDIA), ROCm (для устройств AMD) и OpenCL (кроссплатформенный стандарт). В данной работе используется CUDA Toolkit и библиотека CUB для эффективной параллельной обработки данных на GPU. + +В качестве операционной системы в вычислительных кластерах традиционно используются Linux-дистрибутивы благодаря их производительности, открытости исходного кода и широкой поддержке необходимых технологий. В рамках текущей работы используется Ubuntu Server на виртуальных машинах. + +Доступ к физическим суперкомпьютерным системам не всегда возможен, поэтому для разработки, тестирования и отладки параллельных приложений целесообразно создание собственного виртуального кластера. В данной работе используется технология виртуализации Hyper-V, разработанная Microsoft, которая позволяет создавать и управлять виртуальными машинами с возможностью проброса GPU-ресурсов. \cite{hyperv} + +Задача анализа временных рядов больших объёмов данных является хорошим примером для демонстрации эффективности параллельных вычислений на гетерогенных системах. Обработка исторических данных о стоимости криптовалюты Bitcoin включает операции чтения больших файлов, агрегации данных по временным интервалам и поиска паттернов изменения цены — задачи, которые естественным образом поддаются распараллеливанию между узлами кластера и ускорению на GPU. + +Целью данной работы является создание виртуального гетерогенного вычислительного кластера и разработка параллельного приложения для анализа временных рядов с эффективным использованием ресурсов как CPU, так и GPU узлов. + + +\newpage +\begin{center} +\section*{ПОСТАНОВКА ЗАДАЧИ} +\end{center} +\addcontentsline{toc}{section}{ПОСТАНОВКА ЗАДАЧИ} + +В рамках лабораторных работ необходимо выполнить следующие задачи: +\begin{enumerate} + \item Создание виртуальных машин с разнородными типами вычислительных ресурсов: + \begin{itemize} + \item CPU-узлы; + \item GPU-узлы. + \end{itemize} + \item Настройка сети для связи хост-системы и виртуальных узлов; + \item Решение задачи по выбранному варианту: + \begin{itemize} + \item Необходимо разработать параллельное приложение, задействующее вычислительные +ресурсы CPU-узлов и CUDA-узлов, используя механизм OpenMPI, выполняющее на +предоставленном наборе данных следующие действия. Задача разбита на 2 этапа, которые необходимо выполнить, используя разнородный тип вычислительных ресурсов; + \item Этап 1. Агрегация данных: для временного ряда исторических данных о стоимости Bitcoin (исходные данные содержат информацию по каждым 10 секундам) необходимо выполнить группировку по дням и для каждого дня вычислить среднюю цену как математическое ожидание значений Low и High, а также минимальные и максимальные значения Open и Close. + \item Этап 2. Поиск интервалов изменения цены: на основе дневных агрегированных данных необходимо выявить интервалы дат (начиная с начальной даты в наборе данных), в которых средняя дневная цена изменилась не менее чем на 10\% относительно начала интервала. Для каждого интервала необходимо вывести начальную и конечную даты, а также минимальные и максимальные значения Open и Close за все дни внутри интервала. + \end{itemize} +\end{enumerate} + +Файл с исходными данными доступен в свободном доступе в сети интернет\cite{kaggle}. + +\newpage +\begin{center} +\section{ОСНОВНАЯ ЧАСТЬ РАБОТЫ} +\end{center} + +\subsection{Создание виртуального кластера} +В рамках лабораторной работы необходимо создать виртуальный вычислительный кластер, использующий разнородный вид вычислителей: GPU и CPU. В рамках лабораторной работы необходимо создать виртуальные машины используя нативный механизм аппаратной виртуализации Windows: Hyper-V. + +В качестве общей конфигурации виртуальной машины выбраны следующие параметры: +\begin{itemize} + \item Ubuntu Server 22.04.05 LTS; + \item выделенное ОЗУ: 4096 MB; + \item число виртуальных процессоров: 2; + \item имя виртуальной машины: "tishcpuX", "tishgpuX" (X - номер узла). +\end{itemize} + +\subsubsection*{Создание виртуальной машины} +Для создания виртуальной машины (узла) для будущего кластера, необходимо выполнить следующие шаги: +\begin{enumerate} + \item Открыть "Диспетчер Hyper-V". + \item Открыть меню для сервера (В текущей работе сервер: XSPMAIN). + \item Выбрать "Создать" $\rightarrow$ "Виртуальная машина...". Для мастера создания виртуальной машины выбрать следующие параметры: + \begin{enumerate} + \item "Укажите имя и местанохождение" + \begin{itemize} + \item Имя: \smalltexttt{"tishcpu1"}; + \item Сохранить виртуальную машину: \smalltexttt{V:$\backslash$ Virtual machines}. + \end{itemize} + \item "Укажите поколение" + \begin{itemize} + \item Выбрать \smalltexttt{"Поколение 2"} (Далее этот параметр поменять невозможно). + \end{itemize} + \item "Выделить память": + \begin{itemize} + \item "Память, выделяемое при запуске": \smalltexttt{4096 МБ}; + \item "Использовать для этой виртуальной машины динамическую память": \smalltexttt{Ставим галочку}. + \end{itemize} + \item "Настройка сети": пока данный раздел просто пропускается. + \item "Подключить виртуальный жесткий диск": + \begin{itemize} + \item "Имя": tishcpu1.vhdx; + \item "Расположение": \smalltexttt{V:$\backslash$Virtual machines$\backslash$Virtual Hard Disks$\backslash$}; + \item "Размер": \smalltexttt{100 ГБ}. + \end{itemize} + \item "Параметры установки": + \begin{itemize} + \item "Установить операционную систему из загрузочного образа": выбираем путь к \smalltexttt{*.iso} файлу образа ОС. + \end{itemize} + \end{enumerate} +\end{enumerate} + +После создания виртуальной машины необходимо настроить параметры: +\begin{enumerate} + \item В списке виртуальных машин в контекстном меню выбрать "Параметры...". + \item Далее необходимо настроить следующие параметры: + \begin{itemize} + \item "Процессор"$\rightarrow$"Число виртуальных процессоров": \smalltexttt{2}; + \item "Безопасность"$\rightarrow$"Включить безопасную загрузку": \smalltexttt{Не ставим галочку}; + \item "SCSI-контроллер": DVD-дисковод; + \item "Сетевой адаптер"$\rightarrow$"Виртуальный коммутатор": \smalltexttt{Default Switch}; + \item "Автоматическое действие при запуске": \smalltexttt{Ничего}; + \item "Память"$\rightarrow$"Включить динамическую память": \smalltexttt{Не ставим галочку}. + \end{itemize} + \item Нажать "Применить"$\rightarrow$"Ок". +\end{enumerate} + + +Для установки ОС необходимо запустить созданную ранее виртуальную машину. Для этого необходимо через консоль диспетчера Hyper-V и запустить ее: +\subsubsection*{Установка ОС} +При установке ОС выполняются следующие шаги: +\begin{enumerate} + \item Выбор языка: \smalltexttt{"English"}. + \item Для раскладки выбираем предлагаемую английскую раскладку. + \item Тип установки: \smalltexttt{"Ubuntu Server"}. + \item Настройки сети пока не трогаем. + \item Настройки пространства памяти: + \begin{enumerate} + \item Выбираем опцию: \smalltexttt{"Custom Storage Layout"}. + \item Для нашего свободного пространства: заводим swap и основное пространство памяти. (Размеры 50 и 4 ГБ) + \end{enumerate} + \item Настраиваем параметры профиля системы: + \begin{itemize} + \item "Your name": имя (например: \smalltexttt{Arity}); + \item "Your server's name": \smalltexttt{tishcpu1} (как у виртуальной машины); + \item "Pick a username": \smalltexttt{arity}; + \item Пароль выбирается на свое усмотрение. + \end{itemize} +\end{enumerate} + +\subsubsection*{Создание пользователя root} +При создании виртуальной машины, не был сконфигурирован пользователь root. Для его создания необходимо выполнить следующие команды: + + + + +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +aritytishcpu1:~$ sudo su - +root@tishcpu1:~# passwd +# Конфигурация пароля для пользователя root +\end{lstlisting} + +\subsection{Конфигурация пакетов} +В рамках лабораторной работы необходимо установить следующие пакеты: +\begin{itemize} + \item libopenmpi3 - пакет для библиотеки Open MPI; + \item slurmd - пакет для управляющего задачами демона; + \item openssh-client - клиент OpenSSH; + \item openssh-server - сервер OpenSSH. +\end{itemize} +Для этого необходимо выполнить следующие команды: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +aritytishcpu1:~$ sudo apt update +aritytishcpu1:~$ sudo apt upgrade +aritytishcpu1:~$ sudo apt install libopenmpi3 +aritytishcpu1:~$ sudo apt install slurmd +aritytishcpu1:~$ sudo apt install openssh-client +aritytishcpu1:~$ sudo apt install openssh-server +aritytishcpu1:~$ sudo apt clean +\end{lstlisting} + +\cite{ubuntuDocs} +\begin{enumerate} + \item \smalltexttt{apt update} - обновляет локальный индекс пакетов в системе, скачивая актуальную информацию о доступных пакетах из репозиториев, указанных в файлах \smalltexttt{/etc/apt/sources.list} и \smalltexttt{/etc/apt/sources.list.d/}. Это нужно для того, чтобы ОС знала о новых версиях пакетов и их зависимостях. + + \item \smalltexttt{apt upgrade} - обновляет установленные пакеты в системе. Предварительно рекомендуется обновить локальные индексы пакетов в системе. + + \item \smalltexttt{apt install } - устанавливает в системе пакет с именем \smalltexttt{}. + + \item \smalltexttt{apt clean} - удаляет все загруженные архивы пакетов из кеша APT, освобождая место на диске. +\end{enumerate} + +\subsubsection*{Настройка сети на виртуальной машине} +Для настройки сети на виртуальных машинах необходимо настроить следующие файлы: +\begin{enumerate} + \item \smalltexttt{/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg} - отключение управления сетевыми настройками через cloud-init, чтобы использовать другие способы конфигурации сети на системе. + \item \smalltexttt{/etc/netplan/50-cloud-init.yaml} - используется для конфигурации сетевых настроек в системе через cloud-init, и обычно генерируется автоматически в облачных окружениях для настройки сети при запуске. Однако при настройке файла \smalltexttt{.../99-disable-network-config.cfg} можно самостоятельно настроить сетевые параметры так, чтобы система не генерировала файл автоматически. +\end{enumerate} +Конфигурация netplan: + +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +root@tishgpu1:/home/arity# cat /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg +network: {config: disabled} +\end{lstlisting} + + +Содержимое файла {\small \smalltexttt{/etc/netplan/50-cloud-init.yaml}}: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +aritytishcpu1:~$ sudo vim /etc/netplan/50-cloud-init.yaml +# This file is generated from information provided by the datasource. Changes +# to it will not persist across an instance reboot. To disable cloud-init's +# network configuration capabilities, write a file +# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: +# network: {config: disabled} +network: + ethernets: + eth0: + addresses: + - 10.200.166.125/24 + nameservers: + addresses: + - 8.8.8.8 + search: + - tish + routes: + - to: default + via: 10.200.166.254 + version: 2 +\end{lstlisting} +\begin{enumerate} + \item Интерфейс \smalltexttt{eth0}: + \begin{itemize} + \item \smalltexttt{addresses: [10.200.166.125/24]}: Интерфейсу \smalltexttt{eth0} присваивается IP-адрес \smalltexttt{10.200.166.125} с маской подсети \smalltexttt{255.255.255.0} (CIDR \smalltexttt{/24}). + \item \smalltexttt{routes:} + \begin{itemize} + \item Указывает маршрут по умолчанию (default route), который направляет весь остальной трафик через шлюз \smalltexttt{10.200.166.254}. + \end{itemize} + \item \smalltexttt{nameservers:} + \begin{itemize} + \item Настраиваются DNS-серверы: \smalltexttt{8.8.8.8} (это публичные DNS от Google); + \item Указываем \smalltexttt{tish} для параметра search. + \end{itemize} +\end{itemize} + \item Версия netplan: + \begin{itemize} + \item \smalltexttt{version: 2}: Указывает, что используется вторая версия формата Netplan. +\end{itemize} +\end{enumerate} + + +\subsection{Конфигурация сети} +Для взаимодействия с виртуальными машинами с помощью хост-узла, необходимо пробросить порты на настроенные ранее адреса. Для этого необходимо воспользоваться следующими PowerShell Cmdlet'ами: +\begin{enumerate} + \item New-VMSwitch. + \item New-NetIPAddress. + \item New-NetNat. + \item Add-NetNatStaticMapping. +\end{enumerate} +\subsubsection*{New-VMSwitch} +\smalltexttt{New-VMSwitch} - Создает новый виртуальный сетевой адаптер для виртуальных машин. + +Принимаемые параметры: +\begin{itemize} + \item \smalltexttt{SwitchName} - алиас для \smalltexttt{Name}. Уточняет имя виртуального сетевого адаптера. Обязательный параметр; + \smalltexttt{SwitchType} - Уточняет тип создаваемого адаптера. Доступные значения для типа коммутатора — \smalltexttt{Internal} (внутренний) и \smalltexttt{Private} (частный). Чтобы создать \smalltexttt{External} (внешний) виртуальный коммутатор, нужно указать либо параметр \smalltexttt{NetAdapterInterfaceDescription}, либо \smalltexttt{NetAdapterName}, что автоматически установит тип коммутатора как \smalltexttt{External}. + \smalltexttt{Internal} и \smalltexttt{Private} — это типы сетевых адаптеров, которые могут быть использованы для настройки виртуальных машин в Hyper-V: + \begin{itemize} + \item \smalltexttt{Internal} - позволяет виртуальной машине обмениваться данными с хостовой машиной и другими виртуальными машинами на том же хосте; + \item \smalltexttt{External} - позволяет виртуальной машине общаться только с другими виртуальными машинами, но не имеет доступа к хосту или внешней сети; + \end{itemize} + \cite{newVmSwitch} +\end{itemize} + +Создание виртуального сетевого адаптера в PowerShell: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +PS C:\Windows\system32> New-VMSwitch -SwitchName "TishNet" -SwitchType Internal +\end{lstlisting} + +\subsubsection*{New-NetIPAddress} +\smalltexttt{New-NetIPAddress} - Создает новый IP-адрес и привязывает его к указанному сетевому интерфейсу. + +Принимаемые параметры: +\begin{itemize} + \item \smalltexttt{InterfaceAlias} - Указывает имя сетевого интерфейса, к которому будет привязан новый IP-адрес. Обязательный параметр; + \item \smalltexttt{IPAddress} - Указывает IP-адрес, который нужно настроить. Обязательный параметр; + \item \smalltexttt{PrefixLength} - Указывает длину префикса подсети для IP-адреса. Например, для маски подсети \smalltexttt{255.255.255.0} длина префикса равна \smalltexttt{24}. Обязательный параметр; + \item \smalltexttt{DefaultGateway} - Указывает адрес шлюза по умолчанию, который будет использоваться для указанного IP-адреса. Необязательный параметр. +\end{itemize} + +Пример настройки нового IP-адреса в PowerShell: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +PS C:\Windows\system32> New-NetIPAddress -InterfaceAlias "vEthernet (TishNet)" -IPAddress 10.200.166.254 -PrefixLength 24 +\end{lstlisting} + +Параметры: +\begin{itemize} + \item \smalltexttt{InterfaceAlias} - Имя интерфейса, например, \smalltexttt{"vEthernet (TishNet)"}, к которому привязывается IP-адрес; + \item \smalltexttt{IPAddress} - Указанный IP-адрес (например, \smalltexttt{10.200.166.254}); + \item \smalltexttt{PrefixLength} - Длина префикса подсети, например, \smalltexttt{24} для \smalltexttt{255.255.255.0}; + \item \smalltexttt{DefaultGateway} - Адрес шлюза по умолчанию (опционально). +\end{itemize} + +\cite{newNetIpAddress} + +Для проверки что IP-адресс корректно сконфигурирован можно воспользоваться команд-летом: {\small \smalltexttt{Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias "vEthernet (t1)"}} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +PS C:\Windows\system32> Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias "vEthernet (TishNet)" +IPAddress : 10.200.166.254 +InterfaceIndex : 10 +InterfaceAlias : vEthernet (TishNet) +AddressFamily : IPv4 +Type : Unicast +PrefixLength : 24 +PrefixOrigin : Manual +SuffixOrigin : Manual +AddressState : Preferred +ValidLifetime : +PreferredLifetime : +SkipAsSource : False +PolicyStore : ActiveStore +\end{lstlisting} + +% \iffalse +% \begin{comment} + + +% IPAddress : 172.27.144.1 +% InterfaceIndex : 18 +% InterfaceAlias : vEthernet (Default Switch) +% AddressFamily : IPv4 +% Type : Unicast +% PrefixLength : 20 +% PrefixOrigin : Manual +% SuffixOrigin : Manual +% AddressState : Preferred +% ValidLifetime : +% PreferredLifetime : +% SkipAsSource : False +% PolicyStore : ActiveStore + +% IPAddress : 192.168.0.166 +% InterfaceIndex : 16 +% InterfaceAlias : Ethernet +% AddressFamily : IPv4 +% Type : Unicast +% PrefixLength : 24 +% PrefixOrigin : Dhcp +% SuffixOrigin : Dhcp +% AddressState : Preferred +% ValidLifetime : 21:30:01 +% PreferredLifetime : 21:30:01 +% SkipAsSource : False +% PolicyStore : ActiveStore + +% IPAddress : 127.0.0.1 +% InterfaceIndex : 1 +% InterfaceAlias : Loopback Pseudo-Interface 1 +% AddressFamily : IPv4 +% Type : Unicast +% PrefixLength : 8 +% PrefixOrigin : WellKnown +% SuffixOrigin : WellKnown +% AddressState : Preferred +% ValidLifetime : +% PreferredLifetime : +% SkipAsSource : False +% PolicyStore : ActiveStore + + +% PS C:\Windows\system32> New-NetIPAddress -InterfaceAlias "vEthernet (t1)" -IPAddress "10.200.166.254" -PrefixLength 24 + +% IPAddress : 10.200.166.254 +% InterfaceIndex : 32 +% InterfaceAlias : vEthernet (t1) +% AddressFamily : IPv4 +% Type : Unicast +% PrefixLength : 24 +% PrefixOrigin : Manual +% SuffixOrigin : Manual +% AddressState : Tentative +% ValidLifetime : +% PreferredLifetime : +% SkipAsSource : False +% PolicyStore : ActiveStore + +% IPAddress : 10.200.166.254 +% InterfaceIndex : 32 +% InterfaceAlias : vEthernet (t1) +% AddressFamily : IPv4 +% Type : Unicast +% PrefixLength : 24 +% PrefixOrigin : Manual +% SuffixOrigin : Manual +% AddressState : Invalid +% ValidLifetime : +% PreferredLifetime : +% SkipAsSource : False +% PolicyStore : PersistentStore +% \end{comment} +% \fi + +\subsubsection*{New-NetNat} + +\smalltexttt{New-NetNat} - Создает новый NAT (Network Address Translation) для указанного интерфейса внутренней сети. + +Принимаемые параметры: +\begin{itemize} + \item \smalltexttt{Name} - Указывает имя NAT-объекта. Обязательный параметр; + \item \smalltexttt{InternalIPInterfaceAddressPrefix} - Указывает адресный префикс внутренней сети, который будет использоваться для NAT. Обязательный параметр; + \item \smalltexttt{ExternalIPInterfaceAddressPrefix} - Указывает адресный префикс внешней сети. Необязательный параметр; + \item \smalltexttt{Description} - Добавляет описание к NAT-объекту. Необязательный параметр. +\end{itemize} + +Пример создания NAT в PowerShell: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +PS C:\Windows\system32> New-NetNat -Name TishNat -InternalIPInterfaceAddressPrefix 10.200.166.0/24 +\end{lstlisting} + +Параметры: +\begin{itemize} + \item \smalltexttt{Name} - Имя NAT-объекта, например, \smalltexttt{TishNat}; + \item \smalltexttt{InternalIPInterfaceAddressPrefix} - Префикс внутренней сети, например, \smalltexttt{10.200.166.0/24}. +\end{itemize} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +PS C:\Windows\system32> New-NetNat -Name TishNat -InternalIPInterfaceAddressPrefix 10.200.166.0/24 +Name : TishNat +ExternalIPInterfaceAddressPrefix : +InternalIPInterfaceAddressPrefix : 10.200.166.0/24 +IcmpQueryTimeout : 30 +TcpEstablishedConnectionTimeout : 1800 +TcpTransientConnectionTimeout : 120 +TcpFilteringBehavior : AddressDependentFiltering +UdpFilteringBehavior : AddressDependentFiltering +UdpIdleSessionTimeout : 120 +UdpInboundRefresh : False +Store : Local +Active : True +\end{lstlisting} +\cite{newNetNat} + +\subsubsection*{Add-NetNatStaticMapping} +\smalltexttt{Add-NetNatStaticMapping} - Добавляет статическое сопоставление портов между внешними и внутренними адресами для NAT (Network Address Translation). Это позволяет направлять трафик, поступающий на внешний адрес и порт, к определенному внутреннему адресу и порту. + +Принимаемые параметры: +\begin{itemize} + \item \smalltexttt{NatName} - Указывает имя существующего объекта NAT, к которому применяется статическое сопоставление. Обязательный параметр; + \item \smalltexttt{ExternalIPAddress} - Указывает внешний IP-адрес, на который поступает входящий трафик. Для разрешения любых IP-адресов можно использовать \smalltexttt{0.0.0.0/0}. Обязательный параметр; + \item \smalltexttt{ExternalPort} - Указывает порт внешнего IP-адреса, на который будет перенаправляться трафик. Обязательный параметр; + \item \smalltexttt{InternalIPAddress} - Указывает IP-адрес устройства внутри сети, к которому будет перенаправляться трафик. Обязательный параметр; + \item \smalltexttt{InternalPort} - Указывает порт устройства внутри сети, который будет использоваться для трафика. Обязательный параметр; + \item \smalltexttt{Protocol} - Указывает протокол (например, \smalltexttt{TCP} или \smalltexttt{UDP}), который будет использоваться для сопоставления. Обязательный параметр. +\end{itemize} + +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +PS C:\Windows\system32> Add-NetNatStaticMapping -NatName TishNat -ExternalIPAddress 0.0.0.0/0 -ExternalPort 22801 -InternalIPAddress 10.200.166.125 -InternalPort 22 -Protocol TCP +\end{lstlisting} +\cite{addNetNatStaticMapping} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +PS C:\Windows\system32> Add-NetNatStaticMapping -NatName TishNat -ExternalIPAddress 0.0.0.0/0 -ExternalPort 22801 -InternalIPAddress 10.200.166.125 -InternalPort 22 -Protocol TCP + + +StaticMappingID : 0 +NatName : TishNat +Protocol : TCP +RemoteExternalIPAddressPrefix : 0.0.0.0/0 +ExternalIPAddress : 0.0.0.0 +ExternalPort : 22801 +InternalIPAddress : 10.200.166.125 +InternalPort : 22 +InternalRoutingDomainId : {00000000-0000-0000-0000-000000000000} +Active : True + + + +PS C:\Windows\system32> Add-NetNatStaticMapping -NatName TishNat -ExternalIPAddress 0.0.0.0/0 -ExternalPort 22802 -InternalIPAddress 10.200.166.126 -InternalPort 22 -Protocol TCP + + +StaticMappingID : 1 +NatName : TishNat +Protocol : TCP +RemoteExternalIPAddressPrefix : 0.0.0.0/0 +ExternalIPAddress : 0.0.0.0 +ExternalPort : 22802 +InternalIPAddress : 10.200.166.126 +InternalPort : 22 +InternalRoutingDomainId : {00000000-0000-0000-0000-000000000000} +Active : True + + + +PS C:\Windows\system32> Add-NetNatStaticMapping -NatName TishNat -ExternalIPAddress 0.0.0.0/0 -ExternalPort 22803 -InternalIPAddress 10.200.166.127 -InternalPort 22 -Protocol TCP + + +StaticMappingID : 4 +NatName : TishNat +Protocol : TCP +RemoteExternalIPAddressPrefix : 0.0.0.0/0 +ExternalIPAddress : 0.0.0.0 +ExternalPort : 22803 +InternalIPAddress : 10.200.166.127 +InternalPort : 22 +InternalRoutingDomainId : {00000000-0000-0000-0000-000000000000} +Active : True + + +PS C:\Windows\system32> Add-NetNatStaticMapping -NatName TishNat -ExternalIPAddress 0.0.0.0/0 -ExternalPort 22804 -InternalIPAddress 10.200.166.128 -InternalPort 22 -Protocol TCP + + +StaticMappingID : 5 +NatName : TishNat +Protocol : TCP +RemoteExternalIPAddressPrefix : 0.0.0.0/0 +ExternalIPAddress : 0.0.0.0 +ExternalPort : 22804 +InternalIPAddress : 10.200.166.128 +InternalPort : 22 +InternalRoutingDomainId : {00000000-0000-0000-0000-000000000000} +Active : True +\end{lstlisting} + + +Для более быстрой настройки других виртуальных машин, можно скопировать tishcpu1, а при настройке первой tishgpu1 выполнить копирование в tishgpu2. + +В результате создания виртуального кластеры были получены следующие виртуальные машины: +\begin{itemize} + \item tishcpu1 - "главный" вычислительный узел; + \item tishcpu2; + \item tishgpu1; + \item tishgpu2. +\end{itemize} + +\subsection{Конфигурация ресурсов GPU} +Для конфигурации ресурсов GPU для узлов, необходимо воспользоваться следующими PowerShell Cmdlet'ами: +\begin{enumerate} + \item Get-VMHostPartitionableGpu. + \item Add-VMGpuPartitionAdapter. + \item Set-VM. + %\item Get-CimInstance. +\end{enumerate} +Также необходимо установить драйвера GPU, CUDA-toolkit. + +\subsubsection*{Get-VMHostPartitionableGpu} +\smalltexttt{Get-VMHostPartitionableGpu} - Возвращает список GPU, установленных на хосте Hyper-V, которые поддерживают разделение ресурсов (Partitioning). Эти GPU могут быть разделены и назначены виртуальным машинам для эффективного использования. + +Принимаемые параметры: +\begin{itemize} + \item \smalltexttt{-CimSession} (необязательный) - Позволяет указать удаленную сессию CIM (Common Information Model) для выполнения команды на другом компьютере. Если параметр не указан, команда выполняется локально. + \item \smalltexttt{-ThrottleLimit} (необязательный) - Ограничивает количество одновременных операций. Если параметр не указан, используется системное значение по умолчанию. +\end{itemize} + +Выходные данные команды включают информацию о GPU, например: +\begin{itemize} + \item \smalltexttt{Name} - Имя GPU; + \item \smalltexttt{TotalMemory} - Общий объем памяти GPU; + \item \smalltexttt{AvailableMemory} - Доступный объем памяти GPU; + \item \smalltexttt{Status} - Текущий статус GPU (например, \smalltexttt{OK}). +\end{itemize} + +Пример использования команды: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +PS C:\Windows\system32> Get-VMHostPartitionableGpu +\end{lstlisting} + +Результат выполнения: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +PS C:\Windows\system32> Get-VMHostPartitionableGpu + + +Name : \\?\PCI#VEN_1002&DEV_164E&SUBSYS_D0001458&REV_C5#4&16012499&0&0041#{064092b3-625e-43bf- + 9eb5-dc845897dd59}\GPUPARAV +ValidPartitionCounts : {32} +PartitionCount : 32 +TotalVRAM : 1000000000 +AvailableVRAM : 1000000000 +SupportsIncomingLiveMigration : False +MinPartitionVRAM : 0 +MaxPartitionVRAM : 1000000000 +OptimalPartitionVRAM : 1000000000 +TotalEncode : 18446744073709551615 +AvailableEncode : 18446744073709551615 +MinPartitionEncode : 0 +MaxPartitionEncode : 18446744073709551615 +OptimalPartitionEncode : 18446744073709551615 +TotalDecode : 1000000000 +AvailableDecode : 1000000000 +MinPartitionDecode : 0 +MaxPartitionDecode : 1000000000 +OptimalPartitionDecode : 1000000000 +TotalCompute : 1000000000 +AvailableCompute : 1000000000 +MinPartitionCompute : 0 +MaxPartitionCompute : 1000000000 +OptimalPartitionCompute : 1000000000 +CimSession : CimSession: . +ComputerName : XSPMAIN +IsDeleted : False + +Name : \\?\PCI#VEN_10DE&DEV_2F04&SUBSYS_F3261569&REV_A1#740E0A73882DB04800#{064092b3-625e-43bf + -9eb5-dc845897dd59}\GPUPARAV +ValidPartitionCounts : {32} +PartitionCount : 32 +TotalVRAM : 1000000000 +AvailableVRAM : 1000000000 +SupportsIncomingLiveMigration : False +MinPartitionVRAM : 0 +MaxPartitionVRAM : 1000000000 +OptimalPartitionVRAM : 1000000000 +TotalEncode : 18446744073709551615 +AvailableEncode : 18446744073709551615 +MinPartitionEncode : 0 +MaxPartitionEncode : 18446744073709551615 +OptimalPartitionEncode : 18446744073709551615 +TotalDecode : 1000000000 +AvailableDecode : 1000000000 +MinPartitionDecode : 0 +MaxPartitionDecode : 1000000000 +OptimalPartitionDecode : 1000000000 +TotalCompute : 1000000000 +AvailableCompute : 1000000000 +MinPartitionCompute : 0 +MaxPartitionCompute : 1000000000 +OptimalPartitionCompute : 1000000000 +CimSession : CimSession: . +ComputerName : XSPMAIN +IsDeleted : False +\end{lstlisting} + +\cite{getVmHostPartitionableGpu} +Эта команда является первым шагом в настройке GPU для виртуальных машин. Она позволяет проверить, какие GPU на хосте поддерживают разделение и сколько ресурсов доступно для выделения. + +CUDA-ядра есть в \smalltexttt{VEN\_10DE\&DEV}. Это графическое устройство с ядром Nvidia 5070. +\subsubsection*{Add-VMGpuPartitionAdapter} +\smalltexttt{Add-VMGpuPartitionAdapter} - Добавляет адаптер для разделения ресурсов GPU к указанной виртуальной машине (VM). Этот адаптер позволяет виртуальной машине использовать определенную часть вычислительных, графических и других ресурсов физического GPU. + +Принимаемые параметры: +\begin{itemize} + \item \smalltexttt{-VMName} - Указывает имя виртуальной машины, к которой будет добавлен GPU-адаптер. Обязательный параметр; + \item \smalltexttt{-InstancePath} - Указывает путь к конкретному GPU, который будет разделен для использования виртуальной машиной. Обязательный параметр; + \item \smalltexttt{-MinPartitionVRAM}, \smalltexttt{-MaxPartitionVRAM}, \smalltexttt{-OptimalPartitionVRAM} - Устанавливают минимальный, максимальный и оптимальный объем видеопамяти (VRAM), который будет доступен виртуальной машине; + \item \smalltexttt{-MinPartitionEncode}, \smalltexttt{-MaxPartitionEncode}, \smalltexttt{-OptimalPartitionEncode} - Устанавливают минимальное, максимальное и оптимальное количество ресурсов для кодирования видео; + \item \smalltexttt{-MinPartitionDecode}, \smalltexttt{-MaxPartitionDecode}, \smalltexttt{-OptimalPartitionDecode} - Устанавливают минимальное, максимальное и оптимальное количество ресурсов для декодирования видео; + \item \smalltexttt{-MinPartitionCompute}, \smalltexttt{-MaxPartitionCompute}, + + \smalltexttt{-OptimalPartitionCompute} - Устанавливают минимальное, максимальное и оптимальное количество вычислительных ресурсов (Compute), доступных виртуальной машине. +\end{itemize} +Пример использования: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +PS C:\Windows\system32> Add-VMGpuPartitionAdapter -VMName tishgpu1 -InstancePath "\\?\PCI#VEN_10DE&DEV_2F04&SUBSYS_F3261569&REV_A1#740E0A73882DB04800#{064092b3-625e-43bf-9eb5-dc845897dd59}\GPUPARAV" -MinPartitionVRAM 100000000 -MaxPartitionVRAM 1000000000 -OptimalPartitionVRAM 1000000000 -MinPartitionCompute 100000000 -MaxPartitionCompute 1000000000 -OptimalPartitionCompute 1000000000 + +PS C:\Windows\system32> Get-VMGpuPartitionAdapter -VMName tishgpu1 + + +InstancePath : \\?\PCI#VEN_10DE&DEV_2F04&SUBSYS_F3261569&REV_A1#740E0A73882DB04800#{064092b3-625e-43bf + -9eb5-dc845897dd59}\GPUPARAV +SupportsOutgoingLiveMigration : False +CurrentPartitionVRAM : 1000000000 +MinPartitionVRAM : 100000000 +MaxPartitionVRAM : 1000000000 +OptimalPartitionVRAM : 1000000000 +CurrentPartitionEncode : 1000000000 +MinPartitionEncode : +MaxPartitionEncode : +OptimalPartitionEncode : +CurrentPartitionDecode : 1000000000 +MinPartitionDecode : +MaxPartitionDecode : +OptimalPartitionDecode : +CurrentPartitionCompute : 0 +MinPartitionCompute : 100000000 +MaxPartitionCompute : 1000000000 +OptimalPartitionCompute : 1000000000 +PartitionId : 0 +PartitionVfLuid : 050760292 +Name : Параметры раздела GPU +Id : Microsoft:AE124752-47A6-4788-9C91-8DAE6D45A744\5B2FD022-36CF-4FD9-83D7-D5B60274737E +VMId : ae124752-47a6-4788-9c91-8dae6d45a744 +VMName : tishgpu1 +VMSnapshotId : 00000000-0000-0000-0000-000000000000 +VMSnapshotName : +CimSession : CimSession: . +ComputerName : XSPMAIN +IsDeleted : False +VMCheckpointId : 00000000-0000-0000-0000-000000000000 +VMCheckpointName : +\end{lstlisting} + +\subsubsection*{Set-VM} +\smalltexttt{Set-VM} - Изменяет параметры виртуальной машины (VM), включая настройки памяти, процессора и других ресурсов. + +Принимаемые параметры: +\begin{itemize} + \item \smalltexttt{-VMName} - Указывает имя виртуальной машины, для которой изменяются настройки. Обязательный параметр; + \item \smalltexttt{-GuestControlledCacheTypes} - Указывает, разрешено ли гостевой операционной системе управлять типами кэширования. Значение \smalltexttt{\$true} включает эту возможность; + \item \smalltexttt{-LowMemoryMappedIoSpace} - Устанавливает объем выделенного адресного пространства для низкоуровневого памяти, используемой устройствами, например, 3GB; + \item \smalltexttt{-HighMemoryMappedIoSpace} - Устанавливает объем выделенного адресного пространства для высокоуровневого памяти, например, 32GB; + \item \smalltexttt{-ProcessorCount} (необязательный) - Позволяет задать количество процессоров, доступных виртуальной машине; + \item \smalltexttt{-DynamicMemory} (необязательный) - Разрешает использование динамической памяти для виртуальной машины. +\end{itemize} + +Пример использования команды: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +Set-VM -VMName tishgpu1 -GuestControlledCacheTypes $true -LowMemoryMappedIoSpace 3GB -HighMemoryMappedIoSpace 32GB +\end{lstlisting} + +Команда изменяет настройки виртуальной машины \smalltexttt{tishgpu1}, разрешая гостевой ОС управлять типами кэширования и выделяя 3GB для низкоуровневого адресного пространства и 32GB для высокоуровневого адресного пространства. + +\cite{setVm} + + +% \iffalse +% \begin{comment} + +% \begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +% PS C:\Windows\system32> Get-CimInstance -ClassName Win32_VideoController -Property * + +% Caption : Intel(R) UHD Graphics 750 +% Description : Intel(R) UHD Graphics 750 +% InstallDate : +% Name : Intel(R) UHD Graphics 750 +% Status : OK +% Availability : 8 +% ConfigManagerErrorCode : 0 +% ConfigManagerUserConfig : False +% CreationClassName : Win32_VideoController +% DeviceID : VideoController1 +% ErrorCleared : +% ErrorDescription : +% LastErrorCode : +% PNPDeviceID : PCI\VEN_8086&DEV_4C8A&SUBSYS_7D171462&REV_04\3&11583659&0&10 +% PowerManagementCapabilities : +% PowerManagementSupported : +% StatusInfo : +% SystemCreationClassName : Win32_ComputerSystem +% SystemName : 4E305-10 +% MaxNumberControlled : +% ProtocolSupported : +% TimeOfLastReset : +% AcceleratorCapabilities : +% CapabilityDescriptions : +% CurrentBitsPerPixel : +% CurrentHorizontalResolution : +% CurrentNumberOfColors : +% CurrentNumberOfColumns : +% CurrentNumberOfRows : +% CurrentRefreshRate : +% CurrentScanMode : +% CurrentVerticalResolution : +% MaxMemorySupported : +% MaxRefreshRate : +% MinRefreshRate : +% NumberOfVideoPages : +% VideoMemoryType : 2 +% VideoProcessor : Intel(R) UHD Graphics Family +% NumberOfColorPlanes : +% VideoArchitecture : 5 +% VideoMode : +% AdapterCompatibility : Intel Corporation +% AdapterDACType : Internal +% AdapterRAM : 2147479552 +% ColorTableEntries : +% DeviceSpecificPens : +% DitherType : +% DriverDate : 19.08.2024 3:00:00 +% DriverVersion : 32.0.101.5972 +% ICMIntent : +% ICMMethod : +% InfFilename : oem47.inf +% InfSection : iRKLD_w10_DS +% InstalledDisplayDrivers : C:\Windows\System32\DriverStore\FileRepository\iigd_dch.inf_amd64_4ec1a03daa49235f\igdum +% dim64.dll,C:\Windows\System32\DriverStore\FileRepository\iigd_dch.inf_amd64_4ec1a03daa49 +% 235f\igd10iumd64.dll,C:\Windows\System32\DriverStore\FileRepository\iigd_dch.inf_amd64_4 +% ec1a03daa49235f\igd10iumd64.dll,C:\Windows\System32\DriverStore\FileRepository\iigd_dch. +% inf_amd64_4ec1a03daa49235f\igd12umd64.dll +% Monochrome : False +% ReservedSystemPaletteEntries : +% SpecificationVersion : +% SystemPaletteEntries : +% VideoModeDescription : +% PSComputerName : +% CimClass : root/cimv2:Win32_VideoController +% CimInstanceProperties : {Caption, Description, InstallDate, Name...} +% CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties + + + + + + +% Caption : NVIDIA GeForce RTX 3060 +% Description : NVIDIA GeForce RTX 3060 +% InstallDate : +% Name : NVIDIA GeForce RTX 3060 +% Status : OK +% Availability : 3 +% ConfigManagerErrorCode : 0 +% ConfigManagerUserConfig : False +% CreationClassName : Win32_VideoController +% DeviceID : VideoController2 +% ErrorCleared : +% ErrorDescription : +% LastErrorCode : +% PNPDeviceID : PCI\VEN_10DE&DEV_2487&SUBSYS_40741458&REV_A1\4&D0467E6&0&0008 +% PowerManagementCapabilities : +% PowerManagementSupported : +% StatusInfo : +% SystemCreationClassName : Win32_ComputerSystem +% SystemName : 4E305-10 +% MaxNumberControlled : +% ProtocolSupported : +% TimeOfLastReset : +% AcceleratorCapabilities : +% CapabilityDescriptions : +% CurrentBitsPerPixel : 32 +% CurrentHorizontalResolution : 1920 +% CurrentNumberOfColors : 4294967296 +% CurrentNumberOfColumns : 0 +% CurrentNumberOfRows : 0 +% CurrentRefreshRate : 60 +% CurrentScanMode : 4 +% CurrentVerticalResolution : 1080 +% MaxMemorySupported : +% MaxRefreshRate : 165 +% MinRefreshRate : 50 +% NumberOfVideoPages : +% VideoMemoryType : 2 +% VideoProcessor : NVIDIA GeForce RTX 3060 +% NumberOfColorPlanes : +% VideoArchitecture : 5 +% VideoMode : +% AdapterCompatibility : NVIDIA +% AdapterDACType : Integrated RAMDAC +% AdapterRAM : 4293918720 +% ColorTableEntries : +% DeviceSpecificPens : +% DitherType : 0 +% DriverDate : 14.08.2024 3:00:00 +% DriverVersion : 32.0.15.6094 +% ICMIntent : +% ICMMethod : +% InfFilename : oem43.inf +% InfSection : Section070 +% InstalledDisplayDrivers : C:\Windows\System32\DriverStore\FileRepository +% \nv_dispi.inf_amd64_20ae8f14a487d5db\nvld +% umdx.dll,C:\Windows\System32\DriverSt +% ore\FileRepository\nv_dispig.inf_amd64_0afec3f20500 +% 14a0\nvldumdx.dll,C:\Windows\System32 +% \DriverStore\FileRepository\nv_dispig.inf_amd64_0af +% ec3f2050014a0\nvldumdx.dll,C:\Windows\System32\DriverStore\FileRepository\nv_dispig.inf_ +% amd64_0afec3f2050014a0\nvldumdx.dll +% Monochrome : False +% ReservedSystemPaletteEntries : +% SpecificationVersion : +% SystemPaletteEntries : +% VideoModeDescription : Цвета: 1920 x 1080 x 4294967296 +% PSComputerName : +% CimClass : root/cimv2:Win32_VideoController +% CimInstanceProperties : {Caption, Description, InstallDate, Name...} +% CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties +% \end{lstlisting} + +% \end{comment} +% \fi +\subsubsection*{Установка ПО} +Для установки драйверов графического устройства необходимо скопировать их с хост-устройства с помощью {\small \smalltexttt{scp}}. + +Выполнение копирования драйверов GPU: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +# Копируем содержимое папки с драйверами с хоста на виртуальную машину через SCP +# -r: копирование рекурсивно (включая подкаталоги) +# -P 22803: указание порта для подключения (22803) +scp -r -P 22803 C:\WINDOWS\System32\DriverStore\FileRepository\nv_dispi.inf_amd64_20ae8f14a487d5db arity@127.0.0.1:/tmp/ + +# Создаем директорию для драйверов в WSL (если еще не существует) +root@tishgpu1:/tmp# mkdir -p /usr/lib/wsl/drivers + +# Переходим в созданную директорию +root@tishgpu1:/tmp# cd /usr/lib/wsl/drivers + +# Проверяем содержимое директории (на данный момент она пуста) +root@tishgpu1:/usr/lib/wsl/drivers# ls + +# Переходим на уровень выше, в директорию WSL +root@tishgpu1:/usr/lib/wsl/drivers# cd .. + +# Проверяем содержимое директории /usr/lib/wsl (содержит только папку drivers) +root@tishgpu1:/usr/lib/wsl# ls drivers + +# Создаем новую директорию lib в WSL (может быть нужна для других целей) +root@tishgpu1:/usr/lib/wsl# mkdir lib + +# Проверяем, что теперь в /usr/lib/wsl есть две папки: drivers и lib +root@tishgpu1:/usr/lib/wsl# ls drivers lib + +# Перемещаем скачанную папку драйвера из /tmp в директорию drivers +root@tishgpu1:/usr/lib/wsl# mv /tmp/nv_dispi.inf_amd64_20ae8f14a487d5db/ /usr/lib/wsl/drivers/ + +# Проверяем содержимое директории /usr/lib/wsl (папка drivers содержит перемещенные данные) +root@tishgpu1:/usr/lib/wsl# ls +drivers lib + +# Убеждаемся, что в папке drivers теперь находится папка с драйверами +root@tishgpu1:/usr/lib/wsl# ls drivers/ +nv_dispi.inf_amd64_20ae8f14a487d5db + +# Операция завершена, драйверы находятся в правильной директории +root@tishgpu1:/usr/lib/wsl# +\end{lstlisting} + +Конфигурация драйверов на виртуальной машине: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +# Копируем библиотеку lib из Windows на виртуальную машину через SCP +# -r: копирование рекурсивно +# -P 22803: указание порта для подключения +PS C:\Windows\system32> scp -r -P 22803 C:\Windows\System32\lxss\lib arity@127.0.0.1:/tmp/ + +# Перемещаем скопированную библиотеку в директорию WSL +root@tishgpu1:/usr/lib/wsl# mv /tmp/lib/ /usr/lib/wsl/ + +# Проверяем содержимое директории /usr/lib/wsl, чтобы убедиться, что библиотека перемещена +root@tishgpu1:/usr/lib/wsl# ls +drivers lib + +# Проверяем содержимое директории lib в WSL +root@tishgpu1:/usr/lib/wsl# ls lib +libcudadebugger.so.1 libd3d12core.so libnvcuvid.so.1 libnvidia-ml.so.1 libnvwgf2umx.so +libcuda.so libd3d12.so libnvdxdlkernels.so libnvidia-opticalflow.so nvidia-smi +libcuda.so.1 libdxcore.so libnvidia-encode.so libnvidia-opticalflow.so.1 +libcuda.so.1.1 libnvcuvid.so libnvidia-encode.so.1 libnvoptix.so.1 + +# Устанавливаем права доступа на директорию WSL: только чтение и выполнение (555) +root@tishgpu1:/usr/lib# chmod -R 555 wsl/ + +# Устанавливаем владельцем директории WSL пользователя root и группу root +root@tishgpu1:/usr/lib# chown -R root:root wsl/ + +# Редактируем файл ld.so.conf.d для добавления пути к библиотекам WSL +root@tishgpu1:/usr/lib/wsl/lib# vim /etc/ld.so.conf.d/ld.wsl.conf + +# Добавляем путь к библиотекам в файл ld.wsl.conf +/usr/lib/wsl/lib + +# Применяем изменения с помощью ldconfig, чтобы обновить кэш динамических библиотек +root@tishgpu1:/usr/lib/wsl/lib# ldconfig +/sbin/ldconfig.real: /usr/lib/wsl/lib/libcuda.so.1 is not a symbolic link + +# Редактируем или создаем файл /etc/profile.d/wsl.sh для добавления пути в системный PATH +root@tishgpu1:/usr/lib# vim /etc/profile.d/wsl.sh + +# Проверяем содержимое файла wsl.sh, чтобы убедиться в правильности пути +root@tishgpu1:/usr/lib/wsl/lib# cat /etc/profile.d/wsl.sh +export PATH=$PATH:/usr/lib/wsl/lib + +# Делаем файл wsl.sh исполняемым +root@tishgpu1:/usr/lib# chmod +x /etc/profile.d/wsl.sh +\end{lstlisting} + +Сборка ядра с использованием готового shell скрипта: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +# Загружаем и выполняем скрипт установки DXGKRNL (DirectX Kernel) через DKMS (Dynamic Kernel Module Support): +# - curl: утилита для загрузки данных из интернета. +# -fsSL: +# -f: завершить с ошибкой, если произошел сбой HTTP-запроса. +# -s: тихий режим, скрывающий прогресс загрузки. +# -S: отображение ошибок даже в тихом режиме. +# -L: автоматическое следование за перенаправлениями. +# https://content.staralt.dev/dxgkrnl-dkms/main/install.sh: URL скрипта установки. +# |: передача загруженного содержимого скрипта как ввода следующей команде. +# sudo bash -es: +# - bash: запускает загруженный скрипт в интерпретаторе команд bash. +# - -e: завершает выполнение скрипта при любой ошибке. +# - -s: интерпретирует данные, подаваемые через стандартный ввод, как сценарий bash. +aritytishgpu1:~$ curl -fsSL https://content.staralt.dev/dxgkrnl-dkms/main/install.sh | sudo bash -es + +Target Kernel Version: 5.15.0-124-generic + +Installing dependencies... +\end{lstlisting} + +Для проверки корректности работы GPU на виртуальном узле можно воспользоваться утилитами: +\begin{itemize} + \item lspci; + \begin{itemize} + \item Используется для отображения списка PCI-устройств, подключенных к системе. Ключ -v выводит подробную информацию о каждом устройстве. В данном случае, команда подтверждает наличие GPU, который использует драйвер dxgkrnl; + \end{itemize} + \item nvidia-smi; + \begin{itemize} + \item Утилита для управления и мониторинга графических карт NVIDIA. Показывает информацию о версии драйвера, состоянии GPU, использовании памяти, температуре и запущенных процессах. В данном примере отображается статус GPU NVIDIA GeForce RTX 5070, использование 1543 MiB памяти и базовая загрузка GPU; + \end{itemize} +\end{itemize} + +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +aritytishgpu1:~$ lspci -v +c556:00:00.0 3D controller: Microsoft Corporation Device 008e + Physical Slot: 4111917767 + Flags: bus master, fast devsel, latency 0, NUMA node 0 + Capabilities: + Kernel driver in use: dxgkrnl + Kernel modules: dxgkrnl + + +aritytishgpu1:~$ nvidia-smi +Sat Jan 3 17:25:15 2026 ++-----------------------------------------------------------------------------------------+ +| NVIDIA-SMI 580.102.01 Driver Version: 581.80 CUDA Version: 13.0 | ++-----------------------------------------+------------------------+----------------------+ +| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | +| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | +| | | MIG M. | +|=========================================+========================+======================| +| 0 NVIDIA GeForce RTX 5070 On | 00000000:01:00.0 On | N/A | +| 0% 57C P0 36W / 250W | 1543MiB / 12227MiB | 6% Default | +| | | N/A | ++-----------------------------------------+------------------------+----------------------+ + ++-----------------------------------------------------------------------------------------+ +| Processes: | +| GPU GI CI PID Type Process name GPU Memory | +| ID ID Usage | +|=========================================================================================| +| No running processes found | ++-----------------------------------------------------------------------------------------+ +\end{lstlisting} + +% \iffalse +% \begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +% C:\Windows\System32\DriverStore\FileRepository\nv_dispi.inf_amd64_20ae8f14a487d5db\ + +% PS C:\Windows\system32> scp -P 41122 -r C:\Windows\System32\DriverStore\FileRepository\nv_dispi.inf_amd64_20ae8f14a487d5db arity@127.0.0.1:/tmp/drv + +% PS C:\Windows\system32> scp -P 41122 -r C:\Windows\System32\lxss\lib arity@127.0.0.1:/tmp/drv-lib + +% root@tishgpu1:/tmp# ls -l +% total 36 +% drwx---r-x 5 arity arity 12288 Nov 19 10:27 drv +% drwx------ 2 arity arity 4096 Nov 19 10:32 drv-lib + +% root@tishgpu1:/tmp# mkdir -p /usr/lib/wsl/lib + +% root@tishgpu1:/usr/lib/wsl# cp -r /tmp/drv-lib/ /usr/lib/wsl/ +% root@tishgpu1:/usr/lib/wsl# ls +% drv-lib +% root@tishgpu1:/usr/lib/wsl# mv drv-lib/ lib +% root@tishgpu1:/usr/lib/wsl# ls +% lib + +% root@tishgpu1:/usr/lib/wsl# cp -r /tmp/drv /usr/lib/wsl/lib/ +% root@tishgpu1:/usr/lib/wsl# ls +% lib +% root@tishgpu1:/usr/lib/wsl# ls lib/ +% drv libdxcore.so libnvidia-opticalflow.so +% libcudadebugger.so.1 libnvcuvid.so libnvidia-opticalflow.so.1 +% libcuda.so libnvcuvid.so.1 libnvoptix.so.1 +% libcuda.so.1 libnvdxdlkernels.so libnvwgf2umx.so +% libcuda.so.1.1 libnvidia-encode.so nvidia-smi +% libd3d12core.so libnvidia-encode.so.1 +% libd3d12.so libnvidia-ml.so.1 +% root@tishgpu1:/usr/lib/wsl/lib# mv drv/ drivers +% root@tishgpu1:/usr/lib/wsl/lib# ls -l +% total 242276 +% drwx---r-x 5 root root 12288 Nov 19 10:47 drivers +% -rw-r--r-- 1 root root 10182600 Nov 19 10:46 libcudadebugger.so.1 +% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so +% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so.1 +% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so.1.1 +% -rw-r--r-- 1 root root 5913128 Nov 19 10:46 libd3d12core.so +% -rw-r--r-- 1 root root 789192 Nov 19 10:46 libd3d12.so +% -rw-r--r-- 1 root root 832144 Nov 19 10:46 libdxcore.so +% -rw-r--r-- 1 root root 13061496 Nov 19 10:46 libnvcuvid.so +% -rw-r--r-- 1 root root 13061496 Nov 19 10:46 libnvcuvid.so.1 +% -rw-r--r-- 1 root root 128602944 Nov 19 10:46 libnvdxdlkernels.so +% -rw-r--r-- 1 root root 584296 Nov 19 10:46 libnvidia-encode.so +% -rw-r--r-- 1 root root 584296 Nov 19 10:46 libnvidia-encode.so.1 +% -rw-r--r-- 1 root root 245192 Nov 19 10:46 libnvidia-ml.so.1 +% -rw-r--r-- 1 root root 362960 Nov 19 10:46 libnvidia-opticalflow.so +% -rw-r--r-- 1 root root 362960 Nov 19 10:46 libnvidia-opticalflow.so.1 +% -rw-r--r-- 1 root root 72664 Nov 19 10:46 libnvoptix.so.1 +% -rw-r--r-- 1 root root 72175656 Nov 19 10:46 libnvwgf2umx.so +% -rw-r--r-- 1 root root 729600 Nov 19 10:46 nvidia-smi +% root@tishgpu1:/usr/lib/wsl/lib#root@tishgpu1:/usr/lib/wsl/lib# mv drv/ drivers +% root@tishgpu1:/usr/lib/wsl/lib# ls -l +% total 242276 +% drwx---r-x 5 root root 12288 Nov 19 10:47 drivers +% -rw-r--r-- 1 root root 10182600 Nov 19 10:46 libcudadebugger.so.1 +% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so +% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so.1 +% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so.1.1 +% -rw-r--r-- 1 root root 5913128 Nov 19 10:46 libd3d12core.so +% -rw-r--r-- 1 root root 789192 Nov 19 10:46 libd3d12.so +% -rw-r--r-- 1 root root 832144 Nov 19 10:46 libdxcore.so +% -rw-r--r-- 1 root root 13061496 Nov 19 10:46 libnvcuvid.so +% -rw-r--r-- 1 root root 13061496 Nov 19 10:46 libnvcuvid.so.1 +% -rw-r--r-- 1 root root 128602944 Nov 19 10:46 libnvdxdlkernels.so +% -rw-r--r-- 1 root root 584296 Nov 19 10:46 libnvidia-encode.so +% -rw-r--r-- 1 root root 584296 Nov 19 10:46 libnvidia-encode.so.1 +% -rw-r--r-- 1 root root 245192 Nov 19 10:46 libnvidia-ml.so.1 +% -rw-r--r-- 1 root root 362960 Nov 19 10:46 libnvidia-opticalflow.so +% -rw-r--r-- 1 root root 362960 Nov 19 10:46 libnvidia-opticalflow.so.1 +% -rw-r--r-- 1 root root 72664 Nov 19 10:46 libnvoptix.so.1 +% -rw-r--r-- 1 root root 72175656 Nov 19 10:46 libnvwgf2umx.so +% -rw-r--r-- 1 root root 729600 Nov 19 10:46 nvidia-smi + +% root@tishgpu1:/usr/lib/wsl/lib# chmod 555 /usr/lib/wsl/lib/* +% root@tishgpu1:/usr/lib/wsl/lib# chown -R root:root /usr/lib/wsl + +% root@tishgpu1:/usr/lib/wsl/lib# vim /etc/ld.so.conf.d/ld.wsl.conf + +% /usr/lib/wsl/lib + +% root@tishgpu1:/usr/lib/wsl/lib# root@tishgpu1:/usr/lib/wsl/lib# ldconfig +% /sbin/ldconfig.real: /usr/lib/wsl/lib/libcuda.so.1 is not a symbolic link + +% root@tishgpu1:/usr/lib/wsl/lib# vim /etc/profile.d/wsl.sh +% root@tishgpu1:/usr/lib/wsl/lib# root@tishgpu1:/usr/lib/wsl/lib# cat /etc/profile.d/wsl.sh +% export PATH=$PATH:/usr/lib/wsl/lib +% chmod +x /etc/profile.d/wsl.sh + +% \end{lstlisting} +% \fi + + +Для установки библиотеки для работы с CUDA необходимо выполнить следующие команды: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +# Загружаем публичный ключ репозитория NVIDIA для подписи пакетов. +# Важно для обеспечения безопасности и проверки пакетов. +aritytishgpu1:~$ sudo apt-key adv --fetch-keys https://developer.download. +nvidia.com +/compute/cuda/repos/ubuntu2204/x86_64/3bf863cc.pub + +# Внимание: apt-key устарел. Рекомендуется использовать новый метод управления ключами через директорию trusted.gpg.d. +# Ключ успешно импортирован: +# "cudatools " + +# Добавляем репозиторий CUDA в список источников пакетов. +# Это позволяет получать доступ к последним версиям инструментов CUDA. +aritytishgpu1:~$ sudo add-apt-repository "deb http://developer.download.nvidia.com/compute/ +cuda/repos/ubuntu2204/x86_64/ /" + +# Указанный репозиторий добавлен в файл /etc/apt/sources.list.d. +# Обновляем список пакетов для загрузки метаданных из нового репозитория. +# Получаем пакеты из репозитория NVIDIA и стандартных репозиториев Ubuntu. +# Замечание: ключ репозитория сохранён в устаревшем формате, как указано в предупреждении. + +# Устанавливаем пакет CUDA Toolkit версии 12. +# Этот пакет включает библиотеки, компиляторы и утилиты для разработки с использованием GPU. +aritytishgpu1:~$ sudo apt install cuda-toolkit-12 + +# Создаём скрипт окружения для автоматической настройки переменных PATH, CUDA_HOME и LD_LIBRARY_PATH. +aritytishgpu1:/usr/local/cuda/bin$ sudo touch /etc/profile.d/cuda.sh + +# Редактируем файл cuda.sh для добавления переменных окружения. +# Эти переменные позволяют системе находить бинарные файлы и библиотеки CUDA. +aritytishgpu1:/usr/local/cuda$ sudo vim /etc/profile.d/cuda.sh + +# Проверяем содержимое файла, чтобы убедиться в правильной настройке переменных. +aritytishgpu1:/usr/local/cuda$ cat /etc/profile.d/cuda.sh +export PATH=$PATH:/usr/local/cuda/bin +export CUDA_HOME=$CUDA_HOME:/usr/local/cuda +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64 + +# Делаем файл cuda.sh исполняемым, чтобы переменные окружения применялись при входе в систему. +aritytishgpu1:~$ sudo chmod +x /etc/profile.d/cuda.sh +\end{lstlisting} + +\subsection{Конфигурация NFS} +NFS (Network File System) — это сетевой протокол, разработанный для предоставления общего доступа к файловым системам через сеть. С помощью NFS пользователи или приложения могут работать с файлами, расположенными на удалённом сервере, так, как будто они находятся на локальной машине. \cite{rfc1094} + +Для корректной работы исполняемых и других файлов на виртуальных машинах, вместо постоянного копирования можно воспользоваться протоколом сетевого доступа к файловой системе одной из машин. Для этого можно настроить NFS на одном из узлов (например tishgpu1) и далее выполнить монтирование в каталоги других виртуальных машин. + +\subsubsection*{Настройка etc/hosts} +Для удобной работы в среде виртуальных машин можно задать доменные имена в файле \smalltexttt{etc/hosts}. Для этого его необходимо отредактировать на каждой виртуальной машине. + +Формат создания доменного имени: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +# IP-адрес, Основное имя хоста, Полное доменное имя +10.200.166.126 tishcpu2 tishcpu2.tish +\end{lstlisting} +Пример файла \smalltexttt{etc/hosts}: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +aritytishcpu1:~$ cat /etc/hosts +127.0.0.1 localhost +10.200.166.125 tishcpu1 tishcpu1.tish +10.200.166.126 tishcpu2 tishcpu2.tish +10.200.166.127 tishgpu1 tishgpu1.tish +10.200.166.128 tishgpu2 tishgpu2.tish + +# The following lines are desirable for IPv6 capable hosts +::1 ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +\end{lstlisting} + +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +# Содержимое файла /etc/exports на tishcpu1 +# Экспортируем директорию /home/arity для общего доступа через NFS +# *: доступ разрешён для всех хостов +# rw: разрешение на чтение и запись +# nohide: дочерние файловые системы видны +# no_subtree_check: отключает проверку вложенных поддеревьев для повышения производительности +aritytishcpu1:~$ cat /etc/exports +/home/arity *(rw,nohide,no_subtree_check) + +# Применяем изменения в настройках NFS (перезапускаем экспорт) +# -a: экспорт всех записей +# -r: перезапускает экспорт +# -v: подробный вывод +aritytishcpu1:~$ sudo exportfs -arv +exporting *:/home/arity + +# Устанавливаем клиент NFS на tishcpu2 +sudo apt install nfs-common + +# Удаляем старую папку (если существует) для монтирования +aritytishcpu2:/mnt$ sudo rmdir share + +# Проверяем содержимое директории /mnt +aritytishcpu2:/mnt$ ls + +# Создаём новую папку share для монтирования NFS +aritytishcpu2:/mnt$ sudo mkdir share + +# Монтируем экспортированную директорию /home/arity с tishgpu1 в локальную папку /mnt/share +# -t nfs: указывает тип файловой системы (NFS) +aritytishcpu2:/mnt$ sudo mount -t nfs tishgpu1:/home/arity /mnt/share + +# Проверяем содержимое смонтированной директории +aritytishgpu2:/mnt$ ls share/ +hello.txt install.sh + +# Читаем файл hello.txt из смонтированной директории +aritytishgpu2:/mnt$ cat share/hello.txt +happy hacking +aritytishgpu2:/mnt$ +\end{lstlisting} + +\subsection{Конфигурация slurm} +SLURM (Simple Linux Utility for Resource Management) — это открытая система управления задачами и ресурсами в кластерах. Она используется для распределения вычислительных задач между узлами и управления их выполнением. + +С помощью \smalltexttt{conf.html} файла конфигурации необходимо сформировать конфигурацию в \smalltexttt{/etc/slurm/slurm.conf}. + +\begin{itemize} + \item \textbf{ClusterName} - Имя кластера SLURM (например, "tish"), используется для идентификации кластера. + \item \textbf{SlurmctldHost} - Имя хоста, на котором работает демон управления SLURM (slurmctld), в данном случае это \smalltexttt{tishcpu1}. + \item \textbf{MpiDefault} - Указывает стандартную реализацию MPI (по умолчанию \smalltexttt{none}, т.е. MPI не используется). + \item \textbf{ProctrackType} - Метод отслеживания процессов; \smalltexttt{proctrack/cgroup} означает использование cgroup для изоляции процессов. + \item \textbf{ReturnToService} - Указывает, должен ли узел автоматически возвращаться в работу после восстановления (\smalltexttt{1} - включено). + \item \textbf{SlurmctldPidFile} - Путь к файлу PID для демона slurmctld. + \item \textbf{SlurmdPidFile} - Путь к файлу PID для демона slurmd. + \item \textbf{SlurmdSpoolDir} - Директория, где slurmd хранит временные файлы и информацию о заданиях. + \item \textbf{SlurmUser} - Имя пользователя, под которым запускаются процессы SLURM (обычно \smalltexttt{slurm}). + \item \textbf{StateSaveLocation} - Директория для сохранения состояния кластера, необходима для восстановления после перезапуска. + \item \textbf{SwitchType} - Тип сетевого коммутатора; \smalltexttt{switch/none} указывает, что коммутатор не используется. + \item \textbf{TaskPlugin} - Плагин для управления задачами; \smalltexttt{task/affinity} позволяет задавать привязку задач к CPU. + \item \textbf{SchedulerType} - Тип планировщика задач; \smalltexttt{sched/backfill} разрешает задачи меньшего размера выполняться параллельно с крупными. + \item \textbf{SelectType} - Механизм выбора ресурсов; + \item \textbf{SelectTypeParameters} - Параметры выбора ресурсов; \smalltexttt{CR\_Core} указывает распределение по ядрам. + \item \textbf{JobAcctGatherType} - Метод сбора данных о выполнении задач; + \item \textbf{SlurmctldLogFile} - Путь к файлу журнала для демона slurmctld. + \item \textbf{SlurmdLogFile} - Путь к файлу журнала для демона slurmd. + \item \textbf{NodeName} - Описание вычислительных узлов; описывает 4 узла с 2 CPU на каждом. + \item \textbf{PartitionName} - Имя раздела (partition), включающего все узлы (\smalltexttt{Nodes=ALL}); используется для распределения задач. + \item \textbf{Default} - Указывает, что данный раздел является разделом по умолчанию (\smalltexttt{YES}). + \item \textbf{MaxTime} - Максимальное время выполнения задачи; \smalltexttt{INFINITE} означает, что ограничений нет. + \item \textbf{State} - Статус узлов или раздела (\smalltexttt{UP} - узлы в рабочем состоянии). +\end{itemize} + +Пример установки конфигурации: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +aritytishcpu1:~$ sudo apt install slurm-wlm + +aritytishcpu1:~$ nano /etc/slurm/slurm.conf +#Указываем: + +# slurm.conf file generated by configurator easy.html. +# Put this file on all nodes of your cluster. +# See the slurm.conf man page for more information. +# +ClusterName=tish +SlurmctldHost=tishcpu1 +# +#MailProg=/bin/mail +#MpiDefault= +#MpiParams=ports=#-# +ProctrackType=proctrack/cgroup +ReturnToService=2 +SlurmctldPidFile=/var/run/slurmctld.pid +#SlurmctldPort=6817 +SlurmdPidFile=/var/run/slurmd.pid +#SlurmdPort=6818 +SlurmdSpoolDir=/var/spool/slurmd +SlurmUser=slurm +#SlurmdUser=root +StateSaveLocation=/var/spool/slurmctld +#SwitchType= +TaskPlugin=task/affinity,task/cgroup +# +# +# TIMERS +#KillWait=30 +#MinJobAge=300 +#SlurmctldTimeout=120 +#SlurmdTimeout=300 +# +# +# SCHEDULING +SchedulerType=sched/backfill +SelectType=select/cons_tres +# +# +# LOGGING AND ACCOUNTING +#AccountingStorageType= +#JobAcctGatherFrequency=30 +#JobAcctGatherType= +#SlurmctldDebug=info +SlurmctldLogFile=/var/log/slurmctld.log +#SlurmdDebug=info +SlurmdLogFile=/var/log/slurmd.log +# +# +# COMPUTE NODES +NodeName=tishcpu[1-2],tishgpu[1-2] CPUs=2 State=UNKNOWN +PartitionName=tishpartition Nodes=ALL Default=YES MaxTime=INFINITE State=UP + +#Копируем этот конфиг в машины tishcpu2, tishgpu1, tishgpu2 +\end{lstlisting} +\subsection{Конфигурация munge} +MUNGE — это инструмент для аутентификации, который используется для обеспечения безопасности в кластерах. + +Ниже приведены шаги конфигурации ключей munge для конфигурации машин. +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +# Копируем файл ключа MUNGE с узла tishgpu1 на все машины кластера. +# Этот файл необходим для аутентификации в кластере. + +root@tishcpu2:/tmp# chown munge:munge ./munge.key +# Изменяем владельца и группу файла ключа на пользователя и группу MUNGE. +# Это необходимо для корректной работы службы MUNGE. + +root@tishcpu2:/tmp# ls -l +# Проверяем содержимое текущей директории, чтобы убедиться, что файл ключа имеет нужные права: +# -rw-------: доступ только для владельца. + +root@tishcpu2:/tmp# mv /etc/munge/munge.key /etc/munge/munge_old.key +# Переименовываем существующий ключ в `munge_old.key` для резервного копирования. + +root@tishcpu2:/tmp# mv munge.key /etc/munge/munge.key +# Перемещаем новый файл ключа в директорию /etc/munge и задаём ему правильное имя. + +root@tishcpu2:/etc/munge# ls +# Проверяем содержимое директории /etc/munge: +# Убедились, что есть два файла: новый ключ (`munge.key`) и резервный ключ (`munge_old.key`). + +# Переходим на другой узел (tishgpu1), повторяем процесс. +aritytishgpu1:/tmp$ su +Password: +root@tishgpu1:/tmp# mv /etc/munge/munge.key /etc/munge/munge_old.key +# Создаём резервную копию существующего ключа. + +root@tishgpu1:/tmp# chown munge:munge munge.key +# Изменяем владельца нового ключа на пользователя MUNGE. + +root@tishgpu1:/tmp# mv /tmp/munge.key /etc/munge/munge.key +# Перемещаем новый ключ в директорию /etc/munge. + +# На узле tishgpu2 повторяем процесс. +root@tishgpu2:/tmp# chown munge:munge munge.key +# Меняем владельца файла ключа. + +root@tishgpu2:/tmp# mv /etc/munge/munge.key /etc/munge/munge_old.key +# Резервируем старый ключ. + +root@tishgpu2:/tmp# mv munge.key /etc/munge/munge.key +# Перемещаем новый ключ на место старого. + +root@tishgpu2:/tmp# ls -l /etc/munge +# Проверяем права и владельца ключей в директории /etc/munge: +# Убедились, что оба ключа принадлежат пользователю `munge` и имеют доступ только для владельца. + +# Вносим изменения в конфигурацию SLURM: +# Меняем механизм отслеживания процессов с `cgroup` на `linuxproc` в файле конфигурации /etc/slurm/slurm.conf. + +# Перезапускаем службы, чтобы применить изменения: +sudo systemctl restart munge +# Перезапускаем службу MUNGE для применения нового ключа. + +sudo systemctl restart slurmd +# Перезапускаем демон SLURM для рабочих узлов. + +sudo systemctl restart slurmctld +# Перезапускаем демон SLURM для управляющего узла. + +sudo systemctl status munge +sudo systemctl status slurmd +sudo systemctl status slurmctld +# Проверяем статус всех служб, чтобы убедиться в их корректной работе. + +\end{lstlisting} + +\subsection{Конфигурация OpenMPI} + +OpenMPI (Open Message Passing Interface) — это высокопроизводительная, гибкая и открытая реализация стандарта MPI (Message Passing Interface). MPI — это стандарт для взаимодействия между процессами в распределённых и параллельных вычислительных системах, таких как кластеры и суперкомпьютеры. + +Для обмена сообщениями узлы должны обменяться публичными ключами и каждый имел прямой доступ к друг другу через \smalltexttt{ssh}. + +Чтобы произвести обмен ключами необходимо выполнить следующие команды: + +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +# Создаём новую пару SSH-ключей: +ssh-keygen + +# Копируем публичный SSH-ключ на удалённый узел: +# - ssh-copy-id: утилита для добавления публичного ключа на удалённый сервер. +# - aritytishcpu1: имя пользователя и адрес узла, куда копируется ключ. +# После выполнения этой команды ключ будет добавлен в файл authorized_keys на удалённом сервере, +# что позволит подключаться по SSH без ввода пароля. +ssh-copy-id aritytishcpu1 +\end{lstlisting} + +Для установки библиотеки OpenMPI необходимо выполнить следующие пакеты: + +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +sudo apt install openmpi-bin # установить на все узлы +sudo apt install openmpi-dev # установить на 1 узел где будет разработка приложения +\end{lstlisting} + +\newpage +\subsection{Постановка задачи и прототип решения} + +\subsubsection*{Описание задачи} +Необходимо разработать параллельное приложение, задействующее вычислительные ресурсы двух CPU-узлов и двух CUDA-узлов, используя механизм OpenMPI, выполняющее анализ временных рядов исторических данных о стоимости Bitcoin. + +Задача разбита на два этапа: +\begin{enumerate} + \item \textbf{Этап 1. Агрегация данных}: для временного ряда исторических данных о стоимости Bitcoin (исходные данные содержат информацию по каждым 10 секундам) необходимо выполнить группировку по дням и для каждого дня вычислить среднюю цену как математическое ожидание значений Low и High, а также минимальные и максимальные значения Open и Close. + \item \textbf{Этап 2. Поиск интервалов изменения цены}: на основе дневных агрегированных данных необходимо выявить интервалы дат (начиная с начальной даты в наборе данных), в которых средняя дневная цена изменилась не менее чем на 10\% относительно начала интервала. Для каждого интервала необходимо вывести начальную и конечную даты, а также минимальные и максимальные значения Open и Close за все дни внутри интервала. +\end{enumerate} + +\subsubsection*{Описание входных данных} +В задаче используется файл в формате CSV с историческими данными о стоимости Bitcoin\cite{kaggle}. В качестве разделителя используется запятая. Во входном файле заданы следующие поля: +\begin{enumerate} + \item \textbf{Timestamp} - временная метка Unix в секундах. + \item \textbf{Open} - цена открытия за период. + \item \textbf{High} - максимальная цена за период. + \item \textbf{Low} - минимальная цена за период. + \item \textbf{Close} - цена закрытия за период. + \item \textbf{Volume} - объём торгов (может быть пустым). +\end{enumerate} + +\subsubsection*{Прототип решения на Python} +Для отладки алгоритма был создан прототип на языке Python. Код прототипа представлен ниже: + +\begin{lstlisting}[language=Python, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +import pandas as pd + +# Загрузка данных +df = pd.read_csv("data.csv") +df['Timestamp'] = pd.to_datetime(df['Timestamp'], unit='s', utc=True) + +# Вычисление средней цены +df['Avg'] = (df['Low'] + df['High']) / 2 + +# Группировка по дням и агрегация +df_days = ( + df.groupby(df["Timestamp"].dt.date) + .agg( + Avg=("Avg", "mean"), + OpenMin=("Open", "min"), + OpenMax=("Open", "max"), + CloseMin=("Close", "min"), + CloseMax=("Close", "max"), + ) + .reset_index() +) + +# Поиск интервалов изменения цены на 10% +intervals = [] +start_idx = 0 +price_base = df_days.loc[start_idx, "Avg"] + +for i in range(1, len(df_days)): + price_now = df_days.loc[i, "Avg"] + change = abs(price_now - price_base) / price_base + + if change >= 0.10: + interval = df_days.loc[start_idx:i] + + intervals.append({ + "start_date": df_days.loc[start_idx, "Timestamp"], + "end_date": df_days.loc[i, "Timestamp"], + "min_open": interval["OpenMin"].min(), + "max_open": interval["OpenMax"].max(), + "min_close": interval["CloseMin"].min(), + "max_close": interval["CloseMax"].max(), + "start_avg": price_base, + "end_avg": price_now, + "change": change, + }) + + start_idx = i + 1 + if start_idx >= len(df_days): + break + price_base = df_days.loc[start_idx, "Avg"] + +df_intervals = pd.DataFrame(intervals) +\end{lstlisting} + +\subsubsection*{Увеличение объёма данных} +Исходные данные содержат информацию по каждой минуте и имеют размер около 360 МБ. При тестировании параллельной реализации обработка таких данных занимала слишком мало времени, что не позволяло достоверно оценить эффективность параллельных вычислений и преимущества использования GPU. + +Для решения этой проблемы был разработан скрипт \smalltexttt{upsample.py}, выполняющий линейную интерполяцию данных. Алгоритм работы скрипта следующий: + +\begin{enumerate} + \item Для каждой пары соседних записей $(t_1, o_1, h_1, l_1, c_1, v_1)$ и $(t_2, o_2, h_2, l_2, c_2, v_2)$ вычисляется временной интервал $\Delta t = t_2 - t_1$. + \item Интервал делится на $n = \Delta t / \text{step}$ равных частей, где $\text{step}$ - новый временной шаг (10 секунд). + \item Для каждой промежуточной точки $i \in [0, n)$ вычисляются интерполированные значения с помощью линейной интерполяции: + \begin{align*} + \alpha &= i / n \\ + t_i &= t_1 + i \cdot \text{step} \\ + o_i &= o_1 + (o_2 - o_1) \cdot \alpha \\ + h_i &= h_1 + (h_2 - h_1) \cdot \alpha \\ + l_i &= l_1 + (l_2 - l_1) \cdot \alpha \\ + c_i &= c_1 + (c_2 - c_1) \cdot \alpha \\ + v_i &= v_1 + (v_2 - v_1) \cdot \alpha + \end{align*} +\end{enumerate} + +В результате применения интерполяции данные были преобразованы из формата "каждая минута" в формат "каждые 10 секунд", что увеличило объём данных в 6 раз - с примерно 360 МБ до 2.3 ГБ. Такой объём данных позволяет наглядно продемонстрировать эффективность параллельных вычислений и преимущества использования GPU-ускорения. + +\newpage +\subsection{Параллельная реализация на CPU} + +\subsubsection*{Проблема последовательной обработки} +При профилировании первоначальной реализации было выявлено, что операция чтения и парсинга CSV-файла размером 2.3 ГБ занимает значительную часть времени выполнения программы. Последовательное чтение такого объёма данных на одном узле приводило к неэффективному использованию вычислительных ресурсов кластера, так как остальные узлы простаивали в ожидании данных. + +\subsubsection*{Параллельное чтение с перекрытием} +Для решения этой проблемы было реализовано параллельное чтение файла, при котором каждый MPI-ранк читает только свою часть файла. Алгоритм работает следующим образом: + +\begin{enumerate} + \item \textbf{Вычисление диапазонов байт}: размер файла делится на части пропорционально долям, указанным в переменной окружения \smalltexttt{DATA\_READ\_SHARES}. Для каждого ранка вычисляется диапазон байт $[\text{start}, \text{end})$, который он должен прочитать. + + \item \textbf{Добавление перекрытия}: к каждому диапазону добавляется перекрытие размером \smalltexttt{READ\_OVERLAP\_BYTES} (по умолчанию 128 КБ). Это необходимо для корректной обработки строк CSV, которые могут быть разделены на границах диапазонов: + \begin{align*} + \text{start}_\text{adj} &= \max(0, \text{start} - \text{overlap}) \\ + \text{end}_\text{adj} &= \min(\text{file\_size}, \text{end} + \text{overlap}) + \end{align*} + + \item \textbf{Обработка границ строк}: + \begin{itemize} + \item Ранк 0 пропускает заголовок CSV (первую строку) и начинает парсинг со второй строки. + \item Ранки $1 \ldots n-1$ пропускают неполную строку в начале своего диапазона, начиная парсинг с первого символа новой строки после \texttt{\textbackslash n}. + \item Ранк $n-1$ (последний) читает до конца файла, остальные ранки заканчивают чтение на последнем символе \texttt{\textbackslash n} перед концом диапазона. + \end{itemize} +\end{enumerate} + +Такой подход обеспечивает равномерное распределение нагрузки по чтению между узлами кластера и исключает дублирование или потерю данных на границах диапазонов. + +\subsubsection*{Агрегация данных по периодам} +После параллельного чтения каждый ранк имеет свой набор записей \smalltexttt{Record}. Агрегация выполняется локально на каждом ранке: + +\begin{enumerate} + \item Записи последовательно обрабатываются, для каждой записи вычисляется индекс периода: + $$\text{period} = \lfloor \text{timestamp} / \text{AGGREGATION\_INTERVAL} \rfloor$$ + + \item Для каждого периода накапливаются следующие статистики: + \begin{itemize} + \item Сумма средних цен: $\sum_{i} (Low_i + High_i) / 2$ + \item Минимальное и максимальное значение Open: $\min(Open_i)$, $\max(Open_i)$ + \item Минимальное и максимальное значение Close: $\min(Close_i)$, $\max(Close_i)$ + \item Количество записей в периоде: $count$ + \end{itemize} + + \item При смене периода статистики сохраняются в структуру \smalltexttt{PeriodStats}, и начинается накопление для следующего периода. +\end{enumerate} + +Агрегация может выполняться на CPU (последовательная обработка) или на GPU (параллельная обработка с использованием CUDA). При недоступности GPU автоматически выполняется fallback на CPU-версию. + +\subsubsection*{Удаление граничных периодов} +Из-за параллельного чтения с перекрытием первый и последний периоды каждого ранка могут содержать неполные данные. Например, если период охватывает временной интервал $[t_1, t_2)$, а ранк прочитал записи только начиная с $t_1 + \delta$, то статистики для этого периода будут искажены. + +Для устранения этой проблемы применяется функция \smalltexttt{trim\_edge\_periods}: +\begin{itemize} + \item Ранк 0 удаляет только последний период (первый период гарантированно полный, так как чтение начинается с начала файла). + \item Ранки $1 \ldots n-2$ удаляют первый и последний периоды. + \item Ранк $n-1$ удаляет только первый период (последний период гарантированно полный, так как чтение идёт до конца файла). +\end{itemize} + +\subsubsection*{Параллельный поиск интервалов изменения цены} +После агрегации каждый ранк имеет список периодов \smalltexttt{PeriodStats}, упорядоченных по времени. Для параллельного поиска интервалов используется следующий алгоритм: + +\begin{enumerate} + \item \textbf{Приём данных от предыдущего ранка}: ранк $i > 0$ ожидает от ранка $i-1$ информацию о незавершённом интервале. Если предыдущий ранк передал начало интервала, текущий ранк продолжает его обработку. + + \item \textbf{Локальная обработка периодов}: ранк последовательно обходит свои периоды, проверяя условие изменения цены: + $$\text{change} = \frac{|\text{avg}_\text{current} - \text{avg}_\text{start}|}{\text{avg}_\text{start}} \geq 0.10$$ + + Если условие выполнено, интервал завершается и сохраняется в результаты. Начинается новый интервал. + + \item \textbf{Передача данных следующему ранку}: если у ранка остался незавершённый интервал (не достигнуто изменение на 10\%), информация о начале этого интервала передаётся ранку $i+1$ через MPI. +\end{enumerate} + +Такой подход обеспечивает корректность параллельного поиска интервалов: интервалы, пересекающие границы данных между ранками, корректно обрабатываются через передачу состояния по цепочке. + +\subsubsection*{Сбор результатов} +После завершения локальной обработки ранк 0 собирает найденные интервалы от всех остальных ранков через MPI, сортирует их по времени начала и записывает в выходной файл \smalltexttt{result.csv}. + +\newpage +\subsection{GPU-ускорение агрегации данных} + +\subsubsection*{Общий алгоритм GPU-агрегации} +Агрегация данных на GPU реализована в модуле \smalltexttt{gpu\_plugin.cu} и использует библиотеку CUB (CUDA Unbound) для эффективной параллельной обработки. Общий алгоритм состоит из следующих шагов: + +\begin{enumerate} + \item \textbf{Копирование данных на GPU}: массивы timestamp, open, high, low, close копируются из оперативной памяти CPU в память GPU. + + \item \textbf{Вычисление индексов периодов}: для каждой записи параллельно вычисляется индекс периода: + $$\text{period\_id}_i = \lfloor \text{timestamp}_i / \text{AGGREGATION\_INTERVAL} \rfloor$$ + Используется простое ядро с одномерной сеткой блоков. + + \item \textbf{Run-Length Encoding (RLE)}: применяется операция RLE из библиотеки CUB для нахождения уникальных последовательных периодов и подсчёта количества записей в каждом периоде. На выходе получаем: + \begin{itemize} + \item Массив уникальных периодов: $[\text{period}_0, \text{period}_1, \ldots, \text{period}_{n-1}]$ + \item Массив длин последовательностей: $[\text{count}_0, \text{count}_1, \ldots, \text{count}_{n-1}]$ + \end{itemize} + + \item \textbf{Exclusive Scan}: применяется префиксная сумма (exclusive scan) к массиву длин для вычисления смещений начала каждого периода в исходном массиве данных: + $$\text{offset}_i = \sum_{j=0}^{i-1} \text{count}_j$$ + + \item \textbf{Агрегация по периодам}: для каждого периода параллельно вычисляются статистики (среднее значение, минимумы и максимумы). Используется одно из двух ядер в зависимости от настроек (см. следующий раздел). + + \item \textbf{Копирование результатов обратно на CPU}: агрегированные статистики копируются из памяти GPU обратно в оперативную память CPU. +\end{enumerate} + +\subsubsection*{Два варианта ядер агрегации} +Для агрегации по периодам реализовано два варианта CUDA-ядер, оптимизированных для разных сценариев использования: + +\textbf{1. Блочное ядро (Block Kernel):} + +Используется когда \smalltexttt{USE\_BLOCK\_KERNEL=1}. Оптимизировано для случая, когда в каждом периоде много записей (большой \smalltexttt{AGGREGATION\_INTERVAL}). + +Алгоритм работы: +\begin{itemize} + \item Один блок потоков обрабатывает один период. + \item Потоки внутри блока параллельно обрабатывают записи периода, каждый поток накапливает локальные статистики. + \item Используется shared memory для промежуточного хранения результатов. + \item Атомарные операции (\texttt{atomicAdd}, \texttt{atomicMin}, \texttt{atomicMax}) используются для объединения локальных результатов потоков. + \item Первый поток блока записывает финальный результат в глобальную память. +\end{itemize} + +Преимущества: эффективное использование параллелизма внутри периода, минимизация обращений к глобальной памяти за счёт shared memory. + +\textbf{2. Простое ядро (Simple Kernel):} + +Используется когда \smalltexttt{USE\_BLOCK\_KERNEL=0}. Оптимизировано для случая, когда периодов много, но в каждом периоде мало записей (малый \smalltexttt{AGGREGATION\_INTERVAL}). + +Алгоритм работы: +\begin{itemize} + \item Один поток обрабатывает один период полностью. + \item Поток последовательно обходит все записи своего периода, накапливая статистики. + \item Не используется shared memory и атомарные операции. + \item Результат сразу записывается в глобальную память. +\end{itemize} + +Преимущества: отсутствие overhead на синхронизацию потоков и атомарные операции, эффективно при большом количестве независимых периодов. + +\textbf{Выбор ядра:} + +Выбор между ядрами осуществляется через переменную окружения \smalltexttt{USE\_BLOCK\_KERNEL}: +\begin{itemize} + \item Блочное ядро предпочтительно при агрегации по дням/часам (86400 или 3600 секунд) - много записей в каждом периоде. + \item Простое ядро предпочтительно при агрегации по минутам/секундам (60 или 10 секунд) - мало записей в каждом периоде, но много периодов. +\end{itemize} + +\newpage +\subsection{Конфигурация через переменные окружения} + +Все настройки параллельного приложения вынесены в переменные окружения, которые задаются в SLURM-скрипте \smalltexttt{run.slurm}. Это обеспечивает гибкость настройки без необходимости перекомпиляции программы. + +\subsubsection*{Описание переменных окружения} + +\begin{itemize} + \item \textbf{DATA\_PATH} - полный путь к CSV-файлу с входными данными. Файл должен быть доступен на всех узлах кластера (рекомендуется использовать общую директорию, например, через NFS). + + Пример: \smalltexttt{/mnt/shared/supercomputers/data/data\_10s.csv} + + \item \textbf{DATA\_READ\_SHARES} - доли данных для каждого ранка при параллельном чтении файла, разделённые запятыми. Позволяет неравномерно распределить нагрузку по чтению, если узлы имеют разную производительность. + + Пример: \smalltexttt{10,11,13,14} означает, что файл будет разделён на части пропорционально $10:11:13:14$. Если количество значений не совпадает с количеством ранков, используется равномерное распределение. + + \item \textbf{READ\_OVERLAP\_BYTES} - размер перекрытия в байтах при параллельном чтении файла. Необходим для корректной обработки строк CSV на границах диапазонов. Значение должно быть достаточным для размещения хотя бы одной полной строки CSV. + + Значение по умолчанию: \smalltexttt{131072} (128 КБ) + + \item \textbf{AGGREGATION\_INTERVAL} - интервал агрегации в секундах. Определяет размер временного периода, по которому группируются данные. + + Типичные значения: + \begin{itemize} + \item \smalltexttt{60} - агрегация по минутам + \item \smalltexttt{600} - агрегация по 10 минутам + \item \smalltexttt{3600} - агрегация по часам + \item \smalltexttt{86400} - агрегация по дням + \end{itemize} + + \item \textbf{USE\_CUDA} - флаг использования GPU для агрегации данных. Если установлен в \smalltexttt{1}, программа попытается использовать GPU. Если GPU недоступен или флаг установлен в \smalltexttt{0}, используется CPU-версия агрегации. + + Значения: \smalltexttt{0} (отключено) или \smalltexttt{1} (включено) + + \item \textbf{USE\_BLOCK\_KERNEL} - выбор варианта CUDA-ядра для GPU-агрегации (действует только при \smalltexttt{USE\_CUDA=1}). Определяет, какое ядро будет использоваться для параллельной обработки на GPU. + + Значения: + \begin{itemize} + \item \smalltexttt{0} - использовать простое ядро (один поток на период) + \item \smalltexttt{1} - использовать блочное ядро (один блок на период) + \end{itemize} + + Рекомендации по выбору: + \begin{itemize} + \item Для больших интервалов агрегации (дни, часы) - \smalltexttt{USE\_BLOCK\_KERNEL=1} + \item Для малых интервалов агрегации (минуты, секунды) - \smalltexttt{USE\_BLOCK\_KERNEL=0} + \end{itemize} +\end{itemize} + +Пример конфигурации в \smalltexttt{run.slurm}: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +export DATA_PATH="/mnt/shared/supercomputers/data/data_10s.csv" +export DATA_READ_SHARES="10,11,13,14" +export READ_OVERLAP_BYTES=131072 +export AGGREGATION_INTERVAL=60 +export USE_CUDA=1 +export USE_BLOCK_KERNEL=0 +\end{lstlisting} + +\newpage +\subsection{Структура проекта} +Исходный проект содержит в себе следующие зависимости: +\begin{itemize} + \item CUDA-Toolkit 12.8; + \item OpenMPI 3. +\end{itemize} + +Можно выделить следующие основные сущности: +\begin{itemize} + \item \smalltexttt{run.slurm} - SLURM-скрипт для запуска параллельного приложения на 4 узлах с настройкой переменных окружения (путь к данным, доли данных для каждого ранка, интервал агрегации, использование CUDA). Исходный текст файла представлен в \hyperref[1]{Приложение A}; + \item \smalltexttt{Makefile} - файл системы сборки, описывающий компиляцию C++ исходников с помощью mpic++ и компиляцию CUDA-плагина с помощью nvcc, а также правила запуска и очистки. Исходный текст файла представлен в \hyperref[2]{Приложение Б}; + \item \smalltexttt{src/main.cpp} - основная MPI-программа: координирует выполнение параллельного чтения CSV данных, агрегацию данных по временным периодам (на CPU или GPU), поиск интервалов изменения цены и запись результатов. Исходный текст файла представлен в \hyperref[3]{Приложение В}; + \item \smalltexttt{src/csv\_loader.cpp}, \smalltexttt{src/csv\_loader.hpp} - модуль параллельной загрузки CSV-файла: каждый MPI-ранк читает свою часть файла с перекрытием для обработки границ строк, парсит записи Bitcoin данных. Исходный текст файла представлен в \hyperref[4]{Приложение Г}; + \item \smalltexttt{src/aggregation.cpp}, \smalltexttt{src/aggregation.hpp} - модуль агрегации временных рядов на CPU: группирует записи по временным интервалам, вычисляет среднее значение (Low+High)/2, минимумы и максимумы Open/Close за каждый период. Исходный текст файла представлен в \hyperref[5]{Приложение Д}; + \item \smalltexttt{src/gpu\_loader.cpp}, \smalltexttt{src/gpu\_loader.hpp} - модуль динамической загрузки GPU-плагина: проверяет доступность GPU, загружает функции из \smalltexttt{libgpu\_compute.so}, преобразует данные из AoS в SoA для передачи на GPU. Исходный текст файла представлен в \hyperref[6]{Приложение Е}; + \item \smalltexttt{src/gpu\_plugin.cu} - CUDA-модуль агрегации данных на GPU: использует библиотеку CUB для RLE и scan операций, реализует два ядра агрегации (блочное для больших интервалов и простое для множества малых периодов). Исходный текст файла представлен в \hyperref[7]{Приложение Ж}; + \item \smalltexttt{src/intervals.cpp}, \smalltexttt{src/intervals.hpp} - модуль параллельного поиска интервалов изменения цены: каждый ранк обрабатывает свою часть периодов, передаёт незавершённые интервалы следующему ранку через MPI, собирает результаты на ранке 0. Исходный текст файла представлен в \hyperref[8]{Приложение З}; + \item \smalltexttt{src/utils.cpp}, \smalltexttt{src/utils.hpp} - вспомогательный модуль: чтение переменных окружения, вычисление диапазонов байт для параллельного чтения файла, удаление граничных периодов, получение размера файла. Исходный текст файла представлен в \hyperref[9]{Приложение И}; + \item \smalltexttt{src/period\_stats.hpp} - заголовочный файл с определением структуры \smalltexttt{PeriodStats}, хранящей агрегированные статистики за один временной период (среднее значение, минимумы и максимумы Open/Close). Исходный текст файла представлен в \hyperref[10]{Приложение К}; + \item \smalltexttt{src/record.hpp} - заголовочный файл с определением структуры \smalltexttt{Record} для хранения одной записи из CSV-файла Bitcoin (timestamp, open, high, low, close, volume). Исходный текст файла представлен в \hyperref[11]{Приложение Л}; + \item \smalltexttt{data/data\_10s.csv} - текстовый файл с входными данными о стоимости Bitcoin по каждым 10 секундам в формате CSV; + \item \smalltexttt{result.csv} - выходной файл с найденными интервалами изменения цены не менее чем на 10\%. +\end{itemize} + +Также в рамках проекта используется система автоматизированной сборки. Для сборки и запуска проекта необходимо выполнить следующие команды: +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +make +make run +\end{lstlisting} + +\newpage +\begin{center} +\section*{ЗАКЛЮЧЕНИЕ} +\end{center} +\addcontentsline{toc}{section}{ЗАКЛЮЧЕНИЕ} +В ходе выполнения лабораторной работы были решены задачи, направленные на освоение параллельных вычислений с использованием разнородных типов вычислительных ресурсов. В результате работы удалось: + +\begin{itemize} + \item Создать виртуальные машины, обеспечивающие выполнение задач как на GPU-узлах, так и на CPU-узлах; + \item Настроить сеть для обеспечения стабильной связи между хост-системой и виртуальными узлами; + \item Реализовать параллельное приложение с использованием механизма OpenMPI, задействующее ресурсы разнородных узлов; + \item Изучить технологии CUDA, OpenMPI, Slurm. +\end{itemize} + +Были изучены технологии OpenMPI, CUDA Toolkit и библиотека CUB. В рамках работы было разработано параллельное приложение на языке C++, использующее разнородный вид вычислительных ресурсов для анализа временных рядов исторических данных о стоимости Bitcoin. + +Разработанная программа выполняет параллельную агрегацию временных рядов и поиск интервалов значительного изменения цены. Ключевые особенности реализации: + +\begin{itemize} + \item Параллельное чтение CSV-файла размером 2.3 ГБ с использованием техники перекрытия диапазонов для корректной обработки границ строк. + \item Гибридная агрегация данных, поддерживающая вычисления как на CPU (последовательная обработка), так и на GPU (параллельная обработка с использованием CUDA). + \item Два варианта CUDA-ядер: блочное ядро для больших интервалов агрегации (дни, часы) и простое ядро для малых интервалов (минуты, секунды). + \item Динамическая загрузка GPU-плагина через dlopen, позволяющая запускать приложение на узлах без GPU без перекомпиляции. + \item Параллельный поиск интервалов изменения цены с передачей незавершённых интервалов между ранками через MPI. + \item Гибкая конфигурация через переменные окружения, позволяющая настраивать параметры работы без изменения исходного кода. +\end{itemize} + +Для наглядной демонстрации эффективности параллельных вычислений был разработан скрипт линейной интерполяции данных, увеличивший объём исходного набора данных с 360 МБ до 2.3 ГБ. Это позволило достоверно оценить преимущества параллельной обработки и GPU-ускорения. + + + +В рамках работы получены практические знания о гетерогенных вычислительных системах и реализовано полнофункциональное параллельное приложение, эффективно использующее ресурсы разнородных вычислителей для обработки больших объёмов временных рядов. + + +\newpage +\addcontentsline{toc}{section}{Список литературы} + +\vspace{-1.5cm} +\begin{thebibliography}{0} + \bibitem{kaggle} + Zielak - Bitcoin Historical Data // kaggle URL: https://www.kaggle.com/datasets/mczielinski/bitcoin-historical-data (дата обращения: 10.01.2026). + \bibitem{hyperv} + Hyper-V Documentation // Microsoft URL: https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/ (дата обращения: 10.01.2026). + \bibitem{ubuntuDocs} + Ubuntu OS Docs // Ubuntu URL: https://ubuntu.com/server/docs (дата обращения: 10.01.2026). + \bibitem{newVmSwitch} + New-VMSwitch: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/en-us/powershell/module/hyper-v/new-vmswitch?view=windowsserver2025-ps (дата обращения: 10.01.2026). + \bibitem{newNetIpAddress} + New-NetIPAddress: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/en-us/powershell/module/nettcpip/new-netipaddress?view=windowsserver2025-ps (дата обращения: 10.01.2026). + \bibitem{newNetNat} + New-NetNat: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/en-us/powershell/module/netnat/New-NetNat?view=windowsserver2016-ps (дата обращения: 10.01.2026). + \bibitem{addNetNatStaticMapping} + Add-NetNatStaticMapping: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/ru-ru/powershell/module/netnat/add-netnatstaticmapping?view=windowsserver2022-ps (дата обращения: 10.01.2026). + \bibitem{getVmHostPartitionableGpu} + Get-VMHostPartitionableGpu: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/en-us/powershell/module/hyper-v/get-vmhostpartitionablegpu?view=windowsserver2025-ps (дата обращения: 10.01.2026). + \bibitem{setVm} + Set-VM: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/en-us/powershell/module/hyper-v/set-vm?view=windowsserver2025-ps (дата обращения: 10.01.2026). + \bibitem{rfc1094} + RFC 1094: NFS: Network File System Protocol Specification // Sun Microsystems, Inc. URL: https://datatracker.ietf.org/doc/html/rfc1094 (дата обращения: 10.01.2026). +\end{thebibliography} +\newpage + +\newpage +\begin{center} +\section*{ПРИЛОЖЕНИЕ А} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ А} +\label{1} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +#!/bin/bash +#SBATCH --job-name=btc +#SBATCH --nodes=4 +#SBATCH --ntasks=4 +#SBATCH --cpus-per-task=2 +#SBATCH --output=out.txt + +# Путь к файлу данных (должен существовать на всех узлах) +export DATA_PATH="/mnt/shared/supercomputers/data/data_10s.csv" + +# Доли данных для каждого ранка (сумма определяет пропорции) +export DATA_READ_SHARES="10,11,13,14" + +# Размер перекрытия в байтах для обработки границ строк +export READ_OVERLAP_BYTES=131072 + +# Интервал агрегации в секундах (60 = минуты, 600 = 10 минут, 86400 = дни) +export AGGREGATION_INTERVAL=60 + +# Использовать ли CUDA для агрегации (0 = нет, 1 = да) +export USE_CUDA=1 + +# Использовать ли блочное ядро (быстрее для больших интервалов, 0 = нет, 1 = да) +export USE_BLOCK_KERNEL=0 + +cd /mnt/shared/supercomputers/build +mpirun -np $SLURM_NTASKS ./bitcoin_app +\end{lstlisting} + + +\newpage +\begin{center} +\section*{ПРИЛОЖЕНИЕ Б} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Б} +\label{2} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +CXX = mpic++ +CXXFLAGS = -std=c++17 -O2 -Wall -Wextra -Wno-cast-function-type -fopenmp + +NVCC = nvcc +NVCCFLAGS = -O3 -std=c++17 -arch=sm_86 -Xcompiler -fPIC + +SRC_DIR = src +BUILD_DIR = build + +SRCS = $(wildcard $(SRC_DIR)/*.cpp) +OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRCS)) + +TARGET = $(BUILD_DIR)/bitcoin_app + +PLUGIN_SRC = $(SRC_DIR)/gpu_plugin.cu +PLUGIN = $(BUILD_DIR)/libgpu_compute.so + +all: $(PLUGIN) $(TARGET) + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) $^ -o $@ -ldl + +$(PLUGIN): $(PLUGIN_SRC) | $(BUILD_DIR) + $(NVCC) $(NVCCFLAGS) -shared $< -o $@ + +clean: + rm -rf $(BUILD_DIR) + +run: all + sbatch run.slurm + +.PHONY: all clean run +\end{lstlisting} + + +\newpage +\begin{center} +\section*{ПРИЛОЖЕНИЕ В} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ В} +\label{3} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +#include +#include +#include +#include + +#include "csv_loader.hpp" +#include "record.hpp" +#include "period_stats.hpp" +#include "aggregation.hpp" +#include "intervals.hpp" +#include "utils.hpp" +#include "gpu_loader.hpp" + +int main(int argc, char** argv) { + MPI_Init(&argc, &argv); + double total_start = MPI_Wtime(); + + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + // Проверяем доступность GPU + bool use_cuda = get_use_cuda(); + bool have_gpu = gpu_is_available(); + bool use_gpu = use_cuda && have_gpu; + + std::cout << "Rank " << rank + << ": USE_CUDA=" << use_cuda + << ", GPU available=" << have_gpu + << ", using " << (use_gpu ? "GPU" : "CPU") + << std::endl; + + // Параллельное чтение данных + double read_start = MPI_Wtime(); + std::vector records = load_csv_parallel(rank, size); + double read_time = MPI_Wtime() - read_start; + + std::cout << "Rank " << rank + << ": read " << records.size() << " records" + << " in " << std::fixed << std::setprecision(3) << read_time << " sec" + << std::endl; + + // Агрегация по периодам + double agg_start = MPI_Wtime(); + std::vector periods; + + if (use_gpu) { + int64_t interval = get_aggregation_interval(); + if (!aggregate_periods_gpu(records, interval, periods)) { + std::cerr << "Rank " << rank << ": GPU aggregation failed, falling back to CPU" << std::endl; + periods = aggregate_periods(records); + } + } else { + periods = aggregate_periods(records); + } + + double agg_time = MPI_Wtime() - agg_start; + + std::cout << "Rank " << rank + << ": aggregated " << periods.size() << " periods" + << " [" << (periods.empty() ? 0 : periods.front().period) + << ".." << (periods.empty() ? 0 : periods.back().period) << "]" + << " in " << std::fixed << std::setprecision(3) << agg_time << " sec" + << std::endl; + + // Удаляем крайние периоды (могут быть неполными из-за параллельного чтения) + trim_edge_periods(periods, rank, size); + + std::cout << "Rank " << rank + << ": after trim " << periods.size() << " periods" + << " [" << (periods.empty() ? 0 : periods.front().period) + << ".." << (periods.empty() ? 0 : periods.back().period) << "]" + << std::endl; + + // Параллельное построение интервалов + IntervalResult iv_result = find_intervals_parallel(periods, rank, size); + + std::cout << "Rank " << rank + << ": found " << iv_result.intervals.size() << " intervals" + << ", compute " << std::fixed << std::setprecision(6) << iv_result.compute_time << " sec" + << ", wait " << iv_result.wait_time << " sec" + << std::endl; + + // Сбор интервалов на ранке 0 + double collect_wait = collect_intervals(iv_result.intervals, rank, size); + + if (rank == 0) { + std::cout << "Rank 0: collected " << iv_result.intervals.size() << " total intervals" + << ", wait " << std::fixed << std::setprecision(3) << collect_wait << " sec" + << std::endl; + } + + // Запись результатов в файл (только ранк 0) + if (rank == 0) { + double write_start = MPI_Wtime(); + write_intervals("result.csv", iv_result.intervals); + double write_time = MPI_Wtime() - write_start; + + std::cout << "Rank 0: wrote result.csv" + << " in " << std::fixed << std::setprecision(3) << write_time << " sec" + << std::endl; + } + + // Вывод общего времени выполнения + MPI_Barrier(MPI_COMM_WORLD); + double total_time = MPI_Wtime() - total_start; + if (rank == 0) { + std::cout << "Total execution time: " + << std::fixed << std::setprecision(3) + << total_time << " sec" << std::endl; + } + + MPI_Finalize(); + return 0; +} +\end{lstlisting} + + +\newpage +\begin{center} +\section*{ПРИЛОЖЕНИЕ Г} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Г} +\label{4} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +#include "csv_loader.hpp" +#include +#include +#include +#include + +bool parse_csv_line(const std::string& line, Record& record) { + if (line.empty()) { + return false; + } + + std::stringstream ss(line); + std::string item; + + try { + // timestamp + if (!std::getline(ss, item, ',') || item.empty()) return false; + record.timestamp = std::stod(item); + + // open + if (!std::getline(ss, item, ',') || item.empty()) return false; + record.open = std::stod(item); + + // high + if (!std::getline(ss, item, ',') || item.empty()) return false; + record.high = std::stod(item); + + // low + if (!std::getline(ss, item, ',') || item.empty()) return false; + record.low = std::stod(item); + + // close + if (!std::getline(ss, item, ',') || item.empty()) return false; + record.close = std::stod(item); + + // volume + if (!std::getline(ss, item, ',')) return false; + // Volume может быть пустым или содержать данные + if (item.empty()) { + record.volume = 0.0; + } else { + record.volume = std::stod(item); + } + + return true; + } catch (const std::exception&) { + return false; + } +} + +std::vector load_csv_parallel(int rank, int size) { + std::vector data; + + // Читаем настройки из переменных окружения + std::string data_path = get_data_path(); + std::vector shares = get_data_read_shares(); + int64_t overlap_bytes = get_read_overlap_bytes(); + + // Получаем размер файла + int64_t file_size = get_file_size(data_path); + + // Вычисляем диапазон байт для этого ранка + ByteRange range = calculate_byte_range(rank, size, file_size, shares, overlap_bytes); + + // Открываем файл и читаем нужный диапазон + std::ifstream file(data_path, std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("Cannot open file: " + data_path); + } + + // Переходим к началу диапазона + file.seekg(range.start); + + // Читаем данные в буфер + int64_t bytes_to_read = range.end - range.start; + std::vector buffer(bytes_to_read); + file.read(buffer.data(), bytes_to_read); + int64_t bytes_read = file.gcount(); + + file.close(); + + // Преобразуем в строку для удобства парсинга + std::string content(buffer.data(), bytes_read); + + // Находим позицию начала первой полной строки + size_t parse_start = 0; + if (rank == 0) { + // Первый ранк: пропускаем заголовок (первую строку) + size_t header_end = content.find('\n'); + if (header_end != std::string::npos) { + parse_start = header_end + 1; + } + } else { + // Остальные ранки: начинаем с первого \n (пропускаем неполную строку) + size_t first_newline = content.find('\n'); + if (first_newline != std::string::npos) { + parse_start = first_newline + 1; + } + } + + // Находим позицию конца последней полной строки + size_t parse_end = content.size(); + if (rank != size - 1) { + // Не последний ранк: ищем последний \n + size_t last_newline = content.rfind('\n'); + if (last_newline != std::string::npos && last_newline > parse_start) { + parse_end = last_newline; + } + } + + // Парсим строки + size_t pos = parse_start; + while (pos < parse_end) { + size_t line_end = content.find('\n', pos); + if (line_end == std::string::npos || line_end > parse_end) { + line_end = parse_end; + } + + std::string line = content.substr(pos, line_end - pos); + + // Убираем \r если есть (Windows line endings) + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + + Record record; + if (parse_csv_line(line, record)) { + data.push_back(record); + } + + pos = line_end + 1; + } + + return data; +} +\end{lstlisting} + + +\newpage +\begin{center} +\section*{ПРИЛОЖЕНИЕ Д} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Д} +\label{5} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +#include "aggregation.hpp" +#include "utils.hpp" + +#include +#include +#include +#include + +std::vector aggregate_periods(const std::vector& records) { + const int64_t interval = get_aggregation_interval(); + + std::vector result; + if (records.empty()) return result; + + struct PeriodAccumulator { + double avg_sum = 0.0; + double open_min = std::numeric_limits::max(); + double open_max = std::numeric_limits::lowest(); + double close_min = std::numeric_limits::max(); + double close_max = std::numeric_limits::lowest(); + int64_t count = 0; + + void add(const Record& r) { + const double avg = (r.low + r.high) / 2.0; + avg_sum += avg; + open_min = std::min(open_min, r.open); + open_max = std::max(open_max, r.open); + close_min = std::min(close_min, r.close); + close_max = std::max(close_max, r.close); + ++count; + } + }; + + PeriodIndex current_period = + static_cast(records[0].timestamp) / interval; + + PeriodAccumulator acc; + acc.add(records[0]); + + for (size_t i = 1; i < records.size(); ++i) { + const Record& r = records[i]; + const PeriodIndex period = + static_cast(r.timestamp) / interval; + + if (period != current_period) { + PeriodStats stats; + stats.period = current_period; + stats.avg = acc.avg_sum / static_cast(acc.count); + stats.open_min = acc.open_min; + stats.open_max = acc.open_max; + stats.close_min = acc.close_min; + stats.close_max = acc.close_max; + stats.count = acc.count; + result.push_back(stats); + + current_period = period; + acc = PeriodAccumulator{}; + } + + acc.add(r); + } + + // последний период + PeriodStats stats; + stats.period = current_period; + stats.avg = acc.avg_sum / static_cast(acc.count); + stats.open_min = acc.open_min; + stats.open_max = acc.open_max; + stats.close_min = acc.close_min; + stats.close_max = acc.close_max; + stats.count = acc.count; + result.push_back(stats); + + return result; +} +\end{lstlisting} + + +\newpage +\begin{center} +\section*{ПРИЛОЖЕНИЕ Е} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Е} +\label{6} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +#include "gpu_loader.hpp" +#include +#include +#include + +// Структура результата GPU (должна совпадать с gpu_plugin.cu) +struct GpuPeriodStats { + int64_t period; + double avg; + double open_min; + double open_max; + double close_min; + double close_max; + int64_t count; +}; + +// Типы функций из GPU плагина +using gpu_is_available_fn = int (*)(); + +using gpu_aggregate_periods_fn = int (*)( + const double* h_timestamps, + const double* h_open, + const double* h_high, + const double* h_low, + const double* h_close, + int num_ticks, + int64_t interval, + GpuPeriodStats** h_out_stats, + int* out_num_periods +); + +using gpu_free_results_fn = void (*)(GpuPeriodStats*); + +static void* get_gpu_lib_handle() { + static void* h = dlopen("./libgpu_compute.so", RTLD_NOW | RTLD_LOCAL); + return h; +} + +bool gpu_is_available() { + void* h = get_gpu_lib_handle(); + if (!h) return false; + + auto fn = reinterpret_cast(dlsym(h, "gpu_is_available")); + if (!fn) return false; + + return fn() != 0; +} + +bool aggregate_periods_gpu( + const std::vector& records, + int64_t aggregation_interval, + std::vector& out_stats) +{ + if (records.empty()) { + out_stats.clear(); + return true; + } + + void* h = get_gpu_lib_handle(); + if (!h) { + std::cerr << "GPU: Failed to load libgpu_compute.so" << std::endl; + return false; + } + + auto aggregate_fn = reinterpret_cast( + dlsym(h, "gpu_aggregate_periods")); + auto free_fn = reinterpret_cast( + dlsym(h, "gpu_free_results")); + + if (!aggregate_fn || !free_fn) { + std::cerr << "GPU: Failed to load functions from plugin" << std::endl; + return false; + } + + int num_ticks = static_cast(records.size()); + + // Конвертируем AoS в SoA + std::vector timestamps(num_ticks); + std::vector open(num_ticks); + std::vector high(num_ticks); + std::vector low(num_ticks); + std::vector close(num_ticks); + + for (int i = 0; i < num_ticks; i++) { + timestamps[i] = records[i].timestamp; + open[i] = records[i].open; + high[i] = records[i].high; + low[i] = records[i].low; + close[i] = records[i].close; + } + + // Вызываем GPU функцию + GpuPeriodStats* gpu_stats = nullptr; + int num_periods = 0; + + int result = aggregate_fn( + timestamps.data(), + open.data(), + high.data(), + low.data(), + close.data(), + num_ticks, + aggregation_interval, + &gpu_stats, + &num_periods + ); + + if (result != 0) { + std::cerr << "GPU: Aggregation failed with code " << result << std::endl; + return false; + } + + // Конвертируем результат в PeriodStats + out_stats.clear(); + out_stats.reserve(num_periods); + + for (int i = 0; i < num_periods; i++) { + PeriodStats ps; + ps.period = gpu_stats[i].period; + ps.avg = gpu_stats[i].avg; + ps.open_min = gpu_stats[i].open_min; + ps.open_max = gpu_stats[i].open_max; + ps.close_min = gpu_stats[i].close_min; + ps.close_max = gpu_stats[i].close_max; + ps.count = gpu_stats[i].count; + out_stats.push_back(ps); + } + + // Освобождаем память + free_fn(gpu_stats); + + return true; +} +\end{lstlisting} +\newpage + + +\begin{center} +\section*{ПРИЛОЖЕНИЕ Ж} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Ж} +\label{7} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ============================================================================ +// Структуры данных +// ============================================================================ + +// Результат агрегации одного периода +struct GpuPeriodStats { + int64_t period; + double avg; + double open_min; + double open_max; + double close_min; + double close_max; + int64_t count; +}; + +// ============================================================================ +// Вспомогательные функции +// ============================================================================ + +static double get_time_ms() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0; +} + +#define CUDA_CHECK(call) do { \ + cudaError_t err = call; \ + if (err != cudaSuccess) { \ + printf("CUDA error at %s:%d: %s\n", __FILE__, __LINE__, cudaGetErrorString(err)); \ + return -1; \ + } \ +} while(0) + +// ============================================================================ +// Kernel: вычисление period_id для каждого тика +// ============================================================================ + +__global__ void compute_period_ids_kernel( + const double* __restrict__ timestamps, + int64_t* __restrict__ period_ids, + int n, + int64_t interval) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < n) { + period_ids[idx] = static_cast(timestamps[idx]) / interval; + } +} + +// ============================================================================ +// Kernel: агрегация одного периода (один блок на период) +// ============================================================================ + +__global__ void aggregate_periods_kernel( + const double* __restrict__ open, + const double* __restrict__ high, + const double* __restrict__ low, + const double* __restrict__ close, + const int64_t* __restrict__ unique_periods, + const int* __restrict__ offsets, + const int* __restrict__ counts, + int num_periods, + GpuPeriodStats* __restrict__ out_stats) +{ + int period_idx = blockIdx.x; + if (period_idx >= num_periods) return; + + int offset = offsets[period_idx]; + int count = counts[period_idx]; + + // Используем shared memory для редукции внутри блока + __shared__ double s_avg_sum; + __shared__ double s_open_min; + __shared__ double s_open_max; + __shared__ double s_close_min; + __shared__ double s_close_max; + + // Инициализация shared memory первым потоком + if (threadIdx.x == 0) { + s_avg_sum = 0.0; + s_open_min = DBL_MAX; + s_open_max = -DBL_MAX; + s_close_min = DBL_MAX; + s_close_max = -DBL_MAX; + } + __syncthreads(); + + // Локальные аккумуляторы для каждого потока + double local_avg_sum = 0.0; + double local_open_min = DBL_MAX; + double local_open_max = -DBL_MAX; + double local_close_min = DBL_MAX; + double local_close_max = -DBL_MAX; + + // Каждый поток обрабатывает свою часть тиков + for (int i = threadIdx.x; i < count; i += blockDim.x) { + int tick_idx = offset + i; + double avg = (low[tick_idx] + high[tick_idx]) / 2.0; + local_avg_sum += avg; + local_open_min = min(local_open_min, open[tick_idx]); + local_open_max = max(local_open_max, open[tick_idx]); + local_close_min = min(local_close_min, close[tick_idx]); + local_close_max = max(local_close_max, close[tick_idx]); + } + + // Редукция с использованием атомарных операций + atomicAdd(&s_avg_sum, local_avg_sum); + atomicMin(reinterpret_cast(&s_open_min), + __double_as_longlong(local_open_min)); + atomicMax(reinterpret_cast(&s_open_max), + __double_as_longlong(local_open_max)); + atomicMin(reinterpret_cast(&s_close_min), + __double_as_longlong(local_close_min)); + atomicMax(reinterpret_cast(&s_close_max), + __double_as_longlong(local_close_max)); + + __syncthreads(); + + // Первый поток записывает результат + if (threadIdx.x == 0) { + GpuPeriodStats stats; + stats.period = unique_periods[period_idx]; + stats.avg = s_avg_sum / static_cast(count); + stats.open_min = s_open_min; + stats.open_max = s_open_max; + stats.close_min = s_close_min; + stats.close_max = s_close_max; + stats.count = count; + out_stats[period_idx] = stats; + } +} + +// ============================================================================ +// Простой kernel для агрегации (один поток на период) +// Используется когда периодов много и тиков в каждом мало +// ============================================================================ + +__global__ void aggregate_periods_simple_kernel( + const double* __restrict__ open, + const double* __restrict__ high, + const double* __restrict__ low, + const double* __restrict__ close, + const int64_t* __restrict__ unique_periods, + const int* __restrict__ offsets, + const int* __restrict__ counts, + int num_periods, + GpuPeriodStats* __restrict__ out_stats) +{ + int period_idx = blockIdx.x * blockDim.x + threadIdx.x; + if (period_idx >= num_periods) return; + + int offset = offsets[period_idx]; + int count = counts[period_idx]; + + double avg_sum = 0.0; + double open_min = DBL_MAX; + double open_max = -DBL_MAX; + double close_min = DBL_MAX; + double close_max = -DBL_MAX; + + for (int i = 0; i < count; i++) { + int tick_idx = offset + i; + double avg = (low[tick_idx] + high[tick_idx]) / 2.0; + avg_sum += avg; + open_min = min(open_min, open[tick_idx]); + open_max = max(open_max, open[tick_idx]); + close_min = min(close_min, close[tick_idx]); + close_max = max(close_max, close[tick_idx]); + } + + GpuPeriodStats stats; + stats.period = unique_periods[period_idx]; + stats.avg = avg_sum / static_cast(count); + stats.open_min = open_min; + stats.open_max = open_max; + stats.close_min = close_min; + stats.close_max = close_max; + stats.count = count; + out_stats[period_idx] = stats; +} + + +// ============================================================================ +// Проверка доступности GPU +// ============================================================================ + +extern "C" int gpu_is_available() { + int n = 0; + cudaError_t err = cudaGetDeviceCount(&n); + if (err != cudaSuccess) return 0; + return (n > 0) ? 1 : 0; +} + +// ============================================================================ +// Главная функция агрегации на GPU +// ============================================================================ + +extern "C" int gpu_aggregate_periods( + const double* h_timestamps, + const double* h_open, + const double* h_high, + const double* h_low, + const double* h_close, + int num_ticks, + int64_t interval, + GpuPeriodStats** h_out_stats, + int* out_num_periods) +{ + if (num_ticks == 0) { + *h_out_stats = nullptr; + *out_num_periods = 0; + return 0; + } + + std::ostringstream output; + double total_start = get_time_ms(); + + // ======================================================================== + // Шаг 1: Выделение памяти и копирование данных на GPU + // ======================================================================== + double step1_start = get_time_ms(); + + double* d_timestamps = nullptr; + double* d_open = nullptr; + double* d_high = nullptr; + double* d_low = nullptr; + double* d_close = nullptr; + int64_t* d_period_ids = nullptr; + + size_t ticks_bytes = num_ticks * sizeof(double); + + CUDA_CHECK(cudaMalloc(&d_timestamps, ticks_bytes)); + CUDA_CHECK(cudaMalloc(&d_open, ticks_bytes)); + CUDA_CHECK(cudaMalloc(&d_high, ticks_bytes)); + CUDA_CHECK(cudaMalloc(&d_low, ticks_bytes)); + CUDA_CHECK(cudaMalloc(&d_close, ticks_bytes)); + CUDA_CHECK(cudaMalloc(&d_period_ids, num_ticks * sizeof(int64_t))); + + CUDA_CHECK(cudaMemcpy(d_timestamps, h_timestamps, ticks_bytes, cudaMemcpyHostToDevice)); + CUDA_CHECK(cudaMemcpy(d_open, h_open, ticks_bytes, cudaMemcpyHostToDevice)); + CUDA_CHECK(cudaMemcpy(d_high, h_high, ticks_bytes, cudaMemcpyHostToDevice)); + CUDA_CHECK(cudaMemcpy(d_low, h_low, ticks_bytes, cudaMemcpyHostToDevice)); + CUDA_CHECK(cudaMemcpy(d_close, h_close, ticks_bytes, cudaMemcpyHostToDevice)); + + double step1_ms = get_time_ms() - step1_start; + + // ======================================================================== + // Шаг 2: Вычисление period_id для каждого тика + // ======================================================================== + double step2_start = get_time_ms(); + + const int BLOCK_SIZE = 256; + int num_blocks = (num_ticks + BLOCK_SIZE - 1) / BLOCK_SIZE; + + compute_period_ids_kernel<<>>( + d_timestamps, d_period_ids, num_ticks, interval); + CUDA_CHECK(cudaGetLastError()); + CUDA_CHECK(cudaDeviceSynchronize()); + + double step2_ms = get_time_ms() - step2_start; + + // ======================================================================== + // Шаг 3: RLE (Run-Length Encode) для нахождения уникальных периодов + // ======================================================================== + double step3_start = get_time_ms(); + + int64_t* d_unique_periods = nullptr; + int* d_counts = nullptr; + int* d_num_runs = nullptr; + + CUDA_CHECK(cudaMalloc(&d_unique_periods, num_ticks * sizeof(int64_t))); + CUDA_CHECK(cudaMalloc(&d_counts, num_ticks * sizeof(int))); + CUDA_CHECK(cudaMalloc(&d_num_runs, sizeof(int))); + + // Определяем размер временного буфера для CUB + void* d_temp_storage = nullptr; + size_t temp_storage_bytes = 0; + + cub::DeviceRunLengthEncode::Encode( + d_temp_storage, temp_storage_bytes, + d_period_ids, d_unique_periods, d_counts, d_num_runs, + num_ticks); + + CUDA_CHECK(cudaMalloc(&d_temp_storage, temp_storage_bytes)); + + cub::DeviceRunLengthEncode::Encode( + d_temp_storage, temp_storage_bytes, + d_period_ids, d_unique_periods, d_counts, d_num_runs, + num_ticks); + CUDA_CHECK(cudaGetLastError()); + + // Копируем количество уникальных периодов + int num_periods = 0; + CUDA_CHECK(cudaMemcpy(&num_periods, d_num_runs, sizeof(int), cudaMemcpyDeviceToHost)); + + cudaFree(d_temp_storage); + d_temp_storage = nullptr; + + double step3_ms = get_time_ms() - step3_start; + + // ======================================================================== + // Шаг 4: Exclusive Scan для вычисления offsets + // ======================================================================== + double step4_start = get_time_ms(); + + int* d_offsets = nullptr; + CUDA_CHECK(cudaMalloc(&d_offsets, num_periods * sizeof(int))); + + temp_storage_bytes = 0; + cub::DeviceScan::ExclusiveSum( + d_temp_storage, temp_storage_bytes, + d_counts, d_offsets, num_periods); + + CUDA_CHECK(cudaMalloc(&d_temp_storage, temp_storage_bytes)); + + cub::DeviceScan::ExclusiveSum( + d_temp_storage, temp_storage_bytes, + d_counts, d_offsets, num_periods); + CUDA_CHECK(cudaGetLastError()); + + cudaFree(d_temp_storage); + + double step4_ms = get_time_ms() - step4_start; + + // ======================================================================== + // Шаг 5: Агрегация периодов + // ======================================================================== + double step5_start = get_time_ms(); + + GpuPeriodStats* d_out_stats = nullptr; + CUDA_CHECK(cudaMalloc(&d_out_stats, num_periods * sizeof(GpuPeriodStats))); + + // Выбор ядра через переменную окружения USE_BLOCK_KERNEL + const char* env_block_kernel = std::getenv("USE_BLOCK_KERNEL"); + if (env_block_kernel == nullptr) { + printf("Error: Environment variable USE_BLOCK_KERNEL is not set\n"); + return -1; + } + bool use_block_kernel = std::atoi(env_block_kernel) != 0; + + if (use_block_kernel) { + // Блочное ядро: один блок на период, потоки параллельно обрабатывают тики + // Лучше для больших интервалов с множеством тиков в каждом периоде + aggregate_periods_kernel<<>>( + d_open, d_high, d_low, d_close, + d_unique_periods, d_offsets, d_counts, + num_periods, d_out_stats); + } else { + // Простое ядро: один поток на период + // Лучше для множества периодов с малым количеством тиков в каждом + int agg_blocks = (num_periods + BLOCK_SIZE - 1) / BLOCK_SIZE; + aggregate_periods_simple_kernel<<>>( + d_open, d_high, d_low, d_close, + d_unique_periods, d_offsets, d_counts, + num_periods, d_out_stats); + } + + + CUDA_CHECK(cudaGetLastError()); + CUDA_CHECK(cudaDeviceSynchronize()); + + double step5_ms = get_time_ms() - step5_start; + + // ======================================================================== + // Шаг 6: Копирование результатов на CPU + // ======================================================================== + double step6_start = get_time_ms(); + + GpuPeriodStats* h_stats = new GpuPeriodStats[num_periods]; + CUDA_CHECK(cudaMemcpy(h_stats, d_out_stats, num_periods * sizeof(GpuPeriodStats), + cudaMemcpyDeviceToHost)); + + double step6_ms = get_time_ms() - step6_start; + + // ======================================================================== + // Шаг 7: Освобождение GPU памяти + // ======================================================================== + double step7_start = get_time_ms(); + + cudaFree(d_timestamps); + cudaFree(d_open); + cudaFree(d_high); + cudaFree(d_low); + cudaFree(d_close); + cudaFree(d_period_ids); + cudaFree(d_unique_periods); + cudaFree(d_counts); + cudaFree(d_offsets); + cudaFree(d_num_runs); + cudaFree(d_out_stats); + + double step7_ms = get_time_ms() - step7_start; + + // ======================================================================== + // Итого + // ======================================================================== + double total_ms = get_time_ms() - total_start; + + // Формируем весь вывод одной строкой + output << " GPU aggregation (" << num_ticks << " ticks, interval=" << interval << " sec, kernel=" << (use_block_kernel ? "block" : "simple") << "):\n"; + output << " 1. Malloc + H->D copy: " << std::fixed << std::setprecision(3) << std::setw(7) << step1_ms << " ms\n"; + output << " 2. Compute period_ids: " << std::setw(7) << step2_ms << " ms\n"; + output << " 3. RLE (CUB): " << std::setw(7) << step3_ms << " ms (" << num_periods << " periods)\n"; + output << " 4. Exclusive scan: " << std::setw(7) << step4_ms << " ms\n"; + output << " 5. Aggregation kernel: " << std::setw(7) << step5_ms << " ms (" << (use_block_kernel ? "block" : "simple") << ")\n"; + output << " 6. D->H copy: " << std::setw(7) << step6_ms << " ms\n"; + output << " 7. Free GPU memory: " << std::setw(7) << step7_ms << " ms\n"; + output << " GPU TOTAL: " << std::setw(7) << total_ms << " ms\n"; + + // Выводим всё одним принтом + printf("%s", output.str().c_str()); + fflush(stdout); + + *h_out_stats = h_stats; + *out_num_periods = num_periods; + + return 0; +} + +// ============================================================================ +// Освобождение памяти результатов +// ============================================================================ + +extern "C" void gpu_free_results(GpuPeriodStats* stats) { + delete[] stats; +} +\end{lstlisting} +\newpage + + +\begin{center} +\section*{ПРИЛОЖЕНИЕ З} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ З} +\label{8} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +#include "intervals.hpp" +#include "utils.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +// Вспомогательная структура для накопления min/max в интервале +struct IntervalAccumulator { + PeriodIndex start_period; + double start_avg; + double open_min; + double open_max; + double close_min; + double close_max; + + void init(const PeriodStats& p) { + start_period = p.period; + start_avg = p.avg; + open_min = p.open_min; + open_max = p.open_max; + close_min = p.close_min; + close_max = p.close_max; + } + + void update(const PeriodStats& p) { + open_min = std::min(open_min, p.open_min); + open_max = std::max(open_max, p.open_max); + close_min = std::min(close_min, p.close_min); + close_max = std::max(close_max, p.close_max); + } + + Interval finalize(const PeriodStats& end_period, double change) const { + Interval iv; + iv.start_period = start_period; + iv.end_period = end_period.period; + iv.start_avg = start_avg; + iv.end_avg = end_period.avg; + iv.change = change; + iv.open_min = std::min(open_min, end_period.open_min); + iv.open_max = std::max(open_max, end_period.open_max); + iv.close_min = std::min(close_min, end_period.close_min); + iv.close_max = std::max(close_max, end_period.close_max); + return iv; + } +}; + +// Упакованная структура PeriodStats для MPI передачи (8 doubles) +struct PackedPeriodStats { + double period; // PeriodIndex as double + double avg; + double open_min; + double open_max; + double close_min; + double close_max; + double count; // int64_t as double + double valid; // флаг валидности (1.0 = valid, 0.0 = invalid) + + void pack(const PeriodStats& ps) { + period = static_cast(ps.period); + avg = ps.avg; + open_min = ps.open_min; + open_max = ps.open_max; + close_min = ps.close_min; + close_max = ps.close_max; + count = static_cast(ps.count); + valid = 1.0; + } + + PeriodStats unpack() const { + PeriodStats ps; + ps.period = static_cast(period); + ps.avg = avg; + ps.open_min = open_min; + ps.open_max = open_max; + ps.close_min = close_min; + ps.close_max = close_max; + ps.count = static_cast(count); + return ps; + } + + bool is_valid() const { return valid > 0.5; } + void set_invalid() { valid = 0.0; } +}; + +IntervalResult find_intervals_parallel( + const std::vector& periods, + int rank, int size, + double threshold) +{ + IntervalResult result; + result.compute_time = 0.0; + result.wait_time = 0.0; + + if (periods.empty()) { + if (rank < size - 1) { + PackedPeriodStats invalid; + invalid.set_invalid(); + MPI_Send(&invalid, 8, MPI_DOUBLE, rank + 1, 0, MPI_COMM_WORLD); + } + return result; + } + + double compute_start = MPI_Wtime(); + + size_t process_until = (rank == size - 1) ? periods.size() : periods.size() - 1; + + IntervalAccumulator acc; + size_t start_idx = 0; + bool have_pending_interval = false; + + if (rank > 0) { + double wait_start = MPI_Wtime(); + + PackedPeriodStats received; + MPI_Recv(&received, 8, MPI_DOUBLE, rank - 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + result.wait_time = MPI_Wtime() - wait_start; + compute_start = MPI_Wtime(); + + if (received.is_valid()) { + PeriodStats prev_period = received.unpack(); + + for (start_idx = 0; start_idx < periods.size(); start_idx++) { + if (periods[start_idx].period > prev_period.period) { + break; + } + } + + if (start_idx < process_until) { + acc.init(prev_period); + have_pending_interval = true; + + for (size_t i = start_idx; i < process_until; i++) { + acc.update(periods[i]); + + double change = std::abs(periods[i].avg - acc.start_avg) / acc.start_avg; + + if (change >= threshold) { + result.intervals.push_back(acc.finalize(periods[i], change)); + have_pending_interval = false; + + start_idx = i + 1; + if (start_idx < process_until) { + acc.init(periods[start_idx]); + have_pending_interval = true; + } + } + } + } + } else { + if (process_until > 0) { + acc.init(periods[0]); + have_pending_interval = true; + start_idx = 0; + } + } + } else { + if (process_until > 0) { + acc.init(periods[0]); + have_pending_interval = true; + start_idx = 0; + } + } + + if (rank == 0 && have_pending_interval) { + for (size_t i = 1; i < process_until; i++) { + acc.update(periods[i]); + + double change = std::abs(periods[i].avg - acc.start_avg) / acc.start_avg; + + if (change >= threshold) { + result.intervals.push_back(acc.finalize(periods[i], change)); + have_pending_interval = false; + + start_idx = i + 1; + if (start_idx < process_until) { + acc.init(periods[start_idx]); + have_pending_interval = true; + } + } + } + } + + if (rank == size - 1 && have_pending_interval && !periods.empty()) { + const auto& last_period = periods.back(); + double change = std::abs(last_period.avg - acc.start_avg) / acc.start_avg; + result.intervals.push_back(acc.finalize(last_period, change)); + } + + result.compute_time = MPI_Wtime() - compute_start; + + if (rank < size - 1) { + PackedPeriodStats to_send; + + if (have_pending_interval) { + PeriodStats start_period; + start_period.period = acc.start_period; + start_period.avg = acc.start_avg; + start_period.open_min = acc.open_min; + start_period.open_max = acc.open_max; + start_period.close_min = acc.close_min; + start_period.close_max = acc.close_max; + start_period.count = 0; + to_send.pack(start_period); + } else if (periods.size() >= 2) { + to_send.pack(periods[periods.size() - 2]); + } else { + to_send.set_invalid(); + } + + MPI_Send(&to_send, 8, MPI_DOUBLE, rank + 1, 0, MPI_COMM_WORLD); + } + + return result; +} + +double collect_intervals( + std::vector& local_intervals, + int rank, int size) +{ + double wait_time = 0.0; + + if (rank == 0) { + for (int r = 1; r < size; r++) { + double wait_start = MPI_Wtime(); + + int count; + MPI_Recv(&count, 1, MPI_INT, r, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + if (count > 0) { + std::vector buffer(count * 9); + MPI_Recv(buffer.data(), count * 9, MPI_DOUBLE, r, 2, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + + for (int i = 0; i < count; i++) { + Interval iv; + iv.start_period = static_cast(buffer[i * 9 + 0]); + iv.end_period = static_cast(buffer[i * 9 + 1]); + iv.open_min = buffer[i * 9 + 2]; + iv.open_max = buffer[i * 9 + 3]; + iv.close_min = buffer[i * 9 + 4]; + iv.close_max = buffer[i * 9 + 5]; + iv.start_avg = buffer[i * 9 + 6]; + iv.end_avg = buffer[i * 9 + 7]; + iv.change = buffer[i * 9 + 8]; + local_intervals.push_back(iv); + } + } + + wait_time += MPI_Wtime() - wait_start; + } + + std::sort(local_intervals.begin(), local_intervals.end(), + [](const Interval& a, const Interval& b) { + return a.start_period < b.start_period; + }); + } else { + int count = static_cast(local_intervals.size()); + MPI_Send(&count, 1, MPI_INT, 0, 1, MPI_COMM_WORLD); + + if (count > 0) { + std::vector buffer(count * 9); + for (int i = 0; i < count; i++) { + const auto& iv = local_intervals[i]; + buffer[i * 9 + 0] = static_cast(iv.start_period); + buffer[i * 9 + 1] = static_cast(iv.end_period); + buffer[i * 9 + 2] = iv.open_min; + buffer[i * 9 + 3] = iv.open_max; + buffer[i * 9 + 4] = iv.close_min; + buffer[i * 9 + 5] = iv.close_max; + buffer[i * 9 + 6] = iv.start_avg; + buffer[i * 9 + 7] = iv.end_avg; + buffer[i * 9 + 8] = iv.change; + } + MPI_Send(buffer.data(), count * 9, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD); + } + } + + return wait_time; +} + +std::string period_index_to_datetime(PeriodIndex period) { + int64_t interval = get_aggregation_interval(); + time_t ts = static_cast(period) * interval; + struct tm* tm_info = gmtime(&ts); + + std::ostringstream oss; + oss << std::setfill('0') + << (tm_info->tm_year + 1900) << "-" + << std::setw(2) << (tm_info->tm_mon + 1) << "-" + << std::setw(2) << tm_info->tm_mday << " " + << std::setw(2) << tm_info->tm_hour << ":" + << std::setw(2) << tm_info->tm_min << ":" + << std::setw(2) << tm_info->tm_sec; + + return oss.str(); +} + +void write_intervals(const std::string& filename, const std::vector& intervals) { + std::ofstream out(filename); + + out << std::fixed << std::setprecision(2); + out << "start_datetime,end_datetime,open_min,open_max,close_min,close_max,start_avg,end_avg,change\n"; + + for (const auto& iv : intervals) { + out << period_index_to_datetime(iv.start_period) << "," + << period_index_to_datetime(iv.end_period) << "," + << iv.open_min << "," + << iv.open_max << "," + << iv.close_min << "," + << iv.close_max << "," + << iv.start_avg << "," + << iv.end_avg << "," + << std::setprecision(6) << iv.change << "\n"; + } +} +\end{lstlisting} + + +\newpage +\begin{center} +\section*{ПРИЛОЖЕНИЕ И} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ И} +\label{9} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +#include "utils.hpp" +#include +#include +#include +#include + +int get_num_cpu_threads() { + const char* env_threads = std::getenv("NUM_CPU_THREADS"); + int num_cpu_threads = 1; + if (env_threads) { + num_cpu_threads = std::atoi(env_threads); + if (num_cpu_threads < 1) num_cpu_threads = 1; + } + return num_cpu_threads; +} + +std::string get_env(const char* name) { + const char* env = std::getenv(name); + if (!env) { + throw std::runtime_error(std::string("Environment variable not set: ") + name); + } + return std::string(env); +} + +std::string get_data_path() { + return get_env("DATA_PATH"); +} + +std::vector get_data_read_shares() { + std::vector shares; + std::stringstream ss(get_env("DATA_READ_SHARES")); + std::string item; + while (std::getline(ss, item, ',')) { + shares.push_back(std::stoi(item)); + } + return shares; +} + +int64_t get_read_overlap_bytes() { + return std::stoll(get_env("READ_OVERLAP_BYTES")); +} + +int64_t get_aggregation_interval() { + return std::stoll(get_env("AGGREGATION_INTERVAL")); +} + +bool get_use_cuda() { + return std::stoi(get_env("USE_CUDA")) != 0; +} + +int64_t get_file_size(const std::string& path) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + throw std::runtime_error("Cannot open file: " + path); + } + return static_cast(file.tellg()); +} + +ByteRange calculate_byte_range(int rank, int size, int64_t file_size, + const std::vector& shares, int64_t overlap_bytes) { + std::vector effective_shares; + if (shares.size() == static_cast(size)) { + effective_shares = shares; + } else { + effective_shares.assign(size, 1); + } + + int total_shares = std::accumulate(effective_shares.begin(), effective_shares.end(), 0); + int64_t bytes_per_share = file_size / total_shares; + + int64_t base_start = 0; + for (int i = 0; i < rank; i++) { + base_start += bytes_per_share * effective_shares[i]; + } + + int64_t base_end = base_start + bytes_per_share * effective_shares[rank]; + + ByteRange range; + + if (rank == 0) { + range.start = 0; + range.end = std::min(base_end + overlap_bytes, file_size); + } else if (rank == size - 1) { + range.start = std::max(base_start - overlap_bytes, static_cast(0)); + range.end = file_size; + } else { + range.start = std::max(base_start - overlap_bytes, static_cast(0)); + range.end = std::min(base_end + overlap_bytes, file_size); + } + + return range; +} + +void trim_edge_periods(std::vector& periods, int rank, int size) { + if (periods.empty()) return; + + if (rank == 0) { + periods.pop_back(); + } else if (rank == size - 1) { + periods.erase(periods.begin()); + } else { + periods.pop_back(); + periods.erase(periods.begin()); + } +} +\end{lstlisting} + + +\newpage +\begin{center} +\section*{ПРИЛОЖЕНИЕ К} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ К} +\label{10} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +#pragma once +#include + +using PeriodIndex = int64_t; + +// Агрегированные данные за один период +struct PeriodStats { + PeriodIndex period; // индекс периода (timestamp / AGGREGATION_INTERVAL) + double avg; // среднее значение (Low + High) / 2 по всем записям + double open_min; // минимальный Open за период + double open_max; // максимальный Open за период + double close_min; // минимальный Close за период + double close_max; // максимальный Close за период + int64_t count; // количество записей, по которым агрегировали +}; +\end{lstlisting} + + +\newpage +\begin{center} +\section*{ПРИЛОЖЕНИЕ Л} +\end{center} +\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Л} +\label{11} +\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}] +#pragma once +#include + +struct Record { + double timestamp; + double open; + double high; + double low; + double close; + double volume; +}; +\end{lstlisting} + +\end{document}