CUDA
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,203 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace szilassi::checkpoint {
|
||||
|
||||
// Checkpoints are append-only generations. A generation is a single,
|
||||
// self-contained binary file; there is no separately updated metadata or OBJ
|
||||
// that could get out of sync after a power failure.
|
||||
constexpr std::uint32_t kGlobalCheckpointFormatVersion = 2;
|
||||
constexpr const char* kGlobalCheckpointExtension = ".szcp";
|
||||
|
||||
enum class VerificationPrecision : std::uint32_t {
|
||||
Unverified = 0,
|
||||
Fp32 = 1,
|
||||
Double = 2,
|
||||
DoubleDouble = 3,
|
||||
};
|
||||
|
||||
enum class CheckpointReason : std::uint32_t {
|
||||
Periodic = 0,
|
||||
Improvement = 1,
|
||||
Stop = 2,
|
||||
Final = 3,
|
||||
Imported = 4,
|
||||
};
|
||||
|
||||
enum CheckpointFlags : std::uint32_t {
|
||||
CheckpointFlagNone = 0,
|
||||
CheckpointFlagCanonical = 1u << 0,
|
||||
CheckpointFlagDdVerified = 1u << 1,
|
||||
CheckpointFlagFound = 1u << 2,
|
||||
};
|
||||
|
||||
struct RunIdentity {
|
||||
// run_id is a random UUID and is the merge namespace. A fresh run_id must
|
||||
// be created for every process invocation, including after a crash.
|
||||
std::string run_id;
|
||||
// node_id is diagnostic only (normally the computer name). Correctness
|
||||
// and merge conflict avoidance do not rely on it being globally unique.
|
||||
std::string node_id;
|
||||
};
|
||||
|
||||
struct GlobalCheckpoint {
|
||||
std::string run_id;
|
||||
std::string node_id;
|
||||
std::string producer_id;
|
||||
std::string device_id;
|
||||
|
||||
std::uint64_t sequence = 0;
|
||||
std::int64_t created_unix_ns = 0;
|
||||
|
||||
std::int32_t topology = -1;
|
||||
std::int32_t topology_first = 0;
|
||||
std::int32_t topology_last = 58;
|
||||
std::uint32_t objective_version = 0;
|
||||
std::uint64_t topology_fingerprint = 0;
|
||||
|
||||
std::uint64_t base_seed = 0;
|
||||
// Work identifiers are local to run_id and topology. Persist only the
|
||||
// fully committed frontier; in-flight work may safely be repeated.
|
||||
std::uint64_t next_work_unit = 0;
|
||||
std::uint64_t completed_work_units = 0;
|
||||
std::uint64_t completed_trials = 0;
|
||||
std::uint64_t completed_iterations = 0;
|
||||
std::uint64_t visits = 0;
|
||||
|
||||
std::int32_t crossings = 0;
|
||||
std::int32_t intersections = 0;
|
||||
double crossing_loss = 0.0;
|
||||
double degeneracy_penalty = 0.0;
|
||||
double energy = 0.0;
|
||||
|
||||
VerificationPrecision verification = VerificationPrecision::Unverified;
|
||||
CheckpointReason reason = CheckpointReason::Periodic;
|
||||
std::uint32_t flags = CheckpointFlagNone;
|
||||
|
||||
// CUDA search coordinates are stored as raw IEEE-754 binary32 values in
|
||||
// the file. DD verification should lift these exact values, not a rounded
|
||||
// textual representation.
|
||||
std::vector<float> plane_coefficients;
|
||||
};
|
||||
|
||||
struct CheckpointRecord {
|
||||
std::filesystem::path path;
|
||||
GlobalCheckpoint checkpoint;
|
||||
std::uint32_t crc32 = 0;
|
||||
};
|
||||
|
||||
struct CheckpointIssue {
|
||||
std::filesystem::path path;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct CheckpointConflict {
|
||||
std::string run_id;
|
||||
std::int32_t topology = -1;
|
||||
std::uint64_t sequence = 0;
|
||||
std::vector<std::filesystem::path> paths;
|
||||
};
|
||||
|
||||
struct CommitResult {
|
||||
bool success = false;
|
||||
bool already_existed = false;
|
||||
std::filesystem::path path;
|
||||
std::uint32_t crc32 = 0;
|
||||
std::string error;
|
||||
|
||||
explicit operator bool() const noexcept { return success; }
|
||||
};
|
||||
|
||||
struct ReadResult {
|
||||
std::optional<CheckpointRecord> record;
|
||||
std::string error;
|
||||
|
||||
explicit operator bool() const noexcept { return record.has_value(); }
|
||||
};
|
||||
|
||||
struct LoadLatestResult {
|
||||
std::optional<CheckpointRecord> record;
|
||||
// Corrupt/truncated newer generations and conflicting sequences are
|
||||
// reported here. record, when present, is the newest older valid fallback.
|
||||
std::vector<CheckpointIssue> rejected;
|
||||
|
||||
explicit operator bool() const noexcept { return record.has_value(); }
|
||||
};
|
||||
|
||||
struct CheckpointScan {
|
||||
std::vector<CheckpointRecord> valid;
|
||||
std::vector<CheckpointIssue> rejected;
|
||||
// Same (run_id, topology, sequence) with different CRCs means a run_id was
|
||||
// cloned and advanced independently. Such a sequence is never selected.
|
||||
std::vector<CheckpointConflict> conflicts;
|
||||
};
|
||||
|
||||
struct RunTopologyKey {
|
||||
std::string run_id;
|
||||
std::int32_t topology = -1;
|
||||
|
||||
bool operator<(const RunTopologyKey& other) const noexcept;
|
||||
};
|
||||
|
||||
using LatestCheckpointMap = std::map<RunTopologyKey, CheckpointRecord>;
|
||||
|
||||
std::string generate_run_id();
|
||||
std::string default_node_id();
|
||||
RunIdentity make_run_identity(const std::string& node_id_override = {});
|
||||
std::int64_t unix_time_ns_now();
|
||||
|
||||
bool validate_checkpoint(const GlobalCheckpoint& checkpoint, std::string* error = nullptr);
|
||||
|
||||
// Binary format (all integer and IEEE-754 fields are little-endian):
|
||||
// 32-byte header: magic[8], version:u32, header_size:u32,
|
||||
// payload_size:u64, payload_crc32:u32, reserved:u32
|
||||
// payload: length-prefixed UTF-8 identifiers, progress, metrics, flags,
|
||||
// coefficient_count:u32, coefficient bits.
|
||||
// CRC-32/IEEE covers the complete payload.
|
||||
std::vector<std::uint8_t> serialize_checkpoint(
|
||||
const GlobalCheckpoint& checkpoint,
|
||||
std::uint32_t* payload_crc32 = nullptr,
|
||||
std::string* error = nullptr);
|
||||
|
||||
ReadResult read_checkpoint(const std::filesystem::path& path);
|
||||
|
||||
std::filesystem::path checkpoint_directory(
|
||||
const std::filesystem::path& global_root,
|
||||
const std::string& run_id,
|
||||
std::int32_t topology);
|
||||
|
||||
// Writes a new immutable generation under:
|
||||
// <root>/runs/<run-id>/topology_NNN/checkpoints/
|
||||
// checkpoint_<20-digit-sequence>_<crc32>.szcp
|
||||
// The temporary file is written in the same directory, flushed durably, and
|
||||
// atomically published. Existing different contents are never overwritten.
|
||||
CommitResult commit_checkpoint(
|
||||
const std::filesystem::path& global_root,
|
||||
GlobalCheckpoint checkpoint);
|
||||
|
||||
// Reads all generations in one checkpoint directory. Corrupt latest files or
|
||||
// divergent same-sequence files are skipped, so the result automatically falls
|
||||
// back to the preceding valid generation.
|
||||
LoadLatestResult load_latest_with_fallback(
|
||||
const std::filesystem::path& directory,
|
||||
const std::string& expected_run_id = {},
|
||||
std::int32_t expected_topology = -1);
|
||||
|
||||
// Scans only the merge namespace <root>/runs. Temporary files, root-level
|
||||
// leaderboard/log files, and legacy resume.* files are deliberately ignored.
|
||||
// Consequently merging two result trees is a union/copy of runs/* followed by
|
||||
// this scan; derived reports can then be rebuilt.
|
||||
CheckpointScan scan_checkpoints(const std::filesystem::path& global_root);
|
||||
|
||||
// Selects the newest non-conflicting valid generation for every
|
||||
// (run_id, topology). Aggregate counters by summing these per-run records;
|
||||
// never carry an already aggregated total into a new run checkpoint.
|
||||
LatestCheckpointMap select_latest_per_run_topology(const CheckpointScan& scan);
|
||||
|
||||
} // namespace szilassi::checkpoint
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,152 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cuda_search {
|
||||
|
||||
constexpr int kPlaneCount = 12;
|
||||
constexpr int kPlaneComponents = 3;
|
||||
constexpr int kPlaneValueCount = kPlaneCount * kPlaneComponents;
|
||||
constexpr int kVertexCount = 44;
|
||||
constexpr int kPolygonCount = 12;
|
||||
constexpr int kPolygonVertexCount = 11;
|
||||
constexpr int kEdgeCount = 66;
|
||||
constexpr int kNonincidentEdgesPerPolygon = 44;
|
||||
|
||||
enum AmbiguityFlag : std::uint32_t {
|
||||
kAmbiguityNone = 0,
|
||||
kAmbiguityPlaneTriple = 1u << 0,
|
||||
kAmbiguitySegment = 1u << 1,
|
||||
kAmbiguityEdgePlane = 1u << 2,
|
||||
kAmbiguityPolygonBoundary = 1u << 3,
|
||||
kAmbiguityDegenerateGeometry = 1u << 4,
|
||||
kAmbiguityInvalidState = 1u << 31,
|
||||
};
|
||||
|
||||
// Fixed combinatorial description used by the global search. Indices are
|
||||
// zero-based. vertex_planes[v] contains the three planes whose intersection
|
||||
// produces vertex v.
|
||||
struct Topology {
|
||||
std::array<std::array<std::uint8_t, 3>, kVertexCount> vertex_planes{};
|
||||
std::array<std::array<std::uint8_t, kPolygonVertexCount>, kPolygonCount> polygons{};
|
||||
std::array<std::array<std::uint8_t, 2>, kEdgeCount> edges{};
|
||||
};
|
||||
|
||||
// Plane i is encoded by values[3*i..3*i+2] = normal * distance, matching the
|
||||
// existing VectorXd representation. CUDA keeps these values in FP32.
|
||||
struct PlaneState {
|
||||
std::array<float, kPlaneValueCount> values{};
|
||||
};
|
||||
|
||||
struct SearchConfig {
|
||||
int device_index = 0;
|
||||
// Zero selects the occupancy-derived recommendation for the device.
|
||||
int chain_count = 0;
|
||||
int iterations_per_kernel = 16;
|
||||
int iterations_per_batch = 64;
|
||||
int shortlist_size = 128;
|
||||
|
||||
std::uint64_t seed = 30000157;
|
||||
float initial_step = 0.05f;
|
||||
float minimum_step = 1.0e-6f;
|
||||
float cooling = 0.9995f;
|
||||
float initial_temperature = 0.02f;
|
||||
float minimum_temperature = 8.0e-4f;
|
||||
float jump_chance = 0.08f;
|
||||
float initial_state_jitter = 0.02f;
|
||||
float degeneracy_weight = 0.02f;
|
||||
int stagnation_iterations = 2000;
|
||||
};
|
||||
|
||||
// Per-batch controls which do not change the persistent session layout.
|
||||
// A deterministic fraction of chains can be restarted as fresh independent
|
||||
// trials. The caller remains responsible for retaining the global best.
|
||||
struct BatchRunConfig {
|
||||
int fresh_numerator = 0;
|
||||
int fresh_denominator = 1;
|
||||
float step_scale = 1.0f;
|
||||
};
|
||||
|
||||
struct Candidate {
|
||||
PlaneState state;
|
||||
int crossings = 0;
|
||||
int intersections = 0;
|
||||
float crossing_loss = 0.0f;
|
||||
float geometry_penalty = 0.0f;
|
||||
float degeneracy_penalty = 0.0f;
|
||||
float energy = 0.0f;
|
||||
std::uint32_t ambiguity_flags = kAmbiguityNone;
|
||||
std::uint64_t chain_id = 0;
|
||||
std::uint64_t iterations = 0;
|
||||
};
|
||||
|
||||
struct BackendInfo {
|
||||
bool available = false;
|
||||
int device_count = 0;
|
||||
int selected_device = -1;
|
||||
int multiprocessor_count = 0;
|
||||
int active_blocks_per_multiprocessor = 0;
|
||||
int recommended_chain_count = 0;
|
||||
std::string device_name;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
struct BatchResult {
|
||||
bool success = false;
|
||||
std::string error;
|
||||
std::string device_name;
|
||||
int device_index = -1;
|
||||
int stream_priority = 0;
|
||||
|
||||
double kernel_milliseconds = 0.0;
|
||||
double transfer_milliseconds = 0.0;
|
||||
double wall_milliseconds = 0.0;
|
||||
std::uint64_t evaluated_states = 0;
|
||||
|
||||
std::vector<Candidate> shortlist;
|
||||
};
|
||||
|
||||
BackendInfo query_backend(int device_index = 0);
|
||||
|
||||
// A session owns one topology and persistent independent search chains. run()
|
||||
// launches short kernels and leaves the chain states resident on the device,
|
||||
// so repeated calls continue the same search.
|
||||
class BatchSession {
|
||||
public:
|
||||
BatchSession();
|
||||
~BatchSession();
|
||||
|
||||
BatchSession(BatchSession&& other) noexcept;
|
||||
BatchSession& operator=(BatchSession&& other) noexcept;
|
||||
|
||||
BatchSession(const BatchSession&) = delete;
|
||||
BatchSession& operator=(const BatchSession&) = delete;
|
||||
|
||||
bool initialize(
|
||||
const Topology& topology,
|
||||
const SearchConfig& config,
|
||||
const std::vector<PlaneState>& initial_states,
|
||||
std::string& error);
|
||||
|
||||
BatchResult run(const BatchRunConfig& config = {});
|
||||
void reset();
|
||||
bool initialized() const noexcept;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
// Convenience one-shot wrapper. Use BatchSession directly when search must be
|
||||
// continued over many GUI/background ticks.
|
||||
BatchResult run_batch(
|
||||
const Topology& topology,
|
||||
const SearchConfig& config,
|
||||
const std::vector<PlaneState>& initial_states = {});
|
||||
|
||||
} // namespace cuda_search
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "cuda_search.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
// Build this translation unit only in the CPU-only target. CUDA-enabled
|
||||
// targets compile cuda_search.cu instead; both files intentionally implement
|
||||
// the same public ABI.
|
||||
|
||||
namespace cuda_search {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kUnavailableMessage =
|
||||
"CUDA search backend is not compiled into this executable.";
|
||||
|
||||
} // namespace
|
||||
|
||||
struct BatchSession::Impl {
|
||||
bool is_initialized = false;
|
||||
};
|
||||
|
||||
BackendInfo query_backend(int device_index) {
|
||||
BackendInfo result;
|
||||
result.selected_device = device_index;
|
||||
result.error = kUnavailableMessage;
|
||||
return result;
|
||||
}
|
||||
|
||||
BatchSession::BatchSession() : impl_(std::make_unique<Impl>()) {}
|
||||
|
||||
BatchSession::~BatchSession() = default;
|
||||
|
||||
BatchSession::BatchSession(BatchSession&& other) noexcept = default;
|
||||
|
||||
BatchSession& BatchSession::operator=(BatchSession&& other) noexcept = default;
|
||||
|
||||
bool BatchSession::initialize(
|
||||
const Topology&,
|
||||
const SearchConfig&,
|
||||
const std::vector<PlaneState>&,
|
||||
std::string& error) {
|
||||
if (!impl_) {
|
||||
impl_ = std::make_unique<Impl>();
|
||||
}
|
||||
impl_->is_initialized = false;
|
||||
error = kUnavailableMessage;
|
||||
return false;
|
||||
}
|
||||
|
||||
BatchResult BatchSession::run(const BatchRunConfig&) {
|
||||
BatchResult result;
|
||||
result.error = kUnavailableMessage;
|
||||
return result;
|
||||
}
|
||||
|
||||
void BatchSession::reset() {
|
||||
if (impl_) {
|
||||
impl_->is_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool BatchSession::initialized() const noexcept {
|
||||
return impl_ != nullptr && impl_->is_initialized;
|
||||
}
|
||||
|
||||
BatchResult run_batch(
|
||||
const Topology&,
|
||||
const SearchConfig& config,
|
||||
const std::vector<PlaneState>&) {
|
||||
BatchResult result;
|
||||
result.device_index = config.device_index;
|
||||
result.error = kUnavailableMessage;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace cuda_search
|
||||
Reference in New Issue
Block a user