diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 655f330..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - // Используйте IntelliSense, чтобы узнать о возможных атрибутах. - // Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов. - // Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 63f7a3f..0e1ff7d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,13 +8,13 @@ "-fdiagnostics-color=always", "-g", "-fopenmp", - "${fileDirname}/main.cpp", - "${fileDirname}/Xenith/core.cpp", - "${fileDirname}/Xenith/token/token.cpp", - "-o", - "${fileDirname}/main", - "-I", "${fileDirname}/Xenith", - "-I", "${fileDirname}/Xenith/token" + "${workspaceFolder}/main.cpp", + "${workspaceFolder}/Xenith/core.cpp", + "${workspaceFolder}/Xenith/token/token.cpp", + "-I", "${workspaceFolder}/Xenith", + "-I", "${workspaceFolder}/Xenith/token", + "-o", "${workspaceFolder}/main", + "-lvulkan" ], "options": { "cwd": "${fileDirname}" diff --git a/Xenith/core.cpp b/Xenith/core.cpp index 4ff2a76..3660fa0 100644 --- a/Xenith/core.cpp +++ b/Xenith/core.cpp @@ -2,10 +2,46 @@ #include #include #include +#include +#include +#include +#include +#include -#define MAX_CORES 16 -NeuralNetwork::NeuralNetwork(LayerStructure_t layers[], int count) : numLayers(count) { + +NeuralNetwork::NeuralNetwork(LayerStructure_t layers[], int count, bool useVulkan) : numLayers(count) { + + if (useVulkan) { + + vk::ApplicationInfo appInfo{"Xenith", 1, nullptr, 0, VK_API_VERSION_1_1}; + instance = vk::createInstance({{}, &appInfo}); + + auto physicalDevices = instance.enumeratePhysicalDevices(); + physDev = physicalDevices[0]; + auto props = physDev.getProperties(); + std::cout << "Используем GPU: " << props.deviceName << std::endl; + + // 3. Поиск очереди для вычислений + auto queueProps = physDev.getQueueFamilyProperties(); + int computeFamily = -1; + for (int i = 0; i < queueProps.size(); i++) { + if (queueProps[i].queueFlags & vk::QueueFlagBits::eCompute) { + computeFamily = i; break; + } + } + if (computeFamily == -1) throw std::runtime_error("GPU не поддерживает Compute"); + + // 4. Логическое устройство + float priority = 1.0f; + vk::DeviceQueueCreateInfo queueInfo({}, (uint32_t)computeFamily, 1, &priority); + vk::DeviceCreateInfo deviceCreateInfo({}, 1, &queueInfo); + device = physDev.createDevice(deviceCreateInfo); + + queue = device.getQueue(computeFamily, 0); + + } + for (int i = 0; i < count; i++) sizes.push_back(layers[i].size); for (int i = 0; i < count - 1; i++) { std::vector> layerW; @@ -41,8 +77,9 @@ std::vector NeuralNetwork::feedForward(const std::vector& input) } + double NeuralNetwork::train(const std::vector& input, const std::vector& target, double lr) { - omp_set_num_threads(MAX_CORES); + omp_set_num_threads(cpu_count); std::vector pred = feedForward(input); std::vector> errors(numLayers); @@ -83,3 +120,102 @@ double NeuralNetwork::train(const std::vector& input, const std::vector< return totalErr; } + +uint32_t NeuralNetwork::findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) { + vk::PhysicalDeviceMemoryProperties memProperties = physDev.getMemoryProperties(); + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + throw std::runtime_error("Не удалось найти подходящий тип памяти!"); +} + + +double NeuralNetwork::trainVulkan() { + // 1. Создание буферов + vk::Buffer inputBuffer = device.createBuffer({{}, sizeof(float) * 2, vk::BufferUsageFlagBits::eStorageBuffer}); + vk::Buffer outputBuffer = device.createBuffer({{}, sizeof(float), vk::BufferUsageFlagBits::eStorageBuffer}); + + // 2. Выделение и привязка памяти для ВХОДА + vk::MemoryRequirements inReq = device.getBufferMemoryRequirements(inputBuffer); + vk::DeviceMemory inputMemory = device.allocateMemory({ + inReq.size, + findMemoryType(inReq.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) + }); + device.bindBufferMemory(inputBuffer, inputMemory, 0); // КРИТИЧНО: привязываем память к буферу + + // 3. Копирование данных во входной буфер + float inputData[2] = {2.51f, 2.32f}; + void* pIn = device.mapMemory(inputMemory, 0, sizeof(float) * 2); + memcpy(pIn, inputData, sizeof(float) * 2); + device.unmapMemory(inputMemory); + + // 4. Выделение и привязка памяти для ВЫХОДА + vk::MemoryRequirements outReq = device.getBufferMemoryRequirements(outputBuffer); + vk::DeviceMemory outputMemory = device.allocateMemory({ + outReq.size, + findMemoryType(outReq.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) + }); + device.bindBufferMemory(outputBuffer, outputMemory, 0); + + // 5. ДЕСКРИПТОРЫ (Связь C++ -> Шейдер) + // Описываем, что у нас есть 2 слота (binding 0 и 1) + std::vector bindings = { + {0, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute}, + {1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute} + }; + vk::DescriptorSetLayout dsLayout = device.createDescriptorSetLayout({{}, (uint32_t)bindings.size(), bindings.data()}); + + // Создаем пул и выделяем сет дескрипторов + vk::DescriptorPoolSize poolSize{vk::DescriptorType::eStorageBuffer, 2}; + vk::DescriptorPool pool = device.createDescriptorPool({{}, 1, 1, &poolSize}); + vk::DescriptorSet ds = device.allocateDescriptorSets({pool, 1, &dsLayout})[0]; + + // Указываем, какие именно буферы в какие слоты вставить + vk::DescriptorBufferInfo bInInfo{inputBuffer, 0, VK_WHOLE_SIZE}; + vk::DescriptorBufferInfo bOutInfo{outputBuffer, 0, VK_WHOLE_SIZE}; + device.updateDescriptorSets({ + {ds, 0, 0, 1, vk::DescriptorType::eStorageBuffer, nullptr, &bInInfo}, + {ds, 1, 0, 1, vk::DescriptorType::eStorageBuffer, nullptr, &bOutInfo} + }, {}); + + // 6. ПАЙПЛАЙН (Загрузка шейдера) + auto shaderCode = readFile("shader.comp.spv"); // Твоя функция чтения файла + vk::ShaderModule shaderModule = device.createShaderModule({{}, shaderCode.size(), (uint32_t*)shaderCode.data()}); + vk::PipelineLayout pipeLayout = device.createPipelineLayout({{}, 1, &dsLayout}); + + vk::ComputePipelineCreateInfo pipeInfo{{}, {{}, vk::ShaderStageFlagBits::eCompute, shaderModule, "main"}, pipeLayout}; + vk::Pipeline pipeline = device.createComputePipeline(nullptr, pipeInfo).value; + + // 7. КОМАНДЫ И ЗАПУСК (Command Buffer) + // (Предполагаем, что cmdPool и queue уже созданы в классе) + vk::CommandBufferAllocateInfo cmdAllocInfo(cmdPool, vk::CommandBufferLevel::ePrimary, 1); + vk::CommandBuffer cmd = device.allocateCommandBuffers(cmdAllocInfo)[0]; + + cmd.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + cmd.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline); + cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute, pipeLayout, 0, {ds}, {}); + cmd.dispatch(1, 1, 1); // Запускаем 1 поток + cmd.end(); + + queue.submit(vk::SubmitInfo(0, nullptr, nullptr, 1, &cmd), nullptr); + queue.waitIdle(); + + // 8. ЗАБИРАЕМ РЕЗУЛЬТАТ + float result = 0; + void* pOut = device.mapMemory(outputMemory, 0, sizeof(float)); + memcpy(&result, pOut, sizeof(float)); + device.unmapMemory(outputMemory); + + // Очистка (в реальном коде лучше делать в деструкторе) + device.destroyPipeline(pipeline); + device.destroyPipelineLayout(pipeLayout); + device.destroyShaderModule(shaderModule); + device.destroyDescriptorPool(pool); + device.destroyDescriptorSetLayout(dsLayout); + device.destroyBuffer(inputBuffer); device.freeMemory(inputMemory); + device.destroyBuffer(outputBuffer); device.freeMemory(outputMemory); + + return (double)result; +} \ No newline at end of file diff --git a/Xenith/core.hpp b/Xenith/core.hpp index 67e552e..55c53aa 100644 --- a/Xenith/core.hpp +++ b/Xenith/core.hpp @@ -4,6 +4,12 @@ #include "typedef.hpp" #include #include +#include "core.hpp" +#include +#include +#include +#include +#include class NeuralNetwork { private: @@ -13,13 +19,23 @@ private: std::vector> biases; std::vector> outputs; + vk::Instance instance; + vk::PhysicalDevice physDev; + vk::Device device; + vk::Queue queue; + vk::CommandPool cmdPool; + + uint32_t NeuralNetwork::findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties); + double sigmoid(double x) { return 1.0 / (1.0 + exp(-x)); } double sigmoidDeriv(double x) { return x * (1.0 - x); } public: - NeuralNetwork(LayerStructure_t layers[], int count); + int cpu_count = 1; + NeuralNetwork(LayerStructure_t layers[], int count, bool useVulkan = false); std::vector feedForward(const std::vector& input); double train(const std::vector& input, const std::vector& target, double lr); + double trainVulkan(); }; #endif \ No newline at end of file diff --git a/Xenith/shader.comp b/Xenith/shader.comp index e288648..789b365 100644 --- a/Xenith/shader.comp +++ b/Xenith/shader.comp @@ -1,2 +1,16 @@ #version 450 +layout(local_size_x = 1) in; // Запускаем 1 поток + +layout(std430, binding = 0) buffer InputBuffer { + float a; + float b; +} inputs; + +layout(std430, binding = 1) buffer OutputBuffer { + float result; +} outputs; + +void main() { + outputs.result = inputs.a * inputs.b; +} diff --git a/Xenith/shader.comp.spv b/Xenith/shader.comp.spv new file mode 100644 index 0000000..6ea919e Binary files /dev/null and b/Xenith/shader.comp.spv differ diff --git a/main b/main index 7e88108..392016e 100755 Binary files a/main and b/main differ diff --git a/main.cpp b/main.cpp index a7eef13..c5abef7 100644 --- a/main.cpp +++ b/main.cpp @@ -10,6 +10,8 @@ #include "Xenith/token/token.hpp" #include + + std::string currentSystemPrompt = ""; LayerStructure_t layers[] = { @@ -113,7 +115,7 @@ int main() { int numLayers = sizeof(layers) / sizeof(layers[0]); - NeuralNetwork nn(layers, numLayers); + NeuralNetwork nn(layers, numLayers, true); while (true) { std::cout << "xentith~$ "; @@ -187,8 +189,10 @@ int main() { std::getline(std::cin, currentSystemPrompt); std::cout << "System Prompt updated!" << std::endl; - } - else if (cmdIn == "/help") { + } else if (cmdIn == "/trainVulkan") { + nn.trainVulkan(); + + } else if (cmdIn == "/help") { std::cout << "\n--- MENU ---" << std::endl; std::cout << "/train\n/trainFile\n/sysPrompt\n/help\n/exit\n"; diff --git a/vulkan_test.cpp b/vulkan_test.cpp new file mode 100644 index 0000000..07ce909 --- /dev/null +++ b/vulkan_test.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include + +// --- ШАГ 0: ШЕЙДЕР --- +// Это микро-программа, которая будет работать прямо внутри видеокарты. +// В обучении нейронки один шейдер будет делать FeedForward, другой - расчет ошибок. + +int main() { + try { + const uint32_t N = 1024; + const size_t bufferSize = N * N * sizeof(float); + + // --- ШАГ 1: INSTANCE (ЭКЗЕМПЛЯР) --- + // Это связь твоего приложения с драйвером Vulkan. + // Здесь мы говорим: "Привет, я хочу использовать Vulkan версии 1.1". + vk::ApplicationInfo appInfo{"NN_GPU", 1, nullptr, 0, VK_API_VERSION_1_1}; + vk::Instance instance = vk::createInstance({{}, &appInfo}); + + // --- ШАГ 2: PHYSICAL DEVICE (ЖЕЛЕЗО) --- + // Мы ищем физическую видеокарту. В системе их может быть несколько. + auto physicalDevices = instance.enumeratePhysicalDevices(); + vk::PhysicalDevice physDev = physicalDevices[0]; + + // --- ШАГ 3: QUEUE FAMILY (ОЧЕРЕДЬ КОМАНД) --- + // Видеокарта — это отдельный процессор. Мы не вызываем функции GPU напрямую, + // мы отправляем "список дел" (командный буфер) в очередь (Queue). + // Нам нужна очередь, умеющая в Compute (вычисления). + auto queueProps = physDev.getQueueFamilyProperties(); + uint32_t computeFamily = 0; // Для упрощения берем первую, но в идеале нужно искать флаг eCompute + + // --- ШАГ 4: LOGICAL DEVICE (ЛОГИЧЕСКИЙ ИНТЕРФЕЙС) --- + // Это наш "пульт управления" конкретной картой. + float priority = 1.0f; + vk::DeviceQueueCreateInfo qInfo({}, computeFamily, 1, &priority); + vk::Device device = physDev.createDevice({{}, 1, &qInfo}); + vk::Queue queue = device.getQueue(computeFamily, 0); + + // --- ШАГ 5: МЕНЕДЖМЕНТ ПАМЯТИ (БУФЕРЫ) --- + // Видеокарта не видит твой std::vector напрямую. + // Нужно: 1. Создать буфер в Vulkan. 2. Выделить под него видеопамять. + auto createBuffer = [&](vk::BufferUsageFlags usage) { + // Создаем описание буфера + vk::Buffer buffer = device.createBuffer({{}, bufferSize, usage}); + + // Узнаем требования буфера к памяти (сколько байт и какой тип) + vk::MemoryRequirements memReq = device.getBufferMemoryRequirements(buffer); + vk::PhysicalDeviceMemoryProperties memProp = physDev.getMemoryProperties(); + + // Ищем тип памяти, который доступен и для GPU, и для CPU (HostVisible), + // чтобы мы могли скопировать туда веса нейронки. + uint32_t memType = 0; + for (uint32_t i = 0; i < memProp.memoryTypeCount; i++) { + if ((memReq.memoryTypeBits & (1 << i)) && + (memProp.memoryTypes[i].propertyFlags & (vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent))) { + memType = i; break; + } + } + + vk::DeviceMemory memory = device.allocateMemory({memReq.size, memType}); + device.bindBufferMemory(buffer, memory, 0); + return std::make_pair(buffer, memory); + }; + + // Создаем 3 буфера: Веса, Входы, Результат + auto [bufA, memA] = createBuffer(vk::BufferUsageFlagBits::eStorageBuffer); + auto [bufB, memB] = createBuffer(vk::BufferUsageFlagBits::eStorageBuffer); + auto [bufC, memC] = createBuffer(vk::BufferUsageFlagBits::eStorageBuffer); + + // --- ШАГ 6: DESCRIPTORS (СВЯЗКА ШЕЙДЕРА И ПАМЯТИ) --- + // Шейдеру нужно знать, что в "binding 0" лежит Матрица А, а в "binding 1" - Матрица B. + // Это настраивается через Descriptor Sets. + std::vector bindings = { + {0, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute}, + {1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute}, + {2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute} + }; + vk::DescriptorSetLayout dsLayout = device.createDescriptorSetLayout({{}, (uint32_t)bindings.size(), bindings.data()}); + + // Создаем пул дескрипторов и сам набор + vk::DescriptorPoolSize poolSize{vk::DescriptorType::eStorageBuffer, 3}; + vk::DescriptorPool pool = device.createDescriptorPool({{}, 1, 1, &poolSize}); + vk::DescriptorSet ds = device.allocateDescriptorSets({pool, 1, &dsLayout})[0]; + + // Привязываем наши буферы к дескрипторам + vk::DescriptorBufferInfo bInfoA{bufA, 0, VK_WHOLE_SIZE}, bInfoB{bufB, 0, VK_WHOLE_SIZE}, bInfoC{bufC, 0, VK_WHOLE_SIZE}; + device.updateDescriptorSets({ + {ds, 0, 0, 1, vk::DescriptorType::eStorageBuffer, nullptr, &bInfoA}, + {ds, 1, 0, 1, vk::DescriptorType::eStorageBuffer, nullptr, &bInfoB}, + {ds, 2, 0, 1, vk::DescriptorType::eStorageBuffer, nullptr, &bInfoC} + }, {}); + + // --- ШАГ 7: PIPELINE (КОНВЕЙЕР) --- + // Это замороженное состояние видеокарты: какой шейдер используем, какие связи. + // Настройка Pipeline дорогая, поэтому её делают один раз при старте. + vk::PushConstantRange pushConstant{vk::ShaderStageFlagBits::eCompute, 0, sizeof(uint32_t)}; + vk::PipelineLayout layout = device.createPipelineLayout({{}, 1, &dsLayout, 1, &pushConstant}); + + // Загружаем бинарный SPIR-V шейдер + // ( readFile - самописная функция загрузки файла ) + auto shaderCode = readFile("shader.comp.spv"); + vk::ShaderModule shaderModule = device.createShaderModule({{}, shaderCode.size(), (uint32_t*)shaderCode.data()}); + vk::PipelineShaderStageCreateInfo stageInfo{{}, vk::ShaderStageFlagBits::eCompute, shaderModule, "main"}; + + vk::Pipeline pipeline = device.createComputePipeline(nullptr, {{}, stageInfo, layout}).value; + + // --- ШАГ 8: COMMAND BUFFER (ЗАПИСЬ КОМАНД) --- + // Vulkan работает так: ты записываешь все команды заранее в "плеер", + // а потом говоришь "Играй!". Это очень быстро. + vk::CommandPool cmdPool = device.createCommandPool({{}, computeFamily}); + vk::CommandBuffer cmd = device.allocateCommandBuffers({cmdPool, vk::CommandBufferLevel::ePrimary, 1})[0]; + + cmd.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + cmd.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline); + cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute, layout, 0, {ds}, {}); + cmd.pushConstants(layout, vk::ShaderStageFlagBits::eCompute, 0, sizeof(uint32_t), &N); + // Dispatch - это и есть запуск вычислений. N/16 групп потоков. + cmd.dispatch(N / 16, N / 16, 1); + cmd.end(); + + // --- ШАГ 9: SUBMIT (ОТПРАВКА НА GPU) --- + // Только в этот момент видеокарта начинает реально работать. + queue.submit(vk::SubmitInfo(0, nullptr, nullptr, 1, &cmd), nullptr); + queue.waitIdle(); // Ждем, пока GPU доделает работу + + // Теперь результат в bufC можно забрать через device.mapMemory() + + std::cout << "GPU завершил обучение/расчет!" << std::endl; + + } catch (const std::exception& e) { + std::cerr << "Ошибка: " << e.what() << std::endl; + } + return 0; +} +