CUDA
This commit is contained in:
+11
-1
@@ -34,9 +34,18 @@ Makefile
|
||||
*.tlog
|
||||
*.lastbuildstate
|
||||
|
||||
# Generated searches, checkpoints, reports, and logs
|
||||
# Machine-local runtime files
|
||||
/runtime/
|
||||
|
||||
# Search checkpoints under results/search are intentionally tracked so that
|
||||
# disjoint topology ranges merge through Git. These files are derived locally.
|
||||
/results/search/leaderboard.tsv
|
||||
/results/search/run.log
|
||||
/results/search/runs/**/run.log
|
||||
/results/search/**/*.tmp
|
||||
/results/search/**/*.tmp.*
|
||||
/results/search/**/*.partial
|
||||
|
||||
# Python cache and virtual environments
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
@@ -47,6 +56,7 @@ venv/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
*.lnk
|
||||
|
||||
# Wavefront .obj files are research data in this repository and are intentionally
|
||||
# not ignored globally.
|
||||
|
||||
+30
-1
@@ -6,12 +6,16 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
option(SZILASSI_ENABLE_CUDA "Build the FP32 CUDA global-search backend" ON)
|
||||
|
||||
set(EIGEN_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/external/eigen-3.4.0" CACHE PATH "Eigen include root")
|
||||
if(NOT EXISTS "${EIGEN_ROOT}/Eigen/Dense")
|
||||
message(FATAL_ERROR "Eigen not found at ${EIGEN_ROOT}. Download Eigen 3.4.0 into external/eigen-3.4.0 or pass -DEIGEN_ROOT=...")
|
||||
endif()
|
||||
|
||||
add_library(neighborly_core
|
||||
src/Checkpoint/GlobalCheckpoint.cpp
|
||||
src/Checkpoint/GlobalCheckpoint.h
|
||||
src/NeighborlyCore/plane.h
|
||||
src/NeighborlyCore/precise_geometry.h
|
||||
src/NeighborlyCore/util.cpp
|
||||
@@ -21,9 +25,33 @@ add_library(neighborly_core
|
||||
projects/Szilassi/solver.h
|
||||
)
|
||||
|
||||
include(CheckLanguage)
|
||||
if(SZILASSI_ENABLE_CUDA)
|
||||
check_language(CUDA)
|
||||
endif()
|
||||
|
||||
if(SZILASSI_ENABLE_CUDA AND CMAKE_CUDA_COMPILER)
|
||||
enable_language(CUDA)
|
||||
add_library(neighborly_cuda src/CudaSearch/cuda_search.cu)
|
||||
set_target_properties(neighborly_cuda PROPERTIES
|
||||
CUDA_ARCHITECTURES "89-real;120-real;120-virtual"
|
||||
CUDA_STANDARD 17
|
||||
CUDA_STANDARD_REQUIRED ON
|
||||
)
|
||||
target_compile_definitions(neighborly_cuda PUBLIC SZILASSI_HAS_CUDA=1)
|
||||
else()
|
||||
add_library(neighborly_cuda src/CudaSearch/cuda_search_stub.cpp)
|
||||
message(STATUS "CUDA Toolkit not found: building a diagnostic stub backend")
|
||||
endif()
|
||||
|
||||
target_include_directories(neighborly_cuda PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/CudaSearch"
|
||||
)
|
||||
|
||||
target_include_directories(neighborly_core
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/NeighborlyCore"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/Checkpoint"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/projects/Szilassi"
|
||||
"${EIGEN_ROOT}"
|
||||
)
|
||||
@@ -36,7 +64,7 @@ else()
|
||||
endif()
|
||||
|
||||
add_executable(neighborly_main projects/Szilassi/main.cpp)
|
||||
target_link_libraries(neighborly_main PRIVATE neighborly_core)
|
||||
target_link_libraries(neighborly_main PRIVATE neighborly_core neighborly_cuda)
|
||||
|
||||
add_executable(verify_cpp projects/VerifyCpp/verify_cpp.cpp)
|
||||
target_link_libraries(verify_cpp PRIVATE neighborly_core)
|
||||
@@ -44,5 +72,6 @@ target_link_libraries(verify_cpp PRIVATE neighborly_core)
|
||||
add_executable(polyhedron_gui WIN32 projects/PolyhedronGui/launcher_gui.cpp)
|
||||
if(MSVC)
|
||||
target_compile_definitions(polyhedron_gui PRIVATE UNICODE _UNICODE NOMINMAX)
|
||||
target_compile_options(polyhedron_gui PRIVATE /utf-8)
|
||||
target_link_libraries(polyhedron_gui PRIVATE shell32)
|
||||
endif()
|
||||
|
||||
@@ -9,17 +9,58 @@ The conclusion is: Sort of. I believe there isn't a shape that is _completely_ i
|
||||
|
||||
Build `Szilassi.slnx` as `Release|x64`, then open
|
||||
`build\msbuild\bin\x64\Release\PolyhedronGui.exe`.
|
||||
For an independent overnight run, leave `Threads` at `0`, set the desired number of
|
||||
minutes, and click **Глобальный поиск 59**. This mode does not use the supplied near-miss:
|
||||
The GUI intentionally has only **Запустить поиск** and **Остановить поиск** buttons.
|
||||
Select an inclusive topology range, then start the search. Existing checkpoints are
|
||||
loaded automatically. Stop waits for the current short CUDA kernel, synchronizes the
|
||||
device, writes a durable checkpoint, and only then exits.
|
||||
|
||||
The GUI creates a fresh seed automatically for every new process. It is shown in the
|
||||
GUI log and saved in the durable, Git-tracked `runs/<UUID>/run.tsv` manifest together
|
||||
with the topology range and search parameters, so a run can be reproduced without
|
||||
typing a seed into the interface.
|
||||
|
||||
The search process and CUDA stream run at low scheduling priority, so foreground work
|
||||
keeps priority. There is no artificial duty-cycle throttle. Kernels remain short for
|
||||
responsive stopping and Windows WDDM stability.
|
||||
|
||||
Leave `GPU chains` at `0` for automatic sizing. The CUDA backend derives the chain count
|
||||
from the selected GPU's SM count and measured kernel occupancy, so Ada `sm_89` and
|
||||
Blackwell `sm_120` use different appropriate values. Each selected topology keeps its
|
||||
annealing session resident on the device, so refresh cycles cannot discard cooling,
|
||||
stagnation, or restart state. The full 59-topology range uses about 1.4 GB of VRAM on
|
||||
the 12 GB RTX 4070 Ti; narrower ranges allocate proportionally less.
|
||||
|
||||
For an independent overnight run, set the desired number of minutes and topology range.
|
||||
This mode does not use the supplied near-miss:
|
||||
it gives every topology fresh random starts, then alternates depth passes over the best
|
||||
30/16/8 topologies with periodic breadth refreshes over all 59. A clean stop saves each
|
||||
topology under `runtime\global_search\topology_N`; pressing the same button later resumes only
|
||||
those checkpoints. `runtime\global_search\leaderboard.tsv` is the current ranking.
|
||||
topology under `results\search\topology_N`; pressing the same button later resumes only
|
||||
those checkpoints. `results\search\leaderboard.tsv` is the current ranking.
|
||||
|
||||
Coordinates and optimizer states use 64-bit `double`. Candidates with at most 12 total
|
||||
defects are automatically reclassified with the custom `WideReal` double-double type
|
||||
(about 31 decimal digits), and every claimed `0/0` is serialized and checked again at
|
||||
multiple tolerances down to `1e-13`. OBJ and plane checkpoints retain 17 digits.
|
||||
The CUDA hot path uses standard FP32 math. Persistent chains retain their RNG, cooling,
|
||||
stagnation, and restart state between batches; fixed fresh cohorts preserve global
|
||||
exploration during breadth/refresh passes. Every returned shortlist is reconstructed and checked on
|
||||
the CPU in `double`; candidates with at most 12 total defects are reclassified with the
|
||||
custom `WideReal` double-double type (about 31 decimal digits). Every claimed `0/0` is
|
||||
serialized and checked again at multiple tolerances down to `1e-13`.
|
||||
|
||||
CUDA Toolkit 13.3 with Visual Studio integration is required for the GPU backend. The
|
||||
build contains native targets for Ada `sm_89` and Blackwell `sm_120`. Without the Toolkit,
|
||||
the project builds a diagnostic stub and refuses `--cuda` instead of silently falling
|
||||
back to the CPU.
|
||||
|
||||
### Checkpoints and Git
|
||||
|
||||
Search state is stored in `results/search`, which is intentionally tracked by Git.
|
||||
Each checkpoint is an immutable, self-contained `.szcp` file with CRC-32 and exact FP32
|
||||
bit patterns. It is flushed to disk and atomically published; a damaged newest generation
|
||||
is ignored in favor of the previous valid one.
|
||||
|
||||
For several computers, assign non-overlapping topology ranges. Each process uses a unique
|
||||
run UUID, so checkpoint filenames do not collide. Commit `results/search` normally on each
|
||||
computer and merge the branches with Git. `leaderboard.tsv`, run logs, and temporary files
|
||||
are derived and ignored, so they cannot create merge conflicts; the next search start
|
||||
rescans checkpoints, repeats CPU/DD validation, and rebuilds the leaderboard.
|
||||
|
||||
## Project layout
|
||||
|
||||
@@ -29,7 +70,8 @@ multiple tolerances down to `1e-13`. OBJ and plane checkpoints retain 17 digits.
|
||||
* `src/NeighborlyCore` — shared geometry and utility code.
|
||||
* `data` — topology definitions and input models.
|
||||
* `results/topologies` and `results/showcases` — saved research models.
|
||||
* `runtime` — generated candidates, reports, logs, and resumable search state.
|
||||
* `results/search` — Git-mergeable search states and durable checkpoint generations.
|
||||
* `runtime` — temporary stop files, reports, logs, and local candidates.
|
||||
* `scripts` — helper launch and reporting scripts.
|
||||
|
||||
All Visual Studio projects are collected by the root `Szilassi.slnx`. MSBuild output is
|
||||
@@ -39,7 +81,8 @@ centralized under `build/msbuild`; CMake output remains under `build/vs2026`.
|
||||
To start a search, build and run the `Szilassi` project.
|
||||
|
||||
### Requirements
|
||||
* C++17 compatible compiler (I'm using MSVC 2019).
|
||||
* C++17 compatible compiler (Visual Studio 2026 is configured by the current presets).
|
||||
* [CUDA Toolkit 13.3](https://developer.nvidia.com/cuda-downloads) with Visual Studio integration for GPU search.
|
||||
* [Eigen-3.4.0](https://eigen.tuxfamily.org/) though similar versions should work as well.
|
||||
* [Cairo-1.17.2](https://www.cairographics.org/) (optional) this is only needed to render paper cutouts.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,7 +53,11 @@
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<PropertyGroup>
|
||||
<CudaToolkitAvailable Condition="'$(Platform)'=='x64' and Exists('$(VCTargetsPath)\BuildCustomizations\CUDA 13.3.props')">true</CudaToolkitAvailable>
|
||||
</PropertyGroup>
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\CUDA 13.3.props" Condition="'$(CudaToolkitAvailable)'=='true'" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
@@ -94,7 +98,7 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -111,7 +115,7 @@
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
@@ -132,7 +136,7 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -149,7 +153,7 @@
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)..\..\src\NeighborlyCore;$(ProjectDir)..\..\src\Checkpoint;$(ProjectDir)..\..\src\CudaSearch;$(ProjectDir)..\..\external\eigen-3.4.0;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<EnableEnhancedInstructionSet>AdvancedVectorExtensions2</EnableEnhancedInstructionSet>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
@@ -167,16 +171,28 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="solver.cpp" />
|
||||
<ClCompile Include="..\..\src\Checkpoint\GlobalCheckpoint.cpp" />
|
||||
<ClCompile Include="..\..\src\NeighborlyCore\util.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(CudaToolkitAvailable)'!='true'">
|
||||
<ClCompile Include="..\..\src\CudaSearch\cuda_search_stub.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(CudaToolkitAvailable)'=='true'">
|
||||
<CudaCompile Include="..\..\src\CudaSearch\cuda_search.cu">
|
||||
<CodeGeneration>compute_89,sm_89;compute_120,sm_120;compute_120,compute_120</CodeGeneration>
|
||||
</CudaCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\src\NeighborlyCore\plane.h" />
|
||||
<ClInclude Include="..\..\src\NeighborlyCore\precise_geometry.h" />
|
||||
<ClInclude Include="solver.h" />
|
||||
<ClInclude Include="..\..\src\Checkpoint\GlobalCheckpoint.h" />
|
||||
<ClInclude Include="..\..\src\CudaSearch\cuda_search.h" />
|
||||
<ClInclude Include="..\..\src\NeighborlyCore\util.h" />
|
||||
<ClInclude Include="..\..\src\NeighborlyCore\wide_real.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\CUDA 13.3.targets" Condition="'$(CudaToolkitAvailable)'=='true'" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
|
||||
@@ -21,6 +21,15 @@
|
||||
<ClCompile Include="solver.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\Checkpoint\GlobalCheckpoint.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\CudaSearch\cuda_search_stub.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<CudaCompile Include="..\..\src\CudaSearch\cuda_search.cu">
|
||||
<Filter>Source Files</Filter>
|
||||
</CudaCompile>
|
||||
<ClCompile Include="..\..\src\NeighborlyCore\util.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@@ -29,6 +38,12 @@
|
||||
<ClInclude Include="solver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\Checkpoint\GlobalCheckpoint.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\CudaSearch\cuda_search.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\NeighborlyCore\util.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
||||
+652
-48
@@ -2,6 +2,8 @@
|
||||
#include "util.h"
|
||||
#include "solver.h"
|
||||
#include "wide_real.h"
|
||||
#include "GlobalCheckpoint.h"
|
||||
#include "cuda_search.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
@@ -18,16 +20,25 @@
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#ifdef USE_CAIRO
|
||||
#include <cairo.h>
|
||||
#endif
|
||||
|
||||
#define NUM_TOPOLOGIES 59
|
||||
#define DUAL_PROBLEM 0
|
||||
constexpr int GLOBAL_PLANE_VALUE_COUNT = 36;
|
||||
constexpr int CUDA_SESSION_CACHE_LIMIT = NUM_TOPOLOGIES;
|
||||
|
||||
struct StudyOptions {
|
||||
std::string obj_path = "data/shape_c2_i0_0.obj";
|
||||
@@ -47,7 +58,7 @@ struct LocalRepairOptions {
|
||||
std::string obj_path = "data/shape_c2_i0_0.obj";
|
||||
std::string start_planes_path;
|
||||
std::string stop_file_path;
|
||||
std::string global_dir = "runtime/global_search";
|
||||
std::string global_dir = "results/search";
|
||||
std::string out_prefix = "runtime/candidates/top4_local";
|
||||
std::string report_path = "runtime/reports/03_repair_local_cpp.md";
|
||||
int topology = 4;
|
||||
@@ -65,6 +76,13 @@ struct LocalRepairOptions {
|
||||
int time_limit_seconds = 0;
|
||||
double jump_chance = 0.08;
|
||||
double min_step_ratio = 1e-5;
|
||||
int topology_from = 0;
|
||||
int topology_to = NUM_TOPOLOGIES - 1;
|
||||
int cuda_chains = 0;
|
||||
int cuda_iterations = 64;
|
||||
int checkpoint_seconds = 30;
|
||||
double degeneracy_weight = 0.02;
|
||||
bool use_cuda = false;
|
||||
};
|
||||
|
||||
bool save_plane_state(const std::filesystem::path& path, const VectorXd& x);
|
||||
@@ -122,7 +140,14 @@ void print_usage(const char* exe_name) {
|
||||
<< " --threads <n> batch-hunt worker threads. 0 = all cores.\n"
|
||||
<< " --minutes <x> Clean time limit for batch-hunt. 0 = unlimited.\n"
|
||||
<< " --stop-file <path> Stop cleanly when this file appears.\n"
|
||||
<< " --global-dir <path> Checkpoints for --global-search. Default: runtime/global_search\n"
|
||||
<< " --global-dir <path> Checkpoints for --global-search. Default: results/search\n"
|
||||
<< " --topology-from <n> First topology to search, inclusive. Default: 0\n"
|
||||
<< " --topology-to <n> Last topology to search, inclusive. Default: 58\n"
|
||||
<< " --cuda Require the FP32 CUDA search backend.\n"
|
||||
<< " --cuda-chains <n> Parallel GPU chains. 0 = automatic (default).\n"
|
||||
<< " --cuda-iters <n> Iterations per short GPU batch. Default: 64\n"
|
||||
<< " --checkpoint-seconds <n> Durable checkpoint period. Default: 30\n"
|
||||
<< " --degeneracy-weight <x> Degenerate-geometry penalty. Default: 0.02\n"
|
||||
<< " --start-planes <p> Continue batch-hunt from a saved .planes sidecar.\n"
|
||||
<< " --restarts <n> hunt-local restarts. Default: 256\n"
|
||||
<< " --stagnation <n> Iterations before hunt-local reheat. Default: 2000\n"
|
||||
@@ -2170,15 +2195,50 @@ struct GlobalMetrics {
|
||||
int intersections = std::numeric_limits<int>::max() / 4;
|
||||
double crossing_loss = std::numeric_limits<double>::infinity();
|
||||
double geometry_penalty = std::numeric_limits<double>::infinity();
|
||||
double degeneracy_penalty = std::numeric_limits<double>::infinity();
|
||||
double energy = std::numeric_limits<double>::infinity();
|
||||
bool canonical = false;
|
||||
bool precise = false;
|
||||
};
|
||||
|
||||
double g_global_degeneracy_weight = 0.02;
|
||||
std::string g_search_device_id = "CPU";
|
||||
|
||||
std::uint64_t topology_fingerprint() {
|
||||
std::uint64_t hash = 1469598103934665603ULL;
|
||||
auto add = [&](int value) {
|
||||
hash ^= static_cast<std::uint64_t>(static_cast<std::uint32_t>(value));
|
||||
hash *= 1099511628211ULL;
|
||||
};
|
||||
for (const Face& triangle : g_tris) {
|
||||
add(static_cast<int>(triangle.size()));
|
||||
for (int value : triangle) {
|
||||
add(value);
|
||||
}
|
||||
}
|
||||
for (const Face& polygon : g_polys) {
|
||||
add(static_cast<int>(polygon.size()));
|
||||
for (int value : polygon) {
|
||||
add(value);
|
||||
}
|
||||
}
|
||||
for (const Edge& edge : g_edges) {
|
||||
add(edge.first);
|
||||
add(edge.second);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
struct GlobalTopologyState {
|
||||
int topology = 0;
|
||||
int visits = 0;
|
||||
std::uint64_t visits = 0;
|
||||
std::uint64_t trials = 0;
|
||||
std::uint64_t iterations = 0;
|
||||
std::uint64_t run_visits = 0;
|
||||
std::uint64_t run_trials = 0;
|
||||
std::uint64_t run_iterations = 0;
|
||||
std::uint64_t checkpoint_sequence = 0;
|
||||
std::chrono::steady_clock::time_point last_checkpoint_at{};
|
||||
bool has_state = false;
|
||||
GlobalMetrics best;
|
||||
VectorXd best_x;
|
||||
@@ -2354,13 +2414,57 @@ GlobalMetrics evaluate_global_state(
|
||||
scratch.verts, *metric_planes, 1e-8, &metrics.crossing_loss);
|
||||
metrics.intersections = count_edge_face_intersections_strict(
|
||||
scratch.verts, *metric_planes, 1e-8);
|
||||
metrics.precise = promote_precise_counts_if_close(
|
||||
metrics.precise = canonical && promote_precise_counts_if_close(
|
||||
scratch.verts, *metric_planes, metrics.crossings, metrics.intersections);
|
||||
|
||||
const double condition = std::sqrt(max_edge_sq / min_edge_sq);
|
||||
const double relative_min_edge = std::sqrt(min_edge_sq / max_edge_sq);
|
||||
double min_plane_determinant = std::numeric_limits<double>::infinity();
|
||||
for (const Face& triangle : g_tris) {
|
||||
if (triangle.size() != 3) {
|
||||
continue;
|
||||
}
|
||||
const Vector3d& a = (*metric_planes)[triangle[0]].n;
|
||||
const Vector3d& b = (*metric_planes)[triangle[1]].n;
|
||||
const Vector3d& c = (*metric_planes)[triangle[2]].n;
|
||||
min_plane_determinant = std::min(
|
||||
min_plane_determinant,
|
||||
std::abs(a.dot(b.cross(c))));
|
||||
}
|
||||
if (!std::isfinite(min_plane_determinant)) {
|
||||
return GlobalMetrics{};
|
||||
}
|
||||
double min_turn_sine = std::numeric_limits<double>::infinity();
|
||||
for (const Face& face : g_polys) {
|
||||
for (size_t i = 0; i < face.size(); ++i) {
|
||||
const Vector3d a = scratch.verts[face[(i + face.size() - 1) % face.size()]] -
|
||||
scratch.verts[face[i]];
|
||||
const Vector3d b = scratch.verts[face[(i + 1) % face.size()]] -
|
||||
scratch.verts[face[i]];
|
||||
const double denominator = a.norm() * b.norm();
|
||||
if (denominator > 1e-15) {
|
||||
min_turn_sine = std::min(
|
||||
min_turn_sine,
|
||||
a.cross(b).norm() / denominator);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!std::isfinite(min_turn_sine)) {
|
||||
return GlobalMetrics{};
|
||||
}
|
||||
const double determinant_barrier =
|
||||
std::log1p(0.02 / std::max(1e-10, min_plane_determinant));
|
||||
const double edge_barrier =
|
||||
std::log1p(0.002 / std::max(1e-10, relative_min_edge));
|
||||
const double turn_barrier =
|
||||
std::log1p(0.002 / std::max(1e-10, min_turn_sine));
|
||||
const double extent_barrier = 0.10 * std::log1p(max_vertex_norm / 100.0);
|
||||
metrics.degeneracy_penalty = g_global_degeneracy_weight *
|
||||
(determinant_barrier + edge_barrier + turn_barrier + extent_barrier);
|
||||
metrics.geometry_penalty =
|
||||
0.0010 * std::min(20.0, std::log1p(condition)) +
|
||||
0.0002 * std::min(20.0, std::log1p(max_vertex_norm));
|
||||
0.0002 * std::min(20.0, std::log1p(max_vertex_norm)) +
|
||||
metrics.degeneracy_penalty;
|
||||
metrics.energy =
|
||||
0.05 * static_cast<double>(global_defects(metrics)) +
|
||||
0.01 * static_cast<double>(metrics.crossings) +
|
||||
@@ -2529,11 +2633,131 @@ bool load_global_topology_state(
|
||||
return true;
|
||||
}
|
||||
|
||||
void load_mergeable_checkpoints(
|
||||
const LocalRepairOptions& options,
|
||||
std::vector<GlobalTopologyState>& states
|
||||
) {
|
||||
using namespace szilassi::checkpoint;
|
||||
const CheckpointScan scan = scan_checkpoints(options.global_dir);
|
||||
const LatestCheckpointMap latest = select_latest_per_run_topology(scan);
|
||||
for (const auto& entry : latest) {
|
||||
const GlobalCheckpoint& checkpoint = entry.second.checkpoint;
|
||||
if (checkpoint.topology < 0 || checkpoint.topology >= NUM_TOPOLOGIES ||
|
||||
checkpoint.plane_coefficients.size() != static_cast<size_t>(GLOBAL_PLANE_VALUE_COUNT)) {
|
||||
continue;
|
||||
}
|
||||
if (!load_global_topology_context(checkpoint.topology)) {
|
||||
continue;
|
||||
}
|
||||
if (checkpoint.topology_fingerprint != 0 &&
|
||||
checkpoint.topology_fingerprint != topology_fingerprint()) {
|
||||
std::cerr << "Skipping checkpoint with a different topology fingerprint: "
|
||||
<< entry.second.path.string() << std::endl;
|
||||
continue;
|
||||
}
|
||||
VectorXd x(GLOBAL_PLANE_VALUE_COUNT);
|
||||
for (int i = 0; i < GLOBAL_PLANE_VALUE_COUNT; ++i) {
|
||||
x[i] = static_cast<double>(checkpoint.plane_coefficients[static_cast<size_t>(i)]);
|
||||
}
|
||||
if (!valid_plane_state(x)) {
|
||||
continue;
|
||||
}
|
||||
PlaneEvaluationScratch scratch;
|
||||
GlobalMetrics metrics = evaluate_global_state(x, true, scratch);
|
||||
if (!std::isfinite(metrics.energy)) {
|
||||
continue;
|
||||
}
|
||||
GlobalTopologyState& state = states[checkpoint.topology];
|
||||
state.visits += checkpoint.visits;
|
||||
state.trials += checkpoint.completed_trials;
|
||||
state.iterations += checkpoint.completed_iterations;
|
||||
if (!state.has_state || better_global_metrics(metrics, state.best)) {
|
||||
state.best = metrics;
|
||||
state.best_x = x;
|
||||
state.has_state = true;
|
||||
}
|
||||
}
|
||||
if (!scan.rejected.empty()) {
|
||||
std::cerr << "Ignored " << scan.rejected.size()
|
||||
<< " incomplete or corrupt checkpoint(s); older generations remain usable."
|
||||
<< std::endl;
|
||||
}
|
||||
if (!scan.conflicts.empty()) {
|
||||
std::cerr << "Ignored " << scan.conflicts.size()
|
||||
<< " conflicting checkpoint sequence(s)." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
bool commit_mergeable_checkpoint(
|
||||
const LocalRepairOptions& options,
|
||||
const szilassi::checkpoint::RunIdentity& identity,
|
||||
GlobalTopologyState& state,
|
||||
szilassi::checkpoint::CheckpointReason reason
|
||||
) {
|
||||
using namespace szilassi::checkpoint;
|
||||
if (!state.has_state || state.best_x.size() != GLOBAL_PLANE_VALUE_COUNT) {
|
||||
return false;
|
||||
}
|
||||
GlobalCheckpoint checkpoint;
|
||||
checkpoint.run_id = identity.run_id;
|
||||
checkpoint.node_id = identity.node_id;
|
||||
checkpoint.producer_id = "Szilassi CUDA global search";
|
||||
checkpoint.device_id = g_search_device_id;
|
||||
checkpoint.sequence = ++state.checkpoint_sequence;
|
||||
checkpoint.topology = state.topology;
|
||||
checkpoint.topology_first = options.topology_from;
|
||||
checkpoint.topology_last = options.topology_to;
|
||||
checkpoint.objective_version = 2;
|
||||
checkpoint.topology_fingerprint = topology_fingerprint();
|
||||
checkpoint.base_seed = static_cast<std::uint64_t>(
|
||||
static_cast<std::uint32_t>(options.seed));
|
||||
checkpoint.next_work_unit = state.run_visits + 1;
|
||||
checkpoint.completed_work_units = state.run_visits;
|
||||
checkpoint.completed_trials = state.run_trials;
|
||||
checkpoint.completed_iterations = state.run_iterations;
|
||||
checkpoint.visits = state.run_visits;
|
||||
checkpoint.plane_coefficients.resize(GLOBAL_PLANE_VALUE_COUNT);
|
||||
VectorXd stored_x(GLOBAL_PLANE_VALUE_COUNT);
|
||||
for (int i = 0; i < GLOBAL_PLANE_VALUE_COUNT; ++i) {
|
||||
const float value = static_cast<float>(state.best_x[i]);
|
||||
checkpoint.plane_coefficients[static_cast<size_t>(i)] = value;
|
||||
stored_x[i] = static_cast<double>(value);
|
||||
}
|
||||
PlaneEvaluationScratch scratch;
|
||||
GlobalMetrics stored_metrics = evaluate_global_state(stored_x, true, scratch);
|
||||
if (!std::isfinite(stored_metrics.energy)) {
|
||||
std::cerr << "FP32 checkpoint round-trip is invalid for topology "
|
||||
<< state.topology << std::endl;
|
||||
return false;
|
||||
}
|
||||
checkpoint.crossings = stored_metrics.crossings;
|
||||
checkpoint.intersections = stored_metrics.intersections;
|
||||
checkpoint.crossing_loss = stored_metrics.crossing_loss;
|
||||
checkpoint.degeneracy_penalty = stored_metrics.degeneracy_penalty;
|
||||
checkpoint.energy = stored_metrics.energy;
|
||||
checkpoint.verification = stored_metrics.precise
|
||||
? VerificationPrecision::DoubleDouble
|
||||
: VerificationPrecision::Double;
|
||||
checkpoint.reason = reason;
|
||||
checkpoint.flags = CheckpointFlagCanonical |
|
||||
(stored_metrics.precise ? CheckpointFlagDdVerified : CheckpointFlagNone) |
|
||||
((stored_metrics.crossings == 0 && stored_metrics.intersections == 0)
|
||||
? CheckpointFlagFound
|
||||
: CheckpointFlagNone);
|
||||
const CommitResult result = commit_checkpoint(options.global_dir, std::move(checkpoint));
|
||||
if (!result) {
|
||||
std::cerr << "Durable checkpoint failed for topology " << state.topology
|
||||
<< ": " << result.error << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void save_global_topology_state(
|
||||
const LocalRepairOptions& options,
|
||||
const GlobalTopologyState& state,
|
||||
bool save_history,
|
||||
int seed
|
||||
bool,
|
||||
int
|
||||
) {
|
||||
if (!state.has_state || state.best_x.size() == 0) {
|
||||
return;
|
||||
@@ -2542,23 +2766,6 @@ void save_global_topology_state(
|
||||
std::filesystem::create_directories(dir);
|
||||
save_plane_state(dir / "resume.planes", state.best_x);
|
||||
export_plane_candidate((dir / "resume.obj").string().c_str(), state.best_x);
|
||||
std::ofstream meta(dir / "resume.meta");
|
||||
meta << 1 << " " << state.visits << " " << state.trials << "\n";
|
||||
meta << state.best.crossings << " " << state.best.intersections << " "
|
||||
<< std::setprecision(17) << state.best.energy << "\n";
|
||||
|
||||
if (save_history) {
|
||||
std::ostringstream stem;
|
||||
stem << "best_v" << state.visits
|
||||
<< "_strict_c" << state.best.crossings
|
||||
<< "_i" << state.best.intersections
|
||||
<< "_seed" << seed;
|
||||
const std::filesystem::path obj_path = dir / (stem.str() + ".obj");
|
||||
export_plane_candidate(obj_path.string().c_str(), state.best_x);
|
||||
std::filesystem::path planes_path = obj_path;
|
||||
planes_path.replace_extension(".planes");
|
||||
save_plane_state(planes_path, state.best_x);
|
||||
}
|
||||
}
|
||||
|
||||
void write_global_leaderboard(
|
||||
@@ -2580,7 +2787,7 @@ void write_global_leaderboard(
|
||||
const std::filesystem::path root(options.global_dir);
|
||||
std::filesystem::create_directories(root);
|
||||
std::ofstream out(root / "leaderboard.tsv");
|
||||
out << "rank\ttopology\tC\tI\tdefects\tprecision\tvisits\ttrials\tenergy\n";
|
||||
out << "rank\ttopology\tC\tI\tdefects\tprecision\tvisits\ttrials\tenergy\tdegeneracy\n";
|
||||
int rank = 1;
|
||||
for (int ix : order) {
|
||||
const GlobalTopologyState& state = states[ix];
|
||||
@@ -2590,22 +2797,254 @@ void write_global_leaderboard(
|
||||
<< global_defects(state.best) << "\t"
|
||||
<< (state.best.precise ? "dd31" : "double") << "\t"
|
||||
<< state.visits << "\t"
|
||||
<< state.trials << "\t" << std::setprecision(17) << state.best.energy;
|
||||
<< state.trials << "\t" << std::setprecision(17) << state.best.energy << "\t"
|
||||
<< state.best.degeneracy_penalty;
|
||||
} else {
|
||||
out << "-\t-\t-\t-\t0\t0\t-";
|
||||
out << "-\t-\t-\t-\t0\t0\t-\t-";
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool write_run_manifest(
|
||||
const std::filesystem::path& run_directory,
|
||||
const LocalRepairOptions& options,
|
||||
const szilassi::checkpoint::RunIdentity& identity,
|
||||
int effective_cuda_chains,
|
||||
int effective_cuda_session_cache
|
||||
) {
|
||||
const std::filesystem::path manifest_path = run_directory / "run.tsv";
|
||||
std::ofstream out(manifest_path, std::ios::binary | std::ios::trunc);
|
||||
if (!out) {
|
||||
std::cerr << "Cannot write run manifest: " << manifest_path << std::endl;
|
||||
return false;
|
||||
}
|
||||
out << "format\tszilassi-global-search-v2\n"
|
||||
<< "run_id\t" << identity.run_id << "\n"
|
||||
<< "node_id\t" << identity.node_id << "\n"
|
||||
<< "seed\t" << options.seed << "\n"
|
||||
<< "topology_from\t" << options.topology_from << "\n"
|
||||
<< "topology_to\t" << options.topology_to << "\n"
|
||||
<< "backend\t" << (options.use_cuda ? "cuda-fp32" : "cpu-double") << "\n"
|
||||
<< "cuda_math\tstandard-fp32\n"
|
||||
<< "cuda_chains_requested\t" << options.cuda_chains << "\n"
|
||||
<< "cuda_chains_effective\t" << effective_cuda_chains << "\n"
|
||||
<< "cuda_iterations_per_batch\t" << options.cuda_iterations << "\n"
|
||||
<< "cuda_depth_batches\t6\n"
|
||||
<< "cuda_session_cache\t" << effective_cuda_session_cache << "\n"
|
||||
<< "cpu_iterations_per_trial\t" << options.iterations << "\n"
|
||||
<< "degeneracy_weight\t" << std::setprecision(17)
|
||||
<< options.degeneracy_weight << "\n";
|
||||
out.flush();
|
||||
if (!out) {
|
||||
std::cerr << "Cannot flush run manifest: " << manifest_path << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
const HANDLE handle = CreateFileW(
|
||||
manifest_path.c_str(),
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE || !FlushFileBuffers(handle)) {
|
||||
std::cerr << "Cannot durably flush run manifest: " << manifest_path << std::endl;
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(handle);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
CloseHandle(handle);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
cuda_search::Topology make_cuda_topology() {
|
||||
cuda_search::Topology topology;
|
||||
for (int vertex = 0; vertex < cuda_search::kVertexCount; ++vertex) {
|
||||
for (int component = 0; component < 3; ++component) {
|
||||
topology.vertex_planes[vertex][component] = static_cast<std::uint8_t>(
|
||||
g_tris[static_cast<size_t>(vertex)][static_cast<size_t>(component)]);
|
||||
}
|
||||
}
|
||||
for (int face = 0; face < cuda_search::kPolygonCount; ++face) {
|
||||
for (int vertex = 0; vertex < cuda_search::kPolygonVertexCount; ++vertex) {
|
||||
topology.polygons[face][vertex] = static_cast<std::uint8_t>(
|
||||
g_polys[static_cast<size_t>(face)][static_cast<size_t>(vertex)]);
|
||||
}
|
||||
}
|
||||
for (int edge = 0; edge < cuda_search::kEdgeCount; ++edge) {
|
||||
topology.edges[edge][0] = static_cast<std::uint8_t>(g_edges[edge].first);
|
||||
topology.edges[edge][1] = static_cast<std::uint8_t>(g_edges[edge].second);
|
||||
}
|
||||
return topology;
|
||||
}
|
||||
|
||||
struct CudaSessionSlot {
|
||||
int topology = -1;
|
||||
std::uint64_t last_used = 0;
|
||||
cuda_search::BatchSession session;
|
||||
};
|
||||
|
||||
bool run_global_topology_round_cuda(
|
||||
const LocalRepairOptions& options,
|
||||
GlobalTopologyState& state,
|
||||
bool depth,
|
||||
const std::atomic<bool>& stop_requested,
|
||||
int round_seed,
|
||||
int effective_chain_count,
|
||||
cuda_search::BatchSession& session
|
||||
) {
|
||||
const GlobalMetrics previous_best = state.best;
|
||||
const bool previous_has_state = state.has_state;
|
||||
const bool session_was_initialized = session.initialized();
|
||||
if (!session_was_initialized) {
|
||||
cuda_search::SearchConfig config;
|
||||
config.chain_count = effective_chain_count;
|
||||
config.iterations_per_kernel = std::clamp(options.cuda_iterations, 1, 16);
|
||||
config.iterations_per_batch = options.cuda_iterations;
|
||||
config.shortlist_size = std::min(128, config.chain_count);
|
||||
config.seed = static_cast<std::uint64_t>(static_cast<std::uint32_t>(round_seed));
|
||||
config.initial_step = static_cast<float>(options.step);
|
||||
config.minimum_step = static_cast<float>(
|
||||
std::max(1e-7, options.step * options.min_step_ratio));
|
||||
config.cooling = static_cast<float>(options.beta);
|
||||
config.initial_temperature = static_cast<float>(options.temperature);
|
||||
config.minimum_temperature = static_cast<float>(options.temperature * 0.04);
|
||||
config.jump_chance = static_cast<float>(options.jump_chance);
|
||||
config.initial_state_jitter = static_cast<float>(options.step * 0.10);
|
||||
config.stagnation_iterations = std::max(256, options.stagnation);
|
||||
config.degeneracy_weight = static_cast<float>(options.degeneracy_weight);
|
||||
|
||||
std::vector<cuda_search::PlaneState> initial_states;
|
||||
if (state.has_state && state.best_x.size() == GLOBAL_PLANE_VALUE_COUNT) {
|
||||
cuda_search::PlaneState initial;
|
||||
for (int i = 0; i < GLOBAL_PLANE_VALUE_COUNT; ++i) {
|
||||
initial.values[static_cast<size_t>(i)] =
|
||||
static_cast<float>(state.best_x[i]);
|
||||
}
|
||||
initial_states.push_back(initial);
|
||||
}
|
||||
std::string initialize_error;
|
||||
if (!session.initialize(
|
||||
make_cuda_topology(), config, initial_states, initialize_error)) {
|
||||
std::cerr << "CUDA session initialization failed for topology "
|
||||
<< state.topology << ": " << initialize_error << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const int requested_batches = depth ? 6 : 1;
|
||||
int completed_batches = 0;
|
||||
double kernel_milliseconds = 0.0;
|
||||
double transfer_milliseconds = 0.0;
|
||||
std::uint64_t evaluated_states = 0;
|
||||
std::uint64_t new_trials = session_was_initialized
|
||||
? 0
|
||||
: static_cast<std::uint64_t>(effective_chain_count);
|
||||
std::string device_name;
|
||||
PlaneEvaluationScratch scratch;
|
||||
for (int batch = 0; batch < requested_batches; ++batch) {
|
||||
if (stop_requested.load(std::memory_order_relaxed)) {
|
||||
break;
|
||||
}
|
||||
cuda_search::BatchRunConfig run_config;
|
||||
run_config.step_scale = depth ? 1.0f : 1.5f;
|
||||
if (batch == 0 && (previous_has_state || session_was_initialized)) {
|
||||
run_config.fresh_numerator = depth ? 1 : 7;
|
||||
run_config.fresh_denominator = depth ? 4 : 8;
|
||||
if (session_was_initialized) {
|
||||
const std::uint64_t chain_count =
|
||||
static_cast<std::uint64_t>(effective_chain_count);
|
||||
const std::uint64_t numerator =
|
||||
static_cast<std::uint64_t>(run_config.fresh_numerator);
|
||||
const std::uint64_t denominator =
|
||||
static_cast<std::uint64_t>(run_config.fresh_denominator);
|
||||
new_trials += (chain_count / denominator) * numerator +
|
||||
std::min(chain_count % denominator, numerator);
|
||||
}
|
||||
}
|
||||
const cuda_search::BatchResult gpu = session.run(run_config);
|
||||
if (!gpu.success) {
|
||||
std::cerr << "CUDA batch failed for topology " << state.topology
|
||||
<< ": " << gpu.error << std::endl;
|
||||
session.reset();
|
||||
break;
|
||||
}
|
||||
device_name = gpu.device_name;
|
||||
g_search_device_id = gpu.device_name;
|
||||
kernel_milliseconds += gpu.kernel_milliseconds;
|
||||
transfer_milliseconds += gpu.transfer_milliseconds;
|
||||
evaluated_states += gpu.evaluated_states;
|
||||
completed_batches += 1;
|
||||
|
||||
for (const cuda_search::Candidate& candidate : gpu.shortlist) {
|
||||
VectorXd x(GLOBAL_PLANE_VALUE_COUNT);
|
||||
for (int i = 0; i < GLOBAL_PLANE_VALUE_COUNT; ++i) {
|
||||
x[i] = static_cast<double>(
|
||||
candidate.state.values[static_cast<size_t>(i)]);
|
||||
}
|
||||
if (!valid_plane_state(x)) {
|
||||
continue;
|
||||
}
|
||||
GlobalMetrics verified = evaluate_global_state(x, true, scratch);
|
||||
if (!std::isfinite(verified.energy)) {
|
||||
continue;
|
||||
}
|
||||
if (!state.has_state || better_global_metrics(verified, state.best)) {
|
||||
state.best = verified;
|
||||
state.best_x = x;
|
||||
state.has_state = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (completed_batches == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.visits += 1;
|
||||
state.run_visits += 1;
|
||||
state.trials += new_trials;
|
||||
state.run_trials += new_trials;
|
||||
state.iterations += evaluated_states;
|
||||
state.run_iterations += evaluated_states;
|
||||
std::cout << " CUDA " << device_name
|
||||
<< ": " << std::fixed << std::setprecision(1)
|
||||
<< kernel_milliseconds << " ms kernel, "
|
||||
<< transfer_milliseconds << " ms transfer, "
|
||||
<< evaluated_states << " FP32 states in "
|
||||
<< completed_batches << " batch(es)"
|
||||
<< std::defaultfloat << std::setprecision(6) << std::endl;
|
||||
return state.has_state &&
|
||||
(!previous_has_state || better_global_metrics(state.best, previous_best));
|
||||
}
|
||||
|
||||
bool run_global_topology_round(
|
||||
const LocalRepairOptions& options,
|
||||
GlobalTopologyState& state,
|
||||
bool depth,
|
||||
int worker_count,
|
||||
const std::atomic<bool>& stop_requested,
|
||||
int round_seed
|
||||
int round_seed,
|
||||
int effective_cuda_chains,
|
||||
cuda_search::BatchSession* cuda_session
|
||||
) {
|
||||
if (options.use_cuda) {
|
||||
if (cuda_session == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return run_global_topology_round_cuda(
|
||||
options,
|
||||
state,
|
||||
depth,
|
||||
stop_requested,
|
||||
round_seed,
|
||||
effective_cuda_chains,
|
||||
*cuda_session);
|
||||
}
|
||||
const GlobalMetrics previous_best = state.best;
|
||||
const bool previous_has_state = state.has_state;
|
||||
GlobalMetrics shared_best = state.best;
|
||||
@@ -2620,6 +3059,7 @@ bool run_global_topology_round(
|
||||
: std::max(1000, options.iterations / 3);
|
||||
std::atomic<int> next_trial{0};
|
||||
std::atomic<int> completed{0};
|
||||
std::atomic<std::uint64_t> completed_iterations{0};
|
||||
std::mutex best_mutex;
|
||||
std::vector<std::thread> workers;
|
||||
workers.reserve(worker_count);
|
||||
@@ -2656,6 +3096,9 @@ bool run_global_topology_round(
|
||||
}
|
||||
}
|
||||
completed.fetch_add(1, std::memory_order_relaxed);
|
||||
completed_iterations.fetch_add(
|
||||
static_cast<std::uint64_t>(result.iterations),
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2668,7 +3111,15 @@ bool run_global_topology_round(
|
||||
}
|
||||
|
||||
state.visits += 1;
|
||||
state.trials += static_cast<std::uint64_t>(completed.load(std::memory_order_relaxed));
|
||||
state.run_visits += 1;
|
||||
const std::uint64_t round_trials =
|
||||
static_cast<std::uint64_t>(completed.load(std::memory_order_relaxed));
|
||||
const std::uint64_t round_iterations =
|
||||
completed_iterations.load(std::memory_order_relaxed);
|
||||
state.trials += round_trials;
|
||||
state.iterations += round_iterations;
|
||||
state.run_trials += round_trials;
|
||||
state.run_iterations += round_iterations;
|
||||
state.has_state = shared_has_state;
|
||||
state.best = shared_best;
|
||||
state.best_x = shared_best_x;
|
||||
@@ -2683,9 +3134,38 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
}
|
||||
const std::filesystem::path root(options.global_dir);
|
||||
std::filesystem::create_directories(root);
|
||||
if (!options.stop_file_path.empty()) {
|
||||
std::error_code remove_error;
|
||||
std::filesystem::remove(options.stop_file_path, remove_error);
|
||||
const szilassi::checkpoint::RunIdentity run_identity =
|
||||
szilassi::checkpoint::make_run_identity();
|
||||
g_global_degeneracy_weight = options.degeneracy_weight;
|
||||
#ifdef _WIN32
|
||||
SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
#endif
|
||||
|
||||
cuda_search::BackendInfo cuda_backend;
|
||||
int effective_cuda_chains = 0;
|
||||
if (options.use_cuda) {
|
||||
cuda_backend = cuda_search::query_backend(0);
|
||||
if (!cuda_backend.available) {
|
||||
std::cerr << "CUDA backend is unavailable: " << cuda_backend.error << std::endl;
|
||||
std::cerr << "Install CUDA Toolkit 13.3 with Visual Studio integration, then rebuild x64."
|
||||
<< std::endl;
|
||||
return 3;
|
||||
}
|
||||
effective_cuda_chains = options.cuda_chains > 0
|
||||
? options.cuda_chains
|
||||
: cuda_backend.recommended_chain_count;
|
||||
if (effective_cuda_chains <= 0) {
|
||||
std::cerr << "CUDA backend did not provide a valid chain count." << std::endl;
|
||||
return 3;
|
||||
}
|
||||
g_search_device_id = cuda_backend.device_name;
|
||||
std::cout << "CUDA device: " << cuda_backend.device_name << std::endl;
|
||||
std::cout << "CUDA SMs: " << cuda_backend.multiprocessor_count
|
||||
<< ", resident blocks/SM: "
|
||||
<< cuda_backend.active_blocks_per_multiprocessor << std::endl;
|
||||
std::cout << "GPU scheduling: low-priority stream, no artificial throttle"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
const unsigned int hardware_threads = std::thread::hardware_concurrency();
|
||||
@@ -2720,24 +3200,60 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
load_global_topology_state(options, states[topology]);
|
||||
}
|
||||
}
|
||||
load_mergeable_checkpoints(options, states);
|
||||
write_global_leaderboard(options, states);
|
||||
|
||||
std::ofstream run_log(root / "run.log", std::ios::app);
|
||||
std::vector<int> active_topologies;
|
||||
active_topologies.reserve(options.topology_to - options.topology_from + 1);
|
||||
for (int topology = options.topology_from; topology <= options.topology_to; ++topology) {
|
||||
active_topologies.push_back(topology);
|
||||
}
|
||||
|
||||
const std::filesystem::path run_directory =
|
||||
root / "runs" / run_identity.run_id;
|
||||
std::filesystem::create_directories(run_directory);
|
||||
const int effective_cuda_session_cache = options.use_cuda
|
||||
? std::min(
|
||||
CUDA_SESSION_CACHE_LIMIT,
|
||||
static_cast<int>(active_topologies.size()))
|
||||
: 0;
|
||||
write_run_manifest(
|
||||
run_directory,
|
||||
options,
|
||||
run_identity,
|
||||
effective_cuda_chains,
|
||||
effective_cuda_session_cache);
|
||||
std::ofstream run_log(run_directory / "run.log", std::ios::app);
|
||||
run_log << "\n=== global-search seed " << options.seed
|
||||
<< ", run " << run_identity.run_id
|
||||
<< ", node " << run_identity.node_id
|
||||
<< ", threads " << worker_count
|
||||
<< ", minutes " << (options.time_limit_seconds / 60.0)
|
||||
<< " ===\n";
|
||||
|
||||
std::cout << "===================" << std::endl;
|
||||
std::cout << "Mode : global-search" << std::endl;
|
||||
std::cout << "Topologies: 59" << std::endl;
|
||||
std::cout << "Seed : " << options.seed
|
||||
<< " (saved in " << (run_directory / "run.tsv").string() << ")"
|
||||
<< std::endl;
|
||||
std::cout << "Topologies: " << options.topology_from << ".."
|
||||
<< options.topology_to << " (" << active_topologies.size() << ")" << std::endl;
|
||||
std::cout << "Start : independent random plane arrangements" << std::endl;
|
||||
std::cout << "Coordinates: double (15-16 digits)" << std::endl;
|
||||
std::cout << "Coordinates: "
|
||||
<< (options.use_cuda ? "CUDA FP32 search" : "CPU double search")
|
||||
<< std::endl;
|
||||
std::cout << "Near goal : WideReal double-double (~"
|
||||
<< WideReal::decimal_digits << " digits)" << std::endl;
|
||||
std::cout << "Resume dir: " << options.global_dir << std::endl;
|
||||
std::cout << "Threads : " << worker_count << std::endl;
|
||||
std::cout << "Depth iters/trial: " << options.iterations << std::endl;
|
||||
if (options.use_cuda) {
|
||||
std::cout << "CUDA chains: " << effective_cuda_chains
|
||||
<< (options.cuda_chains == 0 ? " (automatic)" : " (manual)")
|
||||
<< std::endl;
|
||||
std::cout << "CUDA iters/batch: " << options.cuda_iterations << std::endl;
|
||||
} else {
|
||||
std::cout << "Threads : " << worker_count << std::endl;
|
||||
std::cout << "Depth iters/trial: " << options.iterations << std::endl;
|
||||
}
|
||||
if (options.time_limit_seconds > 0) {
|
||||
std::cout << "Time limit: " << options.time_limit_seconds << " sec" << std::endl;
|
||||
}
|
||||
@@ -2745,6 +3261,34 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
|
||||
int completed_rounds = 0;
|
||||
bool found = false;
|
||||
std::vector<CudaSessionSlot> cuda_sessions(effective_cuda_session_cache);
|
||||
std::uint64_t cuda_session_use_counter = 0;
|
||||
auto acquire_cuda_session = [&](int topology) -> cuda_search::BatchSession* {
|
||||
if (!options.use_cuda || cuda_sessions.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
++cuda_session_use_counter;
|
||||
for (CudaSessionSlot& slot : cuda_sessions) {
|
||||
if (slot.topology == topology) {
|
||||
slot.last_used = cuda_session_use_counter;
|
||||
return &slot.session;
|
||||
}
|
||||
}
|
||||
CudaSessionSlot* selected = nullptr;
|
||||
for (CudaSessionSlot& slot : cuda_sessions) {
|
||||
if (slot.topology < 0) {
|
||||
selected = &slot;
|
||||
break;
|
||||
}
|
||||
if (selected == nullptr || slot.last_used < selected->last_used) {
|
||||
selected = &slot;
|
||||
}
|
||||
}
|
||||
selected->session.reset();
|
||||
selected->topology = topology;
|
||||
selected->last_used = cuda_session_use_counter;
|
||||
return &selected->session;
|
||||
};
|
||||
auto run_round = [&](int topology, bool depth, const char* phase, int position, int total) {
|
||||
if (stop_requested.load(std::memory_order_relaxed)) {
|
||||
return;
|
||||
@@ -2756,16 +3300,43 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
GlobalTopologyState& state = states[topology];
|
||||
const int round_seed = static_cast<int>(
|
||||
1 + (static_cast<std::uint64_t>(static_cast<std::uint32_t>(options.seed)) +
|
||||
static_cast<std::uint64_t>(completed_rounds + 1) * 15485863ULL +
|
||||
static_cast<std::uint64_t>(state.visits + 1) * 15485863ULL +
|
||||
static_cast<std::uint64_t>(topology + 1) * 32452843ULL) %
|
||||
2147483646ULL);
|
||||
std::cout << phase << " " << position << "/" << total
|
||||
<< " | topology " << topology
|
||||
<< " | " << (depth ? "depth" : "breadth") << std::endl;
|
||||
cuda_search::BatchSession* cuda_session = acquire_cuda_session(topology);
|
||||
const bool improved = run_global_topology_round(
|
||||
options, state, depth, worker_count, stop_requested, round_seed);
|
||||
options,
|
||||
state,
|
||||
depth,
|
||||
worker_count,
|
||||
stop_requested,
|
||||
round_seed,
|
||||
effective_cuda_chains,
|
||||
cuda_session);
|
||||
completed_rounds += 1;
|
||||
save_global_topology_state(options, state, improved, round_seed);
|
||||
const auto checkpoint_now = std::chrono::steady_clock::now();
|
||||
const bool checkpoint_due = state.last_checkpoint_at.time_since_epoch().count() == 0 ||
|
||||
checkpoint_now - state.last_checkpoint_at >=
|
||||
std::chrono::seconds(options.checkpoint_seconds);
|
||||
if (checkpoint_due) {
|
||||
if (commit_mergeable_checkpoint(
|
||||
options,
|
||||
run_identity,
|
||||
state,
|
||||
improved
|
||||
? szilassi::checkpoint::CheckpointReason::Improvement
|
||||
: szilassi::checkpoint::CheckpointReason::Periodic)) {
|
||||
state.last_checkpoint_at = checkpoint_now;
|
||||
}
|
||||
}
|
||||
// Legacy previews follow the durable checkpoint cadence. The current
|
||||
// in-memory best is always committed on a clean stop below.
|
||||
if (checkpoint_due) {
|
||||
save_global_topology_state(options, state, improved, round_seed);
|
||||
}
|
||||
write_global_leaderboard(options, states);
|
||||
|
||||
const double elapsed_minutes = std::chrono::duration<double>(
|
||||
@@ -2801,9 +3372,9 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<int> breadth_order(NUM_TOPOLOGIES);
|
||||
std::iota(breadth_order.begin(), breadth_order.end(), 0);
|
||||
const int rotation = ((options.seed % NUM_TOPOLOGIES) + NUM_TOPOLOGIES) % NUM_TOPOLOGIES;
|
||||
std::vector<int> breadth_order = active_topologies;
|
||||
const int active_count = static_cast<int>(breadth_order.size());
|
||||
const int rotation = ((options.seed % active_count) + active_count) % active_count;
|
||||
std::rotate(breadth_order.begin(), breadth_order.begin() + rotation, breadth_order.end());
|
||||
std::vector<int> missing;
|
||||
for (int topology : breadth_order) {
|
||||
@@ -2817,8 +3388,7 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
|
||||
int cycle = 0;
|
||||
while (!stop_requested.load(std::memory_order_relaxed)) {
|
||||
std::vector<int> ranked(NUM_TOPOLOGIES);
|
||||
std::iota(ranked.begin(), ranked.end(), 0);
|
||||
std::vector<int> ranked = active_topologies;
|
||||
std::sort(ranked.begin(), ranked.end(), [&](int a, int b) {
|
||||
if (states[a].has_state != states[b].has_state) {
|
||||
return states[a].has_state;
|
||||
@@ -2830,7 +3400,7 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
});
|
||||
|
||||
const bool exploration_cycle = cycle % 5 == 4;
|
||||
int keep = NUM_TOPOLOGIES;
|
||||
int keep = active_count;
|
||||
if (!exploration_cycle) {
|
||||
keep = cycle == 0 ? 30 : (cycle == 1 ? 16 : 8);
|
||||
} else {
|
||||
@@ -2852,14 +3422,25 @@ int global_search_all(const LocalRepairOptions& options) {
|
||||
|
||||
watcher_done.store(true, std::memory_order_relaxed);
|
||||
watcher.join();
|
||||
for (int topology : active_topologies) {
|
||||
GlobalTopologyState& state = states[topology];
|
||||
if (state.run_visits == 0 || !load_global_topology_context(topology)) {
|
||||
continue;
|
||||
}
|
||||
commit_mergeable_checkpoint(
|
||||
options,
|
||||
run_identity,
|
||||
state,
|
||||
szilassi::checkpoint::CheckpointReason::Stop);
|
||||
save_global_topology_state(options, state, true, options.seed);
|
||||
}
|
||||
if (!options.stop_file_path.empty()) {
|
||||
std::error_code remove_error;
|
||||
std::filesystem::remove(options.stop_file_path, remove_error);
|
||||
}
|
||||
write_global_leaderboard(options, states);
|
||||
|
||||
std::vector<int> ranked(NUM_TOPOLOGIES);
|
||||
std::iota(ranked.begin(), ranked.end(), 0);
|
||||
std::vector<int> ranked = active_topologies;
|
||||
std::sort(ranked.begin(), ranked.end(), [&](int a, int b) {
|
||||
if (states[a].has_state != states[b].has_state) {
|
||||
return states[a].has_state;
|
||||
@@ -3101,6 +3682,21 @@ int main(int argc, char* argv[]) {
|
||||
repair_options.stop_file_path = argv[++i];
|
||||
} else if (arg == "--global-dir" && i + 1 < argc) {
|
||||
repair_options.global_dir = argv[++i];
|
||||
} else if (arg == "--topology-from" && i + 1 < argc) {
|
||||
repair_options.topology_from = std::atoi(argv[++i]);
|
||||
} else if (arg == "--topology-to" && i + 1 < argc) {
|
||||
repair_options.topology_to = std::atoi(argv[++i]);
|
||||
} else if (arg == "--cuda") {
|
||||
repair_options.use_cuda = true;
|
||||
} else if (arg == "--cuda-chains" && i + 1 < argc) {
|
||||
repair_options.cuda_chains = std::max(0, std::atoi(argv[++i]));
|
||||
} else if (arg == "--cuda-iters" && i + 1 < argc) {
|
||||
repair_options.cuda_iterations = std::clamp(std::atoi(argv[++i]), 1, 64);
|
||||
} else if (arg == "--checkpoint-seconds" && i + 1 < argc) {
|
||||
repair_options.checkpoint_seconds = std::clamp(std::atoi(argv[++i]), 5, 3600);
|
||||
} else if (arg == "--degeneracy-weight" && i + 1 < argc) {
|
||||
repair_options.degeneracy_weight = std::clamp(
|
||||
std::atof(argv[++i]), 0.0, 1.0);
|
||||
} else if (arg == "--restarts" && i + 1 < argc) {
|
||||
repair_options.restarts = std::atoi(argv[++i]);
|
||||
} else if (arg == "--stagnation" && i + 1 < argc) {
|
||||
@@ -3121,6 +3717,14 @@ int main(int argc, char* argv[]) {
|
||||
return study_shape(options);
|
||||
}
|
||||
if (run_global_search) {
|
||||
if (repair_options.topology_from < 0 ||
|
||||
repair_options.topology_to >= NUM_TOPOLOGIES ||
|
||||
repair_options.topology_from > repair_options.topology_to) {
|
||||
std::cerr << "Invalid topology range: "
|
||||
<< repair_options.topology_from << ".."
|
||||
<< repair_options.topology_to << std::endl;
|
||||
return 2;
|
||||
}
|
||||
return global_search_all(repair_options);
|
||||
}
|
||||
if (run_batch_hunt) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
format szilassi-global-search-v2
|
||||
run_id d27a3577-93be-4570-ae15-6ac4dfb73ac8
|
||||
node_id LOOKICH
|
||||
seed 2005138652
|
||||
topology_from 0
|
||||
topology_to 58
|
||||
backend cuda-fp32
|
||||
cuda_math standard-fp32
|
||||
cuda_chains_requested 0
|
||||
cuda_chains_effective 61440
|
||||
cuda_iterations_per_batch 64
|
||||
cuda_depth_batches 6
|
||||
cuda_session_cache 59
|
||||
cpu_iterations_per_trial 50000
|
||||
degeneracy_weight 0.02
|
||||
|
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user