CUDA
This commit is contained in:
+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) {
|
||||
|
||||
Reference in New Issue
Block a user