From ffd0245bfdd8625acfd1763f9a4bb99938365b35 Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Sun, 23 Aug 2026 03:39:04 -0400 Subject: [PATCH 01/11] Add scikit-build-core packaging and cibuildwheel CI --- .github/workflows/ci.yml | 98 +- CMakeLists.txt | 140 ++- bindings/pybinding.cpp | 36 +- build.py | 906 +----------------- deepity_build/__pycache__/cli.cpython-314.pyc | Bin 0 -> 10451 bytes .../__pycache__/cmake_runner.cpython-314.pyc | Bin 0 -> 5091 bytes .../__pycache__/config.cpython-314.pyc | Bin 0 -> 3875 bytes .../__pycache__/git_info.cpython-314.pyc | Bin 0 -> 1929 bytes .../__pycache__/process.cpython-314.pyc | Bin 0 -> 3071 bytes deepity_build/cli.py | 268 ++++++ deepity_build/cmake_runner.py | 82 ++ deepity_build/config.py | 92 ++ deepity_build/git_info.py | 40 + deepity_build/process.py | 70 ++ deepity_build/reporting/__init__.py | 53 + .../__pycache__/__init__.cpython-314.pyc | Bin 0 -> 2763 bytes .../__pycache__/base.cpython-314.pyc | Bin 0 -> 5945 bytes .../__pycache__/rich_reporter.cpython-314.pyc | Bin 0 -> 19742 bytes deepity_build/reporting/base.py | 65 ++ deepity_build/reporting/plain_reporter.py | 176 ++++ deepity_build/reporting/rich_reporter.py | 331 +++++++ include/deepity/Activations.h | 256 +++-- include/deepity/networks/ConvPCNetwork.h | 8 +- .../networks/DiscriminativePCNetwork.h | 17 +- .../deepity/networks/SimpleConvPCNetwork.h | 9 +- include/deepity/networks/SimplePCNetwork.h | 37 +- logs/build.log | 173 +++- pydeepity/ConvolutionalPCN.py | 79 ++ pydeepity/SequentialPCN.py | 72 ++ pydeepity/SimpleConvolutionalPCN.py | 84 ++ pydeepity/SimplePCN.py | 223 +++++ pydeepity/__init__.py | 597 +----------- .../__pycache__/__init__.cpython-312.pyc | Bin 26142 -> 0 bytes .../__pycache__/__init__.cpython-314.pyc | Bin 33220 -> 0 bytes pydeepity/_backend.py | 7 + pydeepity/utils.py | 113 +++ pyproject.toml | 89 +- setup.py | 48 +- src/ConvPCNetwork.cpp | 26 +- src/DiscriminativePCNetwork.cpp | 42 +- src/ModelIO.cpp | 8 +- src/SimpleConvPCNetwork.cpp | 28 +- src/SimplePCLayer.cpp | 78 +- src/SimplePCNetwork.cpp | 59 +- temp.py | 61 +- tests/tProfile.cpp | 165 ++-- tests/tSimple.cpp | 213 ++-- tests/tThreadSweep.cpp | 81 -- 48 files changed, 2752 insertions(+), 2178 deletions(-) create mode 100644 deepity_build/__pycache__/cli.cpython-314.pyc create mode 100644 deepity_build/__pycache__/cmake_runner.cpython-314.pyc create mode 100644 deepity_build/__pycache__/config.cpython-314.pyc create mode 100644 deepity_build/__pycache__/git_info.cpython-314.pyc create mode 100644 deepity_build/__pycache__/process.cpython-314.pyc create mode 100644 deepity_build/cli.py create mode 100644 deepity_build/cmake_runner.py create mode 100644 deepity_build/config.py create mode 100644 deepity_build/git_info.py create mode 100644 deepity_build/process.py create mode 100644 deepity_build/reporting/__init__.py create mode 100644 deepity_build/reporting/__pycache__/__init__.cpython-314.pyc create mode 100644 deepity_build/reporting/__pycache__/base.cpython-314.pyc create mode 100644 deepity_build/reporting/__pycache__/rich_reporter.cpython-314.pyc create mode 100644 deepity_build/reporting/base.py create mode 100644 deepity_build/reporting/plain_reporter.py create mode 100644 deepity_build/reporting/rich_reporter.py create mode 100644 pydeepity/ConvolutionalPCN.py create mode 100644 pydeepity/SequentialPCN.py create mode 100644 pydeepity/SimpleConvolutionalPCN.py create mode 100644 pydeepity/SimplePCN.py delete mode 100644 pydeepity/__pycache__/__init__.cpython-312.pyc delete mode 100644 pydeepity/__pycache__/__init__.cpython-314.pyc create mode 100644 pydeepity/_backend.py create mode 100644 pydeepity/utils.py delete mode 100644 tests/tThreadSweep.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcb38f4..264b81e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,13 +7,6 @@ on: branches: [main] workflow_dispatch: {} -# Explicit least-privilege GITHUB_TOKEN scope. None of the jobs below write -# to the repo, comment on PRs, or publish anything - they only check out -# code, build/test it, and upload log artifacts - so read-only access to -# contents is all that's needed. Without this block, jobs run with the -# default token scope, which can be broader than necessary depending on -# repo/org settings (this is what CodeQL's "workflow does not contain -# permissions" alert flags). permissions: contents: read @@ -49,9 +42,6 @@ jobs: python-version: "3.x" - name: Install system dependencies - # NOTE: per CMakeLists.txt, SLEEF and Google Benchmark are fetched - # from source via FetchContent at configure time - they are NOT - # system packages, so they are deliberately absent here. run: | sudo apt-get update sudo apt-get install -y \ @@ -194,3 +184,91 @@ jobs: - name: Run pyright run: pyright + + build-wheels: + name: wheels (${{ matrix.os }}) + needs: [build-and-test, build-and-test-windows] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Check out repository + uses: actions/checkout@v4 + + # --- Windows-only: OpenBLAS has no system package, so it comes from + # vcpkg here exactly as it does in build-and-test-windows above. This + # runs on the host (cibuildwheel on Windows isn't containerized), so + # the toolchain file set via GITHUB_ENV is visible to the actual + # CMake configure step cibuildwheel triggers. + - name: Cache vcpkg + if: runner.os == 'Windows' + id: vcpkg-cache + uses: actions/cache@v4 + with: + path: | + C:/vcpkg + !C:/vcpkg/buildtrees + !C:/vcpkg/packages + !C:/vcpkg/downloads + key: vcpkg-windows-openblas-wheels-v1 + + - name: Bootstrap vcpkg + if: runner.os == 'Windows' && steps.vcpkg-cache.outputs.cache-hit != 'true' + run: | + git clone https://github.com/microsoft/vcpkg C:/vcpkg + C:/vcpkg/bootstrap-vcpkg.bat + + - name: Install OpenBLAS via vcpkg + if: runner.os == 'Windows' + run: C:/vcpkg/vcpkg.exe install openblas --triplet x64-windows + + - name: Point CMake at the vcpkg toolchain file + if: runner.os == 'Windows' + run: echo "CMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" | Out-File -FilePath $env:GITHUB_ENV -Append + + # --- Linux and macOS need no host-side setup: OpenBLAS/libgomp + # install inside the manylinux container (Linux) or via Homebrew + # (macOS) through the before-all hooks in pyproject.toml's + # [tool.cibuildwheel.linux] / [tool.cibuildwheel.macos] tables. + - name: Build wheels + uses: pypa/cibuildwheel@v2.21 + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: wheelhouse/*.whl + if-no-files-found: error + + build-sdist: + name: sdist + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Install build + run: pip install build + + - name: Build sdist + run: python -m build --sdist + + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/*.tar.gz + if-no-files-found: error + + # Not wired up yet: publishing needs the PyPI project configured for + # Trusted Publishing (pypi.org project settings -> Publishing) with this + # repo/workflow registered, then a job here gated on a tag push using + # `pypa/gh-action-pypi-publish` and `permissions: id-token: write` - + # no API token needs to live in repo secrets with that approach. diff --git a/CMakeLists.txt b/CMakeLists.txt index a2e0187..2d1750b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,31 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) option(DEEPITY_ENABLE_CUDA "Build with CUDA support" ON) option(DEEPITY_BUILD_TESTS "Build the DeepityTests target" OFF) option(DEEPITY_BUILD_PYTHON_BINDINGS "Build the Python bindings target" ON) -option(DEEPITY_NATIVE_OPT "Enable -march=native for max performance (disable for portable builds)" ON) + +# --- Global Architecture & SIMD Flags ------------------------------- +# NOTE: build.py always sets these explicitly based on --native/--fast/ +# --distributed. These CACHE defaults only matter if someone invokes cmake +# directly. Default to the portable "fast" profile (AVX2+FMA, x86-64-v3) +# rather than -march=native, since native binaries crash on other CPUs. +set(DEEPITY_ARCH_FLAGS "-march=x86-64-v3 -mtune=generic" CACHE STRING "Architecture/SIMD flags (e.g., -march=native, -march=x86-64-v2)") +set(DEEPITY_MSVC_ARCH_FLAGS "/arch:AVX2" CACHE STRING "Architecture flags for MSVC (empty = compiler default / most portable)") + +if(MSVC) + if(DEEPITY_MSVC_ARCH_FLAGS) + add_compile_options(${DEEPITY_MSVC_ARCH_FLAGS}) + endif() + + if(DEEPITY_MSVC_ARCH_FLAGS MATCHES "AVX512") + add_compile_definitions(__SSE2__ __AVX__ __AVX2__ __AVX512F__) + elseif(DEEPITY_MSVC_ARCH_FLAGS MATCHES "AVX2") + add_compile_definitions(__SSE2__ __AVX__ __AVX2__) + elseif(DEEPITY_MSVC_ARCH_FLAGS MATCHES "AVX") + add_compile_definitions(__SSE2__ __AVX__) + endif() +else() + separate_arguments(ARCH_FLAGS_LIST UNIX_COMMAND ${DEEPITY_ARCH_FLAGS}) + add_compile_options(${ARCH_FLAGS_LIST}) +endif() set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -18,9 +42,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) message(STATUS "CXX compiler: ${CMAKE_CXX_COMPILER}") -# -------------------------------------------------------------------- -# Dependencies -# -------------------------------------------------------------------- +# --- DEPENDENCIES --------------------------------------------------- find_package(OpenMP REQUIRED) @@ -40,9 +62,7 @@ endif() include(FetchContent) -# -------------------------------------------------------------------- -# OpenBLAS -# -------------------------------------------------------------------- +# --- OpenBLAS ------------------------------------------------------- if(WIN32) set(OPENBLAS_WIN_URL "https://github.com/OpenMathLib/OpenBLAS/releases/download/v0.3.34/OpenBLAS-0.3.34-x64.zip" @@ -84,10 +104,8 @@ else() ) endif() -# -------------------------------------------------------------------- -# SLEEF -# -------------------------------------------------------------------- - +# --- SLEEF ---------------------------------------------------------- +# These are necessary, but don't ask me why set(SLEEF_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(SLEEF_BUILD_STATIC_TEST_BINS OFF CACHE BOOL "" FORCE) set(SLEEF_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) @@ -115,9 +133,7 @@ set(CMAKE_REQUIRED_QUIET ON) FetchContent_MakeAvailable(sleef) set(CMAKE_REQUIRED_QUIET OFF) -# -------------------------------------------------------------------- -# Google Benchmark -# -------------------------------------------------------------------- +# --- Google Benchmark ----------------------------------------------- if(DEEPITY_BUILD_TESTS) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") @@ -140,11 +156,9 @@ if(DEEPITY_BUILD_TESTS) set(CMAKE_REQUIRED_QUIET OFF) endif() -# -------------------------------------------------------------------- -# Deepity library -# -------------------------------------------------------------------- +# --- Deepity library ------------------------------------------------ -add_library(Deepity STATIC +add_library(Deepity src/DiscriminativePCLayer.cpp src/RBLayer.cpp src/ConvPCLayer.cpp @@ -158,10 +172,6 @@ add_library(Deepity STATIC src/StreamAlignedBatcher.cpp ) -if(NOT MSVC AND DEEPITY_NATIVE_OPT) - target_compile_options(Deepity PUBLIC -march=native) -endif() - target_include_directories(Deepity PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${BLAS_INCLUDE_DIR} @@ -185,9 +195,7 @@ endif() target_compile_definitions(Deepity PUBLIC SLEEF_STATIC_LIBS) -# -------------------------------------------------------------------- -# Compiler flags -# -------------------------------------------------------------------- +# --- Compiler flags ------------------------------------------------- if(MSVC) target_compile_options(Deepity PUBLIC @@ -195,14 +203,6 @@ if(MSVC) $<$:/O2 /Zi /fp:fast /openmp:llvm> $<$:/Od /Zi /W4 /openmp:llvm> ) - target_compile_definitions(Deepity PUBLIC __SSE2__) - - # Note: MSVC handles vectorization under /O2 automatically. - # /arch:AVX2 can be added if you want to force native-like behavior. - if(DEEPITY_NATIVE_OPT) - target_compile_definitions(Deepity PUBLIC __AVX__ __AVX2__) - target_compile_options(Deepity PUBLIC /arch:AVX2) - endif() endif() target_compile_definitions(Deepity PUBLIC $<$:_DEBUG>) @@ -216,15 +216,12 @@ set_target_properties(Deepity PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) -# -------------------------------------------------------------------- -# Python bindings -# -------------------------------------------------------------------- +# --- pydeepity ------------------------------------------------------ if(DEEPITY_BUILD_PYTHON_BINDINGS AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug") pybind11_add_module(pydeepity bindings/pybinding.cpp) target_link_libraries(pydeepity PRIVATE Deepity) - target_compile_options(pydeepity PRIVATE -march=native) if(WIN32) target_compile_definitions(pydeepity PRIVATE NOMINMAX) @@ -234,33 +231,30 @@ if(DEEPITY_BUILD_PYTHON_BINDINGS AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug") PREFIX "" ) - # Modern cross-platform way to copy required DLLs (like OpenMP/BLAS) to the Python package on Windows - if(WIN32) - # Copy .pyd - add_custom_command(TARGET pydeepity POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - $ - ${CMAKE_SOURCE_DIR}/pydeepity/ - ) - # Copy required DLLs - add_custom_command(TARGET pydeepity POST_BUILD - COMMAND ${CMAKE_COMMAND} -E $>,copy_if_different,true> - $ - ${CMAKE_SOURCE_DIR}/pydeepity/ - COMMAND_EXPAND_LISTS - ) - endif() + # Modern cross-platform way to copy the compiled extension and required runtime libs + add_custom_command(TARGET pydeepity POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + ${CMAKE_SOURCE_DIR}/pydeepity/ + ) + add_custom_command(TARGET pydeepity POST_BUILD + COMMAND ${CMAKE_COMMAND} -E $>,copy_if_different,true> + $ + ${CMAKE_SOURCE_DIR}/pydeepity/ + COMMAND_EXPAND_LISTS + ) + + install(TARGETS pydeepity + LIBRARY DESTINATION pydeepity + RUNTIME DESTINATION pydeepity + ) endif() -# -------------------------------------------------------------------- -# Tests -# -------------------------------------------------------------------- +# --- Tests ---------------------------------------------------------- if(DEEPITY_BUILD_TESTS) add_executable(DeepityTests tests/tSimpleConvVerify.cpp) - target_compile_options(DeepityTests PRIVATE -march=native) - target_link_libraries(DeepityTests PRIVATE Deepity benchmark::benchmark @@ -270,7 +264,6 @@ if(DEEPITY_BUILD_TESTS) RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin ) - # Copy DLLs for the test executable on Windows if(WIN32) add_custom_command(TARGET DeepityTests POST_BUILD COMMAND ${CMAKE_COMMAND} -E $>,copy_if_different,true> @@ -281,9 +274,7 @@ if(DEEPITY_BUILD_TESTS) endif() endif() -# -------------------------------------------------------------------- -# Profiling -# -------------------------------------------------------------------- +# --- Profiling ------------------------------------------------------ if(DEEPITY_BUILD_TESTS) add_library(DeepityProfiled STATIC @@ -299,8 +290,6 @@ if(DEEPITY_BUILD_TESTS) src/StreamAlignedBatcher.cpp ) - target_compile_options(DeepityProfiled PRIVATE -march=native) - target_include_directories(DeepityProfiled PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${BLAS_INCLUDE_DIR} @@ -322,16 +311,11 @@ if(DEEPITY_BUILD_TESTS) endif() if(MSVC) - target_compile_definitions(DeepityProfiled PUBLIC __SSE2__) - if(DEEPITY_NATIVE_OPT) - target_compile_definitions(DeepityProfiled PUBLIC __AVX__ __AVX2__) - target_compile_options(DeepityProfiled PUBLIC /arch:AVX2) - endif() target_compile_options(DeepityProfiled PRIVATE /O2 /fp:fast /openmp:llvm) elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - target_compile_options(DeepityProfiled PRIVATE -O3 ${ARCH_FLAGS} -ffast-math -fvectorize -fslp-vectorize) + target_compile_options(DeepityProfiled PRIVATE -O3 -ffast-math -fvectorize -fslp-vectorize) else() - target_compile_options(DeepityProfiled PRIVATE -O3 ${ARCH_FLAGS} -ffast-math -ftree-vectorize -ftree-slp-vectorize) + target_compile_options(DeepityProfiled PRIVATE -O3 -ffast-math -ftree-vectorize -ftree-slp-vectorize) endif() add_executable(DeepityProfile tests/tProfile.cpp) @@ -351,17 +335,3 @@ if(DEEPITY_BUILD_TESTS) ) endif() endif() - -if(DEEPITY_BUILD_TESTS) - add_executable(MuCacheVerify tests/tMuCacheVerify.cpp) - target_link_libraries(MuCacheVerify PRIVATE Deepity) - set_target_properties(MuCacheVerify PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - if(WIN32) - add_custom_command(TARGET MuCacheVerify POST_BUILD - COMMAND ${CMAKE_COMMAND} -E $>,copy_if_different,true> - $ - $ - COMMAND_EXPAND_LISTS - ) - endif() -endif() diff --git a/bindings/pybinding.cpp b/bindings/pybinding.cpp index e8b41fe..bde47cf 100644 --- a/bindings/pybinding.cpp +++ b/bindings/pybinding.cpp @@ -150,7 +150,7 @@ namespace .def_property_readonly("layers", [](NetT &self) { py::list result; - for (auto *layer : self.GetLayers()) result.append(py::cast(layer, py::return_value_policy::reference)); + for (const auto &layer : self.GetLayers()) result.append(py::cast(layer.get(), py::return_value_policy::reference)); return result; }, "List of layer objects owned by the network.") .def("__len__", [](const NetT &self) { return self.GetLayers().size(); }) @@ -159,7 +159,7 @@ namespace auto &layers = self.GetLayers(); if (index < 0) index += static_cast(layers.size()); if (index < 0 || index >= static_cast(layers.size())) throw py::index_error(); - return layers[index]; }, py::return_value_policy::reference_internal) + return layers[index].get(); }, py::return_value_policy::reference_internal) .def("__repr__", [className](const NetT &self) { return "<" + std::string(className) + " layers=" + std::to_string(self.GetLayers().size()) + " batch_size=" + std::to_string(self.GetBatchSize()) + ">"; }); } @@ -263,7 +263,7 @@ void bind_layers(py::module_ &m) .def_property_readonly("weights", [](Deep::ConvPCLayer &self) { return py::array_t({(py::ssize_t)self.GetOutChannels(), (py::ssize_t)self.GetInChannels(), (py::ssize_t)self.GetKernelH(), (py::ssize_t)self.GetKernelW()}, self.GetWeights(), py::cast(&self)); }) .def_property_readonly("biases", [](Deep::ConvPCLayer &self) - { return py::array_t({(py::ssize_t)self.GetOutChannels()}, self.GetBiases(), py::cast(&self)); }) + { return py::array_t((py::ssize_t)self.GetOutChannels(), self.GetBiases(), py::cast(&self)); }) .def_property_readonly("batch_size", &Deep::ConvPCLayer::GetBatchSize) .def_property_readonly("in_channels", &Deep::ConvPCLayer::GetInChannels) .def_property_readonly("out_channels", &Deep::ConvPCLayer::GetOutChannels) @@ -282,11 +282,11 @@ void bind_layers(py::module_ &m) .def_property_readonly("beliefs", [](Deep::SimpleConvPCLayer &self) { size_t n = self.GetBatchSize() * self.GetInputSize(); - return py::array_t({(py::ssize_t)n}, self.GetBeliefs()); }) + return py::array_t((py::ssize_t)n, self.GetBeliefs()); }) .def_property_readonly("errors", [](Deep::SimpleConvPCLayer &self) { size_t n = self.GetBatchSize() * self.GetInputSize(); - return py::array_t({(py::ssize_t)n}, self.GetErrors()); }) + return py::array_t((py::ssize_t)n, self.GetErrors()); }) .def_property_readonly("weights", [](Deep::SimpleConvPCLayer &self) { if (self.GetOutChannels() == 0) return py::array_t(); @@ -295,7 +295,7 @@ void bind_layers(py::module_ &m) .def_property_readonly("biases", [](Deep::SimpleConvPCLayer &self) { if (self.GetOutChannels() == 0) return py::array_t(); - return py::array_t({(py::ssize_t)self.GetOutChannels()}, self.GetBiases()); }) + return py::array_t((py::ssize_t)self.GetOutChannels(), self.GetBiases()); }) .def_property_readonly("in_channels", &Deep::SimpleConvPCLayer::GetInChannels) .def_property_readonly("out_channels", &Deep::SimpleConvPCLayer::GetOutChannels) .def_property_readonly("in_height", &Deep::SimpleConvPCLayer::GetInHeight) @@ -349,12 +349,18 @@ void bind_networks(py::module_ &m) { if (opt == "ADAM") self.SetOptimizer(Deep::OptimizerType::ADAM); else if (opt == "ADAMW") self.SetOptimizer(Deep::OptimizerType::ADAMW); - else self.SetOptimizer(Deep::OptimizerType::SGD); }, py::arg("optimizer"), "Sets the optimizer: ADAM, ADAMW, or SGD."); + else self.SetOptimizer(Deep::OptimizerType::SGD); }, py::arg("optimizer"), "Sets the optimizer: ADAM, ADAMW, or SGD.") - simpleNetCls.def("project_forward", &Deep::SimplePCNetwork::ProjectForward, - "Seeds hidden layers from a genuine forward pass through current " - "weights, instead of zero-init. Call AFTER clamp_input(), BEFORE " - "the settling loop."); + .def("project_forward", &Deep::SimplePCNetwork::ProjectForward, "Seeds hidden layers from a genuine forward pass through current " + "weights, instead of zero-init. Call AFTER clamp_input(), BEFORE " + "the settling loop.") + + .def("train_step_with_projection", [](Deep::SimplePCNetwork &self, py::array_t x, py::array_t y, int steps) + { + auto xbuf = x.request(); auto ybuf = y.request(); + std::vector xvec(static_cast(xbuf.ptr), static_cast(xbuf.ptr) + xbuf.size); + std::vector yvec(static_cast(ybuf.ptr), static_cast(ybuf.ptr) + ybuf.size); + return self.TrainStepWithProjection(xvec, yvec, steps); }, py::arg("x"), py::arg("y"), py::arg("steps")); py::class_(m, "ConvPCNetwork", "Convolutional Predictive Coding Network.") .def(py::init(), py::arg("batch_size"), "Construct a network with a fixed batch size.") @@ -394,7 +400,7 @@ void bind_networks(py::module_ &m) .def_property_readonly("layers", [](Deep::ConvPCNetwork &self) { py::list result; - for (auto *layer : self.GetLayers()) result.append(py::cast(layer, py::return_value_policy::reference)); + for (auto &layer : self.GetLayers()) result.append(py::cast(layer, py::return_value_policy::reference)); return result; }) .def("__len__", [](const Deep::ConvPCNetwork &self) { return self.GetLayers().size(); }) @@ -403,7 +409,7 @@ void bind_networks(py::module_ &m) auto &layers = self.GetLayers(); if (index < 0) index += static_cast(layers.size()); if (index < 0 || index >= static_cast(layers.size())) throw py::index_error(); - return layers[index]; }, py::return_value_policy::reference_internal) + return layers[index].get(); }, py::return_value_policy::reference_internal) .def("__repr__", [](const Deep::ConvPCNetwork &self) { return ""; }); @@ -449,7 +455,7 @@ void bind_networks(py::module_ &m) .def_property_readonly("layers", [](Deep::SimpleConvPCNetwork &self) { py::list result; - for (auto *layer : self.GetLayers()) result.append(py::cast(layer, py::return_value_policy::reference)); + for (auto &layer : self.GetLayers()) result.append(py::cast(layer, py::return_value_policy::reference)); return result; }) .def("__len__", [](const Deep::SimpleConvPCNetwork &self) { return self.GetLayers().size(); }) @@ -458,7 +464,7 @@ void bind_networks(py::module_ &m) auto &layers = self.GetLayers(); if (index < 0) index += static_cast(layers.size()); if (index < 0 || index >= static_cast(layers.size())) throw py::index_error(); - return layers[index]; }, py::return_value_policy::reference_internal) + return layers[index].get(); }, py::return_value_policy::reference_internal) .def("__repr__", [](const Deep::SimpleConvPCNetwork &self) { return ""; }); } diff --git a/build.py b/build.py index 083bf32..40286c4 100644 --- a/build.py +++ b/build.py @@ -1,902 +1,14 @@ -import argparse -import multiprocessing -import os -import re -import shutil -import subprocess -import sys -import time -from abc import ABC, abstractmethod -from collections import deque -from collections.abc import Callable -from pathlib import Path -import importlib.util +#!/usr/bin/env python3 +""" +Deepity build entrypoint. +All real logic lives in the deepity_build package (config resolution, the +CMake command builder, and the rich/plain reporters). This file just wires +argv to that package so `python build.py ...` keeps working exactly as +before. +""" -def is_library_installed(library_name: str) -> bool: - """ - Check if a python library is installed without importing it. - - Args: - library_name (str): The name of the library to check - Returns: - bool: True if installed, False otherwise - """ - if not isinstance(library_name, str) or not library_name.strip(): - raise ValueError("Library name must be a non-empty string.") - return importlib.util.find_spec(library_name) is not None - - -try: - RICH_AVAILABLE = is_library_installed("rich") -except ValueError as e: - print(f"Error: {e}") - RICH_AVAILABLE = False - -if RICH_AVAILABLE: - print("✅ 'rich' is installed. Using the interactive dashboard.") -else: - print("❌ 'rich' is NOT installed. Falling back to plain-text output.") - - -BUILD_PROGRESS_RE = re.compile(r"\[(\d+)/(\d+)\]\s+(.*)") - - -def format_duration(seconds: float | None) -> str: - if seconds is None: - return "—" - - if seconds < 60: - return f"{seconds:.1f}s" - - minutes, seconds = divmod(seconds, 60) - - if minutes < 60: - return f"{int(minutes)}m {seconds:.0f}s" - - hours, minutes = divmod(int(minutes), 60) - return f"{hours}h {minutes}m" - - -def command_string(cmd: list[str]) -> str: - return " ".join(cmd) - -def get_git_info() -> tuple[str, str, bool]: - try: - branch = subprocess.check_output( - ["git", "branch", "--show-current"], - text=True, - stderr=subprocess.DEVNULL, - ).strip() - - commit = subprocess.check_output( - ["git", "rev-parse", "--short", "HEAD"], - text=True, - stderr=subprocess.DEVNULL, - ).strip() - - status = subprocess.call( - ["git", "diff", "--quiet"], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - - dirty = status != 0 - return branch or "detached", commit, dirty - - except (subprocess.CalledProcessError, FileNotFoundError): - return "unknown", "unknown", False - -# ══════════════════════════════════════════════════════════════════════════ -# Reporter interface -# -# main() only ever talks to this interface, never to rich or plain-text -# internals directly. That keeps main() free of "if RICH_AVAILABLE" checks -# (aside from picking which implementation to construct) and means neither -# implementation needs module-level conditional imports of rich symbols - -# each one imports what it needs locally, right where it's used, which is -# also what keeps static type checkers (Pylance/pyright) happy: nothing is -# "possibly unbound" because every rich import lives inside the exact -# function that uses it, in a class that is only ever instantiated when -# rich is actually installed. -# ══════════════════════════════════════════════════════════════════════════ -class Reporter(ABC): - @abstractmethod - def __enter__(self) -> "Reporter": ... - - @abstractmethod - def __exit__(self, exc_type, exc, tb) -> None: ... - - @abstractmethod - def build_summary(self, targets: int) -> None: ... - - @abstractmethod - def clean_reconfigure(self) -> None: ... - - @abstractmethod - def configure_started(self) -> None: ... - - @abstractmethod - def configure_cached(self) -> None: ... - - @abstractmethod - def configure_complete(self, duration: float) -> None: ... - - @abstractmethod - def configure_failed(self, output: str) -> None: ... - - @abstractmethod - def build_started(self) -> None: ... - - @abstractmethod - def build_line(self, line: str) -> None: ... - - @abstractmethod - def build_complete(self, duration: float) -> None: ... - - @abstractmethod - def build_failed(self, output: str) -> None: ... - - @abstractmethod - def tests_missing(self, exe_name: str, paths: list[Path]) -> None: ... - - @abstractmethod - def tests_started(self) -> None: ... - - @abstractmethod - def test_line(self, line: str) -> None: ... - - @abstractmethod - def tests_complete(self, duration: float) -> None: ... - - @abstractmethod - def tests_failed(self, output: str) -> None: ... - - @abstractmethod - def success( - self, - configure_time: float | None, - build_time: float, - test_time: float, - ) -> None: ... - - -# ══════════════════════════════════════════════════════════════════════════ -# Rich-backed reporter (used only when 'rich' is available) -# ══════════════════════════════════════════════════════════════════════════ -class RichReporter(Reporter): - """Single mutable Rich Live dashboard for the entire build.""" - - def __init__( - self, - build_type: str, - generator: str, - jobs: int, - git_branch: str, - git_commit: str, - git_dirty: bool, - ) -> None: - from rich.console import Console - from rich.progress import ( - BarColumn, - Progress, - SpinnerColumn, - TextColumn, - TimeElapsedColumn, - ) - - self.build_type = build_type - self.generator = generator - self.jobs = jobs - - self.git_branch = git_branch - self.git_commit = git_commit - self.git_dirty = git_dirty - - self.targets_built = 0 - self.num_passed = 0 - self.num_failed = 0 - - self.phase = "Starting..." - self.configure_status = "[dim]waiting[/dim]" - self.build_status = "[dim]waiting[/dim]" - self.test_status = "[dim]waiting[/dim]" - - self.build_message = "" - self.test_lines: deque[str] = deque(maxlen=8) - - self._configure_time_display = "—" - self._build_time_display = "—" - self._test_time_display = "—" - self._total_time_display = "—" - - self.console = Console() - self.progress = Progress( - SpinnerColumn(), - TextColumn("[bold blue]{task.description}"), - BarColumn(), - TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), - TimeElapsedColumn(), - ) - self.build_task = self.progress.add_task("Waiting", total=1, completed=0) - - self._live = None - - # -- context manager: owns the Live session -------------------------- - def __enter__(self) -> "RichReporter": - from rich.live import Live - - self.console.clear() - self.console.print() - - self._live = Live( - self._render(), - refresh_per_second=12, - console=self.console, - transient=False, - ) - self._live.__enter__() - return self - - def __exit__(self, exc_type, exc, tb) -> None: - if self._live is not None: - self._live.__exit__(exc_type, exc, tb) - - # -- rendering --------------------------------------------------------- - def _header(self): - from rich.panel import Panel - from rich.table import Table - - table = Table.grid(padding=(0, 2)) - table.add_column() - table.add_column() - table.add_column() - table.add_column() - - table.add_row( - "[bold cyan]Deepity Engine[/bold cyan]", - f"[dim]Build[/dim] [bold]{self.build_type}[/bold]", - f"[dim]Generator[/dim] [bold]{self.generator}[/bold]", - f"[dim]Jobs[/dim] [bold]{self.jobs}[/bold]", - ) - - table.add_row( - "[dim]Git[/dim]", - f"[bold]{self.git_branch}[/bold] @ {self.git_commit}" - + (" [yellow]● modified[/yellow]" if self.git_dirty else " [green]✓ clean[/green]"), - ) - - return Panel(table, border_style="cyan", padding=(0, 1)) - - def _status(self): - from rich.panel import Panel - from rich.table import Table - - table = Table.grid(padding=(0, 2)) - table.add_column(style="bold", width=12) - table.add_column() - - table.add_row("Configure", self.configure_status) - table.add_row("Build", self.build_status) - table.add_row("Tests", self.test_status) - - return Panel(table, title="[bold]Build Status[/bold]", border_style="blue") - - def _activity(self): - from rich.console import Group - from rich.panel import Panel - from rich.table import Table - from rich.text import Text - - if self.phase == "Configuring": - body = Group( - Text("Configuring CMake...", style="bold yellow"), - Text(""), - Text.from_markup( - "[dim]CMake output is hidden while the build is running " - "and shown in full if configuration fails.[/dim]" - ), - ) - return Panel( - body, - title="[bold yellow]⚙ Configuration[/bold yellow]", - border_style="yellow", - ) - - if self.phase == "Building": - current = self.build_message or "Working..." - return Panel( - Group(self.progress, Text.from_markup(f"[dim]{current}[/dim]")), - title="[bold blue]⚙ Compilation[/bold blue]", - border_style="blue", - ) - - if self.phase == "Testing": - body = ( - "\n".join(self.test_lines) - if self.test_lines - else "[dim]Waiting for test output...[/dim]" - ) - return Panel( - body, - title="[bold magenta]▶ Tests[/bold magenta]", - border_style="magenta", - ) - - if self.phase == "Success": - body = Table.grid(padding=(0, 2)) - body.add_column(style="dim") - body.add_column(style="bold") - - body.add_row("Build type", self.build_type) - body.add_row("Generator", self.generator) - body.add_row("Parallel jobs", str(self.jobs)) - body.add_row("Targets", str(self.targets_built)) - body.add_row("Configuration", self._configure_time_display) - body.add_row("Compilation", self._build_time_display) - body.add_row("Tests", self._test_time_display) - body.add_row("Total", self._total_time_display) - - return Panel( - Group( - Text("✓ BUILD SUCCESSFUL", style="bold green", justify="center"), - Text(""), - body, - ), - title="[bold green]Deepity[/bold green]", - border_style="green", - padding=(1, 2), - ) - - return Panel("[dim]Waiting...[/dim]", title="[bold]Activity[/bold]", border_style="dim") - - def _render(self): - from rich.console import Group - - return Group(self._header(), self._status(), self._activity()) - - def _refresh(self) -> None: - if self._live is not None: - self._live.update(self._render(), refresh=True) - - def _print_failure(self, title: str, body: str) -> None: - from rich.panel import Panel - - body = body.rstrip() or "No output" - self.console.print() - self.console.print(Panel(body, title=f"[bold red]{title}[/bold red]", border_style="red")) - - # -- Reporter interface ------------------------------------------------- - - def build_summary(self, targets: int) -> None: - self.targets_built = targets - self._refresh() - - def clean_reconfigure(self) -> None: - self.phase = "Configuring" - self.configure_status = "[yellow]● clean reconfigure[/yellow]" - self._refresh() - - def configure_started(self) -> None: - self.phase = "Configuring" - self.configure_status = "[yellow]● configuring[/yellow]" - self._refresh() - - def configure_cached(self) -> None: - self.configure_status = "[green]✓ cached[/green]" - self._refresh() - - def configure_complete(self, duration: float) -> None: - self.configure_status = ( - f"[bold green]✓ complete[/bold green] [dim]({format_duration(duration)})[/dim]" - ) - self._refresh() - - def configure_failed(self, output: str) -> None: - self._print_failure("✗ CMake Configuration Failed", output) - - def build_started(self) -> None: - self.phase = "Building" - self.build_status = "[yellow]● compiling[/yellow]" - self.progress.update(self.build_task, total=1, completed=0, description="Compiling") - self._refresh() - - def build_line(self, line: str) -> None: - match = BUILD_PROGRESS_RE.search(line) - - if not match: - stripped = line.strip() - if stripped: - self.build_message = stripped[-160:] - self._refresh() - return - - current = int(match.group(1)) - total = int(match.group(2)) - message= match.group(3).strip() - self.targets_built = max(self.targets_built, total) - self.build_message = message - self.progress.update(self.build_task, total=total, completed=current) - - def build_complete(self, duration: float) -> None: - self.build_status = ( - f"[bold green]✓ complete[/bold green] [dim]({format_duration(duration)})[/dim]" - ) - self._refresh() - - def build_failed(self, output: str) -> None: - self.build_status = "[bold red]✗ failed[/bold red]" - self.phase = "Build failed" - self._refresh() - self._print_failure("✗ Build Failed", output) - - def tests_missing(self, exe_name: str, paths: list[Path]) -> None: - self.test_status = "[bold red]✗ binary missing[/bold red]" - self.phase = "Tests unavailable" - self._refresh() - self._print_failure(f"✗ Could Not Find {exe_name}", "\n".join(str(p) for p in paths)) - - def tests_started(self) -> None: - self.phase = "Testing" - self.test_status = "[yellow]● running[/yellow]" - self.test_lines.clear() - self._refresh() - - def test_line(self, line: str) -> None: - line = line.rstrip() - - if line: - self.test_lines.append(line) - self._refresh() - - def tests_complete(self, duration: float) -> None: - self.test_status = ( - f"[bold green]✓ complete[/bold green] [dim]({format_duration(duration)})[/dim]" - ) - self._refresh() - - def tests_failed(self, output: str) -> None: - self.test_status = "[bold red]✗ failed[/bold red]" - self.phase = "Tests failed" - self._refresh() - self._print_failure("✗ Test Suite Failed", output) - - def success( - self, - configure_time: float | None, - build_time: float, - test_time: float, - ) -> None: - self._configure_time_display = ( - format_duration(configure_time) if configure_time else "cached" - ) - self._build_time_display = format_duration(build_time) - self._test_time_display = format_duration(test_time) - - total = sum(value or 0 for value in (configure_time, build_time, test_time)) - self._total_time_display = format_duration(total) - - self.phase = "Success" - self._refresh() - - -# ══════════════════════════════════════════════════════════════════════════ -# Plain-text reporter (used when 'rich' is NOT available) -# ══════════════════════════════════════════════════════════════════════════ -class PlainReporter(Reporter): - """ - stdout-only mirror of RichReporter. Same public interface, no rich - dependency at all: statuses are printed as they change, build progress - is shown as an in-place percentage line, and test output streams - straight through. - """ - - def __init__(self, build_type: str, generator: str, jobs: int) -> None: - self.build_type = build_type - self.generator = generator - self.jobs = jobs - - self.phase = "Starting..." - self.configure_status = "waiting" - self.build_status = "waiting" - self.test_status = "waiting" - - self.targets_built = 0 - self._last_build_pct = -1 - self._build_line_open = False - - def __enter__(self) -> "PlainReporter": - print( - f"Deepity Engine — build={self.build_type} " - f"generator={self.generator} jobs={self.jobs}" - ) - return self - - def __exit__(self, exc_type, exc, tb) -> None: - return None - - def _status_line(self) -> None: - print( - f"[{self.phase}] configure={self.configure_status} " - f"build={self.build_status} tests={self.test_status}" - ) - - def _print_failure(self, title: str, body: str) -> None: - body = body.rstrip() or "No output" - print() - print(f"---- {title} " + "-" * max(0, 60 - len(title))) - print(body) - print("-" * 70) - - def _close_build_line(self) -> None: - if self._build_line_open: - print() - self._build_line_open = False - - # -- Reporter interface ------------------------------------------------- - - def build_summary(self, targets: int) -> None: - print(f"Targets built: {targets}") - - def clean_reconfigure(self) -> None: - self.phase = "Configuring" - self.configure_status = "clean reconfigure" - self._status_line() - - def configure_started(self) -> None: - self.phase = "Configuring" - self.configure_status = "configuring..." - self._status_line() - - def configure_cached(self) -> None: - self.configure_status = "cached" - self._status_line() - - def configure_complete(self, duration: float) -> None: - self.configure_status = f"complete ({format_duration(duration)})" - self._status_line() - - def configure_failed(self, output: str) -> None: - self._print_failure("✗ CMake Configuration Failed", output) - - def build_started(self) -> None: - self.phase = "Building" - self.build_status = "compiling..." - self._status_line() - - def build_line(self, line: str) -> None: - match = BUILD_PROGRESS_RE.search(line) - - if not match: - return - - current = int(match.group(1)) - total = int(match.group(2)) - message = match.group(3).strip() - - pct = int((current / total) * 100) if total else 0 - - # Avoid flooding the terminal: only print when percentage moves. - if pct != self._last_build_pct: - self._last_build_pct = pct - print(f"\r [{current}/{total}] {pct:3d}% {message[:80]}", end="", flush=True) - self._build_line_open = True - - def build_complete(self, duration: float) -> None: - self._close_build_line() - self.build_status = f"complete ({format_duration(duration)})" - self._status_line() - - def build_failed(self, output: str) -> None: - self._close_build_line() - self.build_status = "failed" - self.phase = "Build failed" - self._status_line() - self._print_failure("✗ Build Failed", output) - - def tests_missing(self, exe_name: str, paths: list[Path]) -> None: - self.test_status = "binary missing" - self.phase = "Tests unavailable" - self._status_line() - self._print_failure(f"✗ Could Not Find {exe_name}", "\n".join(str(p) for p in paths)) - - def tests_started(self) -> None: - self.phase = "Testing" - self.test_status = "running..." - self._status_line() - - def test_line(self, line: str) -> None: - line = line.rstrip() - if line: - print(f" {line}") - - def tests_complete(self, duration: float) -> None: - self.test_status = f"complete ({format_duration(duration)})" - self._status_line() - - def tests_failed(self, output: str) -> None: - self.test_status = "failed" - self.phase = "Tests failed" - self._status_line() - self._print_failure("✗ Test Suite Failed", output) - - def success( - self, - configure_time: float | None, - build_time: float, - test_time: float, - ) -> None: - total = sum(value or 0 for value in (configure_time, build_time, test_time)) - - print() - print("=" * 60) - print("BUILD SUCCESSFUL") - print("=" * 60) - print(f"Build type : {self.build_type}") - print(f"Generator : {self.generator}") - print(f"Parallel jobs : {self.jobs}") - print(f"Targets build : {self.targets_built}") - print( - "Configuration : " - + (format_duration(configure_time) if configure_time else "cached") - ) - print(f"Compilation : {format_duration(build_time)}") - print(f"Tests : {format_duration(test_time)}") - print(f"Total : {format_duration(total)}") - - -def run_streaming_command( - cmd: list[str], - *, - on_line: Callable[[str], None], -) -> tuple[int, str]: - """ - Run a command while streaming its combined stdout/stderr to on_line. - - The complete output is also retained so that failures can show the - original diagnostic output in full. - """ - process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1, - ) - - assert process.stdout is not None - - output: list[str] = [] - - for line in process.stdout: - output.append(line) - on_line(line) - - return process.wait(), "".join(output) - - -def run_captured_command(cmd: list[str]) -> tuple[int, str]: - result = subprocess.run( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - ) - - return result.returncode, result.stdout - - -def main() -> None: - parser = argparse.ArgumentParser( - description="Deepity Cross-Platform Build & Test Runner" - ) - parser.add_argument( - "build_type", - nargs="?", - default="Release", - choices=["Release", "Debug"], - ) - parser.add_argument( - "--jobs", - "-j", - type=int, - default=multiprocessing.cpu_count(), - help="Number of parallel build jobs", - ) - parser.add_argument( - "--no-tests", - action="store_true", - help="Skip running tests", - ) - parser.add_argument( - "--clean", - action="store_true", - help="Remove the build directory before building", - ) - parser.add_argument( - "--verbose", - action="store_true", - help="Show full build output", - ) - args = parser.parse_args() - - build_type = args.build_type - jobs = args.jobs - - build_dir = Path("build") / build_type - deps_dir = build_dir / "_deps" - cache_file = build_dir / "CMakeCache.txt" - if args.clean and build_dir.exists(): - shutil.rmtree(build_dir) - - ninja = shutil.which("ninja") - generator = "Ninja" if ninja else "CMake" - - # ──────────────────────────────────────────────────────────────────── - # Setup Logging - # ──────────────────────────────────────────────────────────────────── - log_dir = Path("logs") - log_dir.mkdir(exist_ok=True) - log_file = log_dir / "build.log" - - # Start a fresh log file for this run - with log_file.open("w", encoding="utf-8") as f: - f.write(f"--- Deepity Build Log ({build_type}) ---\n\n") - - def log_output(phase: str, output: str) -> None: - with log_file.open("a", encoding="utf-8") as f: - f.write(f"=== {phase} ===\n") - f.write(output) - f.write("\n\n") - - git_branch, git_commit, git_dirty = get_git_info() - - reporter: Reporter = ( - RichReporter(build_type, generator, jobs, git_branch, git_commit, git_dirty) - if RICH_AVAILABLE - else PlainReporter(build_type, generator, jobs) - ) - - with reporter: - # ──────────────────────────────────────────────────────────────── - # Dependencies - # ──────────────────────────────────────────────────────────────── - if not deps_dir.is_dir(): - reporter.clean_reconfigure() - - if build_dir.exists(): - shutil.rmtree(build_dir) - - # ──────────────────────────────────────────────────────────────── - # Configure - # ──────────────────────────────────────────────────────────────── - configure_time: float | None = None - - if not cache_file.is_file(): - config_cmd = [ - "cmake", - "-B", - str(build_dir), - f"-DCMAKE_BUILD_TYPE={build_type}", - "-DDEEPITY_BUILD_TESTS=ON", - "-DDEEPITY_ENABLE_CUDA=ON", - ] - - if sys.platform == "win32": - config_cmd.extend(["-A", "x64"]) - vcpkg_root = os.environ.get("VCPKG_ROOT") - if vcpkg_root: - toolchain = Path(vcpkg_root) / "scripts/buildsystems/vcpkg.cmake" - if toolchain.is_file(): - config_cmd.append(f"-DCMAKE_TOOLCHAIN_FILE={toolchain.as_posix()}") - else: - print("Warning: VCPKG_ROOT environment variable is not set. Dependencies may fail to resolve.") - else: - if ninja: - config_cmd.extend(["-G", "Ninja"]) - - vcpkg_root = os.environ.get("VCPKG_ROOT") - if vcpkg_root: - config_cmd.append(f"-DCMAKE_TOOLCHAIN_FILE={vcpkg_root}/scripts/buildsystems/vcpkg.cmake") - - reporter.configure_started() - - start = time.perf_counter() - - return_code, config_output = run_captured_command(config_cmd) - log_output("CMake Configuration", config_output) - - configure_time = time.perf_counter() - start - - if return_code != 0: - reporter.configure_failed(config_output) - sys.exit(return_code) - - reporter.configure_complete(configure_time) - - else: - reporter.configure_cached() - - # ──────────────────────────────────────────────────────────────── - # Build - # ──────────────────────────────────────────────────────────────── - build_cmd = [ - "cmake", - "--build", - str(build_dir), - "-j", - str(jobs), - "--config", - build_type, - ] - - reporter.build_started() - - start = time.perf_counter() - - return_code, build_output = run_streaming_command( - build_cmd, - on_line=reporter.build_line, - ) - log_output("Compilation", build_output) - - build_time = time.perf_counter() - start - - if return_code != 0: - reporter.build_failed(build_output) - sys.exit(return_code) - - reporter.build_complete(build_time) - if args.no_tests: - reporter.success(configure_time, build_time, 0.) - return - - # ──────────────────────────────────────────────────────────────── - # Find test executable - # ──────────────────────────────────────────────────────────────── - exe_name = "DeepityTests.exe" if os.name == "nt" else "DeepityTests" - - test_paths = [ - build_dir / "bin" / exe_name, - build_dir / "bin" / build_type / exe_name, - build_dir / exe_name, - build_dir / build_type / exe_name, - ] - - test_exe = next((path for path in test_paths if path.is_file()), None) - - if test_exe is None: - reporter.tests_missing(exe_name, test_paths) - sys.exit(1) - - # ──────────────────────────────────────────────────────────────── - # Tests - # ──────────────────────────────────────────────────────────────── - reporter.tests_started() - - test_cmd = [str(test_exe)] - - start = time.perf_counter() - - return_code, test_output = run_streaming_command( - test_cmd, - on_line=reporter.test_line, - ) - log_output("Tests", test_output) - - test_time = time.perf_counter() - start - - if return_code != 0: - reporter.tests_failed(test_output) - sys.exit(return_code) - - reporter.tests_complete(test_time) - - # ──────────────────────────────────────────────────────────────── - # Success - # ──────────────────────────────────────────────────────────────── - reporter.success(configure_time, build_time, test_time) - +from deepity_build.cli import main if __name__ == "__main__": main() \ No newline at end of file diff --git a/deepity_build/__pycache__/cli.cpython-314.pyc b/deepity_build/__pycache__/cli.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c18f20a28173102f22cc121cc09a06a1e0535543 GIT binary patch literal 10451 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 M$DYCK9y0#_0#3S|UjP6A literal 0 HcmV?d00001 diff --git a/deepity_build/__pycache__/cmake_runner.cpython-314.pyc b/deepity_build/__pycache__/cmake_runner.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ab20446cf98caaf5efce587ccbfd413f5838878 GIT binary patch literal 5091 zcmcH-TW}NC^{$?)hbgnSgzAN-Y)na-4a#h>}iXZw{^ZAR*bPBRnokv}sYhH1Zg&Xv}(6;o%X?HT*t zvv<$E_q@+p9dB`f|#KE5M)2>A{lEU;A&X8m`76bMgrGEI0U#*Er@TPNbSm|eHi z*`Yh=?9`nw+heX#x9$cShIho+QIGEFB)qeg@UB*p3F=)d%M_z`xcELxH>`cBc0^4n|v;&g-yFQaD%ua-e+9^-F*h$W3dV z#H((wu>iZ~prgQKzm@i~AUD{kumoldFy*Vibc2XPRMyfVWMF%z; zXCyf-%!;6nq^_vyZdjxmI0Q(6JoE3mKmPIfQtOKUU}3223x0a_!4bn3F&vS{?#itC zfpUwT zr?{oE@C*_`^i?G4q7qRX(g8)$4#I$j994$}>yl4Bpc(SFb zPI43Dk3xKph1F)>d-ms$K|ZYB(q4OGVZ4v`hc}>WXzNJRd&kv3Y!{hz9ytlS|`AT9?*z^was3e~{3EcLZ_JuQj zrZ+Jd9~%+)$;qiar=?UW3zdgf8f{V2#d$4qH-Oa&mldv_Cc>49CVn zNL$Z{8UQ=z4@P!61XSkEs>+O%5luVfLDNI86Vj4udMl{Dm=#T56&E#KGaad1I%x*7 zi~5`*3%4QVOY*E{dULY0Ak1Wvh|g9Z^EAudh@g9uUZUl#9&A1`WGEv zbUg0(toh4B2KSc1_LsfKmd}(<#o*6475lvJ&pG4RRfA2Gy`7KTCGKRAJ85*DGT6Sg zCXdfm7+l*!*g(nM4u6Kbz0CUVd+<mdLWZ)H_h@9WWw;#_s4UJM_%${qX(Yy#L_6 zRri4^*@NiPp$8XM*@I78S|8r{uIm<7>N!KTx>z zGC=%IWq;uQrF)l_980qge!c2H@@%`?szgS6@ABSdzj6MS(K@~A7rym17B2m`)?>i~ z>oe;dAg^lgu+~Sgsd3UeUvUcDNo%@_DSpRleg=p%OygM1J&fx)4ok<^aC#h~Pc`m< zjDj0R%AF1fcH&U3tL+!TqQ1H0S9TC9LGiXNl*nxzY*1qH-lnjJ-Pp#dZLlrx?&+cN z2$6m#&-V0GTpQkE=}|ctOvfGNwx(if`JAB|qHWsYN-Q)~qS=arvp_`^@+Ke!@+})E zvrTo*UyX+QWB0~Z*tRmetIRf**!CjZz7}Bpt~J6sT{VqaT0xCajqTj7S8RzYu1K*{ zKQ;iA8rm8y$S_09b2=`iDszPwc)bA|ykN=OskP<_Z3=*QXp|UeqtV~&rcKuK!D!`E zUZI|K^pDO+5}5m=0{EsUK2SDvEE`VUqxRQaL?&J0sG5TfGSMR2(#Jfjm9f z{ioK?S|3OM*lGCo8;j3KiGFb9n+5qEM*UlaAFxXr=1;j7_(xOJ z!K-fyJHVG1(+-6*WT(cbAxnc5ya#KX$%2ni=g`n?4_9m0-g@^*!Pm5uPQtP}2z1m! zG9chYf$o*Sp~5KEvOtM#E3$1%r=PIgqq9%g<1}__OJ_>meZ}s+Cv4yH!V~sqwYNaq zU3)rH>NrvCIPru%@fgV7So1hB?w#Ak{r|!>edv|dkM$k%`P(40hCM+g8wfNT2+X67 zHOLAAv)qAML12FDpO|T>f1ab8YyQcTRn0uMIGbI`|8?^EBv?I>Qg z%rtLztvU__)LfOC@YEM52CuDlo&Pei+7(?1UNf%WTJcRAj%jMFP|$P>LRv`)g6S26 znH>C@gTJky=4ql;n-Dt+19Y;WgN$^WHjtaz0_^cwD7m9r1ia8onXkzmvA_N#_d}{G#*14?hFE7-pb-c+rN{ O=miT|@#UTkr~5a@P0bqs literal 0 HcmV?d00001 diff --git a/deepity_build/__pycache__/config.cpython-314.pyc b/deepity_build/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc0c1ae24591d79772d6a322cc5cf3a895222687 GIT binary patch literal 3875 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 aqU4WMP3hPN1P2=%gwehZ#E$dw~ zJ4>jARD%#AAQ20YFaps$h8LdrLtKi~bSYBB#{+K;X@$hoJu_=3xls`hS9hd6b7tr3 zeEiNiGoDJu5wt&Uet2z2M(7td=rl24gw6MXsUZbXG=~&nT6iHuLqm)YO^a0Iy+kGG z#p&=1GL?tWRWYCH9JAdIBSktT1Y;Ui!UIT=2awfGV@d@0DCgr!4EQ+b6G{U3F3xu; zp;44cRxeyGYnFv8#3>rCn=3h_tWj#(3)rKkWm3~{vAd`dL&x*iFi9TTJC5NZ%Nn-TnkX4?+csOi}5MHolV!13C=WmNahUO76ek7sh6&R(5$$B$-@ zWr|FLwiZYN9_%D+Vqj{hfaXFI?;)hazk}!%VNp;7C8UUolw;C|e-FVjKjIoi$JX6y zSIKfTir>Z;9oz6_Vo;CRzUWf&4FXZ*KslFPHsU{-TXf1sjySGyB9|x5HKRz~oNgEu zlV0!i>bz%KdaiAqY~{M&rK)WW4OJywa1{I8$AHw(pC~DeY%tQ8ZLw~x$VZzni@2hU zVJ!?FSM)nzn4%@Fq9Tkkr6(tPLFounx&W|90u)Yy!m}MS@inKI^3ax`C*1|1y=xEK zH4CQ&tsrzD(gkpV)582TY3FH=T0J-UU*``OLa<*h2tXqzVRt5a(PNkVT>GplX8%?4 z1-BY1W8hDes-Z>f3*}76mv!^avZMQ=Y152IASA$DWz+Vk;rii4$0M%GP8q|PB-ms+ z@R6!}gky;G!(VP;3QP^HM^f+JxSQ^~A4%1F-+Oz#cYkBFeyE`})3_PMzYZK~EUc2- z1IKPjeYNxLMfZC9>kFSAuMalJ>f>vptB2NL`H5!q#IOAazB;oS`SQZr$nE|o-nm#i zyPn$f!E5#B>PvOA@kTQ-+?0k%5-$EFBEZO9@uoEm`w17I; z0DlU;fPqNY9McAI24J3xGZr`|RdmW_h;|jEhwMO@R9v!dV8Abl)XK#2ME%T1&orfd z+wyd7WuD(5&))~O5?17#2=YX@-MAcNAMExcj;&gzZB+X^Ddh?`<#8VFfkppHQ?Ftf zR{(CuF}L$SSr^06?ol8tdJhQW8;q>SdRrp&Eg7XoTM@=Zk=(x#18!S4*u)bT^CIXL z-pqowH8jiq^OkQ%XlKm&VX*p+71GYRfZs98thTEu!W^W)(+pG4DxpFs&+gyVf^6~% zS`@OVB0hReH#!FntVjhh&q8lkD{PgPAyg1R>K)^k1`&#(K?LWr;Mmnpwa1E>Pw$$7 zVL|L*?%ynhOVw}Vif3aDxABFS7EQ}wzB4pP6bsm-E}NSN&+G78cRZSd&L9L+2e&bi zjmKx8G{HLXJ~c2bs(2KeF4ip9feJ;nV7~(;3`#^vGcAumX)9_rls9LIRkpZ8%mvfd zEUcT_g6+7}EN-1-&c$=?w$t;IW5=Dx4Kc+o|UQPsqYgi7dkJZlI?;iL(^;zlzX+4@+NiHYr z`VY|qccbaL^l@?{jCzJ3MquCM^5jQTjgvP|e{p*CsV|>xCXO|wW7|Q~NeB%1JRx8% z-rpyLpwjP320{89g026UKBr*OR{D$z$2Sm=hS_4>+9f{PmJZhP#K`mafLyg}9HQs;E`q31#S8pn4KkaDzSrOby*j4y??5IQM8kJ&1)< za_w9zg}Qq0Bo5w69Bh>gsMyAPvK5dNG5B=M8~1LcBz^#A|> literal 0 HcmV?d00001 diff --git a/deepity_build/cli.py b/deepity_build/cli.py new file mode 100644 index 0000000..d8b9239 --- /dev/null +++ b/deepity_build/cli.py @@ -0,0 +1,268 @@ +from __future__ import annotations + +import argparse +import multiprocessing +import shutil +import sys +import time +from pathlib import Path + +from .cmake_runner import ( + build_command, + configure_command, + find_generator, + find_test_executable, + test_executable_candidates, + test_executable_name, +) +from .config import ARCH_PROFILES, DEFAULT_ARCH_PROFILE, BuildConfig +from .git_info import get_git_info +from .process import run_captured_command, run_streaming_command +from .reporting import make_reporter + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Deepity Cross-Platform Build & Test Runner" + ) + parser.add_argument( + "build_type", + nargs="?", + default="Release", + choices=["Release", "Debug"], + ) + parser.add_argument( + "--jobs", + "-j", + type=int, + default=multiprocessing.cpu_count(), + help="Number of parallel build jobs", + ) + + arch_group = parser.add_mutually_exclusive_group() + arch_group.add_argument( + "--native", + action="store_const", + dest="arch_profile", + const="native", + help=( + "Compile with -march=native. Fastest, but the binary is only safe " + "to run on THIS machine. Never use for anything you plan to hand " + "to someone else." + ), + ) + arch_group.add_argument( + "--fast", + action="store_const", + dest="arch_profile", + const="fast", + help=( + "Compile for an AVX2/FMA baseline (x86-64-v3). Portable across " + "most machines from the last ~10 years. Default for local builds." + ), + ) + arch_group.add_argument( + "--distributed", + action="store_const", + dest="arch_profile", + const="distributed", + help=( + "Compile for a maximally portable SSE4.2 baseline (x86-64-v2). " + "Use this for anything you're going to package and ship, e.g. " + "wheels built for PyPI." + ), + ) + parser.set_defaults(arch_profile=None) + + parser.add_argument( + "--cuda", + dest="cuda", + action="store_true", + default=None, + help="Force CUDA support ON (requires CUDAToolkit to be found).", + ) + parser.add_argument( + "--no-cuda", + dest="cuda", + action="store_false", + help="Force CUDA support OFF. Recommended together with --distributed.", + ) + + parser.add_argument( + "--no-tests", + action="store_true", + help="Skip building and running the DeepityTests target", + ) + parser.add_argument( + "--no-python-bindings", + action="store_true", + help="Skip building the pydeepity extension module", + ) + parser.add_argument( + "--clean", + action="store_true", + help="Remove the build directory before building", + ) + parser.add_argument( + "--verbose", + action="store_true", + help="Show full build output", + ) + parser.add_argument( + "--list-profiles", + action="store_true", + help="Print available --native/--fast/--distributed profiles and exit", + ) + + args = parser.parse_args(argv) + + if args.list_profiles: + for profile in ARCH_PROFILES.values(): + marker = " (default)" if profile.name == DEFAULT_ARCH_PROFILE else "" + print(f"{profile.name}{marker}") + print(f" {profile.description}") + print(f" unix: {profile.unix_flags}") + print(f" msvc: {profile.msvc_flags or '(compiler default)'}") + print() + sys.exit(0) + + if args.arch_profile is None: + args.arch_profile = DEFAULT_ARCH_PROFILE + + if args.cuda is None: + # Distributed builds default to CPU-only, since a CUDA-linked binary + # isn't portable either. Everything else keeps the old default (ON). + args.cuda = args.arch_profile != "distributed" + + return args + + +def build_config_from_args(args: argparse.Namespace) -> BuildConfig: + return BuildConfig( + build_type=args.build_type, + jobs=args.jobs, + arch_profile=args.arch_profile, + cuda=args.cuda, + build_tests=not args.no_tests, + run_tests=not args.no_tests, + python_bindings=not args.no_python_bindings, + clean=args.clean, + verbose=args.verbose, + ) + + +def main(argv: list[str] | None = None) -> None: + args = parse_args(argv) + config = build_config_from_args(args) + + if config.clean and config.build_dir.exists(): + shutil.rmtree(config.build_dir) + + ninja, generator = find_generator() + + # ──────────────────────────────────────────────────────────────────── + # Setup logging + # ──────────────────────────────────────────────────────────────────── + log_dir = Path("logs") + log_dir.mkdir(exist_ok=True) + log_file = log_dir / "build.log" + + with log_file.open("w", encoding="utf-8") as f: + f.write(f"--- Deepity Build Log ({config.build_type}, arch={config.arch_profile}) ---\n\n") + + def log_output(phase: str, output: str) -> None: + with log_file.open("a", encoding="utf-8") as f: + f.write(f"=== {phase} ===\n") + f.write(output) + f.write("\n\n") + + git = get_git_info() + reporter = make_reporter(config, git, generator) + + with reporter: + # ──────────────────────────────────────────────────────────────── + # Dependencies: force a clean reconfigure if _deps is missing + # ──────────────────────────────────────────────────────────────── + if not config.deps_dir.is_dir(): + reporter.clean_reconfigure() + if config.build_dir.exists(): + shutil.rmtree(config.build_dir) + + # ──────────────────────────────────────────────────────────────── + # Configure + # ──────────────────────────────────────────────────────────────── + configure_time: float | None = None + + if not config.cache_file.is_file(): + cmd = configure_command(config, ninja) + + reporter.configure_started() + start = time.perf_counter() + + return_code, config_output = run_captured_command(cmd) + log_output("CMake Configuration", config_output) + + configure_time = time.perf_counter() - start + + if return_code != 0: + reporter.configure_failed(config_output) + sys.exit(return_code) + + reporter.configure_complete(configure_time) + else: + reporter.configure_cached() + + # ──────────────────────────────────────────────────────────────── + # Build + # ──────────────────────────────────────────────────────────────── + reporter.build_started() + start = time.perf_counter() + + return_code, build_output = run_streaming_command( + build_command(config), + on_line=reporter.build_line, + ) + log_output("Compilation", build_output) + + build_time = time.perf_counter() - start + + if return_code != 0: + reporter.build_failed(build_output) + sys.exit(return_code) + + reporter.build_complete(build_time) + + if not config.run_tests: + reporter.success(configure_time, build_time, 0.0) + return + + # ──────────────────────────────────────────────────────────────── + # Find + run tests + # ──────────────────────────────────────────────────────────────── + test_exe = find_test_executable(config) + + if test_exe is None: + reporter.tests_missing(test_executable_name(), test_executable_candidates(config)) + sys.exit(1) + + reporter.tests_started() + start = time.perf_counter() + + return_code, test_output = run_streaming_command( + [str(test_exe)], + on_line=reporter.test_line, + ) + log_output("Tests", test_output) + + test_time = time.perf_counter() - start + + if return_code != 0: + reporter.tests_failed(test_output) + sys.exit(return_code) + + reporter.tests_complete(test_time) + reporter.success(configure_time, build_time, test_time) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/deepity_build/cmake_runner.py b/deepity_build/cmake_runner.py new file mode 100644 index 0000000..71c3f7b --- /dev/null +++ b/deepity_build/cmake_runner.py @@ -0,0 +1,82 @@ +"""Turns a BuildConfig into the actual `cmake` command lines.""" + +from __future__ import annotations + +import os +import shutil +import sys +from pathlib import Path + +from .config import BuildConfig + + +def find_generator() -> tuple[str | None, str]: + """Returns (ninja_path_or_None, display_name).""" + ninja = shutil.which("ninja") + return ninja, "Ninja" if ninja else "CMake" + + +def configure_command(config: BuildConfig, ninja: str | None) -> list[str]: + profile = config.profile + + cmd = [ + "cmake", + "-B", + str(config.build_dir), + f"-DCMAKE_BUILD_TYPE={config.build_type}", + f"-DDEEPITY_BUILD_TESTS={'ON' if config.build_tests else 'OFF'}", + f"-DDEEPITY_ENABLE_CUDA={'ON' if config.cuda else 'OFF'}", + f"-DDEEPITY_BUILD_PYTHON_BINDINGS={'ON' if config.python_bindings else 'OFF'}", + f"-DDEEPITY_ARCH_FLAGS={profile.unix_flags}", + ] + + if profile.msvc_flags: + cmd.append(f"-DDEEPITY_MSVC_ARCH_FLAGS={profile.msvc_flags}") + + if sys.platform == "win32": + cmd.extend(["-A", "x64"]) + vcpkg_root = os.environ.get("VCPKG_ROOT") + if vcpkg_root: + toolchain = Path(vcpkg_root) / "scripts/buildsystems/vcpkg.cmake" + if toolchain.is_file(): + cmd.append(f"-DCMAKE_TOOLCHAIN_FILE={toolchain.as_posix()}") + else: + if ninja: + cmd.extend(["-G", "Ninja"]) + + vcpkg_root = os.environ.get("VCPKG_ROOT") + if vcpkg_root: + cmd.append(f"-DCMAKE_TOOLCHAIN_FILE={vcpkg_root}/scripts/buildsystems/vcpkg.cmake") + + return cmd + + +def build_command(config: BuildConfig) -> list[str]: + return [ + "cmake", + "--build", + str(config.build_dir), + "-j", + str(config.jobs), + "--config", + config.build_type, + ] + + +def test_executable_name() -> str: + return "DeepityTests.exe" if os.name == "nt" else "DeepityTests" + + +def test_executable_candidates(config: BuildConfig) -> list[Path]: + exe_name = test_executable_name() + + return [ + config.build_dir / "bin" / exe_name, + config.build_dir / "bin" / config.build_type / exe_name, + config.build_dir / exe_name, + config.build_dir / config.build_type / exe_name, + ] + + +def find_test_executable(config: BuildConfig) -> Path | None: + return next((path for path in test_executable_candidates(config) if path.is_file()), None) \ No newline at end of file diff --git a/deepity_build/config.py b/deepity_build/config.py new file mode 100644 index 0000000..04b21ab --- /dev/null +++ b/deepity_build/config.py @@ -0,0 +1,92 @@ +""" +Build configuration: everything that determines *what* CMake is asked to +build. No process handling and no reporting logic lives here — this module +should be safe to import and unit-test on its own. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + + +# ══════════════════════════════════════════════════════════════════════════ +# Architecture profiles +# +# "native" is fast but only safe on the exact machine that built it — the +# binary can hit an illegal-instruction crash on any other CPU. "fast" and +# "distributed" are portable baselines you can hand to other people; the +# difference is how old a CPU they still cover. +# ══════════════════════════════════════════════════════════════════════════ +@dataclass(frozen=True) +class ArchProfile: + name: str + unix_flags: str + msvc_flags: str + description: str + + +ARCH_PROFILES: dict[str, ArchProfile] = { + "native": ArchProfile( + name="native", + unix_flags="-march=native", + msvc_flags="/arch:AVX2", # MSVC has no true "native"; this is its closest knob + description=( + "Tuned for THIS machine's CPU. Fastest option, but the resulting " + "binary can crash with an illegal instruction on any other machine. " + "Local development only — never distribute a native build." + ), + ), + "fast": ArchProfile( + name="fast", + unix_flags="-march=x86-64-v3 -mtune=generic", + msvc_flags="/arch:AVX2", + description=( + "AVX2 + FMA baseline (x86-64-v3). Covers the large majority of " + "desktops/servers from the last decade. Good default for local " + "builds when you don't need maximum portability." + ), + ), + "distributed": ArchProfile( + name="distributed", + unix_flags="-march=x86-64-v2 -mtune=generic", + msvc_flags="", # MSVC's default codegen is already an SSE2-era baseline + description=( + "SSE4.2 baseline (x86-64-v2), maximally portable. Use this for any " + "build you intend to ship to other people (wheels, releases, CI " + "artifacts)." + ), + ), +} + +DEFAULT_ARCH_PROFILE = "fast" + + +@dataclass(frozen=True) +class BuildConfig: + build_type: str + jobs: int + arch_profile: str + cuda: bool + build_tests: bool + run_tests: bool + python_bindings: bool + clean: bool + verbose: bool + build_root: Path = Path("build") + + @property + def profile(self) -> ArchProfile: + return ARCH_PROFILES[self.arch_profile] + + @property + def build_dir(self) -> Path: + return self.build_root / self.build_type + + @property + def deps_dir(self) -> Path: + return self.build_dir / "_deps" + + @property + def cache_file(self) -> Path: + return self.build_dir / "CMakeCache.txt" \ No newline at end of file diff --git a/deepity_build/git_info.py b/deepity_build/git_info.py new file mode 100644 index 0000000..b2410b1 --- /dev/null +++ b/deepity_build/git_info.py @@ -0,0 +1,40 @@ +"""Best-effort git metadata for the reporter header. Never raises.""" + +from __future__ import annotations + +import subprocess +from dataclasses import dataclass + + +@dataclass(frozen=True) +class GitInfo: + branch: str + commit: str + dirty: bool + + +def get_git_info() -> GitInfo: + try: + branch = subprocess.check_output( + ["git", "branch", "--show-current"], + text=True, + stderr=subprocess.DEVNULL, + ).strip() + + commit = subprocess.check_output( + ["git", "rev-parse", "--short", "HEAD"], + text=True, + stderr=subprocess.DEVNULL, + ).strip() + + status = subprocess.call( + ["git", "diff", "--quiet"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + dirty = status != 0 + return GitInfo(branch or "detached", commit, dirty) + + except (subprocess.CalledProcessError, FileNotFoundError): + return GitInfo("unknown", "unknown", False) \ No newline at end of file diff --git a/deepity_build/process.py b/deepity_build/process.py new file mode 100644 index 0000000..7896ab9 --- /dev/null +++ b/deepity_build/process.py @@ -0,0 +1,70 @@ +"""Small process/formatting utilities shared by the CMake runner and reporters.""" + +from __future__ import annotations + +import re +import subprocess +from collections.abc import Callable + +BUILD_PROGRESS_RE = re.compile(r"\[(\d+)/(\d+)\]\s+(.*)") + + +def format_duration(seconds: float | None) -> str: + if seconds is None: + return "—" + + if seconds < 60: + return f"{seconds:.1f}s" + + minutes, seconds = divmod(seconds, 60) + + if minutes < 60: + return f"{int(minutes)}m {seconds:.0f}s" + + hours, minutes = divmod(int(minutes), 60) + return f"{hours}h {minutes}m" + + +def command_string(cmd: list[str]) -> str: + return " ".join(cmd) + + +def run_streaming_command( + cmd: list[str], + *, + on_line: Callable[[str], None], +) -> tuple[int, str]: + """ + Run a command while streaming its combined stdout/stderr to on_line. + + The complete output is also retained so that failures can show the + original diagnostic output in full. + """ + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + ) + + assert process.stdout is not None + + output: list[str] = [] + + for line in process.stdout: + output.append(line) + on_line(line) + + return process.wait(), "".join(output) + + +def run_captured_command(cmd: list[str]) -> tuple[int, str]: + result = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + + return result.returncode, result.stdout \ No newline at end of file diff --git a/deepity_build/reporting/__init__.py b/deepity_build/reporting/__init__.py new file mode 100644 index 0000000..f1e8a19 --- /dev/null +++ b/deepity_build/reporting/__init__.py @@ -0,0 +1,53 @@ +""" +Reporting package. + +main() only ever talks to the Reporter interface, never to rich or +plain-text internals directly. Each concrete reporter imports rich symbols +locally, right where they're used, so importing this package never requires +rich to be installed, and static type checkers never see a "possibly +unbound" import. +""" + +from __future__ import annotations + +import importlib.util + +from ..config import BuildConfig +from ..git_info import GitInfo +from .base import Reporter + + +def is_library_installed(library_name: str) -> bool: + """Check if a python library is installed without importing it.""" + if not isinstance(library_name, str) or not library_name.strip(): + raise ValueError("Library name must be a non-empty string.") + return importlib.util.find_spec(library_name) is not None + + +def rich_available() -> bool: + try: + return is_library_installed("rich") + except ValueError: + return False + + +def make_reporter(config: BuildConfig, git: GitInfo, generator: str) -> Reporter: + """ + Construct the best available reporter and announce the choice. + + Kept here (rather than in cli.py) so the "which reporter, and why" story + lives next to the reporters themselves. + """ + if rich_available(): + from .rich_reporter import RichReporter + + print("✅ 'rich' is installed. Using the interactive dashboard.") + return RichReporter(config, git, generator) + + from .plain_reporter import PlainReporter + + print("❌ 'rich' is NOT installed. Falling back to plain-text output.") + return PlainReporter(config, git, generator) + + +__all__ = ["Reporter", "make_reporter", "is_library_installed", "rich_available"] \ No newline at end of file diff --git a/deepity_build/reporting/__pycache__/__init__.cpython-314.pyc b/deepity_build/reporting/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4d9bdca808fca0082f63d03b59eef2c5d2f7a75 GIT binary patch literal 2763 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
q8gjvEub_TUnKMF~@ zsmX~*$6oPW4UmzclnFK*kwdrnCf2L1j(SbAd;;t4Y& zGNm@%@D<=%Z7Z4FBA%d zBqovAhq*OHEz6rbq5?hmb^7n%!2uPCZqv27GiuBrlBv(p zBjb>2Vd2$q8OLBHil5pboa$*+h_5QYM_v(Rrm2JDAb#avQyHQzwRA#>OnIk#%2n)= zXDFFAtwJ0|g*X=sD+Pd#)YVFeJQCWJN%F$v5q4Fk$Rn1l0IdiLF&t$ddBj3Ib}sj^ z5i;1wARrBf@T&isY9CK1P)NsPcBJK5ahzVC_Wp(1XFdH7P$f1Fm_WdA3l{)M4b7F9 zv#pDzTOL$5JaE^fYPZiHT67;l@RoeXk#W~7xuvvQS}rlwo-nPL&6QOvc6&|vjTaxe zg_=g>XX=h;TD!isTWZRe1q+j?pb&*>bhc@+baSSzX{oL|UDv%-*O#j6n>&5uBs@qjdsy4QY!}KZAG!1aqNy!KoBD^+j!bpK%z@hnk`uQMEL88j z@giw+&)htd?7DGgQS8=ZTPWsvh&-s|XIql-kKX#=t);Dp(pwKLiib5-{cPJ@&Bx7m zo0mNOX;1%>=SbRfWKle-=ho@D&n^`jAzpr-mE;mBjnVFqy|DA&pRW6IJ@JG2Y) zOgkr$P^ehp)}fzJ(XH^<)*>lrc;@2mi+9GLx48Jh#UG8$4}UWDt3J-isJx5<8(L-3D3oKB=_hkc|7<6FWIIP8=4j^G+7OGU&Wl-I z0)Tfi5CyMhkG)#CRmLp>ksl z4A@%vLW_--%qD}tX;F(!Yg%lb^}io_FZ9RZd4Ec5{Y*SqV7k#4Nuy!YN~bnY>s)Ta z9Xr|DYX-&HxJ8(qV~qd5hnmbl{AAX(e?ajVfe^9^Tdm`!N6b$iTV~I>7=O;kc{*QO zET!Sd62g}DZxG1Yc95%~Kzhsq6_Hg-q9s^VnuG#ZOcAATKPIz3VVw~(5HF0wBPPhU z7FcF*M@&b&I{pW-*fFsVuEib3NT!{G5e-#AiOk>!bn~Orr5dE5h;k7H8SWdz&JR$8 z&R1L|BQ<}NS$>rhNYNEAY{l4OQ90c@s3~B|z05f5B`iv+qUL;MYhET>!B@}*Q^!24 zsDAgYn{U1SgQbe?sfz8%(i@gSvu|Gfy?x1=x#@dd_v-JLrgolAUAVN^?EB1dIlG5- z+O(hx#y}+2*ZLRldP^{URz{bqUAsl2nqljH3bOnk=p#nvBqGZaYXk9>h9oYz;lXXc z4Sa-}QUWCn7Ux_c>~x(jud!tEKvua#tEFd2i0ruvO(J+R15Y3Fa6cZ(pTgsOTH^=W z1)f-93b5^(+LX6COy=-vdF(yNj0w)r+Q}5yA}PB5uwv~5f6E;DOZ2)z)cr~Twh7kulxvJE*q|7wZV&x+G<Inc-`qu&x5psPNu zv+o}WmH$AVaq$Nc=6+3+E}`*U+m=3E5F4}8t^7xnDl*0T!q8ta@jufTbW%6C1p^}*n=ptMC5>69?O#ah8!`$Lh`WRt2Xk$MzVS)29dhB4 zu7wa9P>ND<&Q-y2R!wcqCt_oUd3MgrWu$`}&8!9eJ~5ZRt8rhni+5K;>i?bIlITXezN1#@dny2%EWdxM zwy(F0S^+jyq#(F1JS;!(U!MQ8Z{gr@>cF|wxr-@rl#HKcU`&uYaph_q*7X0hx;CyL z)IhK!o&s%<9rX%D}#{f=JLD4j5BbgDLY} zMZJCbCo^3Cm#<}$PO$s7gU|xrMJat@2z2HxOwR7oZs@MJyLdKxOU^oQBrG|a(~jm; zOaHxz&m70I3|oHzjdgrmLewuYdgF-JH_t34*4QszzJI*6@F z{w%g8J(Hjydam7?BqlKudI-Y0E|AQ^-4IcBfg|I9&Ws_LmCy>2yRwhT>88h_K)M;A zm-mBkAlIAmQI5$(xDq4a4#f8uy$X;d*uq7F>>y5-={2*@F=LFzSS=gYDbdU7PckDG zZQU?`KK5;G7fC709Nx*ThqKQStIq83{>Fwoe<^P+NPaJHL$w77h@ywOwW;Taq3_UU zmd~8W@>LS$29+-O!ArmT)vr9FLVc^Y7%m{=QV^~tkZj77&TL2hd&ro9e}q)Fp?bZ@ezFMDmXmZ(!G8l=| zaIOoVyEiSlyZ_$ZJy)~f?zv(8c|}d8zIkT)_Vk_a-x&DEiuz2~&N=^FAnCnjS#)=h z;_;Q6ue^gx`&Iu`ReLKibM^MsgHogww5+zHP*zn%po-(29F^6yc$995Y8eUQT2ziF>Tt`# zuMV+;m@O-zFXboj^43=S+DU_4`JXcKmE~D%2DhU&4&IhS{KT!@%no2KkmIYKEDY?&4mc?}$gALPga= z#6Zx6Z^KJCge}%frsO!0!zI)FHvYWNcH|}PFgA`p)T5dw5wCrEO}kcdM6Fa+Nn<_@Dkx0sj52=FNCVlOqnNPDRv zQ;QGU0A|neB=9lcSD#9Vr{~M~hx9`z*#2vw{h>#^W(2*H+#@dx;i!xq^#!Yb2{Q!?H3? zxC;fDg2~1Fg1_OB^t0I79okfsP_U+W~lcq${gC{ zw<~PEXcXVxvE_rxj_pc9wRtC-la>k}!~(14e{^T0vuV=KG~N1g!}9ae3PPf=@-K-U zX|W@@mn{9?*81J2m)Hod^wOsL8I@$)J9l0+YWcOpLAFuY_E-xC`5ssY4(4aln`{UN z8-CMpa9{5JuviZf064_q;2{nN4?Sk)f0J-f`Cp)dX>pj279H;q_0YLYiPG6bFdi^; zY?h*j*ILlW7pW-d%gti*q-~I9re?uGwJzw}Ge7c~qd$92=gbsE6?g{=|lNxk?Fam?*oruW9y&MAV>Lgw2qH>Y1325rvQWpi5$4;@f*QS z>=NF?hR$Uxo}kUzre!;Y9Jn#G?4*!OaFstQMToyGZA#r~5ZAdv2mx=KWs@RlQ;Gp1 zT(TwYiauYtWej}>@(nhS(}tnt$konVG|o-gR4>psuzSpF;bPR1(~d<4k!Z(|WFVj9 ze}ir4O7xe-+m~gW(OC=?f3gM<3s1yHlcIR{DFndhY5a2 zpLaYn?^)WzV7#yGsq6=<&*-s%doAV#!*g+v;Abd_pKH!=u+HH(i4SL$?XC_Fr z!_Q2(yk30Imk3c9KeF?^gfFD!RCv7;L0rk8^E@0uw%zOXN5;Kg|zjyd3P!LF?s)qygwuFzmoS?b4#+{bJZ8T8B-)XgOxG`b@ugsl;qOXZDbX%sQ(!79=(DY6lBK$FVAsWJ^9u0R*m=YdMF$5y8b6!{g(n5e|CXv=S2u{FV|{ zLZR)emf{n}5PmL+e#VRc@(Dh0jZcPxmlYyqP6NdJR0n>Z3BMbHA6_D8$3BW=H5 zv7Lh(tG-x3aZ#L2P5yMLj($!AhZO?Y_~Q^_*=#bIz7RToCbWJbH2h4Ez7X2}QRw?Z VX!_drI~G&Jt-gN|=*eE>{{nBSIEw%P literal 0 HcmV?d00001 diff --git a/deepity_build/reporting/base.py b/deepity_build/reporting/base.py new file mode 100644 index 0000000..1f77aac --- /dev/null +++ b/deepity_build/reporting/base.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from pathlib import Path + + +class Reporter(ABC): + @abstractmethod + def __enter__(self) -> "Reporter": ... + + @abstractmethod + def __exit__(self, exc_type, exc, tb) -> None: ... + + @abstractmethod + def build_summary(self, targets: int) -> None: ... + + @abstractmethod + def clean_reconfigure(self) -> None: ... + + @abstractmethod + def configure_started(self) -> None: ... + + @abstractmethod + def configure_cached(self) -> None: ... + + @abstractmethod + def configure_complete(self, duration: float) -> None: ... + + @abstractmethod + def configure_failed(self, output: str) -> None: ... + + @abstractmethod + def build_started(self) -> None: ... + + @abstractmethod + def build_line(self, line: str) -> None: ... + + @abstractmethod + def build_complete(self, duration: float) -> None: ... + + @abstractmethod + def build_failed(self, output: str) -> None: ... + + @abstractmethod + def tests_missing(self, exe_name: str, paths: list[Path]) -> None: ... + + @abstractmethod + def tests_started(self) -> None: ... + + @abstractmethod + def test_line(self, line: str) -> None: ... + + @abstractmethod + def tests_complete(self, duration: float) -> None: ... + + @abstractmethod + def tests_failed(self, output: str) -> None: ... + + @abstractmethod + def success( + self, + configure_time: float | None, + build_time: float, + test_time: float, + ) -> None: ... \ No newline at end of file diff --git a/deepity_build/reporting/plain_reporter.py b/deepity_build/reporting/plain_reporter.py new file mode 100644 index 0000000..626de83 --- /dev/null +++ b/deepity_build/reporting/plain_reporter.py @@ -0,0 +1,176 @@ +from __future__ import annotations + +from pathlib import Path + +from ..config import BuildConfig +from ..git_info import GitInfo +from ..process import BUILD_PROGRESS_RE, format_duration +from .base import Reporter + + +class PlainReporter(Reporter): + """ + stdout-only mirror of RichReporter. Same public interface, no rich + dependency at all: statuses are printed as they change, build progress + is shown as an in-place percentage line, and test output streams + straight through. + """ + + def __init__(self, config: BuildConfig, git: GitInfo, generator: str) -> None: + self.config = config + self.git = git + self.generator = generator + + self.phase = "Starting..." + self.configure_status = "waiting" + self.build_status = "waiting" + self.test_status = "waiting" + + self.targets_built = 0 + self._last_build_pct = -1 + self._build_line_open = False + + def __enter__(self) -> "PlainReporter": + profile = self.config.profile + print( + f"Deepity Engine — build={self.config.build_type} " + f"generator={self.generator} jobs={self.config.jobs} " + f"arch={profile.name} cuda={'on' if self.config.cuda else 'off'}" + ) + if profile.name == "native": + print(" ⚠ arch=native is not portable — do not distribute this binary.") + return self + + def __exit__(self, exc_type, exc, tb) -> None: + return None + + def _status_line(self) -> None: + print( + f"[{self.phase}] configure={self.configure_status} " + f"build={self.build_status} tests={self.test_status}" + ) + + def _print_failure(self, title: str, body: str) -> None: + body = body.rstrip() or "No output" + print() + print(f"---- {title} " + "-" * max(0, 60 - len(title))) + print(body) + print("-" * 70) + + def _close_build_line(self) -> None: + if self._build_line_open: + print() + self._build_line_open = False + + # -- Reporter interface ------------------------------------------------- + + def build_summary(self, targets: int) -> None: + print(f"Targets built: {targets}") + + def clean_reconfigure(self) -> None: + self.phase = "Configuring" + self.configure_status = "clean reconfigure" + self._status_line() + + def configure_started(self) -> None: + self.phase = "Configuring" + self.configure_status = "configuring..." + self._status_line() + + def configure_cached(self) -> None: + self.configure_status = "cached" + self._status_line() + + def configure_complete(self, duration: float) -> None: + self.configure_status = f"complete ({format_duration(duration)})" + self._status_line() + + def configure_failed(self, output: str) -> None: + self._print_failure("✗ CMake Configuration Failed", output) + + def build_started(self) -> None: + self.phase = "Building" + self.build_status = "compiling..." + self._status_line() + + def build_line(self, line: str) -> None: + match = BUILD_PROGRESS_RE.search(line) + + if not match: + return + + current = int(match.group(1)) + total = int(match.group(2)) + message = match.group(3).strip() + + pct = int((current / total) * 100) if total else 0 + + # Avoid flooding the terminal: only print when percentage moves. + if pct != self._last_build_pct: + self._last_build_pct = pct + print(f"\r [{current}/{total}] {pct:3d}% {message[:80]}", end="", flush=True) + self._build_line_open = True + + def build_complete(self, duration: float) -> None: + self._close_build_line() + self.build_status = f"complete ({format_duration(duration)})" + self._status_line() + + def build_failed(self, output: str) -> None: + self._close_build_line() + self.build_status = "failed" + self.phase = "Build failed" + self._status_line() + self._print_failure("✗ Build Failed", output) + + def tests_missing(self, exe_name: str, paths: list[Path]) -> None: + self.test_status = "binary missing" + self.phase = "Tests unavailable" + self._status_line() + self._print_failure(f"✗ Could Not Find {exe_name}", "\n".join(str(p) for p in paths)) + + def tests_started(self) -> None: + self.phase = "Testing" + self.test_status = "running..." + self._status_line() + + def test_line(self, line: str) -> None: + line = line.rstrip() + if line: + print(f" {line}") + + def tests_complete(self, duration: float) -> None: + self.test_status = f"complete ({format_duration(duration)})" + self._status_line() + + def tests_failed(self, output: str) -> None: + self.test_status = "failed" + self.phase = "Tests failed" + self._status_line() + self._print_failure("✗ Test Suite Failed", output) + + def success( + self, + configure_time: float | None, + build_time: float, + test_time: float, + ) -> None: + total = sum(value or 0 for value in (configure_time, build_time, test_time)) + + print() + print("=" * 60) + print("BUILD SUCCESSFUL") + print("=" * 60) + print(f"Build type : {self.config.build_type}") + print(f"Generator : {self.generator}") + print(f"Arch profile : {self.config.profile.name}") + print(f"CUDA : {'ON' if self.config.cuda else 'off'}") + print(f"Parallel jobs : {self.config.jobs}") + print(f"Targets build : {self.targets_built}") + print( + "Configuration : " + + (format_duration(configure_time) if configure_time else "cached") + ) + print(f"Compilation : {format_duration(build_time)}") + print(f"Tests : {format_duration(test_time)}") + print(f"Total : {format_duration(total)}") \ No newline at end of file diff --git a/deepity_build/reporting/rich_reporter.py b/deepity_build/reporting/rich_reporter.py new file mode 100644 index 0000000..9d851f0 --- /dev/null +++ b/deepity_build/reporting/rich_reporter.py @@ -0,0 +1,331 @@ +from __future__ import annotations + +from collections import deque +from pathlib import Path + +from ..config import BuildConfig +from ..git_info import GitInfo +from ..process import BUILD_PROGRESS_RE, format_duration +from .base import Reporter + + +class RichReporter(Reporter): + """Single mutable Rich Live dashboard for the entire build.""" + + def __init__(self, config: BuildConfig, git: GitInfo, generator: str) -> None: + from rich.console import Console + from rich.progress import ( + BarColumn, + Progress, + SpinnerColumn, + TextColumn, + TimeElapsedColumn, + ) + + self.config = config + self.git = git + self.generator = generator + + self.targets_built = 0 + self.num_passed = 0 + self.num_failed = 0 + + self.phase = "Starting..." + self.configure_status = "[dim]waiting[/dim]" + self.build_status = "[dim]waiting[/dim]" + self.test_status = "[dim]waiting[/dim]" + + self.build_message = "" + self.test_lines: deque[str] = deque(maxlen=8) + + self._configure_time_display = "—" + self._build_time_display = "—" + self._test_time_display = "—" + self._total_time_display = "—" + + self.console = Console() + self.progress = Progress( + SpinnerColumn(), + TextColumn("[bold blue]{task.description}"), + BarColumn(), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + TimeElapsedColumn(), + ) + self.build_task = self.progress.add_task("Waiting", total=1, completed=0) + + self._live = None + + # -- context manager: owns the Live session -------------------------- + def __enter__(self) -> "RichReporter": + from rich.live import Live + + self.console.clear() + self.console.print() + + self._live = Live( + self._render(), + refresh_per_second=12, + console=self.console, + transient=False, + ) + self._live.__enter__() + return self + + def __exit__(self, exc_type, exc, tb) -> None: + if self._live is not None: + self._live.__exit__(exc_type, exc, tb) + + # -- rendering --------------------------------------------------------- + def _header(self): + from rich.panel import Panel + from rich.table import Table + + profile = self.config.profile + arch_style = "green" if profile.name == "distributed" else ( + "yellow" if profile.name == "fast" else "red" + ) + + table = Table.grid(padding=(0, 2)) + table.add_column() + table.add_column() + table.add_column() + table.add_column() + + table.add_row( + "[bold cyan]Deepity Engine[/bold cyan]", + f"[dim]Build[/dim] [bold]{self.config.build_type}[/bold]", + f"[dim]Generator[/dim] [bold]{self.generator}[/bold]", + f"[dim]Jobs[/dim] [bold]{self.config.jobs}[/bold]", + ) + + table.add_row( + "[dim]Arch profile[/dim]", + f"[bold {arch_style}]{profile.name}[/bold {arch_style}]" + + ( + " [dim](not portable — local dev only)[/dim]" + if profile.name == "native" + else "" + ), + "[dim]CUDA[/dim]", + "[bold green]ON[/bold green]" if self.config.cuda else "[dim]off[/dim]", + ) + + table.add_row( + "[dim]Git[/dim]", + f"[bold]{self.git.branch}[/bold] @ {self.git.commit}" + + (" [yellow]● modified[/yellow]" if self.git.dirty else " [green]✓ clean[/green]"), + ) + + return Panel(table, border_style="cyan", padding=(0, 1)) + + def _status(self): + from rich.panel import Panel + from rich.table import Table + + table = Table.grid(padding=(0, 2)) + table.add_column(style="bold", width=12) + table.add_column() + + table.add_row("Configure", self.configure_status) + table.add_row("Build", self.build_status) + table.add_row("Tests", self.test_status) + + return Panel(table, title="[bold]Build Status[/bold]", border_style="blue") + + def _activity(self): + from rich.console import Group + from rich.panel import Panel + from rich.table import Table + from rich.text import Text + + if self.phase == "Configuring": + body = Group( + Text("Configuring CMake...", style="bold yellow"), + Text(""), + Text.from_markup( + "[dim]CMake output is hidden while the build is running " + "and shown in full if configuration fails.[/dim]" + ), + ) + return Panel( + body, + title="[bold yellow]⚙ Configuration[/bold yellow]", + border_style="yellow", + ) + + if self.phase == "Building": + current = self.build_message or "Working..." + return Panel( + Group(self.progress, Text.from_markup(f"[dim]{current}[/dim]")), + title="[bold blue]⚙ Compilation[/bold blue]", + border_style="blue", + ) + + if self.phase == "Testing": + body = ( + "\n".join(self.test_lines) + if self.test_lines + else "[dim]Waiting for test output...[/dim]" + ) + return Panel( + body, + title="[bold magenta]▶ Tests[/bold magenta]", + border_style="magenta", + ) + + if self.phase == "Success": + body = Table.grid(padding=(0, 2)) + body.add_column(style="dim") + body.add_column(style="bold") + + body.add_row("Build type", self.config.build_type) + body.add_row("Arch profile", self.config.profile.name) + body.add_row("CUDA", "ON" if self.config.cuda else "off") + body.add_row("Parallel jobs", str(self.config.jobs)) + body.add_row("Targets", str(self.targets_built)) + body.add_row("Configuration", self._configure_time_display) + body.add_row("Compilation", self._build_time_display) + body.add_row("Tests", self._test_time_display) + body.add_row("Total", self._total_time_display) + + return Panel( + Group( + Text("✓ BUILD SUCCESSFUL", style="bold green", justify="center"), + Text(""), + body, + ), + title="[bold green]Deepity[/bold green]", + border_style="green", + padding=(1, 2), + ) + + return Panel("[dim]Waiting...[/dim]", title="[bold]Activity[/bold]", border_style="dim") + + def _render(self): + from rich.console import Group + + return Group(self._header(), self._status(), self._activity()) + + def _refresh(self) -> None: + if self._live is not None: + self._live.update(self._render(), refresh=True) + + def _print_failure(self, title: str, body: str) -> None: + from rich.panel import Panel + + body = body.rstrip() or "No output" + self.console.print() + self.console.print(Panel(body, title=f"[bold red]{title}[/bold red]", border_style="red")) + + # -- Reporter interface ------------------------------------------------- + + def build_summary(self, targets: int) -> None: + self.targets_built = targets + self._refresh() + + def clean_reconfigure(self) -> None: + self.phase = "Configuring" + self.configure_status = "[yellow]● clean reconfigure[/yellow]" + self._refresh() + + def configure_started(self) -> None: + self.phase = "Configuring" + self.configure_status = "[yellow]● configuring[/yellow]" + self._refresh() + + def configure_cached(self) -> None: + self.configure_status = "[green]✓ cached[/green]" + self._refresh() + + def configure_complete(self, duration: float) -> None: + self.configure_status = ( + f"[bold green]✓ complete[/bold green] [dim]({format_duration(duration)})[/dim]" + ) + self._refresh() + + def configure_failed(self, output: str) -> None: + self._print_failure("✗ CMake Configuration Failed", output) + + def build_started(self) -> None: + self.phase = "Building" + self.build_status = "[yellow]● compiling[/yellow]" + self.progress.update(self.build_task, total=1, completed=0, description="Compiling") + self._refresh() + + def build_line(self, line: str) -> None: + match = BUILD_PROGRESS_RE.search(line) + + if not match: + stripped = line.strip() + if stripped: + self.build_message = stripped[-160:] + self._refresh() + return + + current = int(match.group(1)) + total = int(match.group(2)) + message = match.group(3).strip() + self.targets_built = max(self.targets_built, total) + self.build_message = message + self.progress.update(self.build_task, total=total, completed=current) + + def build_complete(self, duration: float) -> None: + self.build_status = ( + f"[bold green]✓ complete[/bold green] [dim]({format_duration(duration)})[/dim]" + ) + self._refresh() + + def build_failed(self, output: str) -> None: + self.build_status = "[bold red]✗ failed[/bold red]" + self.phase = "Build failed" + self._refresh() + self._print_failure("✗ Build Failed", output) + + def tests_missing(self, exe_name: str, paths: list[Path]) -> None: + self.test_status = "[bold red]✗ binary missing[/bold red]" + self.phase = "Tests unavailable" + self._refresh() + self._print_failure(f"✗ Could Not Find {exe_name}", "\n".join(str(p) for p in paths)) + + def tests_started(self) -> None: + self.phase = "Testing" + self.test_status = "[yellow]● running[/yellow]" + self.test_lines.clear() + self._refresh() + + def test_line(self, line: str) -> None: + line = line.rstrip() + + if line: + self.test_lines.append(line) + self._refresh() + + def tests_complete(self, duration: float) -> None: + self.test_status = ( + f"[bold green]✓ complete[/bold green] [dim]({format_duration(duration)})[/dim]" + ) + self._refresh() + + def tests_failed(self, output: str) -> None: + self.test_status = "[bold red]✗ failed[/bold red]" + self.phase = "Tests failed" + self._refresh() + self._print_failure("✗ Test Suite Failed", output) + + def success( + self, + configure_time: float | None, + build_time: float, + test_time: float, + ) -> None: + self._configure_time_display = ( + format_duration(configure_time) if configure_time else "cached" + ) + self._build_time_display = format_duration(build_time) + self._test_time_display = format_duration(test_time) + + total = sum(value or 0 for value in (configure_time, build_time, test_time)) + self._total_time_display = format_duration(total) + + self.phase = "Success" + self._refresh() \ No newline at end of file diff --git a/include/deepity/Activations.h b/include/deepity/Activations.h index 0e92c0f..40833f4 100644 --- a/include/deepity/Activations.h +++ b/include/deepity/Activations.h @@ -26,8 +26,9 @@ * Deep::tanh(array, arraysize) * * @note Separate implementations exist for AVX512F, AVX2, SSE, and naive. - * @version 1.0 - * @date 2026-06-21 + * Memory does NOT need to be aligned. + * @version 1.1 + * @date 2026-08-23 * @author Jack Rose */ @@ -137,40 +138,33 @@ namespace Deep #if defined(__AVX512F__) simd_end = n - (n % 16); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end); i += 16) { - __m512 x_512 = _mm512_load_ps(x + i); - + __m512 x_512 = _mm512_loadu_ps(x + i); __m512 res = Sleef_expf16_u10avx512f(x_512); - - _mm512_store_ps(x + i, res); + _mm512_storeu_ps(x + i, res); } #elif defined(__AVX2__) simd_end = n - (n % 8); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end); i += 8) { - __m256 x_256 = _mm256_load_ps(x + i); - + __m256 x_256 = _mm256_loadu_ps(x + i); __m256 res = Sleef_expf8_u10avx2(x_256); - - _mm256_store_ps(x + i, res); + _mm256_storeu_ps(x + i, res); } #elif defined(__SSE2__) simd_end = n - (n % 4); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end); i += 4) { - __m128 x_128 = _mm_load_ps(x + i); - + __m128 x_128 = _mm_loadu_ps(x + i); __m128 res = Sleef_expf4_u10sse2(x_128); - - _mm_store_ps(x + i, res); + _mm_storeu_ps(x + i, res); } - #endif for (size_t i = simd_end; i < n; ++i) @@ -185,37 +179,32 @@ namespace Deep #if defined(__AVX512F__) simd_end = n - (n % 16); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end); i += 16) { - __m512 x_512 = _mm512_load_ps(x + i); - + __m512 x_512 = _mm512_loadu_ps(x + i); __m512 res = Sleef_logf16_u10avx512f(x_512); - - _mm512_store_ps(x + i, res); + _mm512_storeu_ps(x + i, res); } #elif defined(__AVX2__) simd_end = n - (n % 8); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end); i += 8) { - __m256 x_256 = _mm256_load_ps(x + i); - + __m256 x_256 = _mm256_loadu_ps(x + i); __m256 res = Sleef_logf8_u10avx2(x_256); - - _mm256_store_ps(x + i, res); + _mm256_storeu_ps(x + i, res); } #elif defined(__SSE__) || defined(_M_AMD64) || defined(_M_X64) simd_end = n - (n % 4); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end); i += 4) { - __m128 x_128 = _mm_load_ps(x + i); + __m128 x_128 = _mm_loadu_ps(x + i); __m128 res = Sleef_logf4_u10sse2(x_128); - - _mm_store_ps(x + i, res); + _mm_storeu_ps(x + i, res); } #endif for (size_t i = simd_end; i < n; ++i) @@ -226,7 +215,7 @@ namespace Deep #pragma region relu /// @brief RELU(x) = MAX(0, x) for all x - /// @param x array, \em assumed to be properly aligned + /// @param x array, \em does not need to be aligned /// @param n x length static inline void relu(float *RESTRICT x, const size_t n) noexcept { @@ -239,87 +228,87 @@ namespace Deep __m512 zeros = _mm512_setzero_ps(); size_t simd_end4 = n - (n % 64); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end4); i += 64) { - __m512 x0 = _mm512_load_ps(x + i); - __m512 x1 = _mm512_load_ps(x + i + 16); - __m512 x2 = _mm512_load_ps(x + i + 32); - __m512 x3 = _mm512_load_ps(x + i + 48); + __m512 x0 = _mm512_loadu_ps(x + i); + __m512 x1 = _mm512_loadu_ps(x + i + 16); + __m512 x2 = _mm512_loadu_ps(x + i + 32); + __m512 x3 = _mm512_loadu_ps(x + i + 48); x0 = _mm512_max_ps(zeros, x0); x1 = _mm512_max_ps(zeros, x1); x2 = _mm512_max_ps(zeros, x2); x3 = _mm512_max_ps(zeros, x3); - _mm512_store_ps(x + i, x0); - _mm512_store_ps(x + i + 16, x1); - _mm512_store_ps(x + i + 32, x2); - _mm512_store_ps(x + i + 48, x3); + _mm512_storeu_ps(x + i, x0); + _mm512_storeu_ps(x + i + 16, x1); + _mm512_storeu_ps(x + i + 32, x2); + _mm512_storeu_ps(x + i + 48, x3); } simd_end = n - (n % 16); for (size_t i = simd_end4; i < simd_end; i += 16) { - _mm512_store_ps(x + i, _mm512_max_ps(zeros, _mm512_load_ps(x + i))); + _mm512_storeu_ps(x + i, _mm512_max_ps(zeros, _mm512_loadu_ps(x + i))); } #elif defined(__AVX2__) || defined(__AVX__) __m256 zeros = _mm256_setzero_ps(); size_t simd_end4 = n - (n % 32); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end4); i += 32) { - __m256 x0 = _mm256_load_ps(x + i); - __m256 x1 = _mm256_load_ps(x + i + 8); - __m256 x2 = _mm256_load_ps(x + i + 16); - __m256 x3 = _mm256_load_ps(x + i + 24); + __m256 x0 = _mm256_loadu_ps(x + i); + __m256 x1 = _mm256_loadu_ps(x + i + 8); + __m256 x2 = _mm256_loadu_ps(x + i + 16); + __m256 x3 = _mm256_loadu_ps(x + i + 24); x0 = _mm256_max_ps(zeros, x0); x1 = _mm256_max_ps(zeros, x1); x2 = _mm256_max_ps(zeros, x2); x3 = _mm256_max_ps(zeros, x3); - _mm256_store_ps(x + i, x0); - _mm256_store_ps(x + i + 8, x1); - _mm256_store_ps(x + i + 16, x2); - _mm256_store_ps(x + i + 24, x3); + _mm256_storeu_ps(x + i, x0); + _mm256_storeu_ps(x + i + 8, x1); + _mm256_storeu_ps(x + i + 16, x2); + _mm256_storeu_ps(x + i + 24, x3); } simd_end = n - (n % 8); for (size_t i = simd_end4; i < simd_end; i += 8) { - _mm256_store_ps(x + i, _mm256_max_ps(zeros, _mm256_load_ps(x + i))); + _mm256_storeu_ps(x + i, _mm256_max_ps(zeros, _mm256_loadu_ps(x + i))); } #elif defined(__SSE__) || defined(_M_AMD64) || defined(_M_X64) __m128 zeros = _mm_setzero_ps(); size_t simd_end4 = n - (n % 16); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end4); i += 16) { - __m128 x0 = _mm_load_ps(x + i); - __m128 x1 = _mm_load_ps(x + i + 4); - __m128 x2 = _mm_load_ps(x + i + 8); - __m128 x3 = _mm_load_ps(x + i + 12); + __m128 x0 = _mm_loadu_ps(x + i); + __m128 x1 = _mm_loadu_ps(x + i + 4); + __m128 x2 = _mm_loadu_ps(x + i + 8); + __m128 x3 = _mm_loadu_ps(x + i + 12); x0 = _mm_max_ps(zeros, x0); x1 = _mm_max_ps(zeros, x1); x2 = _mm_max_ps(zeros, x2); x3 = _mm_max_ps(zeros, x3); - _mm_store_ps(x + i, x0); - _mm_store_ps(x + i + 4, x1); - _mm_store_ps(x + i + 8, x2); - _mm_store_ps(x + i + 12, x3); + _mm_storeu_ps(x + i, x0); + _mm_storeu_ps(x + i + 4, x1); + _mm_storeu_ps(x + i + 8, x2); + _mm_storeu_ps(x + i + 12, x3); } simd_end = n - (n % 4); for (size_t i = simd_end4; i < simd_end; i += 4) { - _mm_store_ps(x + i, _mm_max_ps(zeros, _mm_load_ps(x + i))); + _mm_storeu_ps(x + i, _mm_max_ps(zeros, _mm_loadu_ps(x + i))); } #endif @@ -341,31 +330,31 @@ namespace Deep __m512 zeros = _mm512_setzero_ps(); size_t simd_end4 = n - (n % 64); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end4); i += 64) { - __m512 x0 = _mm512_load_ps(x + i); - __m512 x1 = _mm512_load_ps(x + i + 16); - __m512 x2 = _mm512_load_ps(x + i + 32); - __m512 x3 = _mm512_load_ps(x + i + 48); + __m512 x0 = _mm512_loadu_ps(x + i); + __m512 x1 = _mm512_loadu_ps(x + i + 16); + __m512 x2 = _mm512_loadu_ps(x + i + 32); + __m512 x3 = _mm512_loadu_ps(x + i + 48); __mmask16 m0 = _mm512_cmp_ps_mask(x0, zeros, _CMP_GT_OQ); __mmask16 m1 = _mm512_cmp_ps_mask(x1, zeros, _CMP_GT_OQ); __mmask16 m2 = _mm512_cmp_ps_mask(x2, zeros, _CMP_GT_OQ); __mmask16 m3 = _mm512_cmp_ps_mask(x3, zeros, _CMP_GT_OQ); - _mm512_store_ps(x + i, _mm512_mask_blend_ps(m0, zeros, ones)); - _mm512_store_ps(x + i + 16, _mm512_mask_blend_ps(m1, zeros, ones)); - _mm512_store_ps(x + i + 32, _mm512_mask_blend_ps(m2, zeros, ones)); - _mm512_store_ps(x + i + 48, _mm512_mask_blend_ps(m3, zeros, ones)); + _mm512_storeu_ps(x + i, _mm512_mask_blend_ps(m0, zeros, ones)); + _mm512_storeu_ps(x + i + 16, _mm512_mask_blend_ps(m1, zeros, ones)); + _mm512_storeu_ps(x + i + 32, _mm512_mask_blend_ps(m2, zeros, ones)); + _mm512_storeu_ps(x + i + 48, _mm512_mask_blend_ps(m3, zeros, ones)); } simd_end = n - (n % 16); for (size_t i = simd_end4; i < simd_end; i += 16) { - __m512 x0 = _mm512_load_ps(x + i); + __m512 x0 = _mm512_loadu_ps(x + i); __mmask16 m0 = _mm512_cmp_ps_mask(x0, zeros, _CMP_GT_OQ); - _mm512_store_ps(x + i, _mm512_mask_blend_ps(m0, zeros, ones)); + _mm512_storeu_ps(x + i, _mm512_mask_blend_ps(m0, zeros, ones)); } #elif defined(__AVX2__) || defined(__AVX__) @@ -373,30 +362,30 @@ namespace Deep __m256 zeros = _mm256_setzero_ps(); size_t simd_end4 = n - (n % 32); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end4); i += 32) { - __m256 x0 = _mm256_load_ps(x + i); - __m256 x1 = _mm256_load_ps(x + i + 8); - __m256 x2 = _mm256_load_ps(x + i + 16); - __m256 x3 = _mm256_load_ps(x + i + 24); + __m256 x0 = _mm256_loadu_ps(x + i); + __m256 x1 = _mm256_loadu_ps(x + i + 8); + __m256 x2 = _mm256_loadu_ps(x + i + 16); + __m256 x3 = _mm256_loadu_ps(x + i + 24); x0 = _mm256_and_ps(ones, _mm256_cmp_ps(x0, zeros, _CMP_GT_OQ)); x1 = _mm256_and_ps(ones, _mm256_cmp_ps(x1, zeros, _CMP_GT_OQ)); x2 = _mm256_and_ps(ones, _mm256_cmp_ps(x2, zeros, _CMP_GT_OQ)); x3 = _mm256_and_ps(ones, _mm256_cmp_ps(x3, zeros, _CMP_GT_OQ)); - _mm256_store_ps(x + i, x0); - _mm256_store_ps(x + i + 8, x1); - _mm256_store_ps(x + i + 16, x2); - _mm256_store_ps(x + i + 24, x3); + _mm256_storeu_ps(x + i, x0); + _mm256_storeu_ps(x + i + 8, x1); + _mm256_storeu_ps(x + i + 16, x2); + _mm256_storeu_ps(x + i + 24, x3); } simd_end = n - (n % 8); for (size_t i = simd_end4; i < simd_end; i += 8) { - __m256 x0 = _mm256_load_ps(x + i); - _mm256_store_ps(x + i, _mm256_and_ps(ones, _mm256_cmp_ps(x0, zeros, _CMP_GT_OQ))); + __m256 x0 = _mm256_loadu_ps(x + i); + _mm256_storeu_ps(x + i, _mm256_and_ps(ones, _mm256_cmp_ps(x0, zeros, _CMP_GT_OQ))); } #elif defined(__SSE__) || defined(_M_AMD64) || defined(_M_X64) @@ -404,30 +393,30 @@ namespace Deep __m128 zeros = _mm_setzero_ps(); size_t simd_end4 = n - (n % 16); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end4); i += 16) { - __m128 x0 = _mm_load_ps(x + i); - __m128 x1 = _mm_load_ps(x + i + 4); - __m128 x2 = _mm_load_ps(x + i + 8); - __m128 x3 = _mm_load_ps(x + i + 12); + __m128 x0 = _mm_loadu_ps(x + i); + __m128 x1 = _mm_loadu_ps(x + i + 4); + __m128 x2 = _mm_loadu_ps(x + i + 8); + __m128 x3 = _mm_loadu_ps(x + i + 12); x0 = _mm_and_ps(ones, _mm_cmpgt_ps(x0, zeros)); x1 = _mm_and_ps(ones, _mm_cmpgt_ps(x1, zeros)); x2 = _mm_and_ps(ones, _mm_cmpgt_ps(x2, zeros)); x3 = _mm_and_ps(ones, _mm_cmpgt_ps(x3, zeros)); - _mm_store_ps(x + i, x0); - _mm_store_ps(x + i + 4, x1); - _mm_store_ps(x + i + 8, x2); - _mm_store_ps(x + i + 12, x3); + _mm_storeu_ps(x + i, x0); + _mm_storeu_ps(x + i + 4, x1); + _mm_storeu_ps(x + i + 8, x2); + _mm_storeu_ps(x + i + 12, x3); } simd_end = n - (n % 4); for (size_t i = simd_end4; i < simd_end; i += 4) { - __m128 x0 = _mm_load_ps(x + i); - _mm_store_ps(x + i, _mm_and_ps(ones, _mm_cmpgt_ps(x0, zeros))); + __m128 x0 = _mm_loadu_ps(x + i); + _mm_storeu_ps(x + i, _mm_and_ps(ones, _mm_cmpgt_ps(x0, zeros))); } #endif @@ -460,42 +449,39 @@ namespace Deep #if defined(__AVX512F__) simd_end = n - (n % 16); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end); i += 16) { - __m512 x_512 = _mm512_load_ps(x + i); - // u10 guarantees 1.0 ULP accuracy (highly precise) + __m512 x_512 = _mm512_loadu_ps(x + i); __m512 res = Sleef_tanhf16_u10avx512f(x_512); - _mm512_store_ps(x + i, res); + _mm512_storeu_ps(x + i, res); } #elif defined(__AVX2__) simd_end = n - (n % 8); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end); i += 8) { - __m256 x_256 = _mm256_load_ps(x + i); + __m256 x_256 = _mm256_loadu_ps(x + i); __m256 res = Sleef_tanhf8_u10avx2(x_256); - _mm256_store_ps(x + i, res); + _mm256_storeu_ps(x + i, res); } #elif defined(__SSE4_1__) || defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) simd_end = n - (n % 4); -#pragma omp parallel for schedule(static) if (n > 65536) +#pragma omp parallel for schedule(static) if (n > 65536 && !omp_in_parallel()) for (ptrdiff_t i = 0; i < (ptrdiff_t)(simd_end); i += 4) { - __m128 x_128 = _mm_load_ps(x + i); - // Fallback to sse4 or sse2 depending on what your SLEEF build exposes + __m128 x_128 = _mm_loadu_ps(x + i); #if defined(__SSE4_1__) __m128 res = Sleef_tanhf4_u10sse4(x_128); #else __m128 res = Sleef_tanhf4_u10sse2(x_128); #endif - _mm_store_ps(x + i, res); + _mm_storeu_ps(x + i, res); } #endif - // Scalar remainder for (size_t i = simd_end; i < n; i++) { x[i] = Sleef_tanhf_u10(x[i]); @@ -519,14 +505,14 @@ namespace Deep for (; i < simd_end; i += 16) { - __m512 t = _mm512_load_ps(x + i); + __m512 t = _mm512_loadu_ps(x + i); #ifdef __FMA__ __m512 res = _mm512_fnmadd_ps(t, t, ones); // 1 - t*t #else __m512 res = _mm512_sub_ps(ones, _mm512_mul_ps(t, t)); #endif - _mm512_store_ps(x + i, res); + _mm512_storeu_ps(x + i, res); } #elif defined(__AVX2__) @@ -535,13 +521,13 @@ namespace Deep for (; i < simd_end; i += 8) { - __m256 t = _mm256_load_ps(x + i); + __m256 t = _mm256_loadu_ps(x + i); #ifdef __FMA__ __m256 res = _mm256_fnmadd_ps(t, t, ones); // 1 - t*t #else __m256 res = _mm256_sub_ps(ones, _mm256_mul_ps(t, t)); #endif - _mm256_store_ps(x + i, res); + _mm256_storeu_ps(x + i, res); } #elif defined(__SSE4_1__) || defined(_M_AMD64) || defined(_M_X64) __m128 ones = _mm_set1_ps(1.0f); @@ -549,14 +535,14 @@ namespace Deep for (; i < simd_end; i += 4) { - __m128 t = _mm_load_ps(x + i); + __m128 t = _mm_loadu_ps(x + i); #ifdef __FMA__ __m128 res = _mm_fnmadd_ps(t, t, ones); // 1 - t*t #else __m128 res = _mm_sub_ps(ones, _mm_mul_ps(t, t)); #endif - _mm_store_ps(x + i, res); + _mm_storeu_ps(x + i, res); } #endif @@ -569,7 +555,7 @@ namespace Deep #pragma region sigmoid /// @brief Implements the \em Logistic \em Sigmoid approximation, i.e. `S(x) = 1 / (1 + e^(-x))` - /// @param x array, \em assumed to be 64-bit aligned! + /// @param x array, \em does not need to be aligned /// @param n x length static inline void sigmoid(float *RESTRICT x, const size_t n) noexcept { @@ -585,12 +571,12 @@ namespace Deep size_t simd_end = n - r; for (; i < simd_end; i += 16) { - __m512 x_512 = _mm512_load_ps(x + i); + __m512 x_512 = _mm512_loadu_ps(x + i); __m512 neg_x = _mm512_mul_ps(x_512, neg_one); __m512 exp_neg_x = Sleef_expf16_u10avx512f(neg_x); __m512 den = _mm512_add_ps(exp_neg_x, one); __m512 sig = _mm512_div_ps(one, den); - _mm512_store_ps(x + i, sig); + _mm512_storeu_ps(x + i, sig); } #elif defined(__AVX2__) __m256 one = _mm256_set1_ps(1.0f); @@ -599,12 +585,12 @@ namespace Deep size_t simd_end = n - r; for (; i < simd_end; i += 8) { - __m256 x_256 = _mm256_load_ps(x + i); + __m256 x_256 = _mm256_loadu_ps(x + i); __m256 neg_x = _mm256_mul_ps(x_256, neg_one); __m256 exp_neg_x = Sleef_expf8_u10avx2(neg_x); __m256 den = _mm256_add_ps(exp_neg_x, one); __m256 sig = _mm256_div_ps(one, den); - _mm256_store_ps(x + i, sig); + _mm256_storeu_ps(x + i, sig); } #elif defined(__SSE__) || defined(_M_AMD64) || defined(_M_X64) __m128 one = _mm_set1_ps(1.0f); @@ -613,12 +599,12 @@ namespace Deep size_t simd_end = n - r; for (; i < simd_end; i += 4) { - __m128 x_128 = _mm_load_ps(x + i); + __m128 x_128 = _mm_loadu_ps(x + i); __m128 neg_x = _mm_mul_ps(x_128, neg_one); __m128 exp_neg_x = Sleef_expf4_u10sse2(neg_x); __m128 den = _mm_add_ps(exp_neg_x, one); __m128 sig = _mm_div_ps(one, den); - _mm_store_ps(x + i, sig); + _mm_storeu_ps(x + i, sig); } #endif for (; i < n; i++) @@ -628,7 +614,7 @@ namespace Deep } /// @brief Implements the \em Elliot \em Sigmoid approximation, i.e. `S(x) = (1/2)((x / (1 + |x|)) + 1)` - /// @param x array, \em assumed to be 64-bit aligned! + /// @param x array, \em does not need to be aligned /// @param n x length inline void e_sigmoid(float *RESTRICT x, const size_t n) noexcept { @@ -644,14 +630,14 @@ namespace Deep size_t simd_end = n - r; for (; i < simd_end; i += 16) { - __m512 x_512 = _mm512_load_ps(x + i); + __m512 x_512 = _mm512_loadu_ps(x + i); __m512 den = _mm512_add_ps( _mm512_abs_ps(x_512), one); __m512 div = _mm512_div_ps(x_512, den); __m512 sig = _mm512_fmadd_ps(div, half, half); - _mm512_store_ps(x + i, sig); + _mm512_storeu_ps(x + i, sig); } #elif defined(__AVX2__) __m256 half = _mm256_set1_ps(0.5f); @@ -661,7 +647,7 @@ namespace Deep size_t simd_end = n - r; for (; i < simd_end; i += 8) { - __m256 x_256 = _mm256_load_ps(x + i); + __m256 x_256 = _mm256_loadu_ps(x + i); __m256 den = _mm256_add_ps( _mm256_and_ps(x_256, mask), one); @@ -672,7 +658,7 @@ namespace Deep __m256 sig = _mm256_add_ps(_mm256_mul_ps(div, half), half); #endif - _mm256_store_ps(x + i, sig); + _mm256_storeu_ps(x + i, sig); } #elif defined(__SSE__) || defined(_M_AMD64) || defined(_M_X64) __m128 half = _mm_set1_ps(0.5f); @@ -682,7 +668,7 @@ namespace Deep size_t simd_end = n - r; for (; i < simd_end; i += 4) { - __m128 x_128 = _mm_load_ps(x + i); + __m128 x_128 = _mm_loadu_ps(x + i); __m128 den = _mm_add_ps( _mm_and_ps(x_128, mask), one); @@ -693,7 +679,7 @@ namespace Deep __m128 sig = _mm_add_ps(_mm_mul_ps(div, half), half); #endif - _mm_store_ps(x + i, sig); + _mm_storeu_ps(x + i, sig); } #endif for (; i < n; i++) @@ -717,9 +703,9 @@ namespace Deep size_t simd_end = n - r; for (; i < simd_end; i += 16) { - __m512 x_512 = _mm512_load_ps(x + i); + __m512 x_512 = _mm512_loadu_ps(x + i); __m512 d = _mm512_fnmadd_ps(x_512, x_512, x_512); // d = x * (1 - x) = x - x^2 = -x*x + x - _mm512_store_ps(x + i, d); + _mm512_storeu_ps(x + i, d); } #elif defined(__AVX2__) @@ -727,27 +713,27 @@ namespace Deep size_t simd_end = n - r; for (; i < simd_end; i += 8) { - __m256 x_256 = _mm256_load_ps(x + i); + __m256 x_256 = _mm256_loadu_ps(x + i); #ifdef __FMA__ __m256 d = _mm256_fnmadd_ps(x_256, x_256, x_256); // d = x * (1 - x) = x - x^2 = -x*x + x #else __m256 d = _mm256_sub_ps(x_256, _mm256_mul_ps(x_256, x_256)); #endif - _mm256_store_ps(x + i, d); + _mm256_storeu_ps(x + i, d); } #elif defined(__SSE__) || defined(_M_AMD64) || defined(_M_X64) size_t r = n % 4; size_t simd_end = n - r; for (; i < simd_end; i += 4) { - __m128 x_128 = _mm_load_ps(x + i); + __m128 x_128 = _mm_loadu_ps(x + i); #ifdef __FMA__ __m128 d = _mm_fnmadd_ps(x_128, x_128, x_128); // d = x * (1 - x) = x - x^2 = -x*x + x #else __m128 d = _mm_sub_ps(x_128, _mm_mul_ps(x_128, x_128)); #endif - _mm_store_ps(x + i, d); + _mm_storeu_ps(x + i, d); } #endif @@ -764,4 +750,4 @@ namespace Deep { std::fill_n(x, n, 1.0f); } -} \ No newline at end of file +} diff --git a/include/deepity/networks/ConvPCNetwork.h b/include/deepity/networks/ConvPCNetwork.h index b6534db..0af2857 100644 --- a/include/deepity/networks/ConvPCNetwork.h +++ b/include/deepity/networks/ConvPCNetwork.h @@ -65,7 +65,7 @@ namespace Deep * @brief Destroys the network, freeing every owned ConvPCLayer * and releasing the backing MemoryArena. */ - ~ConvPCNetwork(); + ~ConvPCNetwork() = default; // No copy (owns raw pointers + a MemoryArena); move not implemented // either, matching DiscriminativePCNetwork's conventions. @@ -207,7 +207,7 @@ namespace Deep * @return Pointer to the last layer added via AddLayer(); this is * the layer with outChannels==0. */ - ConvPCLayer *GetTerminalLayer() noexcept { return layers.back(); } + ConvPCLayer *GetTerminalLayer() noexcept { return layers.back().get(); } /** * @brief Returns every layer in the network, in the order they @@ -215,7 +215,7 @@ namespace Deep * @return A const reference to the internal layer list. Valid for * the lifetime of this ConvPCNetwork. */ - const std::vector &GetLayers() const noexcept { return layers; } + const auto &GetLayers() const noexcept { return layers; } /** * @brief Returns the fixed batch size this network was @@ -257,7 +257,7 @@ namespace Deep private: /// @brief Every layer in the network, in the order added via /// AddLayer(); owned raw pointers, freed in the destructor. - std::vector layers; + std::vector> layers; /// @brief Single contiguous memory arena backing every layer's /// beliefs/errors/weights, bound during Compile(). diff --git a/include/deepity/networks/DiscriminativePCNetwork.h b/include/deepity/networks/DiscriminativePCNetwork.h index 97d3c78..3efe0a2 100644 --- a/include/deepity/networks/DiscriminativePCNetwork.h +++ b/include/deepity/networks/DiscriminativePCNetwork.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -33,7 +34,7 @@ namespace Deep /// @see https://arxiv.org/pdf/2506.06332 class DiscriminativePCNetwork { - std::vector layers; + std::vector> layers; int batchSize; bool autoSize = true; @@ -49,7 +50,7 @@ namespace Deep DiscriminativePCNetwork(int batchSize) : batchSize(batchSize), autoSize(false) {} /// @brief Default constructor; deletes each layer. - ~DiscriminativePCNetwork(); + ~DiscriminativePCNetwork() = default; DiscriminativePCNetwork(DiscriminativePCNetwork &&other) noexcept = default; DiscriminativePCNetwork &operator=(DiscriminativePCNetwork &&other) noexcept = default; @@ -91,7 +92,7 @@ namespace Deep void ResetState() noexcept; - const std::vector &GetLayers() const noexcept { return layers; } + const auto &GetLayers() const noexcept { return layers; } /// @brief Returns the batch size for the network's layers /// @return size_t batchSize @@ -101,27 +102,27 @@ namespace Deep { if (layers.empty()) return nullptr; - return layers.back(); + return layers.back().get(); } void SetLearningRate(float lr) { - for (auto layer : layers) + for (auto &layer : layers) layer->SetLearningRate(lr); } void SetInferenceRate(float ir) { - for (auto layer : layers) + for (auto &layer : layers) layer->SetLearningRate(ir); } void SetPrecisionRate(float pr) { - for (auto layer : layers) + for (auto &layer : layers) layer->SetPrecisionRate(pr); } void SetLambda(float l) { - for (auto layer : layers) + for (auto &layer : layers) layer->SetLambda(l); } diff --git a/include/deepity/networks/SimpleConvPCNetwork.h b/include/deepity/networks/SimpleConvPCNetwork.h index d490695..9ed80dd 100644 --- a/include/deepity/networks/SimpleConvPCNetwork.h +++ b/include/deepity/networks/SimpleConvPCNetwork.h @@ -34,11 +34,12 @@ namespace Deep { public: explicit SimpleConvPCNetwork(int batchSize) noexcept; - ~SimpleConvPCNetwork(); SimpleConvPCNetwork(const SimpleConvPCNetwork &) = delete; SimpleConvPCNetwork &operator=(const SimpleConvPCNetwork &) = delete; + ~SimpleConvPCNetwork() = default; + /// @brief Adds a convolutional layer. Pass outChannels=0 to mark a /// terminal layer (no outgoing prediction), matching /// SimpleConvPCLayer's nextSize=0 convention. @@ -77,8 +78,8 @@ namespace Deep /// against outChannels==0, so this is belt-and-suspenders). void UpdateWeights() noexcept; - SimpleConvPCLayer *GetTerminalLayer() noexcept { return layers.back(); } - const std::vector &GetLayers() const noexcept { return layers; } + SimpleConvPCLayer *GetTerminalLayer() noexcept { return layers.back().get(); } + const auto &GetLayers() const noexcept { return layers; } int GetBatchSize() const noexcept { return batchSize; } /// @brief Full train step: clamp input+target, settle for @@ -92,7 +93,7 @@ namespace Deep std::vector Predict(const std::vector &x, int inferenceSteps); private: - std::vector layers; + std::vector> layers; std::unique_ptr arena; int batchSize; OptimizerType pendingOpt = OptimizerType::SGD; diff --git a/include/deepity/networks/SimplePCNetwork.h b/include/deepity/networks/SimplePCNetwork.h index b0917dd..f2d10b9 100644 --- a/include/deepity/networks/SimplePCNetwork.h +++ b/include/deepity/networks/SimplePCNetwork.h @@ -37,12 +37,11 @@ namespace Deep /// @param batchSize Batch size explicit SimplePCNetwork(int batchSize) noexcept; - /// @brief Default destructor; deletes each layer. - ~SimplePCNetwork(); - SimplePCNetwork(const SimplePCNetwork &) = delete; SimplePCNetwork &operator=(const SimplePCNetwork &) = delete; + ~SimplePCNetwork() = default; + /// @brief Adds a layer to the network. /// @param size input size /// @param nextSize output size @@ -82,15 +81,15 @@ namespace Deep /// @brief Returns the network's terminal (final) layer. /// @return Pointer to the last layer added via AddLayer(). - SimplePCLayer *GetTerminalLayer() noexcept { return layers.back(); } + SimplePCLayer *GetTerminalLayer() noexcept { return layers.back().get(); } /// @brief Returns every layer in the network, in the order they were added. /// @return A reference to the internal layer list. - std::vector &GetLayers() noexcept { return layers; } + std::vector> &GetLayers() noexcept { return layers; } /// @brief Returns every layer in the network, in the order they were added. /// @return A const reference to the internal layer list. - const std::vector &GetLayers() const noexcept { return layers; } + const std::vector> &GetLayers() const noexcept { return layers; } /// @brief Returns the batch size for the network's layers /// @return size_t batchSize @@ -100,7 +99,7 @@ namespace Deep /// @param o The optimizer type to apply. void SetOptimizer(OptimizerType o) noexcept { - for (SimplePCLayer *layer : layers) + for (auto &layer : layers) layer->SetOptimizer(o); } @@ -134,12 +133,32 @@ namespace Deep /// point each layer's CalculateState() runs). Only mu is used. void ProjectForward() noexcept; + /// @brief Full train step WITH forward-projection initialization, + /// all in ONE call -- reset, clamp, project, settle, update weights, + /// unclamp. Matches TrainStep()'s signature/return convention exactly, + /// just with ProjectForward() inserted between clamping the input and + /// clamping the target. + /// + /// Exists specifically to eliminate the Python/pybind boundary- + /// crossing overhead of doing this same sequence via many separate + /// calls from Python (reset_state, clamp_input, project_forward, + /// clamp_state, then STEPS*2 individual calculate_state/update_state + /// calls, update_weights, unclamp_state -- over 40 individual + /// crossings per batch at STEPS=20). This does the whole sequence in + /// ONE crossing instead. + float TrainStepWithProjection(const std::vector &x, const std::vector &y, int inferenceSteps); + + /// @brief Sets mu-cache threshold on every layer -- see + /// SimplePCLayer::SetMuCacheThreshold() for semantics. Safe to call any + /// time after Compile(). + void SetMuCacheThreshold(float threshold) noexcept; + /// @brief Loads all layers into one contiguous block of memory. void Compile(); private: - std::vector layers; + std::vector> layers; std::unique_ptr arena; int batchSize; }; -} \ No newline at end of file +} diff --git a/logs/build.log b/logs/build.log index 6e73be5..b95e9d4 100644 --- a/logs/build.log +++ b/logs/build.log @@ -1,7 +1,152 @@ ---- Deepity Build Log (Release) --- +--- Deepity Build Log (Release, arch=fast) --- === Compilation === -ninja: no work to do. +[1/131] Building C object _deps/sleef-build/src/common/CMakeFiles/common.dir/common.c.o +[2/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkalias.dir/mkalias.c.o +[3/131] Building C object _deps/sleef-build/src/common/CMakeFiles/addSuffix.dir/addSuffix.c.o +[4/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkrename_gnuabi.dir/mkrename_gnuabi.c.o +[5/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkmasked_gnuabi.dir/mkmasked_gnuabi.c.o +[6/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkrename.dir/mkrename.c.o +[7/131] Linking C executable _deps/sleef-build/bin/mkalias +[8/131] Linking C executable _deps/sleef-build/bin/addSuffix +[9/131] Generating alias_AVX512F_dp.h.tmp +[10/131] Generating alias_AVX512F_sp.h.tmp +[11/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkdisp.dir/mkdisp.c.o +[12/131] Linking C executable _deps/sleef-build/bin/mkrename_gnuabi +[13/131] Generating include/renameavx512f_gnuabi.h +Generating renameavx512f_gnuabi.h: mkrename_gnuabi avx512f e 8 16 __m512d __m512 __m256i __m512i __AVX512F__ +[14/131] Generating include/renameavx2_gnuabi.h +Generating renameavx2_gnuabi.h: mkrename_gnuabi avx2 d 4 8 __m256d __m256 __m128i __m256i __AVX2__ +[15/131] Generating include/renamesse2_gnuabi.h +Generating renamesse2_gnuabi.h: mkrename_gnuabi sse2 b 2 4 _mm128d _mm128 _mm128i _mm128i __SSE2__ +[16/131] Generating include/renameavx_gnuabi.h +Generating renameavx_gnuabi.h: mkrename_gnuabi avx c 4 8 __m256d __m256 __m128i struct\ {\ __m128i\ x,\ y;\ } __AVX__ +[17/131] Building C object _deps/sleef-build/src/common/CMakeFiles/testerutil_obj.dir/testerutil.c.o +[18/131] Building C object _deps/sleef-build/src/common/CMakeFiles/qtesterutil_obj.dir/qtesterutil.c.o +[19/131] Generating include/alias_avx512f.h +[20/131] Linking C executable _deps/sleef-build/bin/mkmasked_gnuabi +[21/131] Generating include/masked_avx512f_dp_gnuabi.h +[22/131] Generating include/masked_avx512f_sp_gnuabi.h +[23/131] Linking C executable _deps/sleef-build/bin/mkrename +[24/131] Generating sleeflibm_AVX.h.tmp +[25/131] Generating sleeflibm_AVX2.h.tmp +[26/131] Generating sleeflibm_AVX2128.h.tmp +[27/131] Generating sleeflibm_AVX512F.h.tmp +[28/131] Generating sleeflibm_AVX512FNOFMA.h.tmp +[29/131] Generating sleeflibm_AVX512F_.h.tmp +[30/131] Generating sleeflibm_AVX_.h.tmp +[31/131] Generating sleeflibm_DSP_SCALAR.h.tmp +[32/131] Building CXX object _deps/sleef-build/src/common/CMakeFiles/psha_obj.dir/psha2_capi.cpp.o +[33/131] Linking C executable _deps/sleef-build/bin/mkdisp +[34/131] Generating sleeflibm_FMA4.h.tmp +[35/131] Generating sleeflibm_PURECFMA_SCALAR.h.tmp +[36/131] Generating sleeflibm_PUREC_SCALAR.h.tmp +[37/131] Generating sleeflibm_SSE2.h.tmp +[38/131] Generating sleeflibm_SSE4.h.tmp +[39/131] Generating sleeflibm_SSE_.h.tmp +[40/131] Generating include/renameavx512fnofma.h +Generating renameavx512fnofma.h: mkrename cinz_ 8 16 avx512fnofma +[41/131] Generating include/renameavx512f.h +Generating renameavx512f.h: mkrename finz_ 8 16 avx512f +[42/131] Generating include/renameavx2.h +Generating renameavx2.h: mkrename finz_ 4 8 avx2 +[43/131] Generating dispscalar.c.body +[44/131] Generating include/renameavx2128.h +Generating renameavx2128.h: mkrename finz_ 2 4 avx2128 +[45/131] Generating dispsse.c.tmp +[46/131] Generating dispavx.c.tmp +[47/131] Generating include/renamefma4.h +Generating renamefma4.h: mkrename finz_ 4 8 fma4 +[48/131] Generating include/renameavx.h +Generating renameavx.h: mkrename cinz_ 4 8 avx +[49/131] Generating include/renamesse4.h +Generating renamesse4.h: mkrename cinz_ 2 4 sse4 +[50/131] Generating include/renamesse2.h +Generating renamesse2.h: mkrename cinz_ 2 4 sse2 +[51/131] Generating include/renamedspscalar.h +[52/131] Generating include/renamepurec_scalar.h +Generating renamepurec_scalar.h: mkrename cinz_ 1 1 purec +[53/131] Generating include/renamepurecfma_scalar.h +Generating renamepurecfma_scalar.h: mkrename finz_ 1 1 purecfma +[54/131] Generating include/renamecuda.h +Generating renamecuda.h: mkrename finz_ 1 1 cuda +[55/131] Generating include/renamedsp128.h +[56/131] Generating include/renamedsp256.h +[57/131] Generating dispscalar.c +[58/131] Generating ../../include/sleef.h +[59/131] Generating dispavx.c +[60/131] Generating dispsse.c +[61/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabi.dir/rempitab.c.o +[62/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512f.dir/sleefsimdsp.c.o +[63/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2.dir/sleefsimdsp.c.o +[64/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512fnofma.dir/sleefsimdsp.c.o +[65/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512f.dir/sleefsimddp.c.o +[66/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512fnofma.dir/sleefsimddp.c.o +[67/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2.dir/sleefsimddp.c.o +[68/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512f.dir/sleefsimdsp.c.o +[69/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx2sp.dir/sleefsimdsp.c.o +[70/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512f.dir/sleefsimddp.c.o +[71/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512fnofma.dir/sleefsimdsp.c.o +[72/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2.dir/sleefsimddp.c.o +[73/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2128.dir/sleefsimdsp.c.o +[74/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512fnofma.dir/sleefsimddp.c.o +[75/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabisse2dp.dir/sleefsimddp.c.o +[76/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx512fdp.dir/sleefsimddp.c.o +[77/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2.dir/sleefsimdsp.c.o +[78/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx2dp.dir/sleefsimddp.c.o +[79/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2128.dir/sleefsimddp.c.o +[80/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabisse2sp.dir/sleefsimdsp.c.o +[81/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx512fsp.dir/sleefsimdsp.c.o +[82/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2128.dir/sleefsimdsp.c.o +[83/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavxdp.dir/sleefsimddp.c.o +[84/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetfma4.dir/sleefsimdsp.c.o +[85/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetfma4.dir/sleefsimddp.c.o +[86/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavxsp.dir/sleefsimdsp.c.o +[87/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse4.dir/sleefsimdsp.c.o +[88/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2128.dir/sleefsimddp.c.o +[89/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx.dir/sleefsimdsp.c.o +[90/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurec_scalar.dir/sleefsimdsp.c.o +[91/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/rempitab.c.o +[92/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse2.dir/sleefsimdsp.c.o +[93/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/sleefld.c.o +[94/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurecfma_scalar.dir/sleefsimdsp.c.o +[95/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx.dir/sleefsimddp.c.o +[96/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/sleefqp.c.o +[97/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleeffma4.dir/sleefsimddp.c.o +[98/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurec_scalar.dir/sleefsimddp.c.o +[99/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurecfma_scalar.dir/sleefsimddp.c.o +[100/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse4.dir/sleefsimddp.c.o +[101/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleeffma4.dir/sleefsimdsp.c.o +[102/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse2.dir/sleefsimddp.c.o +[103/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurecfma_scalar.dir/sleefsimdsp.c.o +[104/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurec_scalar.dir/sleefsimdsp.c.o +[105/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse4.dir/sleefsimdsp.c.o +[106/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurecfma_scalar.dir/sleefsimddp.c.o +[107/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx.dir/sleefsimddp.c.o +[108/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse2.dir/sleefsimdsp.c.o +[109/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispscalar_obj.dir/dispscalar.c.o +[110/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurec_scalar.dir/sleefsimddp.c.o +[111/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse4.dir/sleefsimddp.c.o +[112/131] Linking C static library _deps/sleef-build/lib/libsleefgnuabi.a +[113/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse2.dir/sleefsimddp.c.o +[114/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx.dir/sleefsimdsp.c.o +[115/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispsse_obj.dir/dispsse.c.o +[116/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispavx_obj.dir/dispavx.c.o +[117/131] Building CXX object CMakeFiles/Deepity.dir/src/RBLayer.cpp.o +[118/131] Linking C static library _deps/sleef-build/lib/libsleef.a +[119/131] Building CXX object CMakeFiles/Deepity.dir/src/StreamAlignedBatcher.cpp.o +[120/131] Building CXX object CMakeFiles/Deepity.dir/src/SimplePCNetwork.cpp.o +[121/131] Building CXX object CMakeFiles/Deepity.dir/src/DiscriminativePCNetwork.cpp.o +[122/131] Building CXX object CMakeFiles/Deepity.dir/src/SimpleConvPCNetwork.cpp.o +[123/131] Building CXX object CMakeFiles/Deepity.dir/src/ConvPCNetwork.cpp.o +[124/131] Building CXX object CMakeFiles/Deepity.dir/src/DiscriminativePCLayer.cpp.o +[125/131] Building CXX object CMakeFiles/Deepity.dir/src/SimpleConvPCLayer.cpp.o +[126/131] Building CXX object CMakeFiles/Deepity.dir/src/ConvPCLayer.cpp.o +[127/131] Building CXX object CMakeFiles/Deepity.dir/src/ModelIO.cpp.o +[128/131] Building CXX object CMakeFiles/Deepity.dir/src/SimplePCLayer.cpp.o +[129/131] Linking CXX static library bin/libDeepity.a +[130/131] Building CXX object CMakeFiles/pydeepity.dir/bindings/pybinding.cpp.o +[131/131] Linking CXX shared module bin/pydeepity.cpython-314-x86_64-linux-gnu.so === Tests === @@ -9,25 +154,25 @@ ninja: no work to do. === Part 1: SGD weight-gradient check === W[5]: delta=0.03608 numeric_dE/dW=-0.689149 MATCHES DESCENT rel_err=0.0023544 W[17]: delta=0.0546692 numeric_dE/dW=-1.01769 MATCHES DESCENT rel_err=0.00371893 - W[6]: delta=-0.018854 numeric_dE/dW=0.368714 MATCHES DESCENT rel_err=0.0011345 - W[4]: delta=0.000938594 numeric_dE/dW=-0.026226 MATCHES DESCENT rel_err=0.0142114 + W[6]: delta=-0.018854 numeric_dE/dW=0.368834 MATCHES DESCENT rel_err=0.00111797 + W[4]: delta=0.000938594 numeric_dE/dW=-0.0263453 MATCHES DESCENT rel_err=0.0143733 W[8]: delta=-0.0150366 numeric_dE/dW=0.299931 MATCHES DESCENT rel_err=0.000133743 - W[1]: delta=-0.0190711 numeric_dE/dW=0.371456 MATCHES DESCENT rel_err=0.00134147 - W[14]: delta=0.0116533 numeric_dE/dW=-0.317812 MATCHES DESCENT rel_err=0.0133326 - W[11]: delta=0.0684187 numeric_dE/dW=-1.29008 MATCHES DESCENT rel_err=0.00303433 -Worst relative error: 0.0142114 + W[1]: delta=-0.0190711 numeric_dE/dW=0.371218 MATCHES DESCENT rel_err=0.00137444 + W[14]: delta=0.0116533 numeric_dE/dW=-0.317693 MATCHES DESCENT rel_err=0.0133189 + W[11]: delta=0.0684187 numeric_dE/dW=-1.28996 MATCHES DESCENT rel_err=0.00303923 +Worst relative error: 0.0143733 PASS === Part 2: feedback-term (Col2Im) verification, SimpleConvPCLayer === - z[3]: implied_dz_dt=0.0045494 -numeric_dE/dz=0.00455976 rel_err=0.00227074 - z[19]: implied_dz_dt=-0.0106531 -numeric_dE/dz=-0.0106394 rel_err=0.00128848 + z[3]: implied_dz_dt=0.0045494 -numeric_dE/dz=0.00452995 rel_err=0.00429182 + z[19]: implied_dz_dt=-0.0106531 -numeric_dE/dz=-0.0106096 rel_err=0.00410083 z[15]: implied_dz_dt=0.0041806 -numeric_dE/dz=0.00420213 rel_err=0.00512289 - z[26]: implied_dz_dt=-0.00197962 -numeric_dE/dz=-0.00199676 rel_err=0.00857779 + z[26]: implied_dz_dt=-0.00197962 -numeric_dE/dz=-0.00196695 rel_err=0.00643612 z[1]: implied_dz_dt=-0.0406229 -numeric_dE/dz=-0.0406206 rel_err=5.87843e-05 - z[5]: implied_dz_dt=-0.0247908 -numeric_dE/dz=-0.0248253 rel_err=0.00139243 - z[29]: implied_dz_dt=0.0272506 -numeric_dE/dz=0.0272691 rel_err=0.000677503 + z[5]: implied_dz_dt=-0.0247908 -numeric_dE/dz=-0.0247657 rel_err=0.00101086 + z[29]: implied_dz_dt=0.0272506 -numeric_dE/dz=0.0272393 rel_err=0.000415808 z[12]: implied_dz_dt=0.0424355 -numeric_dE/dz=0.0424385 rel_err=7.01353e-05 -Worst relative error: 0.00857779 +Worst relative error: 0.00643612 PASS === Part 3: AdamW weight-gradient check (NEW port, checking SIGN first) === diff --git a/pydeepity/ConvolutionalPCN.py b/pydeepity/ConvolutionalPCN.py new file mode 100644 index 0000000..1ed1962 --- /dev/null +++ b/pydeepity/ConvolutionalPCN.py @@ -0,0 +1,79 @@ +from ._backend import dy +from .utils import _PCNMixin +from typing import Optional +import numpy as np +import numpy.typing as npt + +class ConvolutionalPCN(dy.ConvPCNetwork, _PCNMixin): + """ + A Convolutional Predictive Coding Network (PCN) wrapper for the Deepity C++ backend. + """ + def __init__(self, batch_size: int) -> None: + super().__init__(batch_size) + self._last_shape: Optional[tuple[int, int, int]] = None + + def add_layer( + self, + out_channels: int, + kernel_h: int, + kernel_w: int, + in_channels: Optional[int] = None, + in_height: Optional[int] = None, + in_width: Optional[int] = None, + stride_h: int = 1, + stride_w: int = 1, + pad_h: int = 0, + pad_w: int = 0, + lr: float = 1e-6, + ir: float = 0.1, + pr: float = 0.0, + lmbda: float = 1e-4, + act: str = "relu", + ) -> None: + shape_args = (in_channels, in_height, in_width) + n_given = sum(a is not None for a in shape_args) + + if n_given == 0: + if self._last_shape is None: + raise ValueError( + "First add_layer() call must specify in_channels, in_height, and in_width explicitly." + ) + in_channels, in_height, in_width = self._last_shape + elif n_given != 3: + raise ValueError("in_channels/in_height/in_width must be given ALL together or OMITTED all together.") + + super().add_layer( + in_channels, out_channels, in_height, in_width, + kernel_h, kernel_w, stride_h=stride_h, stride_w=stride_w, pad_h=pad_h, pad_w=pad_w, + lr=lr, ir=ir, pr=pr, lmbda=lmbda, activation=act, activation_deriv="d" + act, + ) + + if out_channels > 0: + added = self[-1] + self._last_shape = (added.out_channels, added.out_height, added.out_width) + else: + self._last_shape = None + + def compile(self) -> None: + super().compile() + + def randomize_weights(self) -> None: + super().randomize_weights() + + def set_learning_rate(self, lr: float) -> None: + for layer in self.layers: + layer.set_learning_rate(lr) + + def set_inference_rate(self, ir: float) -> None: + for layer in self.layers: + layer.set_inference_rate(ir) + + def set_precision_rate(self, pr: float) -> None: + for layer in self.layers: + layer.set_precision_rate(pr) + + def train_step(self, X: npt.NDArray[np.float32], Y: npt.NDArray[np.float32], steps: int) -> float: + return super().train_step(X.flatten(), Y.flatten(), steps) + + def predict(self, X: npt.NDArray[np.float32], steps: int) -> npt.NDArray[np.float32]: + return super().predict(X.flatten(), steps) diff --git a/pydeepity/SequentialPCN.py b/pydeepity/SequentialPCN.py new file mode 100644 index 0000000..665a199 --- /dev/null +++ b/pydeepity/SequentialPCN.py @@ -0,0 +1,72 @@ +from ._backend import dy +from typing import Optional +import numpy as np +import numpy.typing as npt +from .utils import _PCNMixin + +class SequentialPCN(dy.DiscriminativePCNetwork, _PCNMixin): + """ + A Sequential Predictive Coding Network wrapper for the Deepity C++ backend. + """ + def __init__(self, batch_size: Optional[int] = None) -> None: + bsz = dy.auto_batch_size() if batch_size is None else batch_size + super().__init__(bsz) + + def add_layer( + self, + in_features: int, + out_features: int, + lr: float, + ir: float, + pr: float, + act: str, + lmbda: float = 0.0001 + ) -> None: + super().add_layer( + in_features, out_features, lr=lr, ir=ir, pr=pr, + lmbda=lmbda, activation=act, activation_deriv='d' + act + ) + + def set_learning_rate(self, lr: float) -> None: + super().set_learning_rate(lr) + + def compile(self) -> None: + super().compile() + + def randomize_weights(self) -> None: + super().randomize_weights() + + def train_step(self, X: npt.NDArray[np.float32], Y: npt.NDArray[np.float32], steps: int) -> float: + """Executes a full forward clamp, state relaxation, and weight update cycle.""" + self.reset_state() + self.clamp_input(X) + self[-1].clamp_state(Y) + + total_energy: float = 0.0 + for _ in range(steps): + total_energy += self.calculate_state() + self.update_state() + + self.update_weights() + self.update_precision() + self[-1].unclamp_state() + + return total_energy + + def predict(self, X: npt.NDArray[np.float32], steps: int) -> npt.NDArray[np.float32]: + """Runs generative/discriminative inference on the provided input data.""" + self.reset_state() + self.clamp_input(X.flatten()) + + for _ in range(steps): + self.calculate_state() + self.update_state() + + return np.array(self[-1].beliefs) + + def save(self, dir_path: str) -> bool: + return super().save(dir_path) + + def load(self, dir_path: str) -> bool: + return super().load(dir_path) + diff --git a/pydeepity/SimpleConvolutionalPCN.py b/pydeepity/SimpleConvolutionalPCN.py new file mode 100644 index 0000000..c86f237 --- /dev/null +++ b/pydeepity/SimpleConvolutionalPCN.py @@ -0,0 +1,84 @@ +from ._backend import dy +from .utils import _PCNMixin +from typing import Optional +import numpy as np +import numpy.typing as npt + +class SimpleConvolutionalPCN(dy.SimpleConvPCNetwork, _PCNMixin): + """ + A Convolutional Predictive Coding Network built from precision-free, + AdamW-capable SimpleConvPCLayers. Mirrors ConvolutionalPCN's + shape-inference convenience (in_channels/in_height/in_width can be + omitted after the first add_layer() call, inferred from the previous + layer's output shape) -- minus precision, which doesn't exist here. + """ + def __init__(self, batch_size: int) -> None: + super().__init__(batch_size) + self._last_shape: Optional[tuple[int, int, int]] = None + + def add_layer( + self, + out_channels: int, + kernel_h: int, + kernel_w: int, + in_channels: Optional[int] = None, + in_height: Optional[int] = None, + in_width: Optional[int] = None, + stride_h: int = 1, + stride_w: int = 1, + pad_h: int = 0, + pad_w: int = 0, + lr: float = 1e-6, + ir: float = 0.1, + lmbda: float = 1e-4, + act: str = "relu", + ) -> None: + shape_args = (in_channels, in_height, in_width) + n_given = sum(a is not None for a in shape_args) + + if n_given == 0: + if self._last_shape is None: + raise ValueError( + "First add_layer() call must specify in_channels, in_height, and in_width explicitly." + ) + in_channels, in_height, in_width = self._last_shape + elif n_given != 3: + raise ValueError("in_channels/in_height/in_width must be given ALL together or OMITTED all together.") + + super().add_layer( + in_channels, out_channels, in_height, in_width, + kernel_h, kernel_w, stride_h=stride_h, stride_w=stride_w, pad_h=pad_h, pad_w=pad_w, + lr=lr, ir=ir, lmbda=lmbda, activation=act, activation_deriv="d" + act, + ) + + if out_channels > 0: + added = self[-1] + self._last_shape = (added.out_channels, added.out_height, added.out_width) + else: + self._last_shape = None + + def set_optimizer(self, optimizer: str) -> None: + """Sets the optimizer: ADAM, ADAMW, or SGD. Call BEFORE compile() -- + see SimpleConvPCNetwork's C++ docs; buffer sizing depends on this + being set before Compile() allocates the shared arena.""" + super().set_optimizer(optimizer) + + def compile(self) -> None: + super().compile() + + def randomize_weights(self) -> None: + super().randomize_weights() + + def set_learning_rate(self, lr: float) -> None: + for layer in self.layers: + layer.set_learning_rate(lr) + + def set_inference_rate(self, ir: float) -> None: + for layer in self.layers: + layer.set_inference_rate(ir) + + def train_step(self, X: npt.NDArray[np.float32], Y: npt.NDArray[np.float32], steps: int) -> float: + return super().train_step(X.flatten(), Y.flatten(), steps) + + def predict(self, X: npt.NDArray[np.float32], steps: int) -> npt.NDArray[np.float32]: + return super().predict(X.flatten(), steps) \ No newline at end of file diff --git a/pydeepity/SimplePCN.py b/pydeepity/SimplePCN.py new file mode 100644 index 0000000..10cd912 --- /dev/null +++ b/pydeepity/SimplePCN.py @@ -0,0 +1,223 @@ +from ._backend import dy +import numpy as np +import numpy.typing as npt +from typing import Optional + +from rich.console import Console +from rich.progress import ( + Progress, + SpinnerColumn, + BarColumn, + TextColumn, + MofNCompleteColumn, + TimeElapsedColumn, + TimeRemainingColumn, +) +from .utils import _PCNMixin + +class SimplePCN(dy.SimplePCNetwork, _PCNMixin): + """ + A Sequential Predictive Coding Network built from precision-stripped SimplePCLayers. + """ + def __init__(self, batch_size: Optional[int] = None) -> None: + bsz = dy.auto_batch_size() if batch_size is None else batch_size + super().__init__(bsz) + + def add_layer( + self, + in_features: int, + out_features: int, + lr: float, + ir: float, + act: str, + lmbda: float = 0.0001 + ) -> None: + super().add_layer( + in_features, out_features, lr=lr, ir=ir, + lmbda=lmbda, activation=act, activation_deriv='d' + act + ) + + def set_learning_rate(self, lr: float) -> None: + for layer in self.layers: + layer.set_learning_rate(lr) + + def compile(self) -> None: + super().compile() + + def randomize_weights(self) -> None: + super().randomize_weights() + + def train_step(self, X: npt.NDArray[np.float32], Y: npt.NDArray[np.float32], steps: int) -> float: + self.reset_state() + self.clamp_input(X) + self[-1].clamp_state(Y) + + total_energy: float = 0.0 + for _ in range(steps): + total_energy += self.calculate_state() + self.update_state() + + self.update_weights() + self[-1].unclamp_state() + + return total_energy + + def predict(self, X: npt.NDArray[np.float32], steps: int) -> npt.NDArray[np.float32]: + self.reset_state() + self.clamp_input(X.flatten()) + + for _ in range(steps): + self.calculate_state() + self.update_state() + + return np.array(self[-1].beliefs) + + def set_mu_cache_threshold(self, threshold: float) -> None: + """Sets the mu-cache staleness threshold on every layer: -1 disables + caching (default), 0 reproduces the exact clamped-only behavior + (safe, no approximation), >0 extends caching to unclamped layers + too as a genuine approximation -- validated via a real settling- + trajectory sweep, threshold~0.05-0.1 gave ~56-60% speedup with + final energy staying close to the true baseline. NOT yet validated + for its effect on real multi-epoch training accuracy -- that's a + separate, necessary check before trusting a given threshold in + production training, the same discipline every other optimization + this session has gone through.""" + for layer in self.layers: + layer.set_mu_cache_threshold(threshold) + + def fit_iavg( + self, + X: npt.NDArray[np.float32], + Y: npt.NDArray[np.float32], + labels: npt.NDArray[np.int32], + epochs: int, + steps: int, + per_class: int, + num_classes: int, + cache_layers: list[tuple[int, int]], + initial_lr: float = 0.01, + decay_rate: float = 1.0, + reset_cache_per_epoch: bool = True + ) -> "SimplePCN": + """ + Specialized training loop for deep networks that utilizes Class-Average + Caching (I_avg). Manages the StreamAlignedBatcher and dynamically seeds + MULTIPLE hidden layers simultaneously -- a generalization of the + original single hidden_layer_index/hidden_size version, which could + only ever cache one layer. + + @param cache_layers List of (layer_index, layer_size) pairs -- every + layer listed gets its own independent per-class cache, seeded and + captured every batch. layer_index is the index into self.layers + (0 = the first, input-adjacent layer; the terminal layer, always + the last index, should NOT be included here -- it's clamped to + the target directly, never cached). + """ + console = Console() + bsz = self.batch_size + + batcher = dy.StreamAlignedBatcher( + X, Y, labels, + X.shape[1], Y.shape[1], + num_classes, per_class, 42 + ) + n_batches = batcher.num_batches_per_epoch() + + layer_names = ", ".join(f"L{idx}({sz})" for idx, sz in cache_layers) + console.print( + f"\n[bold cyan]Training I_avg[/bold cyan] [dim]|[/dim] {epochs} epochs [dim]|[/dim] " + f"{steps} inference steps [dim]|[/dim] {n_batches} batches/epoch [dim]|[/dim] " + f"caching: {layer_names}\n" + ) + + progress = Progress( + SpinnerColumn(style="cyan"), + TextColumn("[bold blue]{task.description}"), + BarColumn(bar_width=40, style="blue", complete_style="cyan"), + MofNCompleteColumn(), + TextColumn("[dim]•[/dim]"), + TimeElapsedColumn(), + TextColumn("[dim]•[/dim]"), + TimeRemainingColumn(), + TextColumn("[magenta]{task.fields[stats]}"), + console=console, + ) + + # caches[layer_index] = {class: avg_vector} -- one independent cache + # per listed layer, not a single shared one. + caches: dict[int, dict[int, npt.NDArray[np.float32]]] = {idx: {} for idx, _ in cache_layers} + + with progress: + epoch_task = progress.add_task("[bold]Epochs", total=epochs, stats="") + batch_task = progress.add_task(" Batches", total=n_batches, stats="") + + for epoch in range(epochs): + if reset_cache_per_epoch: + for layer_idx in caches: + caches[layer_idx].clear() + + current_lr = initial_lr * (decay_rate ** epoch) + self.set_learning_rate(current_lr) + + epoch_energy = 0.0 + progress.reset(batch_task, total=n_batches) + + for b in range(n_batches): + X_batch, Y_batch, _ = batcher.get_batch() + self.reset_state() + + # 1. Apply EVERY listed layer's cache independently -- + # each layer seeds from its OWN cache, not a shared one. + for layer_idx, layer_size in cache_layers: + cache = caches[layer_idx] + if cache: + init_beliefs = np.zeros((bsz, layer_size), dtype=np.float32) + for c in range(num_classes): + if c in cache: + init_beliefs[c * per_class : (c + 1) * per_class] = cache[c] + + layer = self.layers[layer_idx] + np.copyto(layer.beliefs, init_beliefs) + + self.clamp_input(X_batch.flatten()) + self[-1].clamp_state(Y_batch.flatten()) + + # 2. Settle & Update + energy = 0.0 + for _ in range(steps): + energy += self.calculate_state() + self.update_state() + + self.update_weights() + + # 3. Cache EVERY listed layer's newly settled beliefs + # independently. + for layer_idx, layer_size in cache_layers: + settled = np.array(self.layers[layer_idx].beliefs, copy=False).reshape(bsz, layer_size) + for c in range(num_classes): + caches[layer_idx][c] = settled[ + c * per_class : (c + 1) * per_class + ].mean(axis=0, dtype=np.float32).astype(np.float32, copy=False) + + self[-1].unclamp_state() + + # 4. Progress tracking + epoch_energy += energy + avg_so_far = epoch_energy / (b + 1) + + progress.update( + batch_task, + advance=1, + stats=f"lr={current_lr:.5f} energy={energy:8.2f} avg={avg_so_far:8.2f}", + ) + + progress.update( + epoch_task, + advance=1, + stats=f"epoch {epoch + 1} avg energy = {epoch_energy / n_batches:.4f}", + ) + + console.print("\n[bold green]✓ I_avg Training complete.[/bold green]\n") + return self + diff --git a/pydeepity/__init__.py b/pydeepity/__init__.py index f550736..046f921 100644 --- a/pydeepity/__init__.py +++ b/pydeepity/__init__.py @@ -1,552 +1,49 @@ -import numpy as np -import numpy.typing as npt -from typing import Optional - -# Import the raw compiled C++ bindings -from . import pydeepity as dy - -from rich.console import Console -from rich.progress import ( - Progress, - SpinnerColumn, - BarColumn, - TextColumn, - MofNCompleteColumn, - TimeElapsedColumn, - TimeRemainingColumn, -) - -__all__ = ["SequentialPCN", "SimplePCN", "ConvolutionalPCN"] - -# ============================================================================ -# Shared Progress & Training Utilities -# ============================================================================ - -def _fit_with_progress( - net, - X: npt.NDArray[np.float32], - Y: npt.NDArray[np.float32], - epochs: int, - steps: int, - initial_lr: float = 0.01, - decay_rate: float = 1.0, - shuffle: bool = True, -) -> None: - """ - Shared training-loop implementation used by all PCN classes. - It delegates to the specific class's `train_step()` method. - """ - console = Console() - n = len(X) - bsz = net.batch_size - n_batches = n // bsz - - console.print( - f"\n[bold cyan]Training[/bold cyan] [dim]|[/dim] {epochs} epochs [dim]|[/dim] " - f"{steps} inference steps [dim]|[/dim] {n_batches} batches/epoch (batch_size={bsz})\n" - ) - - progress = Progress( - SpinnerColumn(style="cyan"), - TextColumn("[bold blue]{task.description}"), - BarColumn(bar_width=40, style="blue", complete_style="cyan"), - MofNCompleteColumn(), - TextColumn("[dim]•[/dim]"), - TimeElapsedColumn(), - TextColumn("[dim]•[/dim]"), - TimeRemainingColumn(), - TextColumn("[magenta]{task.fields[stats]}"), - console=console, - ) - - with progress: - epoch_task = progress.add_task("[bold]Epochs", total=epochs, stats="") - batch_task = progress.add_task(" Batches", total=n_batches, stats="") - - for epoch in range(epochs): - current_lr = initial_lr * (decay_rate ** epoch) - net.set_learning_rate(current_lr) - - if shuffle: - indices = np.random.permutation(n) - X_shuf, Y_shuf = X[indices], Y[indices] - else: - X_shuf, Y_shuf = X, Y - - epoch_energy = 0.0 - progress.reset(batch_task, total=n_batches) - - for b in range(n_batches): - X_batch = X_shuf[b * bsz : (b + 1) * bsz] - Y_batch = Y_shuf[b * bsz : (b + 1) * bsz] - - energy = net.train_step(X_batch, Y_batch, steps) - epoch_energy += energy - avg_so_far = epoch_energy / (b + 1) - - progress.update( - batch_task, - advance=1, - stats=f"lr={current_lr:.5f} energy={energy:8.2f} avg={avg_so_far:8.2f}", - ) - - progress.update( - epoch_task, - advance=1, - stats=f"epoch {epoch + 1} avg energy = {epoch_energy / n_batches:.4f}", - ) - - console.print("\n[bold green]✓ Training complete.[/bold green]\n") - - -class _PCNMixin: - """ - Provides shared top-level Python functionality (like fit()) to all Network wrappers. - """ - def fit( - self, - X: npt.NDArray[np.float32], - Y: npt.NDArray[np.float32], - epochs: int, - steps: int, - initial_lr: float = 0.01, - decay_rate: float = 1.0, - shuffle: bool = True, - ): - """ - Runs a full multi-epoch training loop with a live rich progress display. - Delegates per-batch execution to the class's `train_step()` method. - """ - _fit_with_progress(self, X, Y, epochs, steps, initial_lr, decay_rate, shuffle) - return self - - -# ============================================================================ -# Core Network Wrappers -# ============================================================================ - -class SequentialPCN(dy.DiscriminativePCNetwork, _PCNMixin): - """ - A Sequential Predictive Coding Network wrapper for the Deepity C++ backend. - """ - def __init__(self, batch_size: Optional[int] = None) -> None: - bsz = dy.auto_batch_size() if batch_size is None else batch_size - super().__init__(bsz) +__version__ = "1.0.0" + +from .SequentialPCN import SequentialPCN +from .SimplePCN import SimplePCN +from .ConvolutionalPCN import ConvolutionalPCN +from .SimpleConvolutionalPCN import SimpleConvolutionalPCN + +from ._backend import dy + +StreamAlignedBatcher = dy.StreamAlignedBatcher + +# Hardware & Threading Utilities +get_l2_cache_bytes = dy.get_l2_cache_bytes +auto_batch_size = dy.auto_batch_size +dynamic_thread = dy.dynamic_thread +omp_max_threads = dy.omp_max_threads +omp_num_procs = dy.omp_num_procs + +# In-place SIMD Activation Functions +relu = dy.relu +drelu = dy.drelu +tanh = dy.tanh +dtanh = dy.dtanh +sigmoid = dy.sigmoid +dsigmoid = dy.dsigmoid + +__all__ = [ + # Networks + "SequentialPCN", + "SimplePCN", + "ConvolutionalPCN", + # "SimpleConvolutionalPCN", - def add_layer( - self, - in_features: int, - out_features: int, - lr: float, - ir: float, - pr: float, - act: str, - lmbda: float = 0.0001 - ) -> None: - super().add_layer( - in_features, out_features, lr=lr, ir=ir, pr=pr, - lmbda=lmbda, activation=act, activation_deriv='d' + act - ) - - def set_learning_rate(self, lr: float) -> None: - super().set_learning_rate(lr) - - def compile(self) -> None: - super().compile() - - def randomize_weights(self) -> None: - super().randomize_weights() - - def train_step(self, X: npt.NDArray[np.float32], Y: npt.NDArray[np.float32], steps: int) -> float: - """Executes a full forward clamp, state relaxation, and weight update cycle.""" - self.reset_state() - self.clamp_input(X) - self[-1].clamp_state(Y) - - total_energy: float = 0.0 - for _ in range(steps): - total_energy += self.calculate_state() - self.update_state() - - self.update_weights() - self.update_precision() - self[-1].unclamp_state() - - return total_energy - - def predict(self, X: npt.NDArray[np.float32], steps: int) -> npt.NDArray[np.float32]: - """Runs generative/discriminative inference on the provided input data.""" - self.reset_state() - self.clamp_input(X.flatten()) - - for _ in range(steps): - self.calculate_state() - self.update_state() - - return np.array(self[-1].beliefs) - - def save(self, dir_path: str) -> bool: - return super().save(dir_path) - - def load(self, dir_path: str) -> bool: - return super().load(dir_path) - - -class SimplePCN(dy.SimplePCNetwork, _PCNMixin): - """ - A Sequential Predictive Coding Network built from precision-stripped SimplePCLayers. - """ - def __init__(self, batch_size: Optional[int] = None) -> None: - bsz = dy.auto_batch_size() if batch_size is None else batch_size - super().__init__(bsz) + # Utilities + "StreamAlignedBatcher", + "get_l2_cache_bytes", + "auto_batch_size", + "dynamic_thread", + "omp_max_threads", + "omp_num_procs", - def add_layer( - self, - in_features: int, - out_features: int, - lr: float, - ir: float, - act: str, - lmbda: float = 0.0001 - ) -> None: - super().add_layer( - in_features, out_features, lr=lr, ir=ir, - lmbda=lmbda, activation=act, activation_deriv='d' + act - ) - - def set_learning_rate(self, lr: float) -> None: - for layer in self.layers: - layer.set_learning_rate(lr) - - def compile(self) -> None: - super().compile() - - def randomize_weights(self) -> None: - super().randomize_weights() - - def train_step(self, X: npt.NDArray[np.float32], Y: npt.NDArray[np.float32], steps: int) -> float: - self.reset_state() - self.clamp_input(X) - self[-1].clamp_state(Y) - - total_energy: float = 0.0 - for _ in range(steps): - total_energy += self.calculate_state() - self.update_state() - - self.update_weights() - self[-1].unclamp_state() - - return total_energy - - def predict(self, X: npt.NDArray[np.float32], steps: int) -> npt.NDArray[np.float32]: - self.reset_state() - self.clamp_input(X.flatten()) - - for _ in range(steps): - self.calculate_state() - self.update_state() - - return np.array(self[-1].beliefs) - - def set_mu_cache_threshold(self, threshold: float) -> None: - """Sets the mu-cache staleness threshold on every layer: -1 disables - caching (default), 0 reproduces the exact clamped-only behavior - (safe, no approximation), >0 extends caching to unclamped layers - too as a genuine approximation -- validated via a real settling- - trajectory sweep, threshold~0.05-0.1 gave ~56-60% speedup with - final energy staying close to the true baseline. NOT yet validated - for its effect on real multi-epoch training accuracy -- that's a - separate, necessary check before trusting a given threshold in - production training, the same discipline every other optimization - this session has gone through.""" - for layer in self.layers: - layer.set_mu_cache_threshold(threshold) - - def fit_iavg( - self, - X: npt.NDArray[np.float32], - Y: npt.NDArray[np.float32], - labels: npt.NDArray[np.int32], - epochs: int, - steps: int, - per_class: int, - num_classes: int, - cache_layers: list[tuple[int, int]], - initial_lr: float = 0.01, - decay_rate: float = 1.0, - reset_cache_per_epoch: bool = True - ) -> "SimplePCN": - """ - Specialized training loop for deep networks that utilizes Class-Average - Caching (I_avg). Manages the StreamAlignedBatcher and dynamically seeds - MULTIPLE hidden layers simultaneously -- a generalization of the - original single hidden_layer_index/hidden_size version, which could - only ever cache one layer. - - @param cache_layers List of (layer_index, layer_size) pairs -- every - layer listed gets its own independent per-class cache, seeded and - captured every batch. layer_index is the index into self.layers - (0 = the first, input-adjacent layer; the terminal layer, always - the last index, should NOT be included here -- it's clamped to - the target directly, never cached). - """ - console = Console() - bsz = self.batch_size - - batcher = dy.StreamAlignedBatcher( - X, Y, labels, - X.shape[1], Y.shape[1], - num_classes, per_class, 42 - ) - n_batches = batcher.num_batches_per_epoch() - - layer_names = ", ".join(f"L{idx}({sz})" for idx, sz in cache_layers) - console.print( - f"\n[bold cyan]Training I_avg[/bold cyan] [dim]|[/dim] {epochs} epochs [dim]|[/dim] " - f"{steps} inference steps [dim]|[/dim] {n_batches} batches/epoch [dim]|[/dim] " - f"caching: {layer_names}\n" - ) - - progress = Progress( - SpinnerColumn(style="cyan"), - TextColumn("[bold blue]{task.description}"), - BarColumn(bar_width=40, style="blue", complete_style="cyan"), - MofNCompleteColumn(), - TextColumn("[dim]•[/dim]"), - TimeElapsedColumn(), - TextColumn("[dim]•[/dim]"), - TimeRemainingColumn(), - TextColumn("[magenta]{task.fields[stats]}"), - console=console, - ) - - # caches[layer_index] = {class: avg_vector} -- one independent cache - # per listed layer, not a single shared one. - caches: dict[int, dict[int, npt.NDArray[np.float32]]] = {idx: {} for idx, _ in cache_layers} - - with progress: - epoch_task = progress.add_task("[bold]Epochs", total=epochs, stats="") - batch_task = progress.add_task(" Batches", total=n_batches, stats="") - - for epoch in range(epochs): - if reset_cache_per_epoch: - for layer_idx in caches: - caches[layer_idx].clear() - - current_lr = initial_lr * (decay_rate ** epoch) - self.set_learning_rate(current_lr) - - epoch_energy = 0.0 - progress.reset(batch_task, total=n_batches) - - for b in range(n_batches): - X_batch, Y_batch, _ = batcher.get_batch() - self.reset_state() - - # 1. Apply EVERY listed layer's cache independently -- - # each layer seeds from its OWN cache, not a shared one. - for layer_idx, layer_size in cache_layers: - cache = caches[layer_idx] - if cache: - init_beliefs = np.zeros((bsz, layer_size), dtype=np.float32) - for c in range(num_classes): - if c in cache: - init_beliefs[c * per_class : (c + 1) * per_class] = cache[c] - - layer = self.layers[layer_idx] - np.copyto(layer.beliefs, init_beliefs) - - self.clamp_input(X_batch.flatten()) - self[-1].clamp_state(Y_batch.flatten()) - - # 2. Settle & Update - energy = 0.0 - for _ in range(steps): - energy += self.calculate_state() - self.update_state() - - self.update_weights() - - # 3. Cache EVERY listed layer's newly settled beliefs - # independently. - for layer_idx, layer_size in cache_layers: - settled = np.array(self.layers[layer_idx].beliefs, copy=False).reshape(bsz, layer_size) - for c in range(num_classes): - caches[layer_idx][c] = settled[ - c * per_class : (c + 1) * per_class - ].mean(axis=0, dtype=np.float32).astype(np.float32, copy=False) - - self[-1].unclamp_state() - - # 4. Progress tracking - epoch_energy += energy - avg_so_far = epoch_energy / (b + 1) - - progress.update( - batch_task, - advance=1, - stats=f"lr={current_lr:.5f} energy={energy:8.2f} avg={avg_so_far:8.2f}", - ) - - progress.update( - epoch_task, - advance=1, - stats=f"epoch {epoch + 1} avg energy = {epoch_energy / n_batches:.4f}", - ) - - console.print("\n[bold green]✓ I_avg Training complete.[/bold green]\n") - return self - -class ConvolutionalPCN(dy.ConvPCNetwork, _PCNMixin): - """ - A Convolutional Predictive Coding Network (PCN) wrapper for the Deepity C++ backend. - """ - def __init__(self, batch_size: int) -> None: - super().__init__(batch_size) - self._last_shape: Optional[tuple[int, int, int]] = None - - def add_layer( - self, - out_channels: int, - kernel_h: int, - kernel_w: int, - in_channels: Optional[int] = None, - in_height: Optional[int] = None, - in_width: Optional[int] = None, - stride_h: int = 1, - stride_w: int = 1, - pad_h: int = 0, - pad_w: int = 0, - lr: float = 1e-6, - ir: float = 0.1, - pr: float = 0.0, - lmbda: float = 1e-4, - act: str = "relu", - ) -> None: - shape_args = (in_channels, in_height, in_width) - n_given = sum(a is not None for a in shape_args) - - if n_given == 0: - if self._last_shape is None: - raise ValueError( - "First add_layer() call must specify in_channels, in_height, and in_width explicitly." - ) - in_channels, in_height, in_width = self._last_shape - elif n_given != 3: - raise ValueError("in_channels/in_height/in_width must be given ALL together or OMITTED all together.") - - super().add_layer( - in_channels, out_channels, in_height, in_width, - kernel_h, kernel_w, stride_h=stride_h, stride_w=stride_w, pad_h=pad_h, pad_w=pad_w, - lr=lr, ir=ir, pr=pr, lmbda=lmbda, activation=act, activation_deriv="d" + act, - ) - - if out_channels > 0: - added = self[-1] - self._last_shape = (added.out_channels, added.out_height, added.out_width) - else: - self._last_shape = None - - def compile(self) -> None: - super().compile() - - def randomize_weights(self) -> None: - super().randomize_weights() - - def set_learning_rate(self, lr: float) -> None: - for layer in self.layers: - layer.set_learning_rate(lr) - - def set_inference_rate(self, ir: float) -> None: - for layer in self.layers: - layer.set_inference_rate(ir) - - def set_precision_rate(self, pr: float) -> None: - for layer in self.layers: - layer.set_precision_rate(pr) - - def train_step(self, X: npt.NDArray[np.float32], Y: npt.NDArray[np.float32], steps: int) -> float: - return super().train_step(X.flatten(), Y.flatten(), steps) - - def predict(self, X: npt.NDArray[np.float32], steps: int) -> npt.NDArray[np.float32]: - return super().predict(X.flatten(), steps) - -class SimpleConvolutionalPCN(dy.SimpleConvPCNetwork, _PCNMixin): - """ - A Convolutional Predictive Coding Network built from precision-free, - AdamW-capable SimpleConvPCLayers. Mirrors ConvolutionalPCN's - shape-inference convenience (in_channels/in_height/in_width can be - omitted after the first add_layer() call, inferred from the previous - layer's output shape) -- minus precision, which doesn't exist here. - """ - def __init__(self, batch_size: int) -> None: - super().__init__(batch_size) - self._last_shape: Optional[tuple[int, int, int]] = None - - def add_layer( - self, - out_channels: int, - kernel_h: int, - kernel_w: int, - in_channels: Optional[int] = None, - in_height: Optional[int] = None, - in_width: Optional[int] = None, - stride_h: int = 1, - stride_w: int = 1, - pad_h: int = 0, - pad_w: int = 0, - lr: float = 1e-6, - ir: float = 0.1, - lmbda: float = 1e-4, - act: str = "relu", - ) -> None: - shape_args = (in_channels, in_height, in_width) - n_given = sum(a is not None for a in shape_args) - - if n_given == 0: - if self._last_shape is None: - raise ValueError( - "First add_layer() call must specify in_channels, in_height, and in_width explicitly." - ) - in_channels, in_height, in_width = self._last_shape - elif n_given != 3: - raise ValueError("in_channels/in_height/in_width must be given ALL together or OMITTED all together.") - - super().add_layer( - in_channels, out_channels, in_height, in_width, - kernel_h, kernel_w, stride_h=stride_h, stride_w=stride_w, pad_h=pad_h, pad_w=pad_w, - lr=lr, ir=ir, lmbda=lmbda, activation=act, activation_deriv="d" + act, - ) - - if out_channels > 0: - added = self[-1] - self._last_shape = (added.out_channels, added.out_height, added.out_width) - else: - self._last_shape = None - - def set_optimizer(self, optimizer: str) -> None: - """Sets the optimizer: ADAM, ADAMW, or SGD. Call BEFORE compile() -- - see SimpleConvPCNetwork's C++ docs; buffer sizing depends on this - being set before Compile() allocates the shared arena.""" - super().set_optimizer(optimizer) - - def compile(self) -> None: - super().compile() - - def randomize_weights(self) -> None: - super().randomize_weights() - - def set_learning_rate(self, lr: float) -> None: - for layer in self.layers: - layer.set_learning_rate(lr) - - def set_inference_rate(self, ir: float) -> None: - for layer in self.layers: - layer.set_inference_rate(ir) - - def train_step(self, X: npt.NDArray[np.float32], Y: npt.NDArray[np.float32], steps: int) -> float: - return super().train_step(X.flatten(), Y.flatten(), steps) - - def predict(self, X: npt.NDArray[np.float32], steps: int) -> npt.NDArray[np.float32]: - return super().predict(X.flatten(), steps) \ No newline at end of file + # Activations + "relu", + "drelu", + "tanh", + "dtanh", + "sigmoid", + "dsigmoid", +] \ No newline at end of file diff --git a/pydeepity/__pycache__/__init__.cpython-312.pyc b/pydeepity/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 02c23c3d57f9c8e93b5b1803f8adaa1da5f43299..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26142 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 diff --git a/pydeepity/__pycache__/__init__.cpython-314.pyc b/pydeepity/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index c0045ae0961c7dac6aa6c42237f8ddf017d1e95c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33220 zcmeHw3v?UTdFBj200R&tKoESth7VDMB$BcyTe4)?dRwv{rXd-WCCV@a3`N)=01rS( zl+#pok`pQ~%O`P7InA2NqoI<`M(HMv(#NUXq>ZxM?2@4;>{jsvR$j0Fs`1yCQIIrt*K3VE5^;#=9udRZMI3s-0UB)$X z-a>@f*BaE)B+FP>5yH$zh3-{)VKeumWorwxxD_mJG2&VfcXcL??#qf>#o{^;S3umg zdW_6HD{c*o>qOiV#9g0>qx-Vr)_M76&g1?Wz1r{L72)xCG8~HrBJf+|Lm^3uhm%8i zuxyJ(6S0Wou_!{f96K&ciG)&gARdlJC3#ycGB^-btXl)R*LF}kmDGY=`(l0jx5WnH z5h*EYVI>E{1JaI2AfAvyT2Ltkd8L6sI2w)~*8+E5NyV~%`xaRa3@L_aT(R^;Vu57W8pRlnCKVHVQVNd6V#F-wzL-1^NG7NOnv^x2 z{jmY5Q;sF1)tz9MXQf~=(W!A$XPU1%{eCKu-`^1*QtWOR$EvLMUuDu`Xe8>b)nj$n>%PpryD{U}eZF-2#!Gy>uOxdU@)>fUxZvP}7OpRe3k;VW3HabvvF=k`^uN^>yE=SIl9@~lepJ>FNL{yO>fOmX)WV{PE} zIlW~?oVVOp>8-%M(pN?IHD+&>&O;5{o{w;xm8&P7TPU9*;|YuO&EU;r?Hr;VExjGbeH@IlV~7(U!Gb*wDb$aSiO>-sX+Q zIxft68cx4pJJ27HrI47E)p@o(5{tz}HmeR`o(<4+Dh^^+7LN^yfk;He%qj*WfkZ+| zbcnXyNiifvq~jQ*6JjzZCi^8Z5toADzHm^Dwmcy|!!q~NoYUrcMjVim{jpGo?R1Cj z$gx-?BnF29(W3{o0vzeggo#H&;en$cIMPWs@lKhjPG9qa>&0-iPm-l*5Z|VGE%*KD z8u3^l8SIx5oh-T7#vc8N@M&q&X@ke66i@*!@-!Lhh9=A+mp<;|kQN@;_ zxMGUSST4x4h>|Vj(4-{O7%J1yE>jPcX&Ivkfl$a#^jA!BAbMOgXypEiwyNY{5YpoQYz~iX|Kkg@aN; zF(2~N0!uMJrQYpo9%@@D{4vFHNd1yxc}nw`wU;(DkUtUg_XXqxtvOq@OU%1u8rl_? zzYpuFli_5)UoWy8!V9*P+KG|j40pShbCiEPeCE*`LgDb1kDJeInXwhU)N!`s%(fY4 z$t%K{-8TygKf3SN_RZKHyxe-Za(s(#rqDUOe7vOdl6CBb@eNOo`<@zq^6>cKqbVbA z_4BulMw9urjdNDs5Xwe&UK5&c2(IzcHIqWu4WVRY>uBF)-!)Dll53LDb%GC&NwS>mlZnAXLik1b2itz?zT>FUmj>ds62Wc8YJmYK$;(RE*{9(!W!K@;5GZWF5YW^G%a|InoMo-79;xWigxG6<`Kbzw*UO^g5&}y*h3>#6HAf}Y3 zo1r6HN_!UZ63@Z;Cz8+{Cig9Jeo|Qc(#EqJUzT4z{qpIl!lsmo-k2eF*0Hvdv^33 zU#qRyv6|vQBL4KJY~I0WLJWXmz!L+5kz}}?jmha{k;s-sG>#%xBz!^=<#4cH)W=jY z6i&n=fuRoD_Vj89?9|TY3sE{H1sN`(uaFpR7hf28cs53R1f?J$Mf&cc@e))ceJ)T( zej^wi?WlK`4m`5vp~;O6`qr9}wWC|c2CuBRCirF?rNf1cloz@E7mdeXaQ~M_e{%Z6 zj~qvAT&YJ6qBMfv9}NsZ8dq$7|3EA>7$HC8^ydcy5$%n`j~Pc!BqQOd6pi7v#qSTr zf{@@N$VVvyH_-$g^aqkjIecso>qbN>$>O)56p8r#pg}(pF#KS%%6WZwmdT;N1kGUo z^ifphmcd{W=FDIP#&RiQIaU)EH;8EIt3dP3{$F?kTmT(9JrbQ^BN1>Qj6`KVULT3r zm_(zIz%V`&Bm#O@Kq2g%DgDmO-k8&GEb5!2g@p_0aA1a%TT)=*gdVCn0XbyhRu*oj zCohmg#I&&xAcF`2=7*3%76MF9OIt!dgxguTD;-{{g%^3<$zmU$bojW9e9{T7uaIfX zKE2PjMa*DoVmD*~^m$_NZLtuRS@TCo5x5Rp*ZuXZ@mY<4>3+gpbvT@571_a=RX5 z!HJ+TjZf_35AtzC21`6(^l{Ii9ejo?`3+;zMSlOq0>uy-QXGN7WK3NIGKmEOz#v8z z>WeB=1|rdjD>EV;gb#D1I(Vb2Q4_SbirUcqA$nUrpD=1Rj3_jef+#bstAEtG{u9Th zi^U%mi`R;qEY6_=zOHr3ZXG|Q| zn!E)F>>bR`Y8&dtmTiuNsuU~&q5l{y^0{EBA4gV z`7>e@P61-yc5~KZ;;^b2d&OAOWy6fC;&Ss9o=_5p_)A8GqcJHK-~H0`wSb@TS&V9GnX~Qgj;&v8N@*j8?6LSxswDn>K?3qY2Ub z0cnDvD~o(OGXqf>h=(H*o}WVul~1XHI`O^$zfzVSNz8Kri`qD#X3f>2rvhO{W$jP|N6AcFU9kvDo#_=v1O&D8R)0C#CT5{$xV_BC=&m zf1OlcMZkPM%S*;eO>&b?vWEcqT%bdi?*rS zE?U{ve#iI+h2JTBW9#=Hd*iXG^^dc#i9<)!`!f%aJ5Qh#9i%6pJRaRdXA;9!LIOv-}y>@uZ zjJdD$QQ}#84#yO|$brbfw>-L6QCeBr#5;$wov`{l4)LawlSnhQbLLG8S z=UqP`)L#?Cn~s`sp(cNrBBo`-)NjD^o()q4Y(f0Chq_KU(IbZsE&H0)O-Y*}%aCoX}b&A5iQ_5~{lzzYFjhY{L|L5T!9G=>8 zh(%XPnmwMRM&k&ib=!0o1W*t|Fo@U!d8(4eGCZW*Z`v)sDW^lJ!Wlq0p+AUHvmxt zg@*73gMx@{jzEX}TlmC9tNdSalfMavG&TUcNKrOU$iGeD-zJCcqL>0qv%_*siiD-U zgf3TR*y;BXRg)-N)jqCRFlZ27Fnqkd8Uz@NZC zOB!Z+^*7vsJTW0xL;y+{IDs8Wb(sDWq*GV9lx12*7{`O+${md<87nQdF}nB-hx_cw zvmY4QHR)J3zK~$>HYh6pcPh^h;oO~ae?U?H2RZ0hT<~2Z57d-G@&vL>cwD-btaKrwBaDJWzf&#pHix8|i z0WiZ`gj~$kgWYC{=jO~1ugF(Tz83Pe5Nuk@){b*c7{%EbK3ATdqIHhavJBK#!57mP z@dI76DLC`+)bGBVc&eAMROfq*q7 zA<#ixp)vB=s>ifgixe>7ck5_@#f zhgy#YgxRfdmLlpRkfnh^mb};E71&mrRTU7=BDeWq*{3Ls;bbaMat_Z+HiZ1=h(SYV z;t4nyo98$?z(jHw7D*2(7D+x8izJ_lMUu}>2xJlYiV1;qz?X6o0$Bo|+KjZc)h}(+ zOqhKLA@k-RO9p}Lk$^v%R_Up52mYw#qvMbArxENiWnqP^=AS{R{0f}?@~=|_L7lIb zCf7$0GkbIEj5-R^@T!z%T04y>GLjLGf@HT{If&wWgD1)a63L&1bNBZAQ^d-w3=F0= z+Gc1Y01-C=5OK@zU|Qc7X7?jGlihKje_=)I#){V9Gu%yb-&3wBmgztvw1LbF?M<)= zOJ7HP9G?J~&PF43+EmwkERs@_p?x0G$f`*Q2zC<6s$M*22=7saZ_>xca=wTMb`g2sMuoqEWcd}QRZTW0 zN%yM|T4Aa92rJ zfIRh!{HzT(fRFwI%pJ9VBOECA2OJYiOQIf(xS5bEqb+woZlvgiOHAA z_dDeLE}Vq{FL$Q@XTUDT5icSQMnoOpdOyG}6+HX*0&vM!i1cs3xqBb{2gJ%CR{|kd zaPp8Vgb{LuTOM+SFtzKUtEI1W*ZU9TDoqnE8@ZC-LOAP9@^{Hc%kNywh)KZN)1-zK zu@(y=`l5a0eZh)aP=cjp0d*hQqw1z8R$xR@q91DPs>;DVE(QM&(IpKM*-hBggYykj zif)&&(}$bNezZo=FOB`|@AguVC(tp_hff`llF;J^pg1tt9t=Qfo;EC?!j02?2-a#U z0(69$r933E<>3ag{XSBF3ml6`2^*y%y`i>{)E9szooAJ}8v7jpC_{r#erB0Vr=WbG z?ubaC_EJE!h$u^eh=^q@DXkrECyjAB zpB6*?5+|YMv?^2c53cT5eSiDvj{C&pz|O=E-v2=R1FM(Oi3ce(7#B%D(AEc#Hli!h zgL*?mjbH>v-*olAq&z4=X9~J~C|-xS|M7$3kd(|6%SJj@VKju)*N1`-xmghxR7?y6 zp<*5g4pGID{edLZ3In!;6c5lzKG0DD(GvmG1&tYe4s=H5D2Y6nNK(N-Cg95iBI}JC zj@s15Vn;vNB!-AY1TX{C|EQ^h;W(9D2!S!>yn{b8u(;>&b=q#w<3 zJcj7_NNn(Uf5(0d;C}v>lW zBt+`MCZ=?ituAcx3J53lVN$_j%EVncTf8}WSP@6?+L&7i*Xp%rW8g82UIUs(P zfB%Kj3&GLK&pk3#zA{zH!ri13UBV zPPfncu>GE`D@{NnU2!`$!LsKdHJFPRWwaO)6>F% zT}#HQTOwAmo|n(=b)?VKmJofObUj%*Xzb($4LiXTC(-tCoEY`FI=N$pJ%uWG6n9hD z!IA|};FfeZ=wHszki;VsKLS(~YsLr;%-8Fs#i($|2;Jz0r0uU_7h=(F+Kcpch>>t4Bd zJUTB5dVE7`$I5s;J0+V%m3V`%K`-oaw zxn9?}nAL`sOkH=jqZW<6Mqk5&NUnVyqm!?}*O)Uxh=^m(IcMCMd#X0~?J{mpr`npJ z&YJv^;hb!4}P`%XG4B9-BpTAzJ9F;4IyJhjXs6Y5c ztN*s8;xTUg{R}r|QGR=VI#Qgtd;2ZaHr@(vrOtibkLKr0{HwQKyd{fH+1x2H?rizR zc#r40cjtfa>Fgf0P+zG^mzFT6c~=&d7D}5{*ZIBM*GRe#dycEEvj;odYAbnj`E1|3 z58_eh18`-vuQ`Vssu5C?+yAmpv&WZV{ChkxU$eIk_j=zly5n4Z zgFZ7fU<^#3tY0`Moa*qm{KMn(XI>dc}D8g0A`alwr;=q>bB&xycfES*I%AcGc==Any_j0_P}kZy;N2j&?Jt3-C5OYh30;Ms-0Su!)w1kC*m zcaYoZR7Z2!g^ghzeUSGV;2PGLPH|^oXKRm3>uu0byx$0$69o#tSK-?f-m_nhA|sqv zF?6hk4WZB}#gI6yO8S}(sbYPIbl^_K<%gAJ8DO1`w1>g_9S=of;ONA|9qDMRdB|%b zvi%uKH{P)KJ0JMQqnEagHjZu^y?=}!YZ`NpJ@F;S?|$It43vDavYJ1=b|EViBI{g> z+oF(Z%_O(MIlXM2d5IjG6~s#D9UDZ2x5>1ilQH{pGA*xUtbRC|mRIt9Lvd**dDEkYgk#jcmvT^hi^!e2{%eC5$4-b3E4-HiYYplPgDg0 zXA^P{1s@{kDGIi#Nzj+%!xZ>5Iev1UAtwMwv8i#${0Rt1*(i3VE2AlXDtwS(Zt^jT zZDF{m6q18@O3-RTKj5Y6dgs}l&;sOUN-gMJ6?%4Q#Uee7VJi<75bkh$Jy%-&Y0t1_ zrr7mT>}+h<_*0jA+U5C?%QIsyI@b#4rAN;`dTz@n9~mB?)xU(_f5LHhOIY&_k~{$PtXW3*|2xAeD84KjNo{w=xotU@wO{FudE;6 z^URFHJ$!t;tn1S5OBq@I`boRUUM-)rx81PUP1+l>9vdd@jp>9l?iyIJaNCB3 zTlL%_KEz+!9Gd6}{=j$jz_|ComC`H6{;&ei!{&+2p*M@lK6YrPs&Tq%#YEMLv4^je zUzNwJR!mlTVYnh4yzz246a3Sv`jHc_*PS!{MQPiQm#rMJl7WYzsY=guW#>d?=k?0_ zZW+1ORVg!9-*&ldYURUIb&s60%{blD&ejQM>*(og&NVk1nqY7OCMJqsE*?33sp?$u zHRndkcxbxHb8gGbiuuq)9#iDcgr<*>#V@7d3d6- zeZ)GtVMc5n9hwsF8!`Pwjd=e48!OwtzWyugFCDnNVtnn6KVeGZ*H#`H*?VKzim{E? zm#rJ+XPR5aOuxBd=Frjcr~Olho*CVHspQgA->SRPcqQ;h%dS2+wdLTCTAqYvf9rZ^ z_Am2%dC!GCm#kAQ8%B2AsI7l}_iMXHr7sU$7`W7Xy>{!ZQWOQyZ&{UEy3w95`!Dz> zmpyRJy$(elalNu@+TAwcZX2ukdc(zrNzaCB?u|s8#&bJw)HRLX|DSqCc#v?kWxDBs ziKYi8o7PWNt{*YaRMt*cdL}A8*DF_|DD^8)6#B+;5agQMlTCpeO)Xz;`+VEj+OKcC zxbf1^)XFW>D|b(<+&$j?bkoxlO;3;ekBx_-lTEP^%Zqi{ zM|Xdz2n2ZITV(Y7jE1%KZ`L=R4~_BPbbY(}8`UGB$@+)>!1o>B_t=3TSYf&B zo7{8+CnoEfM<1N3Z66Cv)~*K1-Lw`Z1vOwmp-QdziV63MYi?qOvc}P!Q|=Y#_Rci6 zt6!UHTzBrVo9^0IAAkArnaU=VWJYWreQYc--rRNR!OOlY+b{3CT0Y)=c>IWe{PAbT z>jSVBQq}@%A!X&adU&pSMd}#Ol~iThDFHpl?)%b%qbtXrzPuML`Ov}fCl8Hpd}_S< zFtbvE#IRB_gv85XYoxe5wb#H|oz!8+%W?9<%PbW9eD;8W+rmpcddEwL?y#BuduE5% zW&U2%wt8=+>8fc1+#eL|>cZ_um4)6mD_do#%7Zf)ljInjcc{Pn&y>#!8jJ_YcY+*+ zoFBtsIzhU+ZZUE_CwVBIUlhTIhf=eXu!UA$46SgrrC-WUe0j(`@5< z&W)`?wrx09OBk<;QvU-(sa3oeA5C8vZP_(@8@F7IU06OKTP(1^k}P2QugvNJvvI(z z9Y8Zyw|>BE9+CX{2P_4%cYtu!`T?_f;3ZoJ&ZKJh zfGi#eUb1zd+dUwQ2ZEPu9q4usn6(3z8?$tPaMkVsvv$D3-INC5Mc%SJy9W+bJ^V6S zkE!nFml8TLJK~-)Lv4KsxFLqK}=nGgF>mxDf@gg2d|)p*=7zl z!&;`nw3)xqMf|myAH$wA-SfAZjrcPDgtXy?UIeID{#gV%bL-=xkZ{#2eSjH?gfAk9 z>BB395a1F?n7hIM_u#lr%XSD5T$B?s;LUStXi}|_fP3_%#|b6*Mydt zjgJf$To)?U`k?Y@MPCtqs?+QkoTZxpTUn}Y0s#N;^Yokz$*Hyq@oCmUKF?k>+9>X{xz{U_VTuViQdfo|9yF{QYU) zNoE}(9l;7y3H|Hmm53;dD{wVw7_}%CBBWQXsbD{%>=y$I6uLbmWdwBTxogALOy|fR`S`n z^7`|^SB|C%>4}zK=eNDGDOE&I#awC4`L0*mQx1A^QkoL-xyV;aJ~wTmm65NUd==!Y zB%=&fL6h>mkU1*r`}>U#-v1?1`B4FM895*7~84y3kG> z?FGm?;^uuW1BW<~~pYJ6po?Qn@q1Igj!xCG1e6Wjjs^_(y-|P8v>`@c| zL(?PS?lrB`C;$Wu_O#c3248^8LN)N3vpYxJ<#To0vXW|EgrY?AFND&>IPdDhp}Z({ zUUrc+Y9I>pLOhlnT!Ofg;SwZ{r*R2^jU%@+V4{xg!yx{xIUAIjEWJUqsaBwrc$>fxWy(!~3s)5Y(< zb8siwM-tN+wRr$IF|$($Rc&Vu0mbp_U7AoLtxKzih4pR?Z5U`_1j7JtI_`M0Qgo&h zkdF-k(C0z!fQM9Vk1c!m0t$k#%oI0d@$r4T4<6jHotZ?_!#h+xew$1N)#XRX*-XwB zaz2jg%(E^cgD+GCe})WP6ku^>a6s-tbj9Wiz-Z|M)1$}_(ichDRo+O>L*#5C=Uz)jBbj1jlaC*$S3LeqdwW50M4Nx>O5S(KOaSUbb%7cEeRRL98%YwtmvJe%Nxe&~+~G ziHES9opv-#I2wNBSbn2ioN+%pYcy22Fg2IBuqI!1}?V1;n3^6HKmN6Coya_Q9smexNU!m`?0eVcWw*)SjcrH}ge2u#3KizjskdzcQ~GHC=sHir>WdGNnMd*!3RO`;V#K zv@XefQTrgH^4qBQS17YD!pS+D@KppTO?masQ`u+b`5I-h3=SJ-bqdfB##;WKSF*2B zKA(ki_oc!Qhz@^E@!5CN%SnW2-+j*xz{qdXXMYAqTZn2iz+QwbKr+%c%P~UBivIVM z$2%t(R$7>nwV{=*z3!2IKLq`<&lY)TaqqeCB(3#5V@dY({*xAS>)#L+T|~6Bm1btT zSC;}lLDl_RIJv|p&QqH6_%%NtR&hEoH2<_w%Y?mU)O+3DHoNd?qhFYvHI-l0p4KnY zHWS3#&ye%J^ENtOrUBBYPFVx!N|tQFpWguC#Ty_uA6C1*bnW0t3tj^JY{d9HJWB~) z3$1HOBHxjc=5m-H9>o8i?sm+G+2;HhHX5Va_C~5L(dv3;IrvVA5CWOAn0EU;L9LdX5bbO z?fV4#>ZT3m4^ZJBqp1Rq!?A6F&D*TskXix^UeJ_7AATHRm2FEXFwlduGjTc&qa9M9 zY`eGZRV_P+`@$p#L#-$KV?b}0CsYx&J-twZl1vnbGs5hrE!yrP;Rgdz2>7`LC3@yoWX>;MGUnO$m3*Ah)K>KQYL{;%3va6 zzCu^#3B{yDba@hp1nK9H=E-8VfLp+ZOO4(+D7Z}LFP0odm1cEcc|I+3tXKRMa*;_h zL>(!!WeXZL@%Ij)Zg~x5PKsjMB37FcKZM+2Fp=$iFHk}i&}OB3AYD3m((5N$)=~-Y zY9cYqLdE+JT(e(R>U03IE>VRS!gby*X!8=PD-&L$9jDuhyq4qu`3z#biN6F%t!y4$ z4m6j8!+>UbP=RLhsX#ONGC(suWq@XS$^gyuqyo+4Q-NmksX#ONRG^uBD$q z%dUmsWq`}YtMuC94scnk|6Rdlz~>8s%Owkf%O(Fhz-4&{aVCv8n#nYo;mTc$j0tH_ zcmZ_z=g3LkMb2(G`(^wV2h^a*`82YJsph58P}Z?>AzFDaQhQ3@IntWxv`EAh^A1zLoc7wQO`<8vHnA@{TgckIHN4Ix~+h{jr>yDj|dv~y3 zmxA?X?2)v$<5X+zf=c`G7OYd~M+opM6N!!DG5l~P);-W}qtmUbb}!a3big&7upN^q z6d;m*4tASr9UA%LqfjPgKVycK5LP_2GJ+Egu;Z#PqJl~h!B6Sp>j28xG^f*CotB3` zj$ox_k&d2|5o-n-`oZ0>aHyI=uNw(p7c_G~$SJ)hps|Fl321S!*h)$@&V8@=JF!AJ z@_pa%_xeSsqM`7tEbki%PqnY2+K+*tDz_88^^0iDreuBPn#JwdnIu_S2V1htkIPG- z@NJ9KJ`am$rFx&Sc%nuvQRA;s@BF^6jZwcGEPgS{ zBHS|+e;KHkW5Dt~Cma;+I5`Ao0UX`42*(lO(!wSKg;@>$zXEfq-CC&KBI$Ocw$d+j zZ?*fsPYA3`$`5jk+L1m@YbI@_#PHR|MOre0va-57pRNl$9R1Pt?+!wxwp&hZmq^!_ zhHL#w_qojoc~@Xid5}It!kKQ=Mr#~~bS^fnX?Q0q=_$RW@(_HU!bLDm`836QfgFM{ zU zaf5o2hIm0f14psbFSm6B)gM|_f26dC1;OeG`x*PJGbjS3vXWyXr;r>w9Qsu=EEw^t zW+4jdqo9iIIX1ch=&7gqSPc}1+Ho_XR3)W|M4n6-)lfHUnfrz{3E|0XW|5mIqVSOV z8*C5D7tv1m&n3>o!H;nA{Es=;pK;ZH#?`!K=J@)*=EOOJz#HZ`IIx7p8~(w~6>d6X znX|LsF`qkZDCOO^I5@Z0Zsz&cb6cm~9TV=3OV-;QJ>TAG*lXn7=M2-%rU_@$=Tv%@8bEcbMkb>@`;M&V^y~~dcJ+oun)Hp?{r<)L|xaVr~Z+nXIAw82WK41Qvd(} diff --git a/pydeepity/_backend.py b/pydeepity/_backend.py new file mode 100644 index 0000000..b1d8839 --- /dev/null +++ b/pydeepity/_backend.py @@ -0,0 +1,7 @@ +try: + from . import pydeepity as dy +except ImportError as e: + raise ImportError( + "Could not load the compiled Deepity C++ backend. " + "Ensure the package was installed correctly or compiled for your architecture." + ) from e \ No newline at end of file diff --git a/pydeepity/utils.py b/pydeepity/utils.py new file mode 100644 index 0000000..92d57cb --- /dev/null +++ b/pydeepity/utils.py @@ -0,0 +1,113 @@ +import numpy as np +import numpy.typing as npt + +from rich.console import Console +from rich.progress import ( + Progress, + SpinnerColumn, + BarColumn, + TextColumn, + MofNCompleteColumn, + TimeElapsedColumn, + TimeRemainingColumn, +) + +def _fit_with_progress( + net, + X: npt.NDArray[np.float32], + Y: npt.NDArray[np.float32], + epochs: int, + steps: int, + initial_lr: float = 0.01, + decay_rate: float = 1.0, + shuffle: bool = True, +) -> None: + """ + Shared training-loop implementation used by all PCN classes. + It delegates to the specific class's `train_step()` method. + """ + console = Console() + n = len(X) + bsz = net.batch_size + n_batches = n // bsz + + console.print( + f"\n[bold cyan]Training[/bold cyan] [dim]|[/dim] {epochs} epochs [dim]|[/dim] " + f"{steps} inference steps [dim]|[/dim] {n_batches} batches/epoch (batch_size={bsz})\n" + ) + + progress = Progress( + SpinnerColumn(style="cyan"), + TextColumn("[bold blue]{task.description}"), + BarColumn(bar_width=40, style="blue", complete_style="cyan"), + MofNCompleteColumn(), + TextColumn("[dim]•[/dim]"), + TimeElapsedColumn(), + TextColumn("[dim]•[/dim]"), + TimeRemainingColumn(), + TextColumn("[magenta]{task.fields[stats]}"), + console=console, + ) + + with progress: + epoch_task = progress.add_task("[bold]Epochs", total=epochs, stats="") + batch_task = progress.add_task(" Batches", total=n_batches, stats="") + + for epoch in range(epochs): + current_lr = initial_lr * (decay_rate ** epoch) + net.set_learning_rate(current_lr) + + if shuffle: + indices = np.random.permutation(n) + X_shuf, Y_shuf = X[indices], Y[indices] + else: + X_shuf, Y_shuf = X, Y + + epoch_energy = 0.0 + progress.reset(batch_task, total=n_batches) + + for b in range(n_batches): + X_batch = X_shuf[b * bsz : (b + 1) * bsz] + Y_batch = Y_shuf[b * bsz : (b + 1) * bsz] + + energy = net.train_step(X_batch, Y_batch, steps) + epoch_energy += energy + avg_so_far = epoch_energy / (b + 1) + + progress.update( + batch_task, + advance=1, + stats=f"lr={current_lr:.5f} energy={energy:8.2f} avg={avg_so_far:8.2f}", + ) + + progress.update( + epoch_task, + advance=1, + stats=f"epoch {epoch + 1} avg energy = {epoch_energy / n_batches:.4f}", + ) + + console.print("\n[bold green]✓ Training complete.[/bold green]\n") + + +class _PCNMixin: + """ + Provides shared top-level Python functionality (like fit()) to all Network wrappers. + """ + def fit( + self, + X: npt.NDArray[np.float32], + Y: npt.NDArray[np.float32], + epochs: int, + steps: int, + initial_lr: float = 0.01, + decay_rate: float = 1.0, + shuffle: bool = True, + ): + """ + Runs a full multi-epoch training loop with a live rich progress display. + Delegates per-batch execution to the class's `train_step()` method. + """ + _fit_with_progress(self, X, Y, epochs, steps, initial_lr, decay_rate, shuffle) + return self + + diff --git a/pyproject.toml b/pyproject.toml index 8fe2f47..d39d249 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,88 @@ [build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" +requires = [ + "scikit-build-core>=0.10", + "pybind11>=2.13.0", + "cmake>=3.21", + "ninja", +] +build-backend = "scikit_build_core.build" + +[project] +name = "pydeepity" +version = "1.0.0" +description = "A high-performance Predictive Coding library." +readme = "README.md" +requires-python = ">=3.9" +dependencies = [ + "numpy", +] + +[project.optional-dependencies] +# 'rich' only powers build.py's interactive dashboard during development +dev = ["rich"] + +# ══════════════════════════════════════════════════════════════════════════ +# scikit-build-core: how `pip install .` / `pip wheel .` invokes CMake +# ══════════════════════════════════════════════════════════════════════════ +[tool.scikit-build] +minimum-version = "build-system.requires" +build-dir = "build/{wheel_tag}" + +# Pure-Python sources under pydeepity/ (__init__.py, utils.py, etc.) get +# merged with whatever CMake's install() step places under pydeepity/ (the +# compiled pydeepity*.so) into a single wheel. +wheel.packages = ["pydeepity"] + +sdist.exclude = [ + "tests", + "experiments", + "logs", + "build", + ".github", +] + +[tool.scikit-build.cmake.define] +# every flag here is chosen for maximum portability, not maximum speed. +# See CMakeLists.txt's DEEPITY_ARCH_FLAGS/DEEPITY_MSVC_ARCH_FLAGS comments, +# and build.py's --distributed profile, which mirrors this exact config for +# local testing (`python build.py Release --distributed --no-cuda`). +DEEPITY_BUILD_TESTS = "OFF" +DEEPITY_BUILD_PYTHON_BINDINGS = "ON" +DEEPITY_ENABLE_CUDA = "OFF" +DEEPITY_ARCH_FLAGS = "-march=x86-64-v2 -mtune=generic" +DEEPITY_MSVC_ARCH_FLAGS = "" + +# ══════════════════════════════════════════════════════════════════════════ +# cibuildwheel: which wheels get built, and what each platform needs +# installed before CMake can configure successfully. +# ══════════════════════════════════════════════════════════════════════════ +[tool.cibuildwheel] +build = "cp39-* cp310-* cp311-* cp312-* cp313-*" +# musllinux (Alpine) and 32-bit Windows are skipped: OpenBLAS/SLEEF aren't +# routinely tested there, and almost nobody needs a 32-bit ML build. +skip = "*-musllinux* *-win32" +build-verbosity = 1 + +test-requires = ["numpy"] +test-command = "python -c \"import pydeepity; print('pydeepity OK:', pydeepity.__file__)\"" + +[tool.cibuildwheel.linux] +# manylinux2014's stock GCC is too old for -std=c++20; the _2_28 images +# (AlmaLinux 8 based) ship GCC 12+, which is required here. +manylinux-x86_64-image = "manylinux_2_28" +before-all = "dnf install -y openblas-devel libgomp" + +[tool.cibuildwheel.macos] +before-all = "brew install openblas libomp" +# find_package(BLAS) / OpenMP need an explicit nudge on macOS since Homebrew +# installs outside the paths Apple's toolchain searches by default. +environment = { CMAKE_PREFIX_PATH = "/usr/local/opt/openblas:/usr/local/opt/libomp:/opt/homebrew/opt/openblas:/opt/homebrew/opt/libomp" } +archs = ["x86_64", "arm64"] + +[tool.cibuildwheel.windows] +# OpenBLAS comes from vcpkg here (matching the existing windows-clang CI +# job) since there's no Windows system package for it. VCPKG_ROOT/ +# CMAKE_TOOLCHAIN_FILE are set by the "Install vcpkg dependencies" CI step +# below, before cibuildwheel runs - see ci.yml. +before-build = "pip install delvewheel" +repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" \ No newline at end of file diff --git a/setup.py b/setup.py index 42c6fbd..89992f3 100644 --- a/setup.py +++ b/setup.py @@ -1,15 +1,41 @@ -from setuptools import setup, find_packages +import os +import subprocess +import sys +from setuptools import setup, Extension, find_packages +from setuptools.command.build_ext import build_ext + +class CMakeExtension(Extension): + def __init__(self, name, sourcedir=""): + Extension.__init__(self, name, sources=[]) + self.sourcedir = os.path.abspath(sourcedir) + +class CMakeBuild(build_ext): + def build_extension(self, ext): + extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + if not extdir.endswith(os.path.sep): + extdir += os.path.sep + + os.makedirs(self.build_temp, exist_ok=True) + + # Tell CMake to output the compiled pybind11 library directly to the extdir + cmake_args = [ + f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}", + f"-DPYTHON_EXECUTABLE={sys.executable}", + "-DCMAKE_BUILD_TYPE=Release" + ] + + # Build using all available CPU cores + build_args = ["--config", "Release", "-j", str(os.cpu_count())] + + # Run CMake configure and build + subprocess.check_call(["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp) + subprocess.check_call(["cmake", "--build", "."] + build_args, cwd=self.build_temp) setup( name="pydeepity", version="1.0.0", - author="Ra4ster", - description="A high-performance Predictive Coding Network C++ engine", - packages=find_packages(), - - package_data={ - "pydeepity": ["*.so", "*.pyd", "*.dylib"], - }, - include_package_data=True, - python_requires=">=3.8", -) + packages=find_packages(include=["pydeepity", "pydeepity.*"]), + ext_modules=[CMakeExtension("pydeepity.pydeepity")], + cmdclass={"build_ext": CMakeBuild}, + zip_safe=False, +) \ No newline at end of file diff --git a/src/ConvPCNetwork.cpp b/src/ConvPCNetwork.cpp index 117956b..509db78 100644 --- a/src/ConvPCNetwork.cpp +++ b/src/ConvPCNetwork.cpp @@ -4,12 +4,6 @@ namespace Deep { ConvPCNetwork::ConvPCNetwork(int batchSize) noexcept : batchSize(batchSize) {} - ConvPCNetwork::~ConvPCNetwork() - { - for (auto *l : layers) - delete l; - } - void ConvPCNetwork::AddLayer(int inChannels, int outChannels, int inHeight, int inWidth, int kernelH, int kernelW, @@ -18,39 +12,39 @@ namespace Deep float lr, float ir, float pr, float lmbda, ActivationType aType, ActivationType dType) { - ConvPCLayer *l = new ConvPCLayer( + auto l = std::make_unique( inChannels, outChannels, inHeight, inWidth, kernelH, kernelW, strideH, strideW, padH, padW, batchSize, lr, ir, pr, lmbda, aType, dType); if (!layers.empty()) { - layers.back()->SetLayerAbove(l); - l->SetLayerBelow(layers.back()); + layers.back()->SetLayerAbove(l.get()); + l->SetLayerBelow(layers.back().get()); } - layers.push_back(l); + layers.push_back(std::move(l)); } void ConvPCNetwork::Compile() { size_t total = 0; - for (auto *l : layers) + for (auto &l : layers) total += l->GetRequiredFloats(); arena = std::make_unique(total); - for (auto *l : layers) + for (auto &l : layers) l->BindMemory(*arena); } void ConvPCNetwork::RandomizeWeights(std::mt19937 &rng) noexcept { - for (auto *l : layers) + for (auto &l : layers) l->RandomizeWeights(rng); } void ConvPCNetwork::ResetState() noexcept { - for (auto *l : layers) + for (auto &l : layers) l->ResetState(); } @@ -62,14 +56,14 @@ namespace Deep float ConvPCNetwork::CalculateState() noexcept { float e = 0.0f; - for (auto *l : layers) + for (auto &l : layers) e += l->CalculateState(); return e; } void ConvPCNetwork::UpdateState() noexcept { - for (auto *l : layers) + for (auto &l : layers) l->UpdateState(); } diff --git a/src/DiscriminativePCNetwork.cpp b/src/DiscriminativePCNetwork.cpp index 56921f1..4d75cf6 100644 --- a/src/DiscriminativePCNetwork.cpp +++ b/src/DiscriminativePCNetwork.cpp @@ -7,14 +7,6 @@ namespace Deep { - DiscriminativePCNetwork::~DiscriminativePCNetwork() - { - for (auto l : layers) - delete l; - - layers.clear(); - } - void DiscriminativePCNetwork::AddLayer(int size, int nextSize, float lr, float ir, float pr, float lmbda, void (*act)(float *, size_t), void (*dAct)(float *, size_t, bool)) { @@ -25,15 +17,14 @@ namespace Deep DynamicThread(batchSize); } - // Pass raw pointers directly to Constructor 2 - DiscriminativePCLayer *l = new DiscriminativePCLayer(size, nextSize, batchSize, lr, ir, pr, lmbda, act, dAct); + auto l = std::make_unique(size, nextSize, batchSize, lr, ir, pr, lmbda, act, dAct); if (!layers.empty()) { - layers.back()->SetLayerAbove(l); - l->SetLayerBelow(layers.back()); + layers.back()->SetLayerAbove(l.get()); + l->SetLayerBelow(layers.back().get()); } - layers.push_back(l); + layers.push_back(std::move(l)); } void DiscriminativePCNetwork::AddLayer(int size, int nextSize, float lr, float ir, float pr, float lmbda, @@ -47,25 +38,25 @@ namespace Deep } // Pass enums directly to Constructor 1 - DiscriminativePCLayer *l = new DiscriminativePCLayer(size, nextSize, batchSize, lr, ir, pr, lmbda, aType, dType); + auto l = std::make_unique(size, nextSize, batchSize, lr, ir, pr, lmbda, aType, dType); if (!layers.empty()) { - layers.back()->SetLayerAbove(l); - l->SetLayerBelow(layers.back()); + layers.back()->SetLayerAbove(l.get()); + l->SetLayerBelow(layers.back().get()); } - layers.push_back(l); + layers.push_back(std::move(l)); } void DiscriminativePCNetwork::RandomizeWeights(std::mt19937 &rng) { - for (auto l : layers) + for (auto &l : layers) l->RandomizeWeights(rng); } void DiscriminativePCNetwork::ResetState() noexcept { - for (auto l : layers) + for (auto &l : layers) l->ResetState(); } @@ -84,7 +75,7 @@ namespace Deep void DiscriminativePCNetwork::UpdateState() { - for (auto l : layers) + for (auto &l : layers) l->UpdateState(); } @@ -159,20 +150,13 @@ namespace Deep { size_t total_floats_needed = 0; - // 1. Calculate the exact footprint of the entire network - for (auto *layer : layers) - { + for (auto &layer : layers) total_floats_needed += layer->GetRequiredFloats(); - } - // 2. Allocate the single contiguous block of memory arena = std::make_unique(total_floats_needed); - // 3. Bind every layer sequentially into the arena - for (auto *layer : layers) - { + for (auto &layer : layers) layer->BindMemory(*arena); - } } bool DiscriminativePCNetwork::Save(const std::string &filename) const noexcept diff --git a/src/ModelIO.cpp b/src/ModelIO.cpp index 97e8ddd..c16f7d1 100644 --- a/src/ModelIO.cpp +++ b/src/ModelIO.cpp @@ -52,7 +52,7 @@ namespace Deep std::vector writeBuffer(BUFFER_SIZE); wStream.rdbuf()->pubsetbuf(writeBuffer.data(), BUFFER_SIZE); - for (const auto *layer : layers) + for (const auto &layer : layers) { size_t inputSize = layer->GetInputSize(); size_t outputSize = layer->GetOutputSize(); @@ -91,7 +91,7 @@ namespace Deep for (size_t i = 0; i < layers.size(); ++i) { - const auto *layer = layers[i]; + const auto &layer = layers[i]; mStream << " {\n"; mStream << " \"index\": " << i << ",\n"; mStream << " \"input_size\": " << layer->GetInputSize() << ",\n"; @@ -129,7 +129,7 @@ namespace Deep for (size_t i = 0; i < layers.size(); ++i) { - const auto *layer = layers[i]; + const auto &layer = layers[i]; rStream << "| " << i << " | " << layer->GetInputSize() << " | " << layer->GetOutputSize() << " | " @@ -160,7 +160,7 @@ namespace Deep std::vector readBuffer(BUFFER_SIZE); wStream.rdbuf()->pubsetbuf(readBuffer.data(), BUFFER_SIZE); - for (auto *layer : net.GetLayers()) + for (auto &layer : net.GetLayers()) { size_t inputSize = layer->GetInputSize(); size_t outputSize = layer->GetOutputSize(); diff --git a/src/SimpleConvPCNetwork.cpp b/src/SimpleConvPCNetwork.cpp index 58dfd91..4364f65 100644 --- a/src/SimpleConvPCNetwork.cpp +++ b/src/SimpleConvPCNetwork.cpp @@ -4,12 +4,6 @@ namespace Deep { SimpleConvPCNetwork::SimpleConvPCNetwork(int batchSize) noexcept : batchSize(batchSize) {} - SimpleConvPCNetwork::~SimpleConvPCNetwork() - { - for (auto *l : layers) - delete l; - } - void SimpleConvPCNetwork::AddLayer(int inChannels, int outChannels, int inHeight, int inWidth, int kernelH, int kernelW, @@ -18,17 +12,17 @@ namespace Deep float lr, float ir, float lmbda, ActivationType aType, ActivationType dType) { - SimpleConvPCLayer *l = new SimpleConvPCLayer( + auto l = std::make_unique( inChannels, outChannels, inHeight, inWidth, kernelH, kernelW, strideH, strideW, padH, padW, batchSize, lr, ir, lmbda, aType, dType); if (!layers.empty()) { - layers.back()->SetLayerAbove(l); - l->SetLayerBelow(layers.back()); + layers.back()->SetLayerAbove(l.get()); + l->SetLayerBelow(layers.back().get()); } - layers.push_back(l); + layers.push_back(std::move(l)); } void SimpleConvPCNetwork::SetOptimizer(OptimizerType opt) noexcept @@ -43,27 +37,27 @@ namespace Deep void SimpleConvPCNetwork::Compile() { - for (auto *l : layers) + for (auto &l : layers) l->SetOptimizer(pendingOpt); size_t total = 0; - for (auto *l : layers) + for (auto &l : layers) total += l->GetRequiredFloats(); arena = std::make_unique(total); - for (auto *l : layers) + for (auto &l : layers) l->BindMemory(*arena); } void SimpleConvPCNetwork::RandomizeWeights(std::mt19937 &rng) noexcept { - for (auto *l : layers) + for (auto &l : layers) l->RandomizeWeights(rng); } void SimpleConvPCNetwork::ResetState() noexcept { - for (auto *l : layers) + for (auto &l : layers) l->ResetState(); } @@ -75,14 +69,14 @@ namespace Deep float SimpleConvPCNetwork::CalculateState() noexcept { float e = 0.0f; - for (auto *l : layers) + for (auto &l : layers) e += l->CalculateState(); return e; } void SimpleConvPCNetwork::UpdateState() noexcept { - for (auto *l : layers) + for (auto &l : layers) l->UpdateState(); } diff --git a/src/SimplePCLayer.cpp b/src/SimplePCLayer.cpp index 5596ef6..8ff97b7 100644 --- a/src/SimplePCLayer.cpp +++ b/src/SimplePCLayer.cpp @@ -53,7 +53,7 @@ namespace Deep for (auto &s : seeds) s = seedDist(seedGenerator); -#pragma omp parallel +#pragma omp parallel if(!omp_in_parallel()) { std::mt19937 rng(seeds[omp_get_thread_num()]); std::normal_distribution dist(0.0f, limit); @@ -78,12 +78,9 @@ namespace Deep cblas_saxpy(N, -1.0f, layerBelow->mu, 1, e, 1); } - // No precision weighting: E = 0.5 * sum(e^2), plain SSE. No p - // multiply, no -0.5*log(p) term -- both were exactly inert at - // pr=0.0, which was the only value ever actually used. float totalEnergy = 0.0f; -#pragma omp parallel for schedule(static) reduction(+ : totalEnergy) +#pragma omp parallel for schedule(static) reduction(+ : totalEnergy) if(batchSize > 4 && !omp_in_parallel()) for (int batch = 0; batch < batchSize; ++batch) { const size_t offset = (size_t)batch * size; @@ -143,32 +140,10 @@ namespace Deep size_t Nout = (size_t)batchSize * nextSize; size_t N = (size_t)batchSize * size; - // Mu-caching: recompute only when z has moved enough SINCE THE - // LAST RECOMPUTE (not just the immediately preceding step -- - // comparing against prevZ, updated only on real recomputes, - // catches cumulative drift across several skipped steps that a - // step-to-step comparison would miss). - // - // threshold=0 (or a CLAMPED layer, whose z never changes at - // all) makes the ratio always exactly 0 -- reproducing the - // original, EXACT, validated behavior. threshold>0 extends - // this to UNCLAMPED layers too, as a genuine APPROXIMATION -- - // correctness there means "close enough for real training," - // not "bit-identical," and needs accuracy-impact validation, - // not just a single-batch trajectory diff. - // - // mu itself is NOT a stable buffer between steps -- UpdateState() - // mutates it in place (converts it to its own derivative, for - // the feedback GEMM) -- so a skip must copy a PRESERVED value - // back into mu, not just skip writing to mu. bool shouldRecompute = true; if (muCacheThreshold >= 0.0f && muCacheValid) { float zNorm = cblas_snrm2((int)N, prevZ, 1); - // Reuse dz_dt as scratch -- safe: UpdateState() overwrites - // it fresh every step and nothing reads it between here - // and that call, avoiding a per-call heap allocation on - // this hot path. cblas_scopy((int)N, z, 1, dz_dt, 1); cblas_saxpy((int)N, -1.0f, prevZ, 1, dz_dt, 1); float deltaNorm = cblas_snrm2((int)N, dz_dt, 1); @@ -197,9 +172,6 @@ namespace Deep } else { - // Restore the preserved, correctly-activated prediction -- - // mu was left holding last step's DERIVATIVE by - // UpdateState(), not the prediction itself. cblas_scopy((int)Nout, cachedMu, 1, mu, 1); } } @@ -220,9 +192,7 @@ namespace Deep if (isClamped) return; - // Own term: dz_dt = -e (was -p*e; p=1 always made this a no-op - // multiply, so the multiplication itself is simply removed). -#pragma omp parallel for schedule(static) +#pragma omp parallel for schedule(static) if(batchSize > 4 && !omp_in_parallel()) for (int batch = 0; batch < batchSize; ++batch) { size_t offset = (size_t)batch * size; @@ -263,9 +233,7 @@ namespace Deep { const float *e_above = layerAbove->GetErrors(); - // bottom_up = e_above * mu(f') -- was e_above * p_above * mu(f'); - // p_above=1 always made that multiply a no-op, removed. -#pragma omp parallel for schedule(static) +#pragma omp parallel for schedule(static) if(batchSize > 4 && !omp_in_parallel()) for (int batch = 0; batch < batchSize; ++batch) { size_t offset = (size_t)batch * nextSize; @@ -320,8 +288,7 @@ namespace Deep const float *e_above = layerAbove->GetErrors(); float *local_grad = bottom_up; - // 1. Compute the local gradient delta (shared across all optimizers) -#pragma omp parallel for schedule(static) +#pragma omp parallel for schedule(static) if(batchSize > 4 && !omp_in_parallel()) for (int batch = 0; batch < batchSize; ++batch) { size_t offset = (size_t)batch * nextSize; @@ -358,7 +325,6 @@ namespace Deep local_grad[offset + f] = e_above[offset + f] * mu[offset + f]; } - // 2. Dispatch to the selected optimizer switch (opt) { case OptimizerType::SGD: @@ -366,7 +332,6 @@ namespace Deep if (lmbda > 0.0f) cblas_sscal((size_t)nextSize * size, 1.0f - lmbda, W, 1); - // Writes scaled updates directly into W via beta=1.0f cblas_sgemm( CblasRowMajor, CblasTrans, CblasNoTrans, nextSize, size, batchSize, @@ -382,29 +347,21 @@ namespace Deep case OptimizerType::ADAM: case OptimizerType::ADAMW: { - t++; // Increment layer-wide timestep ONCE + t++; size_t num_weights = (size_t)nextSize * size; - - // NOTE: Using 1.0f / batchSize instead of 1.0f so the gradient - // magnitude doesn't drastically shift Adam's variance estimates - // if you change your batch size. float grad_scale = -1.0f / batchSize; - // Compute raw gradients for W into grad_W - // beta=0.0f overwrites grad_W cleanly, no memset needed cblas_sgemm( CblasRowMajor, CblasTrans, CblasNoTrans, nextSize, size, batchSize, grad_scale, local_grad, nextSize, z, size, 0.0f, grad_W, size); - // Compute raw gradients for biases into grad_b std::memset(grad_b, 0, nextSize * sizeof(float)); for (int batch = 0; batch < batchSize; batch++) cblas_saxpy(nextSize, grad_scale, local_grad + batch * nextSize, 1, grad_b, 1); - // Apply specific Adam flavor to Weights if (opt == OptimizerType::ADAMW) { Deep::AdamWUpdate(W, grad_W, m_W, v_W, num_weights, t, lr, lmbda); @@ -414,9 +371,7 @@ namespace Deep Deep::AdamUpdate(W, grad_W, m_W, v_W, num_weights, t, lr); } - // Apply plain Adam to Biases (biases almost never use weight decay) Deep::AdamUpdate(b, grad_b, m_b, v_b, nextSize, t, lr); - break; } } @@ -433,7 +388,7 @@ namespace Deep size_t copySize = std::min(inputData.size(), (size_t)(batchSize * size)) * sizeof(float); memcpy(z, inputData.data(), copySize); isClamped = true; - muCacheValid = false; // fresh data this batch -- must recompute at least once + muCacheValid = false; } void SimplePCLayer::UnclampState() noexcept @@ -449,7 +404,6 @@ namespace Deep size_t total = 0; size_t own_state_size = (size_t)batchSize * size; - // z, e, dz_dt -- NO p/log_p buffers at all total += pad16(own_state_size) * 3; if (nextSize > 0) @@ -457,16 +411,15 @@ namespace Deep size_t out_state_size = (size_t)batchSize * nextSize; size_t w_size = (size_t)size * nextSize; - total += pad16(w_size); // W - total += pad16(nextSize); // b - total += pad16(out_state_size) * 3; // mu, cachedMu, bottom_up - total += pad16(own_state_size); // prevZ (own_state_size, matches z) + total += pad16(w_size); + total += pad16(nextSize); + total += pad16(out_state_size) * 3; + total += pad16(own_state_size); - // Conditionally allocate Adam variables to save space for SGD users if (opt == OptimizerType::ADAM || opt == OptimizerType::ADAMW) { - total += pad16(w_size) * 3; // grad_W, m_W, v_W - total += pad16(nextSize) * 3; // grad_b, m_b, v_b + total += pad16(w_size) * 3; + total += pad16(nextSize) * 3; } } @@ -503,7 +456,6 @@ namespace Deep std::memset(bottom_up, 0, out_state_size * sizeof(float)); std::memset(prevZ, 0, own_state_size * sizeof(float)); - // Conditionally bind Adam variables if (opt == OptimizerType::ADAM || opt == OptimizerType::ADAMW) { grad_W = arena.AllocateFloats(w_size); @@ -514,13 +466,11 @@ namespace Deep m_b = arena.AllocateFloats(nextSize); v_b = arena.AllocateFloats(nextSize); - // Adam moments must begin at zero std::memset(m_W, 0, w_size * sizeof(float)); std::memset(v_W, 0, w_size * sizeof(float)); std::memset(m_b, 0, nextSize * sizeof(float)); std::memset(v_b, 0, nextSize * sizeof(float)); - // It's good practice to zero the gradients as well std::memset(grad_W, 0, w_size * sizeof(float)); std::memset(grad_b, 0, nextSize * sizeof(float)); } @@ -531,4 +481,4 @@ namespace Deep localArena.reset(); } } -} \ No newline at end of file +} diff --git a/src/SimplePCNetwork.cpp b/src/SimplePCNetwork.cpp index c7e2484..9fee9f1 100644 --- a/src/SimplePCNetwork.cpp +++ b/src/SimplePCNetwork.cpp @@ -4,48 +4,41 @@ namespace Deep { SimplePCNetwork::SimplePCNetwork(int batchSize) noexcept : batchSize(batchSize) {} - SimplePCNetwork::~SimplePCNetwork() - { - for (auto l : layers) - delete l; - layers.clear(); - } - void SimplePCNetwork::AddLayer(int size, int nextSize, float lr, float ir, float lmbda, void (*act)(float *, size_t), void (*dAct)(float *, size_t, bool)) { - SimplePCLayer *l = new SimplePCLayer(size, nextSize, batchSize, lr, ir, lmbda, act, dAct); + std::unique_ptr l = std::make_unique(size, nextSize, batchSize, lr, ir, lmbda, act, dAct); if (!layers.empty()) { - layers.back()->SetLayerAbove(l); - l->SetLayerBelow(layers.back()); + layers.back()->SetLayerAbove(l.get()); + l->SetLayerBelow(layers.back().get()); } - layers.push_back(l); + layers.push_back(std::move(l)); } void SimplePCNetwork::AddLayer(int size, int nextSize, float lr, float ir, float lmbda, ActivationType aType, ActivationType dType) { - SimplePCLayer *l = new SimplePCLayer(size, nextSize, batchSize, lr, ir, lmbda, aType, dType); + std::unique_ptr l = std::make_unique(size, nextSize, batchSize, lr, ir, lmbda, aType, dType); if (!layers.empty()) { - layers.back()->SetLayerAbove(l); - l->SetLayerBelow(layers.back()); + layers.back()->SetLayerAbove(l.get()); + l->SetLayerBelow(layers.back().get()); } - layers.push_back(l); + layers.push_back(std::move(l)); } void SimplePCNetwork::RandomizeWeights(std::mt19937 &rng) { - for (auto l : layers) + for (auto &l : layers) l->RandomizeWeights(rng); } void SimplePCNetwork::ResetState() noexcept { - for (auto l : layers) + for (auto &l : layers) l->ResetState(); } @@ -64,7 +57,7 @@ namespace Deep void SimplePCNetwork::UpdateState() { - for (auto l : layers) + for (auto &l : layers) l->UpdateState(); } @@ -127,14 +120,40 @@ namespace Deep } } + float SimplePCNetwork::TrainStepWithProjection(const std::vector &x, const std::vector &y, int inferenceSteps) + { + ResetState(); + Clamp(x); + ProjectForward(); + GetTerminalLayer()->ClampState(y); + + float finalEnergy = 0.0f; + for (int t = 0; t < inferenceSteps; ++t) + { + finalEnergy = CalculateState(); + UpdateState(); + } + + UpdateWeights(); + GetTerminalLayer()->UnclampState(); + + return finalEnergy; + } + + void SimplePCNetwork::SetMuCacheThreshold(float threshold) noexcept + { + for (auto &l : layers) + l->SetMuCacheThreshold(threshold); + } + void SimplePCNetwork::Compile() { size_t total_floats_needed = 0; - for (auto *layer : layers) + for (auto &layer : layers) total_floats_needed += layer->GetRequiredFloats(); arena = std::make_unique(total_floats_needed); - for (auto *layer : layers) + for (auto &layer : layers) layer->BindMemory(*arena); } } diff --git a/temp.py b/temp.py index 769909c..a5159d2 100644 --- a/temp.py +++ b/temp.py @@ -4,20 +4,25 @@ from pydeepity import SimplePCN from time import perf_counter -# Tests forward-projection initialization -- the real, structural finding -# from reading ngc-learn's actual pcn_model.py source: EVERY batch, they -# run a pure feedforward pass through CURRENT weights first, and seed the -# settling loop's hidden states from THAT, not from zero. This is -# fundamentally different from (and likely much stronger than) I_avg's -# stale class-average caching -- fresh, per-example, current-weights, -# every single batch. +# FINAL VALIDATION -- the complete stack found today: +# - Forward-projection initialization (seeds hidden layers from a real +# forward pass through current weights, not zero-init) +# - +-0.3 uniform weight init (matching ngc-learn's actual convention) +# - AdamW, lr=0.001 (matching ngc-learn's hard-coded Adam + eta=0.001 -- +# the init range and optimizer needed to be changed TOGETHER, not +# independently; tested and confirmed: 73.31% alone with SGD vs +# 93.96% paired with AdamW) +# - mu_cache_threshold=0.05 -- confirmed +30.6% faster than ngc-learn's +# real, measured per-batch time in an isolated speed test. THIS RUN +# checks whether that speed holds without costing the 93.96% accuracy +# already confirmed at threshold=disabled. +# - train_step_with_projection() -- single C++ call per batch, not a +# manual Python loop (confirmed real, if smaller, speedup from +# eliminating ~40 Python/pybind boundary crossings per batch) # -# Matches ngc-learn's real settings as closely as possible now that we -# have the actual source: [0,1] normalization, labels clipped to -# [0.001, 0.999] (not raw one-hot), tanh activation (confirmed as their -# actual default), 784->512->512->10, T=20, plain SGD (isolating THIS -# specific change -- forward-projection init -- from the separate, -# already-known Adam question). +# [0,1] normalization, labels clipped to [0.001, 0.999], tanh activation, +# 784->512->512->10, T=20 -- all matching ngc-learn's actual, real source +# as closely as possible. def load_full_mnist(): @@ -73,6 +78,18 @@ def main() -> None: # without the other may have been the mistake. net.compile() net.randomize_weights() + net.set_mu_cache_threshold(0.0) # REVERTED from 0.05 -- that showed genuine + # instability under real, sustained AdamW + # training (energy trending UP, a 62-point + # accuracy crash at epoch 8), not just noise. + # Likely cause: small caching-approximation + # bias accumulating in Adam's momentum/ + # variance buffers over many updates -- + # something the earlier single-trajectory, + # no-weight-update speed test structurally + # could not have caught. threshold=0 is + # exact (proven bit-identical to no caching) + # and confirmed safe with this exact stack. # Override weight init to match ngc-learn's actual convention: fixed # uniform range +-0.3, INDEPENDENT of layer size -- not our own @@ -112,23 +129,7 @@ def main() -> None: X_batch = X_shuf[b * BATCH_SIZE:(b + 1) * BATCH_SIZE] Y_batch = Y_shuf[b * BATCH_SIZE:(b + 1) * BATCH_SIZE] - # Manual loop instead of train_step() -- need ProjectForward() - # inserted between clamp_input() and the settling loop, which - # the existing train_step() convenience method doesn't do. - net.reset_state() - net.clamp_input(X_batch) - net.project_forward() # THE new step -- seeds hidden layers - # from a genuine forward pass, not zero - net[-1].clamp_state(Y_batch) - - energy = 0.0 - for _ in range(STEPS): - energy += net.calculate_state() - net.update_state() - - net.update_weights() - net[-1].unclamp_state() - + energy = net.train_step_with_projection(X_batch, Y_batch, STEPS) epoch_energy += energy # REAL accuracy check -- genuine UNCLAMPED settle on a cheap diff --git a/tests/tProfile.cpp b/tests/tProfile.cpp index 6687e9d..637593f 100644 --- a/tests/tProfile.cpp +++ b/tests/tProfile.cpp @@ -1,108 +1,95 @@ -// tProfile.cpp -- SIMPLIFIED for this investigation. -// -// The original per-phase instrumentation (PCN_TIME() calls inside -// DiscriminativePCLayer.cpp) was never carried forward through today's -// restructuring -- the registry in Profile.h is empty, confirmed by the -// missing per-layer sections in the last run's output. Rather than -// re-instrument the whole library again, this measures the two coarse -// phases that actually matter for "where is the Windows slowdown coming -// from" directly at the call site: the settling loop (CalculateState + -// UpdateState, repeated INFERENCE_STEPS times) vs UpdateWeights(). No -// changes to any library source file needed -- just std::chrono around -// the existing public methods. -#include -#include +// Isolates ONE 512->512 SimplePCLayer, standalone, matching Layer 1's +// real situation from the granular profile (fed by a 512-wide "layer +// below", feeding into a 10-wide "layer above") but stripped of +// everything else in the network -- to find exactly what's +// pathologically slow about this specific shape. 30.82ms calc / 27.06ms +// update measured in the real network is ~5x more than its FLOP count +// vs Layer 0 would predict. +#include #include +#include +#include #include -#include using namespace Deep; -int main(void) +int main() { - std::cout << "OpenBLAS selected CPU kernel: " << openblas_get_corename() << "\n\n"; - const int BATCH_SIZE = 256; - const int INFERENCE_STEPS = 150; - const int N_BATCHES = 5; - - std::mt19937 rng(42); - std::uniform_real_distribution dist(-1.0f, 1.0f); - - DiscriminativePCNetwork net(BATCH_SIZE); - net.AddLayer(784, 512, 0.002f, 0.05f, 0.0f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); - net.AddLayer(512, 10, 0.002f, 0.05f, 0.0f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); - net.AddLayer(10, 0, 0.002f, 0.05f, 0.0f, 0.0001f, ActivationType::LINEAR, ActivationType::dLINEAR); - net.Compile(); - net.RandomizeWeights(rng); - - std::vector X((size_t)BATCH_SIZE * 784); - std::vector Y((size_t)BATCH_SIZE * 10); - for (auto &v : X) v = dist(rng); - for (auto &v : Y) v = dist(rng); + const int BATCH = 256; + const int STEPS = 20; + const int N_REPS = 40; + + // Matches Layer 1 exactly: 512 in, 512 out, same activation, wired + // to a layer below (512-wide, standing in for Layer 0) and a layer + // above (10-wide, standing in for Layer 2). + SimplePCLayer below(512, 512, BATCH, 0.001f, 0.08f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); + SimplePCLayer layer1(512, 512, BATCH, 0.001f, 0.08f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); + SimplePCLayer above(512, 10, BATCH, 0.001f, 0.08f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); + + below.SetLayerAbove(&layer1); + layer1.SetLayerBelow(&below); + layer1.SetLayerAbove(&above); + above.SetLayerBelow(&layer1); + + std::mt19937 rng(7); + below.RandomizeWeights(rng); + layer1.RandomizeWeights(rng); + above.RandomizeWeights(rng); + + std::uniform_real_distribution initDist(-0.3f, 0.3f); + auto reinit = [&](SimplePCLayer &l, size_t inSz, size_t outSz) + { + if (outSz == 0) return; + float *W = l.GetWeights(); + for (size_t i = 0; i < inSz * outSz; ++i) W[i] = initDist(rng); + }; + reinit(below, 512, 512); + reinit(layer1, 512, 512); + reinit(above, 512, 10); - std::cout << "Profiling (coarse, call-site timing): " << N_BATCHES << " batches, batch_size=" - << BATCH_SIZE << ", inference_steps=" << INFERENCE_STEPS << "\n\n"; + std::vector belowInput((size_t)BATCH * 512); + std::mt19937 dataRng(123); + std::uniform_real_distribution dataDist(-1.0f, 1.0f); + for (auto &v : belowInput) v = dataDist(dataRng); - double totalSettle = 0.0; - double totalCalcState = 0.0; - double totalUpdateState = 0.0; - double totalUpdateWeights = 0.0; - double totalResetClamp = 0.0; + below.ClampState(belowInput); // "below" acts as a clamped input stand-in - auto wallStart = std::chrono::steady_clock::now(); + double t_calc = 0, t_update = 0; + auto totalStart = std::chrono::steady_clock::now(); - for (int b = 0; b < N_BATCHES; ++b) + for (int rep = 0; rep < N_REPS; ++rep) { - auto t0 = std::chrono::steady_clock::now(); - net.ResetState(); - net.Clamp(X); - net.GetTerminalLayer()->ClampState(Y); - auto t1 = std::chrono::steady_clock::now(); - totalResetClamp += std::chrono::duration(t1 - t0).count(); + below.CalculateState(); // seed below's own mu once, matching project_forward's role - for (int s = 0; s < INFERENCE_STEPS; ++s) + for (int step = 0; step < STEPS; ++step) { - auto cs0 = std::chrono::steady_clock::now(); - net.CalculateState(); - auto cs1 = std::chrono::steady_clock::now(); - totalCalcState += std::chrono::duration(cs1 - cs0).count(); - - net.UpdateState(); - auto us1 = std::chrono::steady_clock::now(); - totalUpdateState += std::chrono::duration(us1 - cs1).count(); + auto t0 = std::chrono::steady_clock::now(); + layer1.CalculateState(); + auto t1 = std::chrono::steady_clock::now(); + t_calc += std::chrono::duration(t1 - t0).count(); + + t0 = t1; + layer1.UpdateState(); + t1 = std::chrono::steady_clock::now(); + t_update += std::chrono::duration(t1 - t0).count(); } - - auto t2 = std::chrono::steady_clock::now(); - net.UpdateWeights(); - auto t3 = std::chrono::steady_clock::now(); - totalUpdateWeights += std::chrono::duration(t3 - t2).count(); - - net.GetTerminalLayer()->UnclampState(); - - std::cout << " batch " << (b + 1) << "/" << N_BATCHES << " done\n"; } - auto wallEnd = std::chrono::steady_clock::now(); - double wallSeconds = std::chrono::duration(wallEnd - wallStart).count(); - totalSettle = totalCalcState + totalUpdateState; - - std::cout << "\n=== Coarse phase breakdown (" << N_BATCHES << " batches, " - << INFERENCE_STEPS << " steps each) ===\n"; - std::cout << " Wall clock total: " << wallSeconds << " s\n"; - std::cout << " ResetState+Clamp: " << totalResetClamp << " s (" - << (100.0 * totalResetClamp / wallSeconds) << "%)\n"; - std::cout << " CalculateState (sum): " << totalCalcState << " s (" - << (100.0 * totalCalcState / wallSeconds) << "%)\n"; - std::cout << " UpdateState (sum): " << totalUpdateState << " s (" - << (100.0 * totalUpdateState / wallSeconds) << "%)\n"; - std::cout << " UpdateWeights (sum): " << totalUpdateWeights << " s (" - << (100.0 * totalUpdateWeights / wallSeconds) << "%)\n"; - std::cout << " --------\n"; - std::cout << " Settle loop total: " << totalSettle << " s (" - << (100.0 * totalSettle / wallSeconds) << "%)\n"; - std::cout << " Per-step avg (Calc+Upd): " << (totalSettle / (N_BATCHES * INFERENCE_STEPS)) * 1000.0 << " ms\n"; - std::cout << " Per-step CalculateState: " << (totalCalcState / (N_BATCHES * INFERENCE_STEPS)) * 1000.0 << " ms\n"; - std::cout << " Per-step UpdateState: " << (totalUpdateState / (N_BATCHES * INFERENCE_STEPS)) * 1000.0 << " ms\n"; + auto totalEnd = std::chrono::steady_clock::now(); + double totalTime = std::chrono::duration(totalEnd - totalStart).count(); + + std::cout << "=== Isolated 512->512 layer, " << N_REPS << " reps x " << STEPS << " steps ===\n\n"; + std::cout << "Total: " << totalTime << "s\n"; + std::cout << "calculate_state: " << 1000*t_calc/(N_REPS*STEPS) << " ms/call (avg per single call)\n"; + std::cout << "update_state: " << 1000*t_update/(N_REPS*STEPS) << " ms/call (avg per single call)\n\n"; + std::cout << "Compare against the real network's measured Layer 1 costs:\n"; + std::cout << " calc: 30.82ms/batch / 20 steps = 1.541 ms/call\n"; + std::cout << " update: 27.06ms/batch / 20 steps = 1.353 ms/call\n"; + std::cout << "\nIf THIS isolated test also shows ~1.5ms/call, the slowness is\n"; + std::cout << "intrinsic to a 512x512 SimplePCLayer regardless of context. If\n"; + std::cout << "it's much faster here, something about the FULL 4-layer network's\n"; + std::cout << "context (thread contention between layers, memory layout, etc)\n"; + std::cout << "is the real cause -- not the layer itself in isolation.\n"; return 0; } diff --git a/tests/tSimple.cpp b/tests/tSimple.cpp index 32f11f2..ed69753 100644 --- a/tests/tSimple.cpp +++ b/tests/tSimple.cpp @@ -1,103 +1,148 @@ -#include -#include -#include +// Granular profiling of the EXACT configuration currently under test: +// 784->512->512->10, ADAMW, mu_cache_threshold=0, forward-projection +// init, batch=256, steps=20. Isolates where the remaining ~12ms/batch +// gap vs ngc-learn (82ms measured vs their 70.31ms) actually lives -- +// project_forward specifically, settling loop (per-layer), weight +// updates, or fixed per-region overhead (thread spin-up, virtual +// dispatch) that doesn't show up as "real compute" anywhere. +#include #include -#include -#include -#include -#include -#include +#include +#include +#include -float WeightNorm(Deep::DiscriminativePCLayer *layer, size_t count) -{ - const float *w = layer->GetWeights(); - float sumSq = 0.0f; - for (size_t i = 0; i < count; ++i) - sumSq += w[i] * w[i]; - return std::sqrt(sumSq); -} +using namespace Deep; -int main(void) +int main() { - Deep::DiscriminativePCNetwork net(4); - Timer timer; - - net.AddLayer(2, 8, 0.05f, 0.3f, 0.000f, 0.0001f, Deep::tanh, Deep::dTanh); - net.AddLayer(8, 1, 0.05f, 0.3f, 0.000f, 0.0001f, Deep::tanh, Deep::dTanh); - net.AddLayer(1, 0, 0.05f, 0.3f, 0.000f, 0.0001f, Deep::linear, Deep::dLinear); - - std::mt19937 rng(42); + const int BATCH = 256; + const float LR = 0.001f; + const int STEPS = 20; + const int N_BATCHES = 40; // matches the earlier timing test's sample size + + SimplePCNetwork net(BATCH); + net.AddLayer(784, 512, LR, 0.08f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); + net.AddLayer(512, 512, LR, 0.08f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); + net.AddLayer(512, 10, LR, 0.08f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); + net.AddLayer(10, 0, LR, 0.08f, 0.0001f, ActivationType::LINEAR, ActivationType::dLINEAR); + net.SetOptimizer(OptimizerType::ADAMW); + net.Compile(); + + std::mt19937 rng(7); net.RandomizeWeights(rng); - auto &layers = net.GetLayers(); - auto *layer0 = static_cast(layers[0]); - auto *layer1 = static_cast(layers[1]); - float initialW0Norm = WeightNorm(layer0, 2 * 8); - - std::vector flatX = { - -1.0f, -1.0f, - -1.0f, +1.0f, - +1.0f, -1.0f, - +1.0f, +1.0f}; - std::vector flatY = { - -1.0f, - +1.0f, - +1.0f, - -1.0f}; - - int epochs = 5000; - int inferenceSteps = 50; - int reportEvery = 100; - - std::cout << "Starting Discriminative PC XOR Test (Clean API)...\n"; - - double start = timer.elapsed(); - for (int epoch = 0; epoch < epochs; epoch++) + // Match the real +-0.3 uniform init directly in C++ this time + std::uniform_real_distribution initDist(-0.3f, 0.3f); + for (auto *layer : net.GetLayers()) { - // One clean method to handle the entire training step - float energy = net.TrainStep(flatX, flatY, inferenceSteps); + if (layer->GetOutputSize() == 0) continue; // terminal has no weights + size_t wsz = layer->GetInputSize() * layer->GetOutputSize(); + float *W = layer->GetWeights(); + for (size_t i = 0; i < wsz; ++i) + W[i] = initDist(rng); + } - if (epoch % reportEvery == 0) - { - float w0norm = WeightNorm(layer0, 2 * 8); - float w1norm = WeightNorm(layer1, 8 * 1); + net.SetMuCacheThreshold(0.0f); + + std::mt19937 dataRng(123); + std::uniform_real_distribution dataDist(0.0f, 1.0f); + std::vector X((size_t)BATCH * 784); + std::vector Y((size_t)BATCH * 10, 0.001f); + for (auto &v : X) v = dataDist(dataRng); + for (int b = 0; b < BATCH; ++b) + Y[b * 10 + (b % 10)] = 0.999f; // fake one-hot-ish labels, real values don't matter for timing - std::cout << "Epoch " << std::setw(5) << epoch - << " | Energy: " << std::fixed << std::setprecision(4) << energy / 4.0f - << " | W0 norm: " << w0norm - << " | W1 norm: " << w1norm - << " | Elapsed: " << timer.elapsed() - start << "s\n"; - start = timer.elapsed(); + // Granular phase timers + double t_reset = 0, t_clamp_input = 0, t_project = 0, t_clamp_target = 0; + double t_calc[4] = {0, 0, 0, 0}; + double t_update[4] = {0, 0, 0, 0}; + double t_weights = 0, t_unclamp = 0; - if (w0norm > 10.0f * initialW0Norm && w0norm > 20.0f) + auto &layers = net.GetLayers(); + + auto totalStart = std::chrono::steady_clock::now(); + + for (int rep = 0; rep < N_BATCHES; ++rep) + { + auto t0 = std::chrono::steady_clock::now(); + net.ResetState(); + auto t1 = std::chrono::steady_clock::now(); + t_reset += std::chrono::duration(t1 - t0).count(); + + t0 = t1; + net.Clamp(X); + t1 = std::chrono::steady_clock::now(); + t_clamp_input += std::chrono::duration(t1 - t0).count(); + + t0 = t1; + net.ProjectForward(); + t1 = std::chrono::steady_clock::now(); + t_project += std::chrono::duration(t1 - t0).count(); + + t0 = t1; + layers.back()->ClampState(Y); + t1 = std::chrono::steady_clock::now(); + t_clamp_target += std::chrono::duration(t1 - t0).count(); + + for (int step = 0; step < STEPS; ++step) + { + for (size_t i = 0; i < layers.size(); ++i) { - std::cout << " *** WARNING: W0 norm jumped far beyond init scale ***\n"; + t0 = std::chrono::steady_clock::now(); + layers[i]->CalculateState(); + t1 = std::chrono::steady_clock::now(); + t_calc[i] += std::chrono::duration(t1 - t0).count(); + } + for (size_t i = 0; i < layers.size(); ++i) + { + t0 = std::chrono::steady_clock::now(); + layers[i]->UpdateState(); + t1 = std::chrono::steady_clock::now(); + t_update[i] += std::chrono::duration(t1 - t0).count(); } } - } - - std::cout << "\n=== Predictions ===\n"; - // Effortless prediction API - auto preds = net.Predict(flatX, inferenceSteps); + t0 = std::chrono::steady_clock::now(); + net.UpdateWeights(); + t1 = std::chrono::steady_clock::now(); + t_weights += std::chrono::duration(t1 - t0).count(); - int correct = 0; - for (int i = 0; i < 4; i++) - { - float pred = preds[i]; - float target = flatY[i]; - bool signCorrect = (pred > 0 && target > 0) || (pred < 0 && target < 0); - if (signCorrect) - correct++; - - std::cout << "Input: [" << std::setw(2) << flatX[i * 2] << ", " << std::setw(2) << flatX[i * 2 + 1] << "]" - << " | Target: " << std::showpos << target - << " | Pred: " << std::noshowpos << std::setprecision(4) << pred - << (signCorrect ? " OK" : " WRONG") << "\n"; + t0 = t1; + layers.back()->UnclampState(); + t1 = std::chrono::steady_clock::now(); + t_unclamp += std::chrono::duration(t1 - t0).count(); } - std::cout << "\nAccuracy: " << correct << "/4\n"; - std::cout << (correct == 4 ? "XOR LEARNED SUCCESSFULLY!" : "XOR NOT FULLY LEARNED.") << "\n"; + auto totalEnd = std::chrono::steady_clock::now(); + double totalTime = std::chrono::duration(totalEnd - totalStart).count(); + double msPerBatch = 1000.0 * totalTime / N_BATCHES; + + double calcSum = 0, updateSum = 0; + for (int i = 0; i < 4; ++i) { calcSum += t_calc[i]; updateSum += t_update[i]; } + + std::cout << "=== Granular breakdown, " << N_BATCHES << " batches ===\n\n"; + std::cout << "Total: " << totalTime << "s (" << msPerBatch << " ms/batch)\n"; + std::cout << "ngc-learn reference: 70.31 ms/batch\n\n"; + + auto pct = [&](double t) { return 100.0 * t / totalTime; }; + + std::cout << "reset_state: " << 1000*t_reset/N_BATCHES << " ms/batch (" << pct(t_reset) << "%)\n"; + std::cout << "clamp_input: " << 1000*t_clamp_input/N_BATCHES << " ms/batch (" << pct(t_clamp_input) << "%)\n"; + std::cout << "project_forward: " << 1000*t_project/N_BATCHES << " ms/batch (" << pct(t_project) << "%)\n"; + std::cout << "clamp_target: " << 1000*t_clamp_target/N_BATCHES << " ms/batch (" << pct(t_clamp_target) << "%)\n"; + std::cout << "settling (calc): " << 1000*calcSum/N_BATCHES << " ms/batch (" << pct(calcSum) << "%)\n"; + for (int i = 0; i < 4; ++i) + std::cout << " layer " << i << " calc: " << 1000*t_calc[i]/N_BATCHES << " ms/batch\n"; + std::cout << "settling (update):" << 1000*updateSum/N_BATCHES << " ms/batch (" << pct(updateSum) << "%)\n"; + for (int i = 0; i < 4; ++i) + std::cout << " layer " << i << " update: " << 1000*t_update[i]/N_BATCHES << " ms/batch\n"; + std::cout << "update_weights: " << 1000*t_weights/N_BATCHES << " ms/batch (" << pct(t_weights) << "%)\n"; + std::cout << "unclamp: " << 1000*t_unclamp/N_BATCHES << " ms/batch (" << pct(t_unclamp) << "%)\n"; + + double accountedFor = t_reset + t_clamp_input + t_project + t_clamp_target + calcSum + updateSum + t_weights + t_unclamp; + double unaccounted = totalTime - accountedFor; + std::cout << "\nUnaccounted (measurement overhead, fixed costs not captured above): " + << 1000*unaccounted/N_BATCHES << " ms/batch (" << pct(unaccounted) << "%)\n"; return 0; -} \ No newline at end of file +} diff --git a/tests/tThreadSweep.cpp b/tests/tThreadSweep.cpp deleted file mode 100644 index 83fbccd..0000000 --- a/tests/tThreadSweep.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -using namespace Deep; - -double RunConfig(int batchSize, int threads, int steps, int nBatches) -{ - omp_set_num_threads(threads); - openblas_set_num_threads(threads); - - std::mt19937 rng(42); - std::uniform_real_distribution dist(-1.0f, 1.0f); - - DiscriminativePCNetwork net(batchSize); - net.AddLayer(784, 512, 0.002f, 0.05f, 0.0f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); - net.AddLayer(512, 10, 0.002f, 0.05f, 0.0f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); - net.AddLayer(10, 0, 0.002f, 0.05f, 0.0f, 0.0001f, ActivationType::LINEAR, ActivationType::dLINEAR); - net.Compile(); - - // Force thread count again AFTER Compile() -- DynamicThread() runs - // inside AddLayer()'s layer constructors and would otherwise override - // this with its own (currently hardcoded) choice. - omp_set_num_threads(threads); - openblas_set_num_threads(threads); - - net.RandomizeWeights(rng); - - std::vector X((size_t)batchSize * 784); - std::vector Y((size_t)batchSize * 10); - for (auto &v : X) - v = dist(rng); - for (auto &v : Y) - v = dist(rng); - - auto start = std::chrono::steady_clock::now(); - for (int b = 0; b < nBatches; ++b) - net.TrainStep(X, Y, steps); - auto end = std::chrono::steady_clock::now(); - - double totalSeconds = std::chrono::duration(end - start).count(); - return (totalSeconds / (nBatches * steps)) * 1000.0; // ms/step -} - -int main() -{ - const int STEPS = 60; // matches real training's confirmed value, not the profiler's 150 - const int N_BATCHES = 3; // short -- just enough for a stable reading per cell - - std::vector batchSizes = {256, 512, 1024}; - std::vector threadCounts = {4, 8, 12, 16, 24, 32, 48}; - - std::cout << "Sweeping batch_size x thread_count (" << STEPS << " steps, " - << N_BATCHES << " batches per cell)...\n\n"; - - std::cout << "batch\\threads"; - for (int t : threadCounts) - std::cout << "\t" << t; - std::cout << "\n"; - - for (int bsz : batchSizes) - { - std::cout << bsz; - for (int t : threadCounts) - { - double msPerStep = RunConfig(bsz, t, STEPS, N_BATCHES); - std::cout << "\t" << msPerStep; - std::cout.flush(); - } - std::cout << "\n"; - } - - std::cout << "\nLook for the LOWEST ms/step value in the whole grid -- that's the\n"; - std::cout << "real Pareto-optimal (batch_size, thread_count) combination for this\n"; - std::cout << "machine, not necessarily matching today's batch=256/threads=8 guess.\n"; - - return 0; -} \ No newline at end of file From d0460118e6e39b78bd3cb48827d3a3cb0947116a Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Sun, 23 Aug 2026 03:40:40 -0400 Subject: [PATCH 02/11] Add scikit-build-core packaging and cibuildwheel CI --- .gitignore | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index cb7e3f1..a6d472d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .vscode/* -build/* -bin/* +build/ +bin/ __pycache__/* MNIST/* pyrightconfig.json @@ -10,6 +10,7 @@ repomix-output.xml experiments/outputs/ experiments/data/ experiments/checkpoints/ + *.lib *.dll *.so @@ -17,3 +18,11 @@ experiments/checkpoints/ *.exe *.obj *.o +CMakeCache.txt +CMakeFiles/ +.ninja_log + +wheelhouse/ +dist/ +*.egg-info/ +_skbuild/ \ No newline at end of file From 0ead8f652651c0d84abec7c825d4630190f8511a Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Sun, 23 Aug 2026 03:57:19 -0400 Subject: [PATCH 03/11] Fix pybind11 move-only holder cast in ConvPCNetwork/SimpleConvPCNetwork .layers --- bindings/pybinding.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/pybinding.cpp b/bindings/pybinding.cpp index bde47cf..2c41922 100644 --- a/bindings/pybinding.cpp +++ b/bindings/pybinding.cpp @@ -400,7 +400,7 @@ void bind_networks(py::module_ &m) .def_property_readonly("layers", [](Deep::ConvPCNetwork &self) { py::list result; - for (auto &layer : self.GetLayers()) result.append(py::cast(layer, py::return_value_policy::reference)); + for (auto &layer : self.GetLayers()) result.append(py::cast(layer.get(), py::return_value_policy::reference)); return result; }) .def("__len__", [](const Deep::ConvPCNetwork &self) { return self.GetLayers().size(); }) @@ -455,7 +455,7 @@ void bind_networks(py::module_ &m) .def_property_readonly("layers", [](Deep::SimpleConvPCNetwork &self) { py::list result; - for (auto &layer : self.GetLayers()) result.append(py::cast(layer, py::return_value_policy::reference)); + for (auto &layer : self.GetLayers()) result.append(py::cast(layer.get(), py::return_value_policy::reference)); return result; }) .def("__len__", [](const Deep::SimpleConvPCNetwork &self) { return self.GetLayers().size(); }) @@ -527,4 +527,4 @@ PYBIND11_MODULE(pydeepity, m) bind_layers(m); bind_networks(m); bind_utilities(m); -} +} \ No newline at end of file From 1a2dc9547c3bfcc95cc0892eba4fbb4d48e4fd3e Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Sun, 23 Aug 2026 04:04:22 -0400 Subject: [PATCH 04/11] Fix sdist build.py/build package name collision; swap broken fit_iavg example --- .github/workflows/ci.yml | 24 +++- examples/train_mnist_deep.py | 249 ++++++++++++++++++++++++----------- temp.py | 2 +- 3 files changed, 198 insertions(+), 77 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 264b81e..fce4f9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,13 @@ on: branches: [main] workflow_dispatch: {} +# Explicit least-privilege GITHUB_TOKEN scope. None of the jobs below write +# to the repo, comment on PRs, or publish anything - they only check out +# code, build/test it, and upload log artifacts - so read-only access to +# contents is all that's needed. Without this block, jobs run with the +# default token scope, which can be broader than necessary depending on +# repo/org settings (this is what CodeQL's "workflow does not contain +# permissions" alert flags). permissions: contents: read @@ -42,6 +49,9 @@ jobs: python-version: "3.x" - name: Install system dependencies + # NOTE: per CMakeLists.txt, SLEEF and Google Benchmark are fetched + # from source via FetchContent at configure time - they are NOT + # system packages, so they are deliberately absent here. run: | sudo apt-get update sudo apt-get install -y \ @@ -187,6 +197,12 @@ jobs: build-wheels: name: wheels (${{ matrix.os }}) + # Deliberately decoupled from build-and-test above: those jobs exercise + # dev-style builds (GCC/Clang directly, DEEPITY_BUILD_TESTS=ON) to + # validate the C++ core itself. This job validates the actual artifact + # that gets published to users - the --distributed-equivalent config in + # pyproject.toml's [tool.scikit-build.cmake.define] (portable arch + # baseline, CUDA off, tests off) - so it only runs once those pass. needs: [build-and-test, build-and-test-windows] strategy: fail-fast: false @@ -258,7 +274,13 @@ jobs: run: pip install build - name: Build sdist - run: python -m build --sdist + # NOT `python -m build` - `-m` makes Python search sys.path with the + # current working directory prepended, and this repo has a + # `build.py` at the root, which shadows the installed `build` + # package entirely (the error you'd see is literally build.py's own + # --help text). `pyproject-build` is the console-script entry point + # the `build` package ships specifically to avoid this collision. + run: pyproject-build --sdist - name: Upload sdist uses: actions/upload-artifact@v4 diff --git a/examples/train_mnist_deep.py b/examples/train_mnist_deep.py index e031307..4e4be64 100644 --- a/examples/train_mnist_deep.py +++ b/examples/train_mnist_deep.py @@ -4,95 +4,194 @@ from pydeepity import SimplePCN from time import perf_counter -from rich.console import Console -from rich.progress import track +# FINAL VALIDATION -- the complete stack found today: +# - Forward-projection initialization (seeds hidden layers from a real +# forward pass through current weights, not zero-init) +# - +-0.3 uniform weight init (matching ngc-learn's actual convention) +# - AdamW, lr=0.001 (matching ngc-learn's hard-coded Adam + eta=0.001 -- +# the init range and optimizer needed to be changed TOGETHER, not +# independently; tested and confirmed: 73.31% alone with SGD vs +# 93.96% paired with AdamW) +# - mu_cache_threshold=0.05 -- confirmed +30.6% faster than ngc-learn's +# real, measured per-batch time in an isolated speed test. THIS RUN +# checks whether that speed holds without costing the 93.96% accuracy +# already confirmed at threshold=disabled. +# - train_step_with_projection() -- single C++ call per batch, not a +# manual Python loop (confirmed real, if smaller, speedup from +# eliminating ~40 Python/pybind boundary crossings per batch) +# +# [0,1] normalization, labels clipped to [0.001, 0.999], tanh activation, +# 784->512->512->10, T=20 -- all matching ngc-learn's actual, real source +# as closely as possible. -console = Console() def load_full_mnist(): - console.print("[bold cyan]Fetching full MNIST dataset...[/bold cyan]") + print("Fetching full MNIST dataset (70,000 images)...") X, y = fetch_openml('mnist_784', version=1, return_X_y=True, as_frame=False, parser='auto') - X = (X.astype(np.float32) / 127.5) - 1.0 - y = y.astype(int) + X = X.astype(np.float32) / 255.0 # [0,1], matching ngc-learn + y = y.astype(int) # type: ignore - Y_bipolar = np.full((y.shape[0], 10), -0.9, dtype=np.float32) - Y_bipolar[np.arange(y.shape[0]), y] = 0.9 + # Clipped one-hot, matching ngc-learn's `jnp.clip(lab, eps, 1-eps)` + eps = 0.001 + Y = np.full((y.shape[0], 10), eps, dtype=np.float32) + Y[np.arange(y.shape[0]), y] = 1.0 - eps X_train, X_test, Y_train, Y_test, y_train_labels, y_test_labels = train_test_split( - X, Y_bipolar, y, train_size=60000, test_size=10000, stratify=y, random_state=42 + X, Y, y, train_size=60000, test_size=10000, stratify=y, random_state=42 ) - return X_train, Y_train, X_test, y_test_labels, y_train_labels - - -def train_and_evaluate(X_train, Y_train, y_train_labels, X_test, y_test_labels, - train_steps, test_steps, lr, epochs, batch_size): - - net = SimplePCN(batch_size=batch_size) - - # 3 Hidden Layers (784 -> 512 -> 256 -> 128 -> 10) with cooled-down ir=0.02 - net.add_layer(784, 512, lr=lr, ir=0.02, act="tanh", lmbda=0.0001) - net.add_layer(512, 256, lr=lr, ir=0.02, act="tanh", lmbda=0.0001) - net.add_layer(256, 128, lr=lr, ir=0.02, act="tanh", lmbda=0.0001) - net.add_layer(128, 10, lr=lr, ir=0.02, act="tanh", lmbda=0.0001) - net.add_layer( 10, 0, lr=lr, ir=0.02, act="linear", lmbda=0.0001) - - O_: str = "ADAMW" - net.set_optimizer(O_) - console.print(f"[bold blue]Set optimizer to {O_}[/bold blue]") - + return X_train, Y_train, X_test, y_test_labels + + +def main() -> None: + X_train, Y_train, X_test, y_test_labels = load_full_mnist() + + BATCH_SIZE = 256 + STEPS = 20 # matches ngc-learn's T=20 + EPOCHS = 15 + LR = 0.001 # Adam-appropriate scale, NOT the SGD-tuned 0.06 -- + # matches ngc-learn's own stated eta=0.001 directly, + # and today's earlier finding that Adam needs a + # much smaller rate than SGD on this codebase + DECAY_RATE = 0.98 + + print(f"\nBuilding network (784->512->512->10)...") + net = SimplePCN(batch_size=BATCH_SIZE) + net.add_layer(784, 512, lr=LR, ir=0.08, act="tanh", lmbda=0.0001) + net.add_layer(512, 512, lr=LR, ir=0.08, act="tanh", lmbda=0.0001) + net.add_layer(512, 10, lr=LR, ir=0.08, act="tanh", lmbda=0.0001) + # Explicit TERMINAL layer -- outChannels/nextSize=0. Without this, + # net[-1] is the 512->10 layer itself, whose OWN beliefs are its + # 512-dim INPUT, not the 10-dim prediction it produces -- exactly + # the bug that just crashed this script (and was already silently + # corrupting every batch before that: clamp_state() truncates rather + # than erroring on a size mismatch). + net.add_layer(10, 0, lr=LR, ir=0.08, act="linear", lmbda=0.0001) + net.set_optimizer("ADAMW") # THE missing paired piece -- ngc-learn hard-codes + # Adam for every synapse. The larger +-0.3 init + # tested ALONE (with SGD) made things WORSE + # (73.31% vs 86.41%) -- likely because SGD's + # step size scales with raw gradient magnitude, + # unlike Adam's per-parameter normalization, + # which can handle a larger weight scale + # gracefully. Init range and optimizer were + # tuned TOGETHER in the original; testing one + # without the other may have been the mistake. net.compile() net.randomize_weights() - - net.summary() - - # Drop the base learning rate significantly for deep ADAMW - adjusted_lr = lr / 1000.0 - + net.set_mu_cache_threshold(0.0) # REVERTED from 0.05 -- that showed genuine + # instability under real, sustained AdamW + # training (energy trending UP, a 62-point + # accuracy crash at epoch 8), not just noise. + # Likely cause: small caching-approximation + # bias accumulating in Adam's momentum/ + # variance buffers over many updates -- + # something the earlier single-trajectory, + # no-weight-update speed test structurally + # could not have caught. threshold=0 is + # exact (proven bit-identical to no caching) + # and confirmed safe with this exact stack. + + # Override weight init to match ngc-learn's actual convention: fixed + # uniform range +-0.3, INDEPENDENT of layer size -- not our own + # size-scaled Gaussian (std ~0.039 for the 784->512 layer, ~8x + # smaller). This directly affects how informative the very first + # forward-projection pass is -- larger initial weights produce more + # differentiated activations from a single forward pass. Biases + # already default to 0 in Deepity (never touched by + # RandomizeWeights()), matching ngc-learn's own bias_init=constant(0). + rng_init = np.random.default_rng(7) + for layer in net.layers[:-1]: # skip the terminal -- it has no weights (nextSize=0) + w_shape = layer.weights.shape + layer.weights[:] = rng_init.uniform(-0.3, 0.3, w_shape).astype(np.float32) + + print(f"\nTraining with FORWARD-PROJECTION init: {EPOCHS} epochs, {STEPS} steps, " + f"lr={LR}, decay_rate={DECAY_RATE}...\n") + print("Reference (ngc-learn, real run): 26.91, 42.96, 60.12, 75.20, 84.68, 89.52,") + print(" 91.90, 93.45, 94.30, 94.80, 95.13, 95.38, 95.63, 95.74, 95.95 -- test 95.09%\n") + + rng = np.random.default_rng(42) + n_batches = len(X_train) // BATCH_SIZE start_time = perf_counter() - - # Train with I_avg caching and decay_rate=0.95 to prevent late-stage explosions - net.fit_iavg( - X=X_train, - Y=Y_train, - labels=y_train_labels, - epochs=epochs, - steps=train_steps, - per_class=batch_size // 10, - num_classes=10, - hidden_layer_index=1, - hidden_size=512, - initial_lr=adjusted_lr, - decay_rate=0.95, - reset_cache_per_epoch=True - ) + epoch_accs = [] + + for epoch in range(EPOCHS): + current_lr = LR * (DECAY_RATE ** epoch) + net.set_learning_rate(current_lr) + + indices = rng.permutation(len(X_train)) + X_shuf, Y_shuf = X_train[indices], Y_train[indices] + + correct = 0 + total = 0 + epoch_energy = 0.0 + + for b in range(n_batches): + X_batch = X_shuf[b * BATCH_SIZE:(b + 1) * BATCH_SIZE] + Y_batch = Y_shuf[b * BATCH_SIZE:(b + 1) * BATCH_SIZE] + + energy = net.train_step_with_projection(X_batch, Y_batch, STEPS) + epoch_energy += energy + + # REAL accuracy check -- genuine UNCLAMPED settle on a cheap + # subset, not reading beliefs right after unclamp_state() (which + # only flips a flag, never resets z -- that was reading back the + # clamped TARGET itself, trivially "matching" 100% every time). + N_ACC_BATCHES = 10 + for b in range(min(N_ACC_BATCHES, n_batches)): + X_batch = X_shuf[b * BATCH_SIZE:(b + 1) * BATCH_SIZE] + Y_batch = Y_shuf[b * BATCH_SIZE:(b + 1) * BATCH_SIZE] + + net.reset_state() + net.clamp_input(X_batch) + net.project_forward() + for _ in range(STEPS): # same step count as training, genuinely unclamped + net.calculate_state() + net.update_state() + + terminal_beliefs = np.array(net[-1].beliefs).reshape(BATCH_SIZE, 10) + pred = np.argmax(terminal_beliefs, axis=1) + true = np.argmax(Y_batch, axis=1) + correct += np.sum(pred == true) + total += BATCH_SIZE + + epoch_acc = 100.0 * correct / total + epoch_accs.append(epoch_acc) + avg_energy = epoch_energy / n_batches + elapsed = perf_counter() - start_time + print(f"Epoch {epoch+1}/{EPOCHS} | Time: {elapsed:.1f}s | Acc: {epoch_acc:.2f}% | Avg energy: {avg_energy:.4f}") train_time = perf_counter() - start_time - console.print(f"[bold green]Training complete in {train_time:.1f}s.[/bold green]\n") - - console.print(f"[bold yellow]Evaluating on held-out test set with {test_steps} inference steps...[/bold yellow]") - correct, total = 0, 0 - - for i in track(range(0, len(X_test), batch_size), description="[cyan]Evaluating..."): - X_batch = X_test[i:i + batch_size] - y_labels_batch = y_test_labels[i:i + batch_size] - if len(X_batch) != batch_size: continue - - # Use the longer zero-init test steps to let the network settle fully - pred_classes = np.argmax(net.predict(X_batch, steps=test_steps), axis=1) + print(f"\nTraining complete in {train_time:.1f}s.") + + print("\nRunning final test evaluation (with forward-projection init)...") + correct = 0 + total = 0 + for i in range(0, len(X_test), BATCH_SIZE): + X_batch = X_test[i:i + BATCH_SIZE] + y_labels_batch = y_test_labels[i:i + BATCH_SIZE] + if len(X_batch) != BATCH_SIZE: + continue + + net.reset_state() + net.clamp_input(X_batch) + net.project_forward() + for _ in range(300): # generous settle for final eval readout + net.calculate_state() + net.update_state() + + terminal_beliefs = np.array(net[-1].beliefs).reshape(BATCH_SIZE, 10) + pred_classes = np.argmax(terminal_beliefs, axis=1) correct += np.sum(pred_classes == y_labels_batch) - total += batch_size + total += BATCH_SIZE + + test_acc = 100.0 * correct / total + print(f"\n=== Result ===") + print(f"Deepity + forward-projection test accuracy: {test_acc:.2f}% (ngc-learn: 95.09%)") + print(f"Train time: {train_time:.1f}s") + print(f"\nDeepity per-epoch: {[round(a,2) for a in epoch_accs]}") + print(f"ngc-learn per-epoch: [26.91, 42.96, 60.12, 75.20, 84.68, 89.52, 91.90,") + print(f" 93.45, 94.30, 94.80, 95.13, 95.38, 95.63, 95.74, 95.95]") - console.print(f"\n[bold magenta]Test Accuracy: {correct}/{total} ({(correct/total)*100:.2f}%)[/bold magenta]") if __name__ == "__main__": - X_train, Y_train, X_test, y_test_labels, y_train_labels = load_full_mnist() - - # Decouple the steps: 6 for lightning-fast training, 60 for accurate prediction - train_and_evaluate( - X_train, Y_train, y_train_labels, X_test, y_test_labels, - train_steps=60, - test_steps=60, - lr=0.06, - epochs=50, - batch_size=250 - ) + main() \ No newline at end of file diff --git a/temp.py b/temp.py index a5159d2..acbf087 100644 --- a/temp.py +++ b/temp.py @@ -29,7 +29,7 @@ def load_full_mnist(): print("Fetching full MNIST dataset (70,000 images)...") X, y = fetch_openml('mnist_784', version=1, return_X_y=True, as_frame=False, parser='auto') X = X.astype(np.float32) / 255.0 # [0,1], matching ngc-learn - y = y.astype(int) + y = y.astype(int) # type: ignore # Clipped one-hot, matching ngc-learn's `jnp.clip(lab, eps, 1-eps)` eps = 0.001 From 867d5a4789c098f92cb17a6e94c27314f01dad8e Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Sun, 23 Aug 2026 04:22:32 -0400 Subject: [PATCH 05/11] Excluding MacOS temporarily. --- .github/workflows/ci.yml | 34 +++++----------------------------- pyproject.toml | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fce4f9e..22fcedb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,13 +7,6 @@ on: branches: [main] workflow_dispatch: {} -# Explicit least-privilege GITHUB_TOKEN scope. None of the jobs below write -# to the repo, comment on PRs, or publish anything - they only check out -# code, build/test it, and upload log artifacts - so read-only access to -# contents is all that's needed. Without this block, jobs run with the -# default token scope, which can be broader than necessary depending on -# repo/org settings (this is what CodeQL's "workflow does not contain -# permissions" alert flags). permissions: contents: read @@ -24,9 +17,6 @@ jobs: strategy: fail-fast: false matrix: - # Both compilers are exercised in CI since this project's behavior - # (SIMD dispatch, precision) has measurably differed between them - - # see CONTRIBUTING.md and the README's GCC vs. Clang benchmarks. include: - compiler: gcc cc: gcc @@ -49,9 +39,6 @@ jobs: python-version: "3.x" - name: Install system dependencies - # NOTE: per CMakeLists.txt, SLEEF and Google Benchmark are fetched - # from source via FetchContent at configure time - they are NOT - # system packages, so they are deliberately absent here. run: | sudo apt-get update sudo apt-get install -y \ @@ -82,15 +69,6 @@ jobs: build-and-test-windows: name: build-and-test (windows, clang) runs-on: windows-latest - # NOTE: unlike the Linux job above, this one does NOT go through - # build.py. build.py's configure step is hardcoded to - # `-DCMAKE_BUILD_TYPE=... -DDEEPITY_BUILD_TESTS=ON` with no way to pass - # extra defines, but Windows+Clang needs several more (OpenMP_libomp_ - # LIBRARY, CMAKE_TOOLCHAIN_FILE, BLA_VENDOR). Those live in the - # "windows-clang" preset in CMakePresets.json, which this job invokes - # directly via `cmake --preset` - see the Configure step below. If - # build.py grows support for passthrough CMake args or preset selection, - # this job should switch to using it, the same as the Linux job. steps: - name: Check out repository uses: actions/checkout@v4 @@ -197,17 +175,15 @@ jobs: build-wheels: name: wheels (${{ matrix.os }}) - # Deliberately decoupled from build-and-test above: those jobs exercise - # dev-style builds (GCC/Clang directly, DEEPITY_BUILD_TESTS=ON) to - # validate the C++ core itself. This job validates the actual artifact - # that gets published to users - the --distributed-equivalent config in - # pyproject.toml's [tool.scikit-build.cmake.define] (portable arch - # baseline, CUDA off, tests off) - so it only runs once those pass. needs: [build-and-test, build-and-test-windows] strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + # macOS intentionally excluded for now: macos-latest runners are + # arm64, and CMakeLists.txt's SIMD flags (DEEPITY_ARCH_FLAGS's + # x86-64-v2, SLEEF_ENABLE_AVX2/AVX512F) are x86-only with no + # arch-conditional branching yet. + os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - name: Check out repository diff --git a/pyproject.toml b/pyproject.toml index d39d249..a670042 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,12 +15,9 @@ readme = "README.md" requires-python = ">=3.9" dependencies = [ "numpy", + "rich", ] -[project.optional-dependencies] -# 'rich' only powers build.py's interactive dashboard during development -dev = ["rich"] - # ══════════════════════════════════════════════════════════════════════════ # scikit-build-core: how `pip install .` / `pip wheel .` invokes CMake # ══════════════════════════════════════════════════════════════════════════ @@ -58,8 +55,6 @@ DEEPITY_MSVC_ARCH_FLAGS = "" # ══════════════════════════════════════════════════════════════════════════ [tool.cibuildwheel] build = "cp39-* cp310-* cp311-* cp312-* cp313-*" -# musllinux (Alpine) and 32-bit Windows are skipped: OpenBLAS/SLEEF aren't -# routinely tested there, and almost nobody needs a 32-bit ML build. skip = "*-musllinux* *-win32" build-verbosity = 1 @@ -73,11 +68,21 @@ manylinux-x86_64-image = "manylinux_2_28" before-all = "dnf install -y openblas-devel libgomp" [tool.cibuildwheel.macos] +# NOT currently exercised by CI (see ci.yml's build-wheels matrix comment): +# macos-latest runners are arm64, and CMakeLists.txt's SIMD flags are +# x86-only with no arch-conditional branching yet, so a wheel build here +# fails before it gets anywhere near this config actually mattering. Left +# in place as the starting point for whoever picks that fix up. before-all = "brew install openblas libomp" # find_package(BLAS) / OpenMP need an explicit nudge on macOS since Homebrew # installs outside the paths Apple's toolchain searches by default. environment = { CMAKE_PREFIX_PATH = "/usr/local/opt/openblas:/usr/local/opt/libomp:/opt/homebrew/opt/openblas:/opt/homebrew/opt/libomp" } -archs = ["x86_64", "arm64"] +# x86_64 only for now: DEEPITY_ARCH_FLAGS above is an x86 -march string, and +# CMakeLists.txt's SLEEF config force-enables AVX2/AVX512F unconditionally - +# neither means anything when cross-compiling for arm64. Building real +# arm64/Apple Silicon wheels needs CMakeLists.txt itself to branch SIMD +# flags on target architecture, not just this file. Tracked as follow-up. +archs = ["x86_64"] [tool.cibuildwheel.windows] # OpenBLAS comes from vcpkg here (matching the existing windows-clang CI From 633765f48cacd82f384a293a8282220f5da46785 Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Sun, 23 Aug 2026 13:47:22 -0400 Subject: [PATCH 06/11] Fixing Windows buildwheel. --- .github/workflows/ci.yml | 32 -------------------------------- CMakeLists.txt | 1 + pyproject.toml | 2 +- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22fcedb..ad20d03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,38 +188,6 @@ jobs: steps: - name: Check out repository uses: actions/checkout@v4 - - # --- Windows-only: OpenBLAS has no system package, so it comes from - # vcpkg here exactly as it does in build-and-test-windows above. This - # runs on the host (cibuildwheel on Windows isn't containerized), so - # the toolchain file set via GITHUB_ENV is visible to the actual - # CMake configure step cibuildwheel triggers. - - name: Cache vcpkg - if: runner.os == 'Windows' - id: vcpkg-cache - uses: actions/cache@v4 - with: - path: | - C:/vcpkg - !C:/vcpkg/buildtrees - !C:/vcpkg/packages - !C:/vcpkg/downloads - key: vcpkg-windows-openblas-wheels-v1 - - - name: Bootstrap vcpkg - if: runner.os == 'Windows' && steps.vcpkg-cache.outputs.cache-hit != 'true' - run: | - git clone https://github.com/microsoft/vcpkg C:/vcpkg - C:/vcpkg/bootstrap-vcpkg.bat - - - name: Install OpenBLAS via vcpkg - if: runner.os == 'Windows' - run: C:/vcpkg/vcpkg.exe install openblas --triplet x64-windows - - - name: Point CMake at the vcpkg toolchain file - if: runner.os == 'Windows' - run: echo "CMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" | Out-File -FilePath $env:GITHUB_ENV -Append - # --- Linux and macOS need no host-side setup: OpenBLAS/libgomp # install inside the manylinux container (Linux) or via Homebrew # (macOS) through the before-all hooks in pyproject.toml's diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d1750b..5fcd13d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,7 @@ if(WIN32) PATHS "${openblas_prebuilt_SOURCE_DIR}/bin" REQUIRED ) + message(STATUS "OPENBLAS_DLL resolved to: ${OPENBLAS_DLL}") add_library(openblas_dll SHARED IMPORTED) set_target_properties(openblas_dll PROPERTIES diff --git a/pyproject.toml b/pyproject.toml index a670042..5258ebf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,4 +90,4 @@ archs = ["x86_64"] # CMAKE_TOOLCHAIN_FILE are set by the "Install vcpkg dependencies" CI step # below, before cibuildwheel runs - see ci.yml. before-build = "pip install delvewheel" -repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" \ No newline at end of file +repair-wheel-command = "delvewheel repair -w {dest_dir} --add-path build\\{wheel_tag}\\_deps\\openblas_prebuilt-src\\bin {wheel}" From c0d65e4fa7204a9ba7d1c6a0ee9dceeab3ae948d Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Mon, 24 Aug 2026 21:44:53 -0400 Subject: [PATCH 07/11] See previous commit. --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fcd13d..fb28e44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,8 @@ if(MSVC) add_compile_definitions(__SSE2__ __AVX__ __AVX2__) elseif(DEEPITY_MSVC_ARCH_FLAGS MATCHES "AVX") add_compile_definitions(__SSE2__ __AVX__) + else() + add_compile_definitions(__SSE2__) endif() else() separate_arguments(ARCH_FLAGS_LIST UNIX_COMMAND ${DEEPITY_ARCH_FLAGS}) From f910a07cc8e4065e2505cd773c6d5cd57d541f51 Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Mon, 24 Aug 2026 22:45:18 -0400 Subject: [PATCH 08/11] Fixing OpenBLAS workflow issues. --- CMakeLists.txt | 3 +++ pyproject.toml | 6 +----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb28e44..aff2f2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,6 +253,9 @@ if(DEEPITY_BUILD_PYTHON_BINDINGS AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug") ) endif() +if(WIN32) + install(FILES ${OPENBLAS_DLL} DESTINATION pydeepity) +endif() # --- Tests ---------------------------------------------------------- if(DEEPITY_BUILD_TESTS) diff --git a/pyproject.toml b/pyproject.toml index 5258ebf..002ea4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,9 +85,5 @@ environment = { CMAKE_PREFIX_PATH = "/usr/local/opt/openblas:/usr/local/opt/libo archs = ["x86_64"] [tool.cibuildwheel.windows] -# OpenBLAS comes from vcpkg here (matching the existing windows-clang CI -# job) since there's no Windows system package for it. VCPKG_ROOT/ -# CMAKE_TOOLCHAIN_FILE are set by the "Install vcpkg dependencies" CI step -# below, before cibuildwheel runs - see ci.yml. before-build = "pip install delvewheel" -repair-wheel-command = "delvewheel repair -w {dest_dir} --add-path build\\{wheel_tag}\\_deps\\openblas_prebuilt-src\\bin {wheel}" +repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" From 54ed16afd6b66f87aa2d163dd9734bfa6e2be28c Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Tue, 25 Aug 2026 13:18:27 -0400 Subject: [PATCH 09/11] WIP on tLayer513 --- .ipynb_checkpoints/build-checkpoint.py | 14 + CMakeLists.txt | 13 + logs/build.log | 399 ++++++++++++++++--------- tests/t513.cpp | 90 ++++++ 4 files changed, 372 insertions(+), 144 deletions(-) create mode 100644 .ipynb_checkpoints/build-checkpoint.py create mode 100644 tests/t513.cpp diff --git a/.ipynb_checkpoints/build-checkpoint.py b/.ipynb_checkpoints/build-checkpoint.py new file mode 100644 index 0000000..40286c4 --- /dev/null +++ b/.ipynb_checkpoints/build-checkpoint.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +""" +Deepity build entrypoint. + +All real logic lives in the deepity_build package (config resolution, the +CMake command builder, and the rich/plain reporters). This file just wires +argv to that package so `python build.py ...` keeps working exactly as +before. +""" + +from deepity_build.cli import main + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index aff2f2e..a79ec99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,6 +281,19 @@ if(DEEPITY_BUILD_TESTS) endif() # --- Profiling ------------------------------------------------------ +if(DEEPITY_BUILD_TESTS) + add_executable(Layer1Isolate513 tests/t513.cpp) + target_link_libraries(Layer1Isolate513 PRIVATE Deepity) + set_target_properties(Layer1Isolate513 PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + if(WIN32) + add_custom_command(TARGET Layer1Isolate513 POST_BUILD + COMMAND ${CMAKE_COMMAND} -E $>,copy_if_different,true> + $ + $ + COMMAND_EXPAND_LISTS + ) + endif() +endif() if(DEEPITY_BUILD_TESTS) add_library(DeepityProfiled STATIC diff --git a/logs/build.log b/logs/build.log index b95e9d4..c7c7f7f 100644 --- a/logs/build.log +++ b/logs/build.log @@ -1,178 +1,289 @@ --- Deepity Build Log (Release, arch=fast) --- +=== CMake Configuration === +-- The C compiler identification is GNU 16.2.1 +-- The CXX compiler identification is GNU 16.2.1 +-- Detecting C compiler ABI info +-- Detecting C compiler ABI info - done +-- Check for working C compiler: /usr/bin/cc - skipped +-- Detecting C compile features +-- Detecting C compile features - done +-- Detecting CXX compiler ABI info +-- Detecting CXX compiler ABI info - done +-- Check for working CXX compiler: /usr/bin/c++ - skipped +-- Detecting CXX compile features +-- Detecting CXX compile features - done +-- CXX compiler: /usr/bin/c++ +-- Found OpenMP_C: -fopenmp (found version "5.2") +-- Found OpenMP_CXX: -fopenmp (found version "5.2") +-- Found OpenMP: TRUE (found version "5.2") +-- CUDA toolkit not found. Building CPU-only. +-- Found Python: /home/rose0/Projects/deepity/.venv/bin/python3.14 (found suitable version "3.14.7", minimum required is "3.8") found components: Interpreter Development.Module Development.Embed +-- Performing Test HAS_FLTO_AUTO +-- Performing Test HAS_FLTO_AUTO - Success +-- Found pybind11: /usr/include (found version "3.0.4") +-- Looking for sgemm_ +-- Looking for sgemm_ - found +-- Found BLAS: /usr/lib64/libopenblas.so +-- Configuring SLEEF 3.9.0 +-- Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY OPENSSL_INCLUDE_DIR) +-- Found PkgConfig: /usr/bin/pkg-config (found version "2.5.1") +-- Found OpenMP_C: -fopenmp (found version "5.2") +-- Found OpenMP_CXX: -fopenmp (found version "5.2") +-- Configuring build for SLEEF-v3.9.0 + Target system: Linux-7.1.9-200.fc44.x86_64 + Target processor: x86_64 + Host system: Linux-7.1.9-200.fc44.x86_64 + Host processor: x86_64 + Detected C compiler: GNU @ /usr/bin/cc + CMake: 4.3.0 + Make program: /usr/bin/ninja-build + CMake build type: Release +-- Using option `-Wall -Wno-unused-function -Wno-attributes -Wno-unused-result -Wno-psabi -ffp-contract=off -fno-math-errno -fno-trapping-math` to compile libsleef +-- Building shared libs : OFF +-- Building static test bins: OFF +-- MPFR : LIB_MPFR-NOTFOUND +-- GMP : LIBGMP-NOTFOUND +-- RT : /usr/lib64/librt.a +-- FFTW3 : LIBFFTW3-NOTFOUND +-- FFTW3F : LIBFFTW3F-NOTFOUND +-- OPENSSL : +-- SDE : SDE_COMMAND-NOTFOUND +-- COMPILER_SUPPORTS_OPENMP : 1 +-- A version of SLEEF compatible with libm and libmvec in GNU libc will be produced (sleefgnuabi.so) +-- Failed to find LLVM FileCheck +-- Found Git: /usr/bin/git (found version "2.55.0") +-- Google Benchmark version: v1.9.1, normalized to 1.9.1 +-- Compiling and running to test HAVE_STD_REGEX +-- Performing Test HAVE_STD_REGEX -- success +-- Compiling and running to test HAVE_GNU_POSIX_REGEX +-- Performing Test HAVE_GNU_POSIX_REGEX -- failed to compile +-- Compiling and running to test HAVE_POSIX_REGEX +-- Performing Test HAVE_POSIX_REGEX -- success +-- Compiling and running to test HAVE_STEADY_CLOCK +-- Performing Test HAVE_STEADY_CLOCK -- success +-- Performing Test CMAKE_HAVE_LIBC_PTHREAD +-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success +-- Found Threads: TRUE +-- Compiling and running to test HAVE_PTHREAD_AFFINITY +-- Performing Test HAVE_PTHREAD_AFFINITY -- success +-- Configuring done (301.0s) +-- Generating done (0.1s) +-- Build files have been written to: /home/rose0/Projects/deepity/build/Release + + === Compilation === -[1/131] Building C object _deps/sleef-build/src/common/CMakeFiles/common.dir/common.c.o -[2/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkalias.dir/mkalias.c.o -[3/131] Building C object _deps/sleef-build/src/common/CMakeFiles/addSuffix.dir/addSuffix.c.o -[4/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkrename_gnuabi.dir/mkrename_gnuabi.c.o -[5/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkmasked_gnuabi.dir/mkmasked_gnuabi.c.o -[6/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkrename.dir/mkrename.c.o -[7/131] Linking C executable _deps/sleef-build/bin/mkalias -[8/131] Linking C executable _deps/sleef-build/bin/addSuffix -[9/131] Generating alias_AVX512F_dp.h.tmp -[10/131] Generating alias_AVX512F_sp.h.tmp -[11/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkdisp.dir/mkdisp.c.o -[12/131] Linking C executable _deps/sleef-build/bin/mkrename_gnuabi -[13/131] Generating include/renameavx512f_gnuabi.h -Generating renameavx512f_gnuabi.h: mkrename_gnuabi avx512f e 8 16 __m512d __m512 __m256i __m512i __AVX512F__ -[14/131] Generating include/renameavx2_gnuabi.h +[1/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkrename_gnuabi.dir/mkrename_gnuabi.c.o +[2/170] Building C object _deps/sleef-build/src/common/CMakeFiles/common.dir/common.c.o +[3/170] Linking C executable _deps/sleef-build/bin/mkrename_gnuabi +[4/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkalias.dir/mkalias.c.o +[5/170] Generating include/renameavx2_gnuabi.h Generating renameavx2_gnuabi.h: mkrename_gnuabi avx2 d 4 8 __m256d __m256 __m128i __m256i __AVX2__ -[15/131] Generating include/renamesse2_gnuabi.h +[6/170] Generating include/renameavx512f_gnuabi.h +Generating renameavx512f_gnuabi.h: mkrename_gnuabi avx512f e 8 16 __m512d __m512 __m256i __m512i __AVX512F__ +[7/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkmasked_gnuabi.dir/mkmasked_gnuabi.c.o +[8/170] Generating include/renamesse2_gnuabi.h Generating renamesse2_gnuabi.h: mkrename_gnuabi sse2 b 2 4 _mm128d _mm128 _mm128i _mm128i __SSE2__ -[16/131] Generating include/renameavx_gnuabi.h +[9/170] Generating include/renameavx_gnuabi.h Generating renameavx_gnuabi.h: mkrename_gnuabi avx c 4 8 __m256d __m256 __m128i struct\ {\ __m128i\ x,\ y;\ } __AVX__ -[17/131] Building C object _deps/sleef-build/src/common/CMakeFiles/testerutil_obj.dir/testerutil.c.o -[18/131] Building C object _deps/sleef-build/src/common/CMakeFiles/qtesterutil_obj.dir/qtesterutil.c.o -[19/131] Generating include/alias_avx512f.h -[20/131] Linking C executable _deps/sleef-build/bin/mkmasked_gnuabi -[21/131] Generating include/masked_avx512f_dp_gnuabi.h -[22/131] Generating include/masked_avx512f_sp_gnuabi.h -[23/131] Linking C executable _deps/sleef-build/bin/mkrename -[24/131] Generating sleeflibm_AVX.h.tmp -[25/131] Generating sleeflibm_AVX2.h.tmp -[26/131] Generating sleeflibm_AVX2128.h.tmp -[27/131] Generating sleeflibm_AVX512F.h.tmp -[28/131] Generating sleeflibm_AVX512FNOFMA.h.tmp -[29/131] Generating sleeflibm_AVX512F_.h.tmp -[30/131] Generating sleeflibm_AVX_.h.tmp -[31/131] Generating sleeflibm_DSP_SCALAR.h.tmp -[32/131] Building CXX object _deps/sleef-build/src/common/CMakeFiles/psha_obj.dir/psha2_capi.cpp.o -[33/131] Linking C executable _deps/sleef-build/bin/mkdisp -[34/131] Generating sleeflibm_FMA4.h.tmp -[35/131] Generating sleeflibm_PURECFMA_SCALAR.h.tmp -[36/131] Generating sleeflibm_PUREC_SCALAR.h.tmp -[37/131] Generating sleeflibm_SSE2.h.tmp -[38/131] Generating sleeflibm_SSE4.h.tmp -[39/131] Generating sleeflibm_SSE_.h.tmp -[40/131] Generating include/renameavx512fnofma.h +[10/170] Linking C executable _deps/sleef-build/bin/mkalias +[11/170] Generating alias_AVX512F_dp.h.tmp +[12/170] Generating alias_AVX512F_sp.h.tmp +[13/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkdisp.dir/mkdisp.c.o +[14/170] Generating include/alias_avx512f.h +[15/170] Linking C executable _deps/sleef-build/bin/mkmasked_gnuabi +[16/170] Generating include/masked_avx512f_dp_gnuabi.h +[17/170] Generating include/masked_avx512f_sp_gnuabi.h +[18/170] Linking C executable _deps/sleef-build/bin/mkdisp +[19/170] Generating dispscalar.c.body +[20/170] Generating dispsse.c.tmp +[21/170] Generating dispavx.c.tmp +[22/170] Generating dispscalar.c +[23/170] Generating dispsse.c +[24/170] Generating dispavx.c +[25/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkrename.dir/mkrename.c.o +[26/170] Linking C executable _deps/sleef-build/bin/mkrename +[27/170] Generating sleeflibm_AVX.h.tmp +[28/170] Generating sleeflibm_AVX2.h.tmp +[29/170] Generating sleeflibm_AVX2128.h.tmp +[30/170] Generating sleeflibm_AVX512F.h.tmp +[31/170] Generating sleeflibm_AVX512FNOFMA.h.tmp +[32/170] Generating sleeflibm_AVX512F_.h.tmp +[33/170] Generating sleeflibm_AVX_.h.tmp +[34/170] Generating sleeflibm_DSP_SCALAR.h.tmp +[35/170] Generating sleeflibm_FMA4.h.tmp +[36/170] Generating sleeflibm_PURECFMA_SCALAR.h.tmp +[37/170] Generating sleeflibm_PUREC_SCALAR.h.tmp +[38/170] Generating sleeflibm_SSE2.h.tmp +[39/170] Generating sleeflibm_SSE4.h.tmp +[40/170] Generating sleeflibm_SSE_.h.tmp +[41/170] Generating include/renameavx512fnofma.h Generating renameavx512fnofma.h: mkrename cinz_ 8 16 avx512fnofma -[41/131] Generating include/renameavx512f.h +[42/170] Generating include/renameavx512f.h Generating renameavx512f.h: mkrename finz_ 8 16 avx512f -[42/131] Generating include/renameavx2.h +[43/170] Generating include/renameavx2.h Generating renameavx2.h: mkrename finz_ 4 8 avx2 -[43/131] Generating dispscalar.c.body -[44/131] Generating include/renameavx2128.h +[44/170] Generating include/renameavx2128.h Generating renameavx2128.h: mkrename finz_ 2 4 avx2128 -[45/131] Generating dispsse.c.tmp -[46/131] Generating dispavx.c.tmp -[47/131] Generating include/renamefma4.h +[45/170] Generating include/renamefma4.h Generating renamefma4.h: mkrename finz_ 4 8 fma4 -[48/131] Generating include/renameavx.h +[46/170] Generating include/renameavx.h Generating renameavx.h: mkrename cinz_ 4 8 avx -[49/131] Generating include/renamesse4.h +[47/170] Generating include/renamesse4.h Generating renamesse4.h: mkrename cinz_ 2 4 sse4 -[50/131] Generating include/renamesse2.h +[48/170] Generating include/renamesse2.h Generating renamesse2.h: mkrename cinz_ 2 4 sse2 -[51/131] Generating include/renamedspscalar.h -[52/131] Generating include/renamepurec_scalar.h +[49/170] Generating include/renamepurec_scalar.h Generating renamepurec_scalar.h: mkrename cinz_ 1 1 purec -[53/131] Generating include/renamepurecfma_scalar.h +[50/170] Generating include/renamepurecfma_scalar.h Generating renamepurecfma_scalar.h: mkrename finz_ 1 1 purecfma -[54/131] Generating include/renamecuda.h +[51/170] Generating include/renamecuda.h Generating renamecuda.h: mkrename finz_ 1 1 cuda -[55/131] Generating include/renamedsp128.h -[56/131] Generating include/renamedsp256.h -[57/131] Generating dispscalar.c -[58/131] Generating ../../include/sleef.h -[59/131] Generating dispavx.c -[60/131] Generating dispsse.c -[61/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabi.dir/rempitab.c.o -[62/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512f.dir/sleefsimdsp.c.o -[63/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2.dir/sleefsimdsp.c.o -[64/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512fnofma.dir/sleefsimdsp.c.o -[65/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512f.dir/sleefsimddp.c.o -[66/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512fnofma.dir/sleefsimddp.c.o -[67/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2.dir/sleefsimddp.c.o -[68/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512f.dir/sleefsimdsp.c.o -[69/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx2sp.dir/sleefsimdsp.c.o -[70/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512f.dir/sleefsimddp.c.o -[71/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512fnofma.dir/sleefsimdsp.c.o -[72/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2.dir/sleefsimddp.c.o -[73/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2128.dir/sleefsimdsp.c.o -[74/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512fnofma.dir/sleefsimddp.c.o -[75/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabisse2dp.dir/sleefsimddp.c.o -[76/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx512fdp.dir/sleefsimddp.c.o -[77/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2.dir/sleefsimdsp.c.o -[78/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx2dp.dir/sleefsimddp.c.o -[79/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2128.dir/sleefsimddp.c.o -[80/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabisse2sp.dir/sleefsimdsp.c.o -[81/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx512fsp.dir/sleefsimdsp.c.o -[82/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2128.dir/sleefsimdsp.c.o -[83/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavxdp.dir/sleefsimddp.c.o -[84/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetfma4.dir/sleefsimdsp.c.o -[85/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetfma4.dir/sleefsimddp.c.o -[86/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavxsp.dir/sleefsimdsp.c.o -[87/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse4.dir/sleefsimdsp.c.o -[88/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2128.dir/sleefsimddp.c.o -[89/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx.dir/sleefsimdsp.c.o -[90/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurec_scalar.dir/sleefsimdsp.c.o -[91/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/rempitab.c.o -[92/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse2.dir/sleefsimdsp.c.o -[93/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/sleefld.c.o -[94/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurecfma_scalar.dir/sleefsimdsp.c.o -[95/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx.dir/sleefsimddp.c.o -[96/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/sleefqp.c.o -[97/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleeffma4.dir/sleefsimddp.c.o -[98/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurec_scalar.dir/sleefsimddp.c.o -[99/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurecfma_scalar.dir/sleefsimddp.c.o -[100/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse4.dir/sleefsimddp.c.o -[101/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleeffma4.dir/sleefsimdsp.c.o -[102/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse2.dir/sleefsimddp.c.o -[103/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurecfma_scalar.dir/sleefsimdsp.c.o -[104/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurec_scalar.dir/sleefsimdsp.c.o -[105/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse4.dir/sleefsimdsp.c.o -[106/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurecfma_scalar.dir/sleefsimddp.c.o -[107/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx.dir/sleefsimddp.c.o -[108/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse2.dir/sleefsimdsp.c.o -[109/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispscalar_obj.dir/dispscalar.c.o -[110/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurec_scalar.dir/sleefsimddp.c.o -[111/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse4.dir/sleefsimddp.c.o -[112/131] Linking C static library _deps/sleef-build/lib/libsleefgnuabi.a -[113/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse2.dir/sleefsimddp.c.o -[114/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx.dir/sleefsimdsp.c.o -[115/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispsse_obj.dir/dispsse.c.o -[116/131] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispavx_obj.dir/dispavx.c.o -[117/131] Building CXX object CMakeFiles/Deepity.dir/src/RBLayer.cpp.o -[118/131] Linking C static library _deps/sleef-build/lib/libsleef.a -[119/131] Building CXX object CMakeFiles/Deepity.dir/src/StreamAlignedBatcher.cpp.o -[120/131] Building CXX object CMakeFiles/Deepity.dir/src/SimplePCNetwork.cpp.o -[121/131] Building CXX object CMakeFiles/Deepity.dir/src/DiscriminativePCNetwork.cpp.o -[122/131] Building CXX object CMakeFiles/Deepity.dir/src/SimpleConvPCNetwork.cpp.o -[123/131] Building CXX object CMakeFiles/Deepity.dir/src/ConvPCNetwork.cpp.o -[124/131] Building CXX object CMakeFiles/Deepity.dir/src/DiscriminativePCLayer.cpp.o -[125/131] Building CXX object CMakeFiles/Deepity.dir/src/SimpleConvPCLayer.cpp.o -[126/131] Building CXX object CMakeFiles/Deepity.dir/src/ConvPCLayer.cpp.o -[127/131] Building CXX object CMakeFiles/Deepity.dir/src/ModelIO.cpp.o -[128/131] Building CXX object CMakeFiles/Deepity.dir/src/SimplePCLayer.cpp.o -[129/131] Linking CXX static library bin/libDeepity.a -[130/131] Building CXX object CMakeFiles/pydeepity.dir/bindings/pybinding.cpp.o -[131/131] Linking CXX shared module bin/pydeepity.cpython-314-x86_64-linux-gnu.so +[52/170] Generating ../../include/sleef.h +[53/170] Generating include/renamedspscalar.h +[54/170] Generating include/renamedsp128.h +[55/170] Generating include/renamedsp256.h +[56/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/check.cc.o +[57/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/benchmark_name.cc.o +[58/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/colorprint.cc.o +[59/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/counter.cc.o +[60/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/perf_counters.cc.o +[61/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/benchmark_api_internal.cc.o +[62/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/reporter.cc.o +[63/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/timers.cc.o +[64/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/console_reporter.cc.o +[65/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/string_util.cc.o +[66/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/csv_reporter.cc.o +[67/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512fnofma.dir/sleefsimdsp.c.o +[68/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/commandlineflags.cc.o +[69/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/complexity.cc.o +[70/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/json_reporter.cc.o +[71/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512f.dir/sleefsimdsp.c.o +[72/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512fnofma.dir/sleefsimddp.c.o +[73/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2.dir/sleefsimdsp.c.o +[74/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512f.dir/sleefsimddp.c.o +[75/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/sysinfo.cc.o +[76/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512fnofma.dir/sleefsimdsp.c.o +[77/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/statistics.cc.o +[78/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512f.dir/sleefsimdsp.c.o +[79/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2.dir/sleefsimddp.c.o +[80/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2128.dir/sleefsimdsp.c.o +[81/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512fnofma.dir/sleefsimddp.c.o +[82/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512f.dir/sleefsimddp.c.o +[83/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2.dir/sleefsimdsp.c.o +[84/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2128.dir/sleefsimddp.c.o +[85/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2.dir/sleefsimddp.c.o +[86/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetfma4.dir/sleefsimdsp.c.o +[87/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/benchmark_runner.cc.o +[88/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2128.dir/sleefsimdsp.c.o +[89/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2128.dir/sleefsimddp.c.o +[90/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetfma4.dir/sleefsimddp.c.o +[91/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx.dir/sleefsimdsp.c.o +[92/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse4.dir/sleefsimdsp.c.o +[93/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleeffma4.dir/sleefsimddp.c.o +[94/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx.dir/sleefsimddp.c.o +[95/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurec_scalar.dir/sleefsimdsp.c.o +[96/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleeffma4.dir/sleefsimdsp.c.o +[97/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/rempitab.c.o +[98/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/sleefld.c.o +[99/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse4.dir/sleefsimddp.c.o +[100/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse2.dir/sleefsimdsp.c.o +[101/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/sleefqp.c.o +[102/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurecfma_scalar.dir/sleefsimdsp.c.o +[103/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurec_scalar.dir/sleefsimddp.c.o +[104/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse2.dir/sleefsimddp.c.o +[105/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurecfma_scalar.dir/sleefsimddp.c.o +[106/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx.dir/sleefsimddp.c.o +[107/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse4.dir/sleefsimdsp.c.o +[108/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse2.dir/sleefsimdsp.c.o +[109/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx.dir/sleefsimdsp.c.o +[110/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse4.dir/sleefsimddp.c.o +[111/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse2.dir/sleefsimddp.c.o +[112/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispscalar_obj.dir/dispscalar.c.o +[113/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurecfma_scalar.dir/sleefsimdsp.c.o +[114/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurec_scalar.dir/sleefsimdsp.c.o +[115/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurecfma_scalar.dir/sleefsimddp.c.o +[116/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurec_scalar.dir/sleefsimddp.c.o +[117/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/benchmark.cc.o +[118/170] Building CXX object CMakeFiles/Deepity.dir/src/RBLayer.cpp.o +[119/170] Building CXX object CMakeFiles/Deepity.dir/src/StreamAlignedBatcher.cpp.o +[120/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispavx_obj.dir/dispavx.c.o +[121/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispsse_obj.dir/dispsse.c.o +[122/170] Building CXX object CMakeFiles/Deepity.dir/src/DiscriminativePCNetwork.cpp.o +[123/170] Linking C static library _deps/sleef-build/lib/libsleef.a +[124/170] Building CXX object CMakeFiles/Deepity.dir/src/SimplePCNetwork.cpp.o +[125/170] Building CXX object CMakeFiles/Deepity.dir/src/ConvPCNetwork.cpp.o +[126/170] Building CXX object CMakeFiles/Deepity.dir/src/SimpleConvPCNetwork.cpp.o +[127/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/RBLayer.cpp.o +[128/170] Building CXX object CMakeFiles/Deepity.dir/src/DiscriminativePCLayer.cpp.o +[129/170] Building CXX object CMakeFiles/Deepity.dir/src/ConvPCLayer.cpp.o +[130/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/StreamAlignedBatcher.cpp.o +[131/170] Building CXX object CMakeFiles/Deepity.dir/src/ModelIO.cpp.o +[132/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/DiscriminativePCNetwork.cpp.o +[133/170] Building CXX object CMakeFiles/Deepity.dir/src/SimpleConvPCLayer.cpp.o +[134/170] Building CXX object CMakeFiles/Deepity.dir/src/SimplePCLayer.cpp.o +[135/170] Linking CXX static library bin/libDeepity.a +[136/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/ConvPCNetwork.cpp.o +[137/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabi.dir/rempitab.c.o +[138/170] Building C object _deps/sleef-build/src/common/CMakeFiles/addSuffix.dir/addSuffix.c.o +[139/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/SimplePCNetwork.cpp.o +[140/170] Linking C executable _deps/sleef-build/bin/addSuffix +[141/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/benchmark_register.cc.o +[142/170] Linking CXX static library bin/libbenchmark.a +[143/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/ConvPCLayer.cpp.o +[144/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/DiscriminativePCLayer.cpp.o +[145/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/SimpleConvPCLayer.cpp.o +[146/170] Building C object _deps/sleef-build/src/common/CMakeFiles/testerutil_obj.dir/testerutil.c.o +[147/170] Building C object _deps/sleef-build/src/common/CMakeFiles/qtesterutil_obj.dir/qtesterutil.c.o +[148/170] Building CXX object _deps/sleef-build/src/common/CMakeFiles/psha_obj.dir/psha2_capi.cpp.o +[149/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark_main.dir/benchmark_main.cc.o +[150/170] Linking CXX static library bin/libbenchmark_main.a +[151/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/ModelIO.cpp.o +[152/170] Building CXX object CMakeFiles/Layer1Isolate513.dir/tests/t513.cpp.o +[153/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/SimplePCLayer.cpp.o +[154/170] Linking CXX static library bin/libDeepityProfiled.a +[155/170] Linking CXX executable bin/Layer1Isolate513 +[156/170] Building CXX object CMakeFiles/DeepityTests.dir/tests/tSimpleConvVerify.cpp.o +[157/170] Building CXX object CMakeFiles/DeepityProfile.dir/tests/tProfile.cpp.o +[158/170] Linking CXX executable bin/DeepityTests +[159/170] Linking CXX executable bin/DeepityProfile +[160/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx2sp.dir/sleefsimdsp.c.o +[161/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabisse2dp.dir/sleefsimddp.c.o +[162/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabisse2sp.dir/sleefsimdsp.c.o +[163/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx2dp.dir/sleefsimddp.c.o +[164/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavxdp.dir/sleefsimddp.c.o +[165/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx512fsp.dir/sleefsimdsp.c.o +[166/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx512fdp.dir/sleefsimddp.c.o +[167/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavxsp.dir/sleefsimdsp.c.o +[168/170] Linking C static library _deps/sleef-build/lib/libsleefgnuabi.a +[169/170] Building CXX object CMakeFiles/pydeepity.dir/bindings/pybinding.cpp.o +[170/170] Linking CXX shared module bin/pydeepity.cpython-314-x86_64-linux-gnu.so === Tests === === Part 1: SGD weight-gradient check === - W[5]: delta=0.03608 numeric_dE/dW=-0.689149 MATCHES DESCENT rel_err=0.0023544 - W[17]: delta=0.0546692 numeric_dE/dW=-1.01769 MATCHES DESCENT rel_err=0.00371893 - W[6]: delta=-0.018854 numeric_dE/dW=0.368834 MATCHES DESCENT rel_err=0.00111797 - W[4]: delta=0.000938594 numeric_dE/dW=-0.0263453 MATCHES DESCENT rel_err=0.0143733 - W[8]: delta=-0.0150366 numeric_dE/dW=0.299931 MATCHES DESCENT rel_err=0.000133743 + W[5]: delta=0.03608 numeric_dE/dW=-0.68903 MATCHES DESCENT rel_err=0.00236346 + W[17]: delta=0.0546692 numeric_dE/dW=-1.01757 MATCHES DESCENT rel_err=0.00372522 + W[6]: delta=-0.018854 numeric_dE/dW=0.368714 MATCHES DESCENT rel_err=0.0011345 + W[4]: delta=0.000938594 numeric_dE/dW=-0.0264645 MATCHES DESCENT rel_err=0.0145338 + W[8]: delta=-0.0150366 numeric_dE/dW=0.30005 MATCHES DESCENT rel_err=0.000113825 W[1]: delta=-0.0190711 numeric_dE/dW=0.371218 MATCHES DESCENT rel_err=0.00137444 - W[14]: delta=0.0116533 numeric_dE/dW=-0.317693 MATCHES DESCENT rel_err=0.0133189 - W[11]: delta=0.0684187 numeric_dE/dW=-1.28996 MATCHES DESCENT rel_err=0.00303923 -Worst relative error: 0.0143733 + W[14]: delta=0.0116533 numeric_dE/dW=-0.317812 MATCHES DESCENT rel_err=0.0133326 + W[11]: delta=0.0684187 numeric_dE/dW=-1.28984 MATCHES DESCENT rel_err=0.00304413 +Worst relative error: 0.0145338 PASS === Part 2: feedback-term (Col2Im) verification, SimpleConvPCLayer === z[3]: implied_dz_dt=0.0045494 -numeric_dE/dz=0.00452995 rel_err=0.00429182 z[19]: implied_dz_dt=-0.0106531 -numeric_dE/dz=-0.0106096 rel_err=0.00410083 - z[15]: implied_dz_dt=0.0041806 -numeric_dE/dz=0.00420213 rel_err=0.00512289 - z[26]: implied_dz_dt=-0.00197962 -numeric_dE/dz=-0.00196695 rel_err=0.00643612 + z[15]: implied_dz_dt=0.0041806 -numeric_dE/dz=0.00417233 rel_err=0.00198167 + z[26]: implied_dz_dt=-0.00197962 -numeric_dE/dz=-0.00199676 rel_err=0.00857779 z[1]: implied_dz_dt=-0.0406229 -numeric_dE/dz=-0.0406206 rel_err=5.87843e-05 z[5]: implied_dz_dt=-0.0247908 -numeric_dE/dz=-0.0247657 rel_err=0.00101086 - z[29]: implied_dz_dt=0.0272506 -numeric_dE/dz=0.0272393 rel_err=0.000415808 + z[29]: implied_dz_dt=0.0272506 -numeric_dE/dz=0.0272691 rel_err=0.000677503 z[12]: implied_dz_dt=0.0424355 -numeric_dE/dz=0.0424385 rel_err=7.01353e-05 -Worst relative error: 0.00643612 +Worst relative error: 0.00857779 PASS === Part 3: AdamW weight-gradient check (NEW port, checking SIGN first) === diff --git a/tests/t513.cpp b/tests/t513.cpp new file mode 100644 index 0000000..d26f77a --- /dev/null +++ b/tests/t513.cpp @@ -0,0 +1,90 @@ +// Confirms the cache-aliasing theory directly: SAME isolated-layer test +// as tLayer1Isolate.cpp, but at size=513 instead of 512 -- a single +// dimension off from a power of two. If the ~1.5ms/call slowness was +// genuinely caused by 512's power-of-two stride aliasing in the CPU +// cache, 513 should show DRAMATICALLY better throughput despite being +// virtually the same amount of actual work (~0.4% more FLOPs). +#include +#include +#include +#include +#include + +using namespace Deep; + +int main() +{ + const int BATCH = 256; + const int STEPS = 20; + const int N_REPS = 40; + const int SIZE = 513; // ONLY difference from tLayer1Isolate.cpp: 512 -> 513 + + SimplePCLayer below(SIZE, SIZE, BATCH, 0.001f, 0.08f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); + SimplePCLayer layer1(SIZE, SIZE, BATCH, 0.001f, 0.08f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); + SimplePCLayer above(SIZE, 10, BATCH, 0.001f, 0.08f, 0.0001f, ActivationType::TANH, ActivationType::dTANH); + + below.SetLayerAbove(&layer1); + layer1.SetLayerBelow(&below); + layer1.SetLayerAbove(&above); + above.SetLayerBelow(&layer1); + + std::mt19937 rng(7); + below.RandomizeWeights(rng); + layer1.RandomizeWeights(rng); + above.RandomizeWeights(rng); + + std::uniform_real_distribution initDist(-0.3f, 0.3f); + auto reinit = [&](SimplePCLayer &l, size_t inSz, size_t outSz) + { + if (outSz == 0) return; + float *W = l.GetWeights(); + for (size_t i = 0; i < inSz * outSz; ++i) W[i] = initDist(rng); + }; + reinit(below, SIZE, SIZE); + reinit(layer1, SIZE, SIZE); + reinit(above, SIZE, 10); + + std::vector belowInput((size_t)BATCH * SIZE); + std::mt19937 dataRng(123); + std::uniform_real_distribution dataDist(-1.0f, 1.0f); + for (auto &v : belowInput) v = dataDist(dataRng); + + below.ClampState(belowInput); + + double t_calc = 0, t_update = 0; + auto totalStart = std::chrono::steady_clock::now(); + + for (int rep = 0; rep < N_REPS; ++rep) + { + below.CalculateState(); + + for (int step = 0; step < STEPS; ++step) + { + auto t0 = std::chrono::steady_clock::now(); + layer1.CalculateState(); + auto t1 = std::chrono::steady_clock::now(); + t_calc += std::chrono::duration(t1 - t0).count(); + + t0 = t1; + layer1.UpdateState(); + t1 = std::chrono::steady_clock::now(); + t_update += std::chrono::duration(t1 - t0).count(); + } + } + + auto totalEnd = std::chrono::steady_clock::now(); + double totalTime = std::chrono::duration(totalEnd - totalStart).count(); + + std::cout << "=== Isolated " << SIZE << "->" << SIZE << " layer (non-power-of-2), " + << N_REPS << " reps x " << STEPS << " steps ===\n\n"; + std::cout << "Total: " << totalTime << "s\n"; + std::cout << "calculate_state: " << 1000*t_calc/(N_REPS*STEPS) << " ms/call\n"; + std::cout << "update_state: " << 1000*t_update/(N_REPS*STEPS) << " ms/call\n\n"; + std::cout << "Compare against size=512's measured: calc=1.670ms, update=1.487ms\n"; + std::cout << "If 513 is DRAMATICALLY faster despite ~0.4% MORE actual work,\n"; + std::cout << "that confirms cache-associativity aliasing at the 512 power-of-2\n"; + std::cout << "stride as the real cause -- a well-known, well-understood HPC\n"; + std::cout << "pathology with a simple, standard fix (pad the leading dimension).\n"; + + return 0; +} From 883b1a92e54e2db82363a52a8a2189e4ad82d620 Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Tue, 25 Aug 2026 18:43:34 -0400 Subject: [PATCH 10/11] Fixed skip in pyproject. --- .ipynb_checkpoints/build-checkpoint.py | 14 -- logs/build.log | 276 +------------------------ pyproject.toml | 2 +- 3 files changed, 11 insertions(+), 281 deletions(-) delete mode 100644 .ipynb_checkpoints/build-checkpoint.py diff --git a/.ipynb_checkpoints/build-checkpoint.py b/.ipynb_checkpoints/build-checkpoint.py deleted file mode 100644 index 40286c4..0000000 --- a/.ipynb_checkpoints/build-checkpoint.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 -""" -Deepity build entrypoint. - -All real logic lives in the deepity_build package (config resolution, the -CMake command builder, and the rich/plain reporters). This file just wires -argv to that package so `python build.py ...` keeps working exactly as -before. -""" - -from deepity_build.cli import main - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/logs/build.log b/logs/build.log index c7c7f7f..0851ab6 100644 --- a/logs/build.log +++ b/logs/build.log @@ -1,289 +1,33 @@ --- Deepity Build Log (Release, arch=fast) --- -=== CMake Configuration === --- The C compiler identification is GNU 16.2.1 --- The CXX compiler identification is GNU 16.2.1 --- Detecting C compiler ABI info --- Detecting C compiler ABI info - done --- Check for working C compiler: /usr/bin/cc - skipped --- Detecting C compile features --- Detecting C compile features - done --- Detecting CXX compiler ABI info --- Detecting CXX compiler ABI info - done --- Check for working CXX compiler: /usr/bin/c++ - skipped --- Detecting CXX compile features --- Detecting CXX compile features - done --- CXX compiler: /usr/bin/c++ --- Found OpenMP_C: -fopenmp (found version "5.2") --- Found OpenMP_CXX: -fopenmp (found version "5.2") --- Found OpenMP: TRUE (found version "5.2") --- CUDA toolkit not found. Building CPU-only. --- Found Python: /home/rose0/Projects/deepity/.venv/bin/python3.14 (found suitable version "3.14.7", minimum required is "3.8") found components: Interpreter Development.Module Development.Embed --- Performing Test HAS_FLTO_AUTO --- Performing Test HAS_FLTO_AUTO - Success --- Found pybind11: /usr/include (found version "3.0.4") --- Looking for sgemm_ --- Looking for sgemm_ - found --- Found BLAS: /usr/lib64/libopenblas.so --- Configuring SLEEF 3.9.0 --- Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY OPENSSL_INCLUDE_DIR) --- Found PkgConfig: /usr/bin/pkg-config (found version "2.5.1") --- Found OpenMP_C: -fopenmp (found version "5.2") --- Found OpenMP_CXX: -fopenmp (found version "5.2") --- Configuring build for SLEEF-v3.9.0 - Target system: Linux-7.1.9-200.fc44.x86_64 - Target processor: x86_64 - Host system: Linux-7.1.9-200.fc44.x86_64 - Host processor: x86_64 - Detected C compiler: GNU @ /usr/bin/cc - CMake: 4.3.0 - Make program: /usr/bin/ninja-build - CMake build type: Release --- Using option `-Wall -Wno-unused-function -Wno-attributes -Wno-unused-result -Wno-psabi -ffp-contract=off -fno-math-errno -fno-trapping-math` to compile libsleef --- Building shared libs : OFF --- Building static test bins: OFF --- MPFR : LIB_MPFR-NOTFOUND --- GMP : LIBGMP-NOTFOUND --- RT : /usr/lib64/librt.a --- FFTW3 : LIBFFTW3-NOTFOUND --- FFTW3F : LIBFFTW3F-NOTFOUND --- OPENSSL : --- SDE : SDE_COMMAND-NOTFOUND --- COMPILER_SUPPORTS_OPENMP : 1 --- A version of SLEEF compatible with libm and libmvec in GNU libc will be produced (sleefgnuabi.so) --- Failed to find LLVM FileCheck --- Found Git: /usr/bin/git (found version "2.55.0") --- Google Benchmark version: v1.9.1, normalized to 1.9.1 --- Compiling and running to test HAVE_STD_REGEX --- Performing Test HAVE_STD_REGEX -- success --- Compiling and running to test HAVE_GNU_POSIX_REGEX --- Performing Test HAVE_GNU_POSIX_REGEX -- failed to compile --- Compiling and running to test HAVE_POSIX_REGEX --- Performing Test HAVE_POSIX_REGEX -- success --- Compiling and running to test HAVE_STEADY_CLOCK --- Performing Test HAVE_STEADY_CLOCK -- success --- Performing Test CMAKE_HAVE_LIBC_PTHREAD --- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success --- Found Threads: TRUE --- Compiling and running to test HAVE_PTHREAD_AFFINITY --- Performing Test HAVE_PTHREAD_AFFINITY -- success --- Configuring done (301.0s) --- Generating done (0.1s) --- Build files have been written to: /home/rose0/Projects/deepity/build/Release - - === Compilation === -[1/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkrename_gnuabi.dir/mkrename_gnuabi.c.o -[2/170] Building C object _deps/sleef-build/src/common/CMakeFiles/common.dir/common.c.o -[3/170] Linking C executable _deps/sleef-build/bin/mkrename_gnuabi -[4/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkalias.dir/mkalias.c.o -[5/170] Generating include/renameavx2_gnuabi.h -Generating renameavx2_gnuabi.h: mkrename_gnuabi avx2 d 4 8 __m256d __m256 __m128i __m256i __AVX2__ -[6/170] Generating include/renameavx512f_gnuabi.h -Generating renameavx512f_gnuabi.h: mkrename_gnuabi avx512f e 8 16 __m512d __m512 __m256i __m512i __AVX512F__ -[7/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkmasked_gnuabi.dir/mkmasked_gnuabi.c.o -[8/170] Generating include/renamesse2_gnuabi.h -Generating renamesse2_gnuabi.h: mkrename_gnuabi sse2 b 2 4 _mm128d _mm128 _mm128i _mm128i __SSE2__ -[9/170] Generating include/renameavx_gnuabi.h -Generating renameavx_gnuabi.h: mkrename_gnuabi avx c 4 8 __m256d __m256 __m128i struct\ {\ __m128i\ x,\ y;\ } __AVX__ -[10/170] Linking C executable _deps/sleef-build/bin/mkalias -[11/170] Generating alias_AVX512F_dp.h.tmp -[12/170] Generating alias_AVX512F_sp.h.tmp -[13/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkdisp.dir/mkdisp.c.o -[14/170] Generating include/alias_avx512f.h -[15/170] Linking C executable _deps/sleef-build/bin/mkmasked_gnuabi -[16/170] Generating include/masked_avx512f_dp_gnuabi.h -[17/170] Generating include/masked_avx512f_sp_gnuabi.h -[18/170] Linking C executable _deps/sleef-build/bin/mkdisp -[19/170] Generating dispscalar.c.body -[20/170] Generating dispsse.c.tmp -[21/170] Generating dispavx.c.tmp -[22/170] Generating dispscalar.c -[23/170] Generating dispsse.c -[24/170] Generating dispavx.c -[25/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/mkrename.dir/mkrename.c.o -[26/170] Linking C executable _deps/sleef-build/bin/mkrename -[27/170] Generating sleeflibm_AVX.h.tmp -[28/170] Generating sleeflibm_AVX2.h.tmp -[29/170] Generating sleeflibm_AVX2128.h.tmp -[30/170] Generating sleeflibm_AVX512F.h.tmp -[31/170] Generating sleeflibm_AVX512FNOFMA.h.tmp -[32/170] Generating sleeflibm_AVX512F_.h.tmp -[33/170] Generating sleeflibm_AVX_.h.tmp -[34/170] Generating sleeflibm_DSP_SCALAR.h.tmp -[35/170] Generating sleeflibm_FMA4.h.tmp -[36/170] Generating sleeflibm_PURECFMA_SCALAR.h.tmp -[37/170] Generating sleeflibm_PUREC_SCALAR.h.tmp -[38/170] Generating sleeflibm_SSE2.h.tmp -[39/170] Generating sleeflibm_SSE4.h.tmp -[40/170] Generating sleeflibm_SSE_.h.tmp -[41/170] Generating include/renameavx512fnofma.h -Generating renameavx512fnofma.h: mkrename cinz_ 8 16 avx512fnofma -[42/170] Generating include/renameavx512f.h -Generating renameavx512f.h: mkrename finz_ 8 16 avx512f -[43/170] Generating include/renameavx2.h -Generating renameavx2.h: mkrename finz_ 4 8 avx2 -[44/170] Generating include/renameavx2128.h -Generating renameavx2128.h: mkrename finz_ 2 4 avx2128 -[45/170] Generating include/renamefma4.h -Generating renamefma4.h: mkrename finz_ 4 8 fma4 -[46/170] Generating include/renameavx.h -Generating renameavx.h: mkrename cinz_ 4 8 avx -[47/170] Generating include/renamesse4.h -Generating renamesse4.h: mkrename cinz_ 2 4 sse4 -[48/170] Generating include/renamesse2.h -Generating renamesse2.h: mkrename cinz_ 2 4 sse2 -[49/170] Generating include/renamepurec_scalar.h -Generating renamepurec_scalar.h: mkrename cinz_ 1 1 purec -[50/170] Generating include/renamepurecfma_scalar.h -Generating renamepurecfma_scalar.h: mkrename finz_ 1 1 purecfma -[51/170] Generating include/renamecuda.h -Generating renamecuda.h: mkrename finz_ 1 1 cuda -[52/170] Generating ../../include/sleef.h -[53/170] Generating include/renamedspscalar.h -[54/170] Generating include/renamedsp128.h -[55/170] Generating include/renamedsp256.h -[56/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/check.cc.o -[57/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/benchmark_name.cc.o -[58/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/colorprint.cc.o -[59/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/counter.cc.o -[60/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/perf_counters.cc.o -[61/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/benchmark_api_internal.cc.o -[62/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/reporter.cc.o -[63/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/timers.cc.o -[64/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/console_reporter.cc.o -[65/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/string_util.cc.o -[66/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/csv_reporter.cc.o -[67/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512fnofma.dir/sleefsimdsp.c.o -[68/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/commandlineflags.cc.o -[69/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/complexity.cc.o -[70/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/json_reporter.cc.o -[71/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512f.dir/sleefsimdsp.c.o -[72/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512fnofma.dir/sleefsimddp.c.o -[73/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2.dir/sleefsimdsp.c.o -[74/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx512f.dir/sleefsimddp.c.o -[75/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/sysinfo.cc.o -[76/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512fnofma.dir/sleefsimdsp.c.o -[77/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/statistics.cc.o -[78/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512f.dir/sleefsimdsp.c.o -[79/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2.dir/sleefsimddp.c.o -[80/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2128.dir/sleefsimdsp.c.o -[81/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512fnofma.dir/sleefsimddp.c.o -[82/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx512f.dir/sleefsimddp.c.o -[83/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2.dir/sleefsimdsp.c.o -[84/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx2128.dir/sleefsimddp.c.o -[85/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2.dir/sleefsimddp.c.o -[86/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetfma4.dir/sleefsimdsp.c.o -[87/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/benchmark_runner.cc.o -[88/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2128.dir/sleefsimdsp.c.o -[89/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx2128.dir/sleefsimddp.c.o -[90/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetfma4.dir/sleefsimddp.c.o -[91/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx.dir/sleefsimdsp.c.o -[92/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse4.dir/sleefsimdsp.c.o -[93/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleeffma4.dir/sleefsimddp.c.o -[94/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetavx.dir/sleefsimddp.c.o -[95/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurec_scalar.dir/sleefsimdsp.c.o -[96/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleeffma4.dir/sleefsimdsp.c.o -[97/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/rempitab.c.o -[98/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/sleefld.c.o -[99/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse4.dir/sleefsimddp.c.o -[100/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse2.dir/sleefsimdsp.c.o -[101/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleef.dir/sleefqp.c.o -[102/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurecfma_scalar.dir/sleefsimdsp.c.o -[103/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurec_scalar.dir/sleefsimddp.c.o -[104/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetsse2.dir/sleefsimddp.c.o -[105/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefdetpurecfma_scalar.dir/sleefsimddp.c.o -[106/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx.dir/sleefsimddp.c.o -[107/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse4.dir/sleefsimdsp.c.o -[108/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse2.dir/sleefsimdsp.c.o -[109/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefavx.dir/sleefsimdsp.c.o -[110/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse4.dir/sleefsimddp.c.o -[111/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefsse2.dir/sleefsimddp.c.o -[112/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispscalar_obj.dir/dispscalar.c.o -[113/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurecfma_scalar.dir/sleefsimdsp.c.o -[114/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurec_scalar.dir/sleefsimdsp.c.o -[115/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurecfma_scalar.dir/sleefsimddp.c.o -[116/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefpurec_scalar.dir/sleefsimddp.c.o -[117/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/benchmark.cc.o -[118/170] Building CXX object CMakeFiles/Deepity.dir/src/RBLayer.cpp.o -[119/170] Building CXX object CMakeFiles/Deepity.dir/src/StreamAlignedBatcher.cpp.o -[120/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispavx_obj.dir/dispavx.c.o -[121/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/dispsse_obj.dir/dispsse.c.o -[122/170] Building CXX object CMakeFiles/Deepity.dir/src/DiscriminativePCNetwork.cpp.o -[123/170] Linking C static library _deps/sleef-build/lib/libsleef.a -[124/170] Building CXX object CMakeFiles/Deepity.dir/src/SimplePCNetwork.cpp.o -[125/170] Building CXX object CMakeFiles/Deepity.dir/src/ConvPCNetwork.cpp.o -[126/170] Building CXX object CMakeFiles/Deepity.dir/src/SimpleConvPCNetwork.cpp.o -[127/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/RBLayer.cpp.o -[128/170] Building CXX object CMakeFiles/Deepity.dir/src/DiscriminativePCLayer.cpp.o -[129/170] Building CXX object CMakeFiles/Deepity.dir/src/ConvPCLayer.cpp.o -[130/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/StreamAlignedBatcher.cpp.o -[131/170] Building CXX object CMakeFiles/Deepity.dir/src/ModelIO.cpp.o -[132/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/DiscriminativePCNetwork.cpp.o -[133/170] Building CXX object CMakeFiles/Deepity.dir/src/SimpleConvPCLayer.cpp.o -[134/170] Building CXX object CMakeFiles/Deepity.dir/src/SimplePCLayer.cpp.o -[135/170] Linking CXX static library bin/libDeepity.a -[136/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/ConvPCNetwork.cpp.o -[137/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabi.dir/rempitab.c.o -[138/170] Building C object _deps/sleef-build/src/common/CMakeFiles/addSuffix.dir/addSuffix.c.o -[139/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/SimplePCNetwork.cpp.o -[140/170] Linking C executable _deps/sleef-build/bin/addSuffix -[141/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark.dir/benchmark_register.cc.o -[142/170] Linking CXX static library bin/libbenchmark.a -[143/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/ConvPCLayer.cpp.o -[144/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/DiscriminativePCLayer.cpp.o -[145/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/SimpleConvPCLayer.cpp.o -[146/170] Building C object _deps/sleef-build/src/common/CMakeFiles/testerutil_obj.dir/testerutil.c.o -[147/170] Building C object _deps/sleef-build/src/common/CMakeFiles/qtesterutil_obj.dir/qtesterutil.c.o -[148/170] Building CXX object _deps/sleef-build/src/common/CMakeFiles/psha_obj.dir/psha2_capi.cpp.o -[149/170] Building CXX object _deps/benchmark-build/src/CMakeFiles/benchmark_main.dir/benchmark_main.cc.o -[150/170] Linking CXX static library bin/libbenchmark_main.a -[151/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/ModelIO.cpp.o -[152/170] Building CXX object CMakeFiles/Layer1Isolate513.dir/tests/t513.cpp.o -[153/170] Building CXX object CMakeFiles/DeepityProfiled.dir/src/SimplePCLayer.cpp.o -[154/170] Linking CXX static library bin/libDeepityProfiled.a -[155/170] Linking CXX executable bin/Layer1Isolate513 -[156/170] Building CXX object CMakeFiles/DeepityTests.dir/tests/tSimpleConvVerify.cpp.o -[157/170] Building CXX object CMakeFiles/DeepityProfile.dir/tests/tProfile.cpp.o -[158/170] Linking CXX executable bin/DeepityTests -[159/170] Linking CXX executable bin/DeepityProfile -[160/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx2sp.dir/sleefsimdsp.c.o -[161/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabisse2dp.dir/sleefsimddp.c.o -[162/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabisse2sp.dir/sleefsimdsp.c.o -[163/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx2dp.dir/sleefsimddp.c.o -[164/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavxdp.dir/sleefsimddp.c.o -[165/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx512fsp.dir/sleefsimdsp.c.o -[166/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavx512fdp.dir/sleefsimddp.c.o -[167/170] Building C object _deps/sleef-build/src/libm/CMakeFiles/sleefgnuabiavxsp.dir/sleefsimdsp.c.o -[168/170] Linking C static library _deps/sleef-build/lib/libsleefgnuabi.a -[169/170] Building CXX object CMakeFiles/pydeepity.dir/bindings/pybinding.cpp.o -[170/170] Linking CXX shared module bin/pydeepity.cpython-314-x86_64-linux-gnu.so +ninja: no work to do. === Tests === === Part 1: SGD weight-gradient check === W[5]: delta=0.03608 numeric_dE/dW=-0.68903 MATCHES DESCENT rel_err=0.00236346 - W[17]: delta=0.0546692 numeric_dE/dW=-1.01757 MATCHES DESCENT rel_err=0.00372522 - W[6]: delta=-0.018854 numeric_dE/dW=0.368714 MATCHES DESCENT rel_err=0.0011345 - W[4]: delta=0.000938594 numeric_dE/dW=-0.0264645 MATCHES DESCENT rel_err=0.0145338 + W[17]: delta=0.0546692 numeric_dE/dW=-1.01769 MATCHES DESCENT rel_err=0.00371893 + W[6]: delta=-0.018854 numeric_dE/dW=0.368834 MATCHES DESCENT rel_err=0.00111797 + W[4]: delta=0.000938594 numeric_dE/dW=-0.0263453 MATCHES DESCENT rel_err=0.0143733 W[8]: delta=-0.0150366 numeric_dE/dW=0.30005 MATCHES DESCENT rel_err=0.000113825 - W[1]: delta=-0.0190711 numeric_dE/dW=0.371218 MATCHES DESCENT rel_err=0.00137444 + W[1]: delta=-0.0190711 numeric_dE/dW=0.371337 MATCHES DESCENT rel_err=0.00135795 W[14]: delta=0.0116533 numeric_dE/dW=-0.317812 MATCHES DESCENT rel_err=0.0133326 W[11]: delta=0.0684187 numeric_dE/dW=-1.28984 MATCHES DESCENT rel_err=0.00304413 -Worst relative error: 0.0145338 +Worst relative error: 0.0143733 PASS === Part 2: feedback-term (Col2Im) verification, SimpleConvPCLayer === z[3]: implied_dz_dt=0.0045494 -numeric_dE/dz=0.00452995 rel_err=0.00429182 z[19]: implied_dz_dt=-0.0106531 -numeric_dE/dz=-0.0106096 rel_err=0.00410083 - z[15]: implied_dz_dt=0.0041806 -numeric_dE/dz=0.00417233 rel_err=0.00198167 - z[26]: implied_dz_dt=-0.00197962 -numeric_dE/dz=-0.00199676 rel_err=0.00857779 + z[15]: implied_dz_dt=0.0041806 -numeric_dE/dz=0.00420213 rel_err=0.00512289 + z[26]: implied_dz_dt=-0.00197962 -numeric_dE/dz=-0.00196695 rel_err=0.00643612 z[1]: implied_dz_dt=-0.0406229 -numeric_dE/dz=-0.0406206 rel_err=5.87843e-05 - z[5]: implied_dz_dt=-0.0247908 -numeric_dE/dz=-0.0247657 rel_err=0.00101086 + z[5]: implied_dz_dt=-0.0247908 -numeric_dE/dz=-0.0247955 rel_err=0.000192225 z[29]: implied_dz_dt=0.0272506 -numeric_dE/dz=0.0272691 rel_err=0.000677503 z[12]: implied_dz_dt=0.0424355 -numeric_dE/dz=0.0424385 rel_err=7.01353e-05 -Worst relative error: 0.00857779 +Worst relative error: 0.00643612 PASS === Part 3: AdamW weight-gradient check (NEW port, checking SIGN first) === diff --git a/pyproject.toml b/pyproject.toml index 002ea4e..abe94d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ DEEPITY_MSVC_ARCH_FLAGS = "" # ══════════════════════════════════════════════════════════════════════════ [tool.cibuildwheel] build = "cp39-* cp310-* cp311-* cp312-* cp313-*" -skip = "*-musllinux* *-win32" +skip = "*-musllinux* *-win32 *-manylinux_i686" build-verbosity = 1 test-requires = ["numpy"] From 8f57678350f8aa6a32a5ea25122794a2397c37f5 Mon Sep 17 00:00:00 2001 From: Ra4ster Date: Tue, 25 Aug 2026 19:09:15 -0400 Subject: [PATCH 11/11] (Hopefully) fixing Windows wheel. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index abe94d8..5bf206f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,4 +86,4 @@ archs = ["x86_64"] [tool.cibuildwheel.windows] before-build = "pip install delvewheel" -repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" +repair-wheel-command = "delvewheel repair --add-path pydeepity -w {dest_dir} {wheel}" \ No newline at end of file